-
Notifications
You must be signed in to change notification settings - Fork 0
Description
重学webpack
在开始接触前端工程化的时候,第一个上手的工具就是webpack。最开始使用webpack,只是用在html+css+js
的场景下进行打包配置。在这种场景下,项目工程的复杂度较低,基本上参照着webpack官网,配置好相关参数就可以跑通项目了。后来接触vue后,开始使用vue-cli脚手架简单搭建主体项目环境,也就只是稍微改动一些插件、loader以及其他配置。但是在接触到vue ssr后,逐渐意识到在webpack配置对前端开发的重要性,所以也就有了这篇笔记,重学webpack。
1 初始化项目
1.1 安装依赖
首先初始化项目:
npm init
npm install webpack webpack-cli webpack-dev-server --save-dev
在执行完npm init
后,项目目录下会多出一个package.json
的文件,在这里可以看得到项目下安装的webpack插件,也修改npm命令。
补充说明:
在npm install
安装相关工具时,如果使用了--save
,依赖会添加到package.json
中的dependencies
选项中;如果使用了--save-dev
,依赖会添加到package.json
中的devDependencies
中,注意区分。
在上述提到的插件中,webpack-dev-server
可以提供一个简单的本地服务,并且具有热重载的功能。
1.2 编辑配置
在项目路径下,新建一个名为webpack.config.js
的配置文件,基本结构如下:
module.exports = {
mode: 'development',
entry: ['./src/index.js'],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js' //如果担心缓存影响热更新效果,可以使用[name].[hash:8].js
},
externals: {},
devtool: 'eval',
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
},
module: {
rules: []
},
plugins: [],
optimization: {},
devServer: {}
}
简要说明一下该配置文件的内容:
- mode:开发环境
- devtool:顾名思义,就是方便我们开发调试的配置选项,我们可以在此配置source-map,打包出来的js文件代码映射到代码文件的具体位置,方便我们更快定位代码异常位置。
其中,开发环境推荐使用cheap-module-eval-source-map
,生产环境推荐:
cheap-module-source-map
(这也是下版本 webpack 使用-d命令启动debug模式时的默认选项)
其他的具体配置参数可以参考:webpack官方文档 - devtool以及深入浅出的webpack构建工具---devTool中SourceMap模式详解(四)
- entry:入口文件
- output:打包生成文件,按上述配置,就是将
./src/index.js
打包为./dist/bundle.js
。其中,这里有几个关键的path
变量信息,可参考:webpack中的path、publicPath和contentBase - externals:提供了“从输出的bundle中排除依赖的方法”,比如项目中通过CDN引入了jQuery这样的类库,然后我们又不需要将其打包(如果是通过CDN引入的工具库,都通过这种方式配置),就可以有以下配置:
external: {
jquery: 'jQuery'
}
具体可参考:外部扩展(externals)
另外补充一点,如果需要全局使用jQuery,那么可以在webpack公共配置文件(webpack.base.conf.js)中设置,其中ProvidePlugin
提供和暴露全局变量:
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
}
假如我们要单独打包类似jQuery这样的类库,可以在公共配置中加上设置如下:
module.exports = {
plugins: [],
optmization: {
cacheGroup: {
commons: {
test: /jquery/,
name: 'jquery',
chunks: 'all'
}
}
}
}
- resolve:指定模块如何被解析
- module:模块,每个模块对应一个文件,webpack会从配置的entry开始递归查找出所有依赖的模块
- plugins:扩展插件,webpack在构建的过程中会在特定时机广播对应事件,插件可以监听这些事件,在特定时机做对应的事情
- optimization:优化策略,详见 https://webpack.docschina.org/configuration/optimization/
- devServer:webpack-dev-server配置,比如代理之类的,具体可以查看vue-cli提供的模板
另外的一些概念:
- loader:模块转换器,将原内容转换为新内容
- chunk:代码块,一个chunk由多个模块组合形成,可用于代码分割以及合并
关于webpack配置中的process.env
在node当中,process表示的是当前node的进程,process.env就包括了系统环境信息,但是process.env不包括NODE_ENV这个属性,她是用户自定义的变量,用来判断当前环境。
而我们在package.json
配置中就有关于环境变量NODE_ENV的配置(当然你放在其他文件下也没问题),如:
{
'script': {
'dev': "NODE_ENV=development webpack --config webpack.dev.conf.js"
}
}
这样,我们就将NODE_ENV绑定到了process.env上,并且按以上配置,我们只能在webpack.dev.conf.js中以及它所引入的脚本中访问到process.env.NODE_ENV,其他地方都不可访问到。
2 构建流程
webpack的构建流程是串行的,如下:
- 1.初始化参数:在
package.json
读取对应的命令配置,得出最终参数 - 2.开始编译前的准备:实例化
Compiler
,加载所有plugin,执行对象的run方法并开始编译。接着根据配置中的entry找到所有入口文件 - 3.编译模块:从入口文件出发,调用所有loader对模块进行编译,再找到模块依赖,重复上述步骤知道所有入口文件都经过处理。在loader处理完所有模块后,得到的每个模块以及它们之间的依赖关系图
- 4.导出:根据入口和模块之间的依赖图,将代码组成一个个包含多个模块的chunk文件加入到输出列表,然后根据配置确定的输出路径和文件名,将chunk导出到项目目录中
流程图可以参考这里:https://juejin.im/post/5c6b78cdf265da2da15db125
webpack打包的规则是,一个入口文件对应一个bundle,该bundle包括了入口文件模块和其他依赖模块,按需加载的模块或者需要单独加载的模块则是分开打包生成其他的bundle。在这些bundle中,有一个较为特殊,就是manifest.bundle.js,被称作webpackBootstrap,它是最先加载的,负责解析webpack生成的其他bundle。
3.具体配置
3.1 loader
3.1.1 babel
安装:
npm i babel-loader babel-core babel-preset-env babel/plugin-transform-runtime --save-dev
babel是JavaScript的转码器,可以现代js脚本内容转换为ES5、ES3。下面介绍几个常用的babel loader:
- babel-loader:不解释,必需
- babel/core:核心包,不解释,必需
- babel/preset-env:包括了所有ECMA标准
- babel/preset-es2015:按照ES6标准编译,如果需要更高版本,可以使用如babeil-preset-es2017(ES8),如果需要覆盖最广的,就使用前面提到的babel-preset-env,等价于babel-preset-latest,包括了所有的bebal-preset-esxxxx
- babel-stage-x:其概念类似于前面的preset-esxxxx,所包含的功能和支持的功能插件各有不同,其中,stage-0的功能范围最大,包括了stage-1、stage-2和stage-3的全部功能
- babel-polyfill:polyfill,不解释
- babel/plugin-transform-runtime:按需转换js,可以避免如babel-polyfill出现的全局转换,避免代码污染,也减小了打包体积
补充说明babel-stage-x对应的ECMA标准:
- stage-0:strawman,任何TC39成员都可以提出的草案,随时被废弃。
- stage-1:proposal,这是比较正式的提议,表示要进一步讨论。
- stage-2:draft,在上一步基础上进行尽可能详细的讨论,到了这个阶段后,只允许增量修改。
- stage-3:candidate,对提案的讨论基本完成,等待用户的反馈,只有发生重大问题时才会修改。
- stage-4:经过了充分测试,已经准备写进新标准。
stage标准是向下兼容的,比如stage0包括了stage1、stage2、stage3、stage4的内容。
可以参考:What's the difference between babel-preset-stage-0, babel-preset-stage-1 etc?
在webpack配置对应的babel loader时,也需要在项目下创建.babelrc
文件,下面举出demo:
// vue-cli的配置:
{
"presets": [
"es2015",
"stage-3"
],
"plugins": [
"syntax-dynamic-import"
]
}
// react配置可以参考:
{
"presets": ["es2015", "stage-2", "react"],
"plugins": [
"react-hot-loader/babel",
"transform-function-bind",
"transform-class-properties",
"transform-export-extensions",
],
"env": {
"backend": {
"plugins": [
[ "webpack-loaders",
{ "config": "./webpack.config.babel.js"
, "verbose": true
}
]
]
}
}
}
}
webpack的配置举例如下:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
user: {
loader: 'babel-loader?cacheDirectory=true', // cacheDirectory用于缓存babel的编译结果,加快重新编译的速度
options: {
presets: ['@babel/preset-env'],
plugins: [
'@babel/plughin-transform-runtime',
'@babel/plugin-transform-modules-commonjs'
]
}
}
}
]
}
}
3.1.2 样式相关的loader
常见的css预处理器有less、sass、stylus等,它们都有对应的loader,以less和css的配置为例:
rules: [
{
test: /\.less$/,
exclude: /node_modules/,
use: ['style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
}, 'less-loader', 'postcss-loader']
},
{
test: /\.scss$/,
use: [
"style-loader", // 将 JS 字符串生成为 style 节点
"css-loader", // �将 CSS 转化成 CommonJS 模块
"sass-loader" // 将 Sass 编译成 CSS,默认使用 Node Sass
]
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}
]
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
3.1.3 静态资源相关loader
如果我们要处理一些图片,就很大概率上会接触到file-loader和url-loader,二者有相似之处,二者在处理图片时可以将其打包到dist目录,接着会获取图片模块的地址,并将地址返回到引入模块的变量中。url-loader基本可以实现file-loader的功能,但是在url-loader是将图片转换为base64直接放入bundle.js下,而file-loader会将图片放到dist目录下。
rules: [
{
test: /\.(png|jpg|gif|jpeg)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]', // placeholder 占位符
outputPath: 'images/', // 打包文件名
limit: 204800, // 小于200kb则打包到js文件里,大于则使用file-loader的打包方式打包到imgages里
},
},
},
{
test: /\.(eot|woff2?|ttf|svg)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]-[hash:5].min.[ext]', // 和上面同理
outputPath: 'fonts/',
limit: 5000,
}
},
}
]
3.2 plugin
3.2.1 常用插件
- webpack-merge:提取公共配置,分离生产环境和开发环境的配置文件。比如在vue-cli中会生成
webpack.base.conf.js
(公共配置)、webpack.dev.conf.js
(开发环境配置)、webpack.prod.conf.js
(生产环境配置)的配置文件。然后再合并webpack配置项,比如合并webpack.base.conf.js
和webpack.dev.conf.js
- clean-webpack-plugin:每次编译前自动清空dist目录
- html-webpack-plugin:从html模板自动生成最终的html
- defineplugin:声明process.env.NODE_ENV的值
- extract-text-webpack-plugin:提取css到文件中
- optimize-css-assets-webpack-plugin:合并相同的css样式文件
- css-split-webpack-plugin:拆分过大的csss文件
- mini-css-extract-plugin:分开打包css文件
- webpack-md5-hash:webpack自身生成文件hash值的方法是不确定的,为了确保hash值是根据文件内容生成的,可以使用该插件
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = reuqire('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
// 多入口文件打包
entry: {
index: './src/js/index.js',
login: './src/js/login.js'
},
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
},
plugins: [
new webpack.HotModuleReplacementPlugin(), // 开启全局的模块热替换(HMR)
new webpack.NamedModulesPlugin(), // 当模块热替换(HMR)时在浏览器控制台输出对用户更友好的模块名字信息
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
new CleanWebpackPlugin(),
// 以下两个热重载插件
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
new ExtractTextPlugin('css/[name].[hash].css'),
// 多页打包
new HtmlWebpackPlugin({
filename: 'index.html',
template: './src/index.html',
chunks: ['index']
}),
new HtmlWebpackPlugin({
filename: 'login.html',
template: './src/login.html',
chunks: ['login']
}),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: devMode ? '[name].css' : '[name].[hash].css',
chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
}),
new CSSSplitWebpackPlugin({
size: 4000,
filename: '[name]-[part].[ext]'
})
],
module: {
noParse:/jquery|lodash/, // 对于没有使用require和import,而是通过CDN引用的模块,不需要使用webpack进行解析依赖
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader',
],
}
]
},
output: {
filename: 'js/[name].js',
path: path.resolve(__dirname, 'dist')
}
}
4 优化
4.1 巧用resolve
可以通过resolve.modules:[path.resolve(__dirname, 'node_modules')]
来定位第三方模块,默认是node_modules
;设置resolve.alias
来生成一个快速引用符号指向目标目录;在使用loader的时候,可以通过test、exclude、include、正则来缩小搜索范围
- resolve.extensions:使用require或import引入文件时可以省略后缀:
- resolve.alias:简化引用路径:
- resolve.mainFields:通过第三方插件的package.json中main字段(大多数第三方模块都采用这个字段)中的地址来引用文件
- resolve.modules:限制引入第三方包的范围
- resolve.mainFiles:在一定范围下默认优先查找的文件名
另外,使用alias
可以加快webpack查找模块的速度。
resolve: {
extensions: ['.js', '.css', '.vue'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
},
mainFields: ['style', 'main'],
modules:[path.resolve(__dirname, node_modules), my_modules], //只能引入node_modules和mu_modules下的包
mianFiles:['index.js','main.js']
}
4.2 抽离公共代码
如果在两个组件中都引用了共同的组件,那么我们可以将公共组件代码抽离出来
const path = require('path');
module.export = {
entry: {
index: './src/index',
login: './src/login'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
optimization: {
splitChunks: {
cacheGroups: {
common: {
chunks: 'initial',
minChunks: 2, // 使用两个同时引用的模块时才抽离出来
minSize: 0 // 限制大小,小于指定值就不抽离
}
}
}
}
}
最后打包的结果中会生成index.js
和login.js
或者是:
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
plugins:[
new CommonsChunkPlugin({
chunks:['chunkA','chunkB'], //从哪些chunk中提取
name:'common', // 提取出的公共部分生成一个新的chunk,文件为common.js
})
]
4.3 使用DllPlugin减少基础模块的编译次数
DllPlugin是动态链接库插件,原理是将网页依赖的基础模块抽离出来打包到dll文件中,当需要导入的模块存在于某个dll文件中,该模块不再被打包,而是通过dll获取。
// webpack.dll.config.js
const path = require('path');
const webpack = require('webpack');
module.exports ={
entry: {
vendor: ['react', 'redux', 'react-router'],
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].dll.js',
library: '[name]_[hash]' //提供全局的变量
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, 'dist', '[name].manifest.json'),
name: '[name]_[hash]',
}),
],
};
// package.json
"scripts": {
"dev": "webpack-dev-server",
"build": "webpack",
"dll":"webpack --config webpack.dll.config.js"
},
// webpack.base.conf.js
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname,'dist'),
filename: 'index_bundle.js',
},
plugins: [
new webpack.DllReferencePlugin({
context: path.join(__dirname),
manifest:path.resolve(__dirname,'dist','vendor.manifest.json')
}),
new HtmlWebpackPlugin(),
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname,'dist','vendor.manifest.json')
}),
],
};
作者:梦想攻城狮
链接:https://juejin.im/post/5bd128b56fb9a05ce1729333
来源:掘金
打包后生成:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
</head>
<body>
<script type="text/javascript" src="vendor-manifest.json"></script>
<script type="text/javascript" src="index_bundle.js"></script>
</body>
</html>
4.4 使用IgnorePlugin忽略某些模块打包
IgnorePlugin可以让webpack不打包指定的模块:
module.exports = {
...
plugins: [
new webpack.IgnorePlugin(/moment$/)
]
...
}
4.5 生产环境中将提前构建的包同步到dist
const CopyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
plugins: [
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
}
4.6 代码压缩
webpack内置的uglifyjs插件可以实现js代码压缩:
const UglifyJSPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
//...
plugins: [
new UglifyJSPlugin({
compress: {
warnings: false, //删除无用代码时不输出警告
drop_console: true, //删除所有console语句,可以兼容IE
collapse_vars: true, //内嵌已定义但只使用一次的变量
reduce_vars: true, //提取使用多次但没定义的静态值到变量
},
output: {
beautify: false, //最紧凑的输出,不保留空格和制表符
comments: false, //删除所有注释
}
})
]
作者:superMaryyy
链接:https://juejin.im/post/5b652b036fb9a04fa01d616b
来源:掘金
另外,使用css-loader?minimize
不仅可以删除样式文件的空格,还可以语义化地压缩css代码(rgb转色彩名)
4.7 使用静态资源CDN
参考:
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const {WebPlugin} = require('web-webpack-plugin');
//...
output:{
filename: '[name]_[chunkhash:8].js',
path: path.resolve(__dirname, 'dist'),
publicPatch: '//js.cdn.com/id/', //指定存放JS文件的CDN地址
},
module:{
rules:[{
test: /\.css/,
use: ExtractTextPlugin.extract({
use: ['css-loader?minimize'],
publicPatch: '//img.cdn.com/id/', //指定css文件中导入的图片等资源存放的cdn地址
}),
},{
test: /\.png/,
use: ['file-loader?name=[name]_[hash:8].[ext]'], //为输出的PNG文件名加上Hash值
}]
},
plugins:[
new WebPlugin({
template: './template.html',
filename: 'index.html',
stylePublicPath: '//css.cdn.com/id/', //指定存放CSS文件的CDN地址
}),
new ExtractTextPlugin({
filename:`[name]_[contenthash:8].css`, //为输出的CSS文件加上Hash
})
]
作者:superMaryyy
链接:https://juejin.im/post/5b652b036fb9a04fa01d616b
来源:掘金
4.8 多进程打包
可以使用HappyPack
这个插件进行webpack的多进程打包,以下放出网上找来的一段相关配置,来自:加速 Webpack,文章内也有具体的配置说明,详情可点击链接访问
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const HappyPack = require('happypack');
module.exports = {
module: {
rules: [
{ test: /\.js$/,
// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use:['happypack/loader?id=babel'],
// 排除 node_modules 目录下的文件,node_modules目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换
exclude: path.resolve(__dirname, 'node_modules'),
},
{
// 把对 .css 文件的处理转交给 id 为 css 的 HappyPack 实例
test: /\.css$/,
use:ExtractTextPlugin.extract({
use: ['happypack/loader?id=css'],
}),
},
] },
plugins: [
new HappyPack({
// 用唯一的标识符 id 来代表当前的HappyPack 是用来处理一类特定的文件
id: 'babel',
// 如何处理 .js 文件,用法和 Loader配置中一样
loaders: ['babel-loader?cacheDirectory'],
}),
new HappyPack({
id: 'css',
// 如何处理 .css 文件,用法和Loader 配置中一样
loaders: ['css-loader'], }),
new ExtractTextPlugin({
filename: `[name].css`,
}),
],
};
4.9 runtime和manifest
在webpack项目构建中,有三种基本的代码类型:
- 1.自身编写的代码
- 2.第三方依赖代码(vendor)
- 3.webpack中的runtime和manifest,管理所有模块的交互
在单页应用中,客户端发起网络请求后,从服务端返回的文件主要是入口的html文件和一系列bundle文件,然后交由浏览器解析处理。经由webpack打包过后的项目,在不同的打包策略模式下,代码的执行逻辑是不同的,但是它们都需要通过webpack来进行管理模块间的交互。而runtime和manifest就是指在浏览器运行时,webpack通过连接模块化应用程序的所有代码。runtime包括了在模块交互时,连接模块所需的加载和解析逻辑,包括浏览器中已加载模块的连接、懒加载模块的执行逻辑。
为了更加清晰地理解manifest,我们简要复述一遍webpack的工作流程:
- 从入口文件开始递归,生成依赖关系图
- 将所有文件转换为模块
- 根据依赖关系图,按照配置文件将模块分组打包生成若干bundle
- 通过script标签将打包的bundle引入html,通过manifest管理bundle文件的运行和加载
当编译器(compiler)开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "Manifest",当完成打包并发送到浏览器时,会在运行时通过 Manifest 来解析和加载模块。无论你选择哪种模块语法,那些 import 或 require 语句现在都已经转换为 __webpack_require__ 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够查询模块标识符,检索出背后对应的模块。
-- webpack官方文档
当webpack写入bundle时,它会维护一个manifest,你可以在项目中生成的bundle找到它,它内部描述了webpack应该加载的文件。如果文件的hash值改变,manifest也会改变,然后也跟着一些分离出来的代码共同打包。这显然不是我们想要的,所以我们需要提取出manifest:
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest' // 用于提取manifest
})
4.10 缓存优化
浏览器加载js文件时,如果文件名和浏览器本地缓存一致,那么就不会向后台发送请求。假如我们的业务代码中做了修改,但是webpack打包出来的文件名对应的hash值不变,那么就不会用上新代码。另外,在前端项目中一般是只有业务代码才会频繁变化,而第三方依赖是很少变化的,所以我们希望浏览器不用再重复地向服务端发送请求,这就要求我们进行代码分块打包,将一些第三方依赖集中整合进单个bundle下。所以,我们可以借助webpack.optimize.CommonsChunkPlugin将manifest、第三方依赖、业务代码分别独立打包
4.11 打包分析
可以使用webpack-bundle-analyzer
对webpack打包结果进行分析,支持可视化的。
module.exports = {
entry: './index.js',
output: {
filename: 'bundle[chunkhash:8].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// 将node_modules的依赖集中打包,注意主次关系,依赖要放在manifest前面
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// 抽出mainfest
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
})
]
}
参考:
- Github 【翻译】Webpack——令人困惑的地方
- 掘金 - 超详细使用webpack4.x搭建标准前端项目
- 掘金 - webpack4 的30个步骤打造优化到极致的 react 开发环境,如约而至
- 掘金 - 利用webpack搭建脚手架一套完整流程
- 掘金 - 三十分钟掌握Webpack性能优化
- 掘金 - webpack由浅入深——(webpack优化配置)
- 使用webpack4提升180%编译速度
- 掘金 - 用 webpack 实现持久化缓存
- CSDN - webpack打包原理和manifest文件分析
- 掘金 - webpack增量打包多页应用
扩展阅读: