SPA (single-page architecture) schemes are very popular nowadays, but most websites still choose multi-page or single-page + multi-page hybrid architecture. Using Express, WebPack this article is a low-cost implementation of concepts including multi-page architecture, automatic refresh, and front and back end separation
On the first project
-
git repo
node-pages-webpack-hot -
The development of
npm install npm install supervisor -g npm run start # Development environment, configure hot Reload npm run prod # Production environment npm run build Build the front-end production environmentCopy the code
-
DEMO
ezgif-2-5c3bbb985e.gif -
FE directory:
Paste_Image.png -
SERVER directory:
Paste_Image.png
In order not to waste your time, read the following:
- Basic knowledge of Express, and a brief understanding of Node
- webpackIntermediate understanding, this article uses
webpack2
implementation
1. Configure the FE terminal
The front-end configuration needs to achieve the following functions:
-
Multi-page architecture automatically generates entry and generates templates for each page through HTML-webpack-plugin. Layout template function is required to select any template engine (SWIg is used as the template engine in this paper)
-
Configure loaders with various file suffixes
-
Use HotModuleReplacementPlugin implement changes since the refresh
1.1 Automatic Entry Analysis
It is stipulated that each page must have a JS file with the same name as the entry of this page. The directory depth is variable, as shown in the figure below, which is decomposed into two entries:
Paste_Image.png
To achieve automatic fetching, glob is used to fetch all.js files and determine if there is.html with the same name. If there is, an entry is generated. If it is dev environment, more hotMiddlewareScript module is added
// get all js files
let files = glob.sync(config.src + '/**/*.js');
let srcLength = config.src.length;
let entrys = {};
files.forEach(function (_file) {
let file = path.parse(_file);
let htmlFile = path.resolve(file.dir, file.name + '. ' + config.ext);
// if has same name template file, it is a entry
if (fs.existsSync(htmlFile)) {
let pathIndex = file.dir.indexOf(config.src);
if (config.dev == 'dev') {
entrys[config.staticRoot + file.dir.slice(srcLength) + '/' + file.name] = [path.resolve(_file), hotMiddlewareScript];
} else {
entrys[config.staticRoot + file.dir.slice(srcLength) + '/'+ file.name] = path.resolve(_file); }}});return entrys;Copy the code
1.2 Automatic Generationhtml-webpack-plugin
The template
The essentials of generating a series of HTMLWebPackPlugins are as follows:
-
After obtaining all the.html files, determine whether there are corresponding entry files. If so, create HtmlWebpackPlugin
-
If the page is a Layout template, you need to inject the Common module generated by the CommonsChunkPlugin
Automatically generate HtmlWebpackPlugin code as follows:
let htmls = [];
// get all templates
let files = glob.sync(config.src + '/ * * / *. + config.ext);
let srcLength = config.src.length;
files.forEach(function (_file) {
let file = path.parse(_file);
let chunks = [];
let chunkName = config.staticRoot + file.dir.slice(srcLength) + '/' + file.name;
// if has same name entry, create a html plugin
let c = entrys[chunkName];
c && chunks.push(chunkName);
// layout will contains common chunk
if (file.name == config.serverLayoutName) {
chunks.push(config.staticRoot + '/common');
}
let plugin = new HtmlWebpackPlugin({
filename: config.templateRoot + file.dir.slice(srcLength) + '/' + file.base,
template: path.resolve(file.dir, file.base),
chunks: chunks,
inject: false
});
htmls.push(plugin);
});
return htmls;Copy the code
Due to the introduction of template extends support, inject=false will not automatically inject assets files
Write a webpack plug-in to inject the js assets and CSS assets of the page into:
two replacement text places, such as the page template:
{% extends'.. /base/base.html' %}
{% block title %}My Page{% endblock %}
{% block style %}<! --webpack_style_placeholder-->{%endblock%}
{% block head %}
{% parent %}
{% endblock %}
{% block content %}
<p>This is just an home page!!</p>
<div class="color-area">
clouds
</div>
<a href="/users/list">link page2</a>
{% endblock %}
{% block script %}<! --webpack_script_placeholder-->{%endblock%}Copy the code
After compilation and replacement:
{% extends'.. /base/base.html' %}
{% block title %}My Page{% endblock %}
{% block style %}<link rel="stylesheet" href="/static/page/home/home.css"/>{%endblock%}
{% block head %}
{% parent %}
{% endblock %}
{% block content %}
<p>This is just an home page!!</p>
<div class="color-area">
clouds
</div>
<a href="/users/list">link page2</a>
{% endblock %}
{% block script %}<script src="/static/page/home/home.js"></script>{%endblock%}Copy the code
1.3 Various Loader Configurations to extract the CSS
The Dev environment is configured with Webpack-hot-middleware so CSS cannot be extracted or hot updated
The loader-related configurations are as follows:
var extractInstance = new ExtractTextPlugin('[name].css');
if (config.env == 'dev') {
var stylusLoader = [
{
loader: 'style-loader'
},
{
loader: 'css-loader'
},
{
loader: 'stylus-loader'
}
];
var cssLoader = [
{
loader: 'style-loader'
},
{
loader: 'css-loader'
}
];
} else {
var stylusLoader = extractInstance.extract(['css-loader', 'stylus-loader']);
var cssLoader = extractInstance.extract(['css-loader']);
}Copy the code
And put all loaders in the same file for maintenance:
var rules = [
{
test: /\.styl$/,
exclude: /node_modules/,
use: stylusLoader
},
{
test: /\.css$/,
exclude: /node_modules/,
use: cssLoader
},
{
test: /\.html$/,
use: {
loader: 'html-loader'. options: {
minimize: false}}},... . ]Copy the code
1.4 Path Configuration
Unified control of generated templates, static file output directory, easy to combine various back-end architectures
const port = process.env.PORT || 8080;
const env = process.env.NODE_ENV || 'dev';
const CONFIG_BUILD = {
env: env,
ext: 'html'.// tempate ext
src: path.resolve(__dirname, '.. /src'), // source code path
path: env= ='dev' ? '/' : path.resolve(__dirname, '.. /dist'), // base output path
templateRoot: 'templates'.// tempate output path
staticRoot: 'static'.// static output path
serverLayoutName: 'base'.// swig layout name , only one file
publicPath: env= ='dev' ? ('http://localhost:' + port + '/') : '/'
}Copy the code
2. Configure the SERVER
The Express service is set up on the server and provides the following functions:
-
Use Webpack-dev-Middleware for Webpack compilation
-
Hot Reload was implemented using Webpack-hot-middleware
-
The Supervisor service listens for node file changes and restarts automatically
-
The render template writes files in memory to hard disk for rendering
2.1 WebPack access express
- generate
webpack
的compiler
var webpack = require('webpack'),
webpackDevConfig = require(path.resolve(config.root, './fe/webpack.config.js'));
var compiler = webpack(webpackDevConfig);Copy the code
- will
compiler
As aexpress
The middleware
// attach to the compiler & the server
app.use(webpackDevMiddleware(compiler, {
// public path should be the same with webpack config
publicPath: webpackDevConfig.output.publicPath,
noInfo: false,
stats: {
colors: true
}
}));Copy the code
In the command, publicPath indicates the root path of the assets request. The value is http://localhost:8080/
2.2 hot reload
plan
2.2.1 Js, CSS
Self-refresh
Self-refresh of JS and CSS is implemented by configuring Webpack-hot-middleware (fe is also required).
// server
const webpackHotMiddleware = require('webpack-hot-middleware');
app.use(webpackHotMiddleware(compiler));
// fe
webpackPlugins.push(
new webpack.HotModuleReplacementPlugin()
);Copy the code
2.2.2 node
Self-refresh
Node file changes are automatically refreshed by configuring the Supervisor service
Installation services:
npm install supervisor -gCopy the code
Configure startup parameters:
// package.json
"scripts": {
"start": "cross-env NODE_ENV=dev supervisor -w server -e fe server/server.js"
}Copy the code
Supervisor monitors all changes in the server folder. After changes are made, the Express service restarts. To implement automatic browser refresh, add the following code to the layout template:
{% if env == 'dev' %}
<script src="/reload/reload.js"></script>
{% endif %}Copy the code
2.3 Render the Template
When WebPack is used as express middleware, all files generated are stored in memory, including of course the template files generated by the HTML-webpack-plugin. Express’s render function, however, can only specify one template that exists in the file system, so the Dev environment requires the render template to be retrieved from memory and stored in the file system.
module.exports = (res, template) = > {if (config.env == 'dev') {
let filename = compiler.outputPath + template;
// load template from
compiler.outputFileSystem.readFile(filename, function(err, result) {
let fileInfo = path.parse(path.join(config.templateRoot, filename));
mkdirp(fileInfo.dir, () => {
fs.writeFileSync(path.join(config.templateRoot, filename), result);
res.render(template);
});
});
} else {
res.render(template); }}Copy the code
Layout template storage requires a middleware:
app.use((req, res, next) => {
let layoutPath = path.join(config.templateRoot, config.layoutTemplate);
let filename = compiler.outputPath + config.layoutTemplate;
compiler.outputFileSystem.readFile(filename, function(err, result) {
let fileInfoLayout = path.parse(layoutPath);
mkdirp(fileInfoLayout.dir, () => {
fs.writeFileSync(layoutPath, result);
next();
});
});
});Copy the code
The rest are express basic use, just refer to the documentation
supplement
In this paper, a simple frame for front and rear end separation is constructed, but there are still many imperfections. Real online applications also need to consider nodeJS maintenance costs, logging, monitoring, and more.