引言
对于一般的项目,特别是移动端项目,我们所熟知的都是webpack+vue,webpack+react等打包成一个单页应用,包括我在github上所能找到的很多都是将我们所有的模块打包成一个大大的js包(甚至css也打包进去)然后在html中引入。
众所周知,单页应用依赖模块很大时会有很大的性能问题,当我们的web应用越来越大时,我们不可能会将所有的功能都放在同一个网页上,这时一般会做的是按功能划分模块,分成多个单页应用,这样做会有利于后期功能放入扩展调整。
要构建多页应用,我们应该要考虑以下的问题:
- 单页应用应该会有公共的代码部分,我们要将这些公用的部分抽离出来,以免重复引用加载。
- 随着业务的发展,后面可能会不断加入新的单页应用,但是在加入新应用时都不
能改动构建相关的代码。
先看看一般单页应用的配置:
const path = require('path');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const { WebPlugin } = require('web-webpack-plugin');
module.exports = {
entry: {
app: './main.js'// Chunk app 的 JS 执行入口文件
},
output: {
filename: '[name]_[chunkhash:8].js',// 给输出的文件名称加上 hash 值
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
// 排除 node_modules 目录下的文件,node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
exclude: path.resolve(__dirname, 'node_modules'),
},
{
test: /\.css/,// 增加对 CSS 文件的支持
// 提取出 Chunk 中的 CSS 代码到单独的文件中
use: ExtractTextPlugin.extract({
use: ['css-loader?minimize'] // 压缩 CSS 代码
}),
},
]
},
plugins: [
// 使用WebPlugin,一个 WebPlugin 对应一个 HTML 文件
new WebPlugin({
template: './template.html', // HTML 模版文件所在的文件路径
filename: 'index.html' // 输出的 HTML 的文件名称
}),
new ExtractTextPlugin({
filename: `[name]_[contenthash:8].css`,// 给输出的 CSS 文件名称加上 hash 值
}),
new DefinePlugin({
// 定义 NODE_ENV 环境变量为 production 去除 react 代码中的开发时才需要的部分
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
// 压缩输出的 JS 代码
new UglifyJsPlugin({
// 最紧凑的输出
beautify: false,
// 删除所有的注释
comments: false,
//忽略无关紧要的...
}),
],
};
复制代码
这里最主要的是WebPlugin
这个插件,能够让我们实现自动化输出html文件并引入打包后的模块,虽然这样已经对我们打包应用提供了很大的便利,但对于多个页面的管理依然是个麻烦的问题。如果我们要增加一个页面我们需要new一个webPlugin实例并在entry入口增加配置项。
解决方案
这里用AutoWebPlugin来构建我们的多页应用,项目源码的目录结构如下:
从目录结构中可以看出以下几点要求:
- 所有单页应用的代码都需要放到一个 目录下,例如都放在 pages 目录下;
- 一个单页应用对应一个单独的文件夹,例如最后生成的 index.html 相关的代码都在 index 目录下, login.html 同理:
- 每个单页应用的目录下都有一个 index.js 文件作为入口执行文件。AutoWebP lugin 强制性地规定了项目部分的目录结构,从实战经验来看,这是一种优雅的目录规范,合理地拆分了代码,又能让新人快速看懂项目的结构,方便日后维护。
webpack配置文件如下:
const path = require('path');
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const { AutoWebPlugin } = require('web-webpack-plugin');
// 使用AutoWebPlugin,自动寻找 pages 目录下的所有目录,把每一个目录看成一个单页应用
const autoWebPlugin = new AutoWebPlugin('pages', {
template: './template.html', // HTML 模版文件所在的文件路径
postEntrys: ['./common.css'],// 所有页面都依赖这份通用的 CSS 样式文件
// 提取出所有页面公共的代码
commonsChunk: {
name: 'common',// 提取出公共代码 Chunk 的名称
},
});
module.exports = {
// AutoWebPlugin 会找为寻找到的所有单页应用,生成对应的入口配置,
// autoWebPlugin.entry 方法可以获取到生成入口配置
entry: autoWebPlugin.entry({
// 这里可以加入你额外需要的 Chunk 入口
}),
output: {
filename: '[name]_[chunkhash:8].js',// 给输出的文件名称加上 hash 值
path: path.resolve(__dirname, './dist'),
},
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
// 排除 node_modules 目录下的文件,node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
exclude: path.resolve(__dirname, 'node_modules'),
},
{
test: /\.css/,// 增加对 CSS 文件的支持
// 提取出 Chunk 中的 CSS 代码到单独的文件中
use: ExtractTextPlugin.extract({
use: ['css-loader?minimize'] // 压缩 CSS 代码
}),
},
]
},
plugins: [
autoWebPlugin,
new ExtractTextPlugin({
filename: `[name]_[contenthash:8].css`,// 给输出的 CSS 文件名称加上 hash 值
}),
new DefinePlugin({
// 定义 NODE_ENV 环境变量为 production 去除 react 代码中的开发时才需要的部分
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
// 压缩输出的 JS 代码
new UglifyJsPlugin({
// 最紧凑的输出
beautify: false,
// 删除所有的注释
comments: false,
//忽略无关紧要的...
}),
],
};
复制代码
AutoWebPlugin 会找出 pages 目录下的两个文件夹 index 和 login ,将这两个文件夹看作两个单页应用,并且分别为每个单页应用生成一个 Chunk 配置和 WebPlugin 配置 。每个单页应用的 Chunk 名称等同于文件夹的名称,也就是说 autoWebPlugin.entry()方法返回的内容其实是:
{ "index:["./pages/index/index.js", "./common.css"],
"login": ["./pages/login/index.js", "./common.css"]
}复制代码
template.html 模板文件如下:
<html>
<head>
<meta charset="UTF-8">
<!--该页面所依赖的其它剩下的 CSS 注入的地方-->
<!--STYLE-->
<!--已忽略无关代码-->
</head>
<body>
<div id="app"></div>
<!--该页面所依赖的其它剩下的 JavaScript 注入的地方-->
<!--SCRIPT-->
<!--已忽略无关代码-->
</body>
</html>
复制代码
注意到在模板文件中出现了两个重要的新关键字<!--STYLE-->
和<!--SCRIPT-->
,
它们是什么意思呢?
由于该模板文件被当作项目中所有单页应用的模板,因为需要被注入当前页面的 Chunk 名称是不固定的,每个单页应用都会有自己的名称。<!--STYLE-->
和<!--SCRIPT-->
的作用在于保证该页面所依赖的资源都会被注入生成的 HTML 模板里。
web-webpack-plugin 能分析出每个页面依赖哪些资源,例如对于 login.html 来说,
- 所有页面都依赖的公共 css 代码 common. css;
- 所有页面都依赖的公共 JavaScript 代码 common. js;
- 只有这个页面依赖的 css 代码 login.css;
- 只有这个页面依赖的 JavaScript 代码 login .css 。
<!--STYLE-->
和
<!--SCRIPT-->
所在的位置。
- 将 css 类型的文件注入<!--STYLE-->所在的位置,如果<!--STYLE-->不存在,就注入 HTML HEAD 标签的最后 。
- 将 JavaScript 类型的文件注入<!--SCRIPT-->所在的位置,如果<<!--SCRIPT-->不存在,就注入 HTML BODY 标签的最后 。
输出 HTML 文件的名称,在目录下放这个页面相关的代码即可,无须改动构建代码。
在npm run build
后打包的代码如下:
login.html:
<html>
<head>
<meta charset="UTF-8">
<!--该页面所依赖的其它剩下的 CSS 注入的地方-->
<link rel="stylesheet" href="common_7cc98ad0.css">
<link rel="stylesheet" href="login_e31e214b.css">
<!--已忽略无关代码-->
</head>
<body>
<div id="app"></div>
<!--该页面所依赖的其它剩下的 JavaScript 注入的地方-->
<script src="common_bdac69cb.js"></script>
<script src="login_0a3feca9.js"></script>
<!--已忽略无关代码-->
</body>
</html>
复制代码
参考阅读:
- https://www.npmjs.com/package/web-webpack-plugin
- 深入浅出webpack