Webpack
构建的基于zepto
的多页应用脚手架,本文聊聊本次项目中Webpack
构建多页应用的一些心得体会。javascript
因为公司旧版的脚手架是基于Gulp
构建的zepto
多页应用(有兴趣能够看看web-mobile-cli),有着很多的痛点。例如:css
promise
,不能使用await
、generator
等。(由于babel-runtime
须要模块化);Gulp
插件相对Webpack
少且久远,维护成本高等等。此次升级有几个地方须要注意和改进:html
Github仓库:前端
Webpack
的多页应用经过多入口entry
和多实例html-webpack-plugin
配合来构建,html-webpack-plugin
的chunk
属性传入对应entry
的key
就能够作到关联,例如:java
module.exports = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
},
plugins: [
new HtmlWebpackPlugin({
filename: `pageOne.html`,
template: `./src/pageOne.html`,
chunks: ['pageOne']
}),
new HtmlWebpackPlugin({
filename: `pageTwo.html`,
template: `./src/pageTwo.html`,
chunks: ['pageTwo']
}),
new HtmlWebpackPlugin({
filename: `pageTwo.html`,
template: `./src/pageTwo.html`,
chunks: ['pageTwo']
})
]
}
复制代码
那么问题来了,开发新的页面每次都得添加岂不是很麻烦。这里推荐神器glob根据正则规则匹配。node
const glob = require('glob')
module.exports = {
entry: glob.sync('./src/js/*.js').reduce((pre, filepath) => {
const tempList = filepath.split('src/')[1].split(/js\//)
const filename = `${tempList[0]}${tempList[1].replace(/\.js/g, '')}`
return Object.assign(pre, {[filename]: filepath})
}, {}),
plugins: [
...glob.sync('./src/html/*.ejs').map((filepath, i) => {
const tempList = filepath.split('src/')[1].split(/html\//)
const fileName = tempList[1].split('.')[0].split(/[\/|\/\/|\\|\\\\]/g).pop()
const fileChunk = `${tempList[0]}${fileName}`
return new HtmlWebpackPlugin({
filename: `${fileChunk}.html`,
template: filepath,
chunks: [fileChunk]
})
})
]
}
复制代码
项目没有直接使用html
,而是使用了ejs
做为模板,这里有至少两个好处:webpack
// header.ejs
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= title %></title>
</head>
// index.ejs
<!DOCTYPE html>
<html lang="en">
<% include ./header.ejs %>
<body>
<!-- page -->
</body>
<script src="<%= publicPath %>lib/zepto.js"></script>
</html>
复制代码
<% include ./header.ejs %>
就是引用了header.ejs
文件,<%= title %>
和<%= publicPath %>
是我在配置文件定义的两个变量,publicPath
是为了统一cdn
缓存服务器的域名,很是有用。git
项目中使用了zepto
,因此须要垫片,所谓垫片就是shim 预置依赖,即全局依赖。github
webpack compiler 可以识别遵循 ES2015 模块语法、CommonJS 或 AMD 规范编写的模块。然而,一些 third party(第三方库) 可能会引用一些全局依赖(例如 jQuery 中的 $)。所以这些 library 也可能会建立一些须要导出的全局变量。这些 "broken modules(不符合规范的模块)" 就是 shim(预置依赖) 发挥做用的地方。web
垫片有两种方式:
html
文件中,全部引用的js
文件的最前面引用的文件(例如zepto
);Webpack
配置shim预置依赖
。最终我选择了Webpack
配置shim预置依赖
这种方式,由于:
ejs
能够抽离出来成为公共模块,但仍是须要每一个页面手动引入公共模块);Webpack
能够把全部第三方插件的代码都拆分打包成为一个独立的chunk
,只需一个请求。module.exports = {
entry: {...},
module: {
rules: [
{
test: require.resolve('zepto'),
use: 'imports-loader?this=>window'
}
]
},
plugins: [
new webpack.ProvidePlugin({$: 'zepto'})
]
}
复制代码
通常来说Webpack
的配置entry
中每一个key
就对应输出一个chunk
,那么该项目中会提取这几类chunk
:
entry
)对应的chunk
;common
:屡次引用的公共文件;vender
:第三方依赖;manifest
:Webpack
运行时(runtime
)代码,它存储着Webpack
对module
和chunk
的信息。module.exports = {
entry: {...},
module: {...},
plugins: [],
optimization: {
runtimeChunk: {
name: 'manifest'
},
splitChunks: {
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
chunks: 'all',
name: 'vendors',
filename: 'js/vendors.[contenthash:8].js',
priority: 2,
reuseExistingChunk: true
},
common: {
test: /\.m?js$/,
chunks: 'all',
name: 'common',
filename: 'js/common.[contenthash:8].js',
minSize: 0,
minChunks: 2,
priority: 1,
reuseExistingChunk: true
}
}
}
}
}
复制代码
这里注意的有两点:
priority
比common
代码的priority
大;common
代码:minChunks
为引用次数,我设置为引用2次即提取为公共代码。minSize
为最小字节,设置为0。缓存的目的是为了提升加载速度,Webpack
在缓存方面已是老生常谈的了,每一个文件赋予惟一的hash值,只有更新过的文件,hash值才改变,以达到总体项目最少文件改动。
Webpack
中有三种hash
值:
hash
:所有文件同一hash
,一旦某个文件改变,所有文件的hash都将改变(同一hash
不知足需求);chunkhash
:根据不一样的入口文件(Entry
)进行依赖文件解析、构建对应的chunk,生成对应的哈希值(问题是css
做为模块import
到JavaScript
文件中的,它们的chunkhash
是一致的,一旦改变js
文件,即便import
的css
文件内容没有改变,其chunkhash
值也会一同改变,不知足需求);contexthash
:只有模块的内容变了,那么hash值才改变(采用)。module.exports = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
},
output: {
path: 'src',
chunkFilename: 'j[name].[contenthash:8].js',
filename: '[name].[contenthash:8].js'
},
plugins: [
new HtmlWebpackPlugin({
filename: `pageOne.html`,
template: `./src/pageOne.html`,
chunks: ['pageOne']
}),
new HtmlWebpackPlugin({
filename: `pageTwo.html`,
template: `./src/pageTwo.html`,
chunks: ['pageTwo']
}),
new HtmlWebpackPlugin({
filename: `pageTwo.html`,
template: `./src/pageTwo.html`,
chunks: ['pageTwo']
})
]
}
复制代码
仅仅使用contexthash
还不足够,每当import
的资源文件顺序改变时,chunk
依然会改变,目的没有达成。要解决这个问题首先要理解module
和chunk
分别是什么,简单理解:
module
:一个import
对应一个module
(例如:import zepto from 'zepto'
中的zepto
就是一个module
);chunk
:根据配置文件打包出来的包,就是chunk
。(例如多页应用中每一个entry
的key
值对应的文件)。由于Webpack
内部维护了一个自增的id
,依照顺序赋予给每一个module
,每当新增或者删减致使module
的顺序改变时,受影响的chunk
的hash
值也会改变。解决办法就是使用惟一的hash
值替代自增的id
。
module.exports = {
entry: {...},
module: {...},
plugins: [],
optimization: {
moduleIds: 'hashed'
}
}
复制代码
优化的目的是提升执行和打包的速度。
告诉Webpack
解析模块时应该搜索的目录,缩小编译范围,减小没必要要的编译工做。
const {resolve} = require('path')
module.exports = {
entry: {...},
module: {...},
plugins: [],
optimization: {...},
resolve: {
alias: {
'@': resolve(__dirname, '../src'),
},
modules: [
resolve('src'),
resolve('node_modules'),
]
}
}
复制代码
指定loader
的include
目录,做用是缩小编译范围。
const {resolve} = require('path')
module.exports = {
entry: {...},
module: {
rules: [
{
test: /\.css$/,
include: [
resolve("src"),
],
use: ['style-loader', 'css-loader']
}
]
},
plugins: [],
optimization: {...},
resolve: {...}
}
复制代码
babel
缓存目录babel-loader
开始缓存目录cacheDirectory
。
const {resolve} = require('path')
module.exports = {
entry: {...},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
include: [
resolve("src"),
],
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-transform-runtime']
}
}
}
]
},
plugins: [],
optimization: {...},
resolve: {...}
}
复制代码
TerserJSPlugin
TerserJSPlugin
插件的做用是压缩JavaScript
,优化的地方是开启缓存目录和开启多线程。
const {resolve} = require('path')
module.exports = {
entry: {...},
module: {...},
plugins: [],
optimization: {
minimizer: [
new TerserJSPlugin({
parallel: true,
cache: true,
})
]
},
resolve: {...}
}
复制代码
经过此次学习Webpack
到升级脚手架,对前端工程化有了进一步的了解,也感觉到了Webpack4
带来的开箱即用,挺方便的。
参考文章:
Webpack官方文档
【实战】webpack4 + ejs + express 带你撸一个多页应用项目架构
基于 webpack 的持久化缓存方案