Webpack
启动后会从配置的 Entry
出发,解析出文件中的导入语句,再递归的解析。
缩小文件搜索范围
- 优化
loader
配置
为了尽可能少的让文件被Loader
处理,可以通过include
去命中只有哪些文件需要被处理。 - 优化
resolve.modules
配置
可以指明存放第三方模块的绝对路径,以减少寻找。 - 优化
resolve.mainFields
配置
用于配置采用哪个字段作为入口文件的描述。
使用本方法优化时,需要考虑到所有运行时依赖的第三方模块的入口文件描述字段,就算有一个模块搞错了都可能会造成构建出的代码无法正常运行。
- 优化
resolve.alias
配置
通过别名来把原导入路径映射成一个新的导入路径。 - 优化
resolve.extensions
配置
在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试询问文件是否存在,resolve.extensions
用于配置在尝试过程中用到的后缀列表。 - 优化
module.noParse
配置
让Webpack
忽略对部分没采用模块化的文件的递归解析处理,提高构建性能。
被忽略掉的文件里不应该包含
import 、 require 、 define
等模块化语句,不然会导致构建出的代码中包含无法在浏览器环境下执行的模块化语句。
使用 DllPlugin(webpack4不建议使用)
把网页依赖的基础模块抽离出来,打包到一个个单独的动态链接库中去。一个动态链接库中可以包含多个模块。
Webpack
已经内置了对动态链接库的支持,需要通过2个内置的插件接入,它们分别是:
-
DllPlugin
插件:用于打包出一个个单独的动态链接库文件。 -
DllReferencePlugin
插件:用于在主要配置文件中去引入DllPlugin
插件打包好的动态链接库文件。
使用 HappyPack
运行在 Node.js
之上的 Webpack
是单线程模型的,
HappyPack
把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程,从而让 Webpack
同一时刻处理多个任务,发挥多核 CPU
电脑的威力。
由于
JavaScript
是单线程模型,要想发挥多核CPU
的能力,只能通过多进程去实现,而无法通过多线程实现。
使用 ParallelUglifyPlugin
当 Webpack
有多个 JavaScript
文件需要输出和压缩时,会使用 UglifyJS
去一个个挨着压缩再输出, 但是 ParallelUglifyPlugin
则会开启多个子进程,把对多个文件的压缩工作分配给多个子进程去完成,每个子进程其实还是通过 UglifyJS
去压缩代码,但是变成了并行执行。 所以 ParallelUglifyPlugin
能更快的完成对多个文件的压缩工作。
使用自动刷新
让 Webpack
开启监听模式,有两种方式:
- 在配置文件
webpack.config.js
中设置watch: true
。 - 在执行启动
Webpack
命令时,带上--watch
参数,完整命令是webpack --watch
。
文件监听工作原理:
定时获取文件的最后编辑时间,并存储,如果发现当前获取的和最后一次保存的最后编辑时间不一致,就认为该文件发生了变化。 watchOptions.poll
就是用于控制定时检查的周期,具体含义是每隔多少毫秒检查一次。
当发现某个文件发生了变化时,并不会立刻告诉监听者,而是先缓存起来,收集一段时间的变化后,再一次性告诉监听者。 watchOptions.aggregateTimeout
就是用于配置这个等待时间。
默认情况下 Webpack
会从配置的 Entry
文件出发,递归解析出 Entry
文件所依赖的文件,并都加入到监听列表中去。
由于保存文件的路径和最后编辑时间需要占用内存,定时检查周期检查需要占用 CPU
以及文件 I/O
,所以最好减少需要监听的文件数量和降低检查频率。
优化文件监听性能:ignored: /node_modules/
自动刷新浏览器:
webpack
模块负责监听文件,webpack-dev-server
模块则负责刷新浏览器。 在使用 webpack-dev-server
模块去启动 webpack
模块时,webpack
模块的监听模式默认会被开启。 webpack
模块会在文件发生变化时告诉 webpack-dev-server
模块。
自动刷新的原理:
- 借助浏览器扩展去通过浏览器提供的接口刷新,WebStorm IDE 的 LiveEdit 功能就是这样实现的。
- 往要开发的网页中注入代理客户端代码,通过代理客户端去刷新整个页面。
- 把要开发的网页装进一个 iframe 中,通过刷新 iframe 去看到最新效果。
DevServer
支持第2、3种方法,第2种是DevServer
默认采用的刷新方法。
开启模块热替换
当一个源码发生变化时,只重新编译发生变化的模块,再用新输出的模块替换掉浏览器中对应的老模块。
DevServer
默认不会开启模块热替换模式,要开启该模式,只需在启动时带上参数 --hot
,完整命令是 webpack-dev-server --hot
。
区分环境
当你的代码中出现了使用 process
模块的语句时,Webpack
就自动打包进 process
模块的代码以支持非 Node.js
的运行环境。 这个注入的 process
模块作用是为了模拟 Node.js
中的 process
,以支持上面使用的 process.env.NODE_ENV === 'production'
语句。
const DefinePlugin = require('webpack/lib/DefinePlugin');
module.exports = {
plugins: [
new DefinePlugin({
// 定义 NODE_ENV 环境变量为 production
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
],
};
注意在定义环境变量的值时用
JSON.stringify
包裹字符串的原因是环境变量的值需要是一个由双引号包裹的字符串,而JSON.stringify('production')
的值正好等于'"production"'
。
压缩代码
压缩
JavaScript
目前最成熟的JavaScript
代码压缩工具是UglifyJS
, 它会分析JavaScript
代码语法树,理解代码含义,从而能做到诸如去掉无效代码、去掉日志输出代码、缩短变量名等优化。
直接在启动Webpack
时带上--optimize-minimize
参数,即webpack --optimize-minimize
, 这样Webpack
会自动为你注入一个带有默认配置的UglifyJSPlugin
。压缩
ES6
UglifyJS
只认识ES5
语法的代码。 为了压缩ES6
代码,需要使用专门针对ES6
代码的UglifyES
。压缩
CSS
要开启cssnano
去压缩代码只需要开启css-loader
的minimize
选项。
CDN 加速
CDN
又叫内容分发网络,通过把资源部署到世界各地,用户在访问时按照就近原则从离用户最近的服务器获取资源,从而加速资源的获取速度。
CDN
其实是通过优化物理链路层传输过程中的网速有限、丢包等问题来加速网络传输的。
过 publicPath
参数设置存放静态资源的 CDN
目录 URL
, 为了让不同类型的资源输出到不同的 CDN
,需要分别在:
-
output.publicPath
中设置JavaScript
的地址。 -
css-loader.publicPath
中设置被CSS
导入的资源的的地址。 -
WebPlugin.stylePublicPath
中设置CSS
文件的地址。
使用 Tree Shaking
Tree Shaking
可以用来剔除 JavaScript
中用不上的死代码。它依赖静态的 ES6
模块化语法。
提取公共代码
Webpack
内置了专门用于提取多个 Chunk
中公共部分的插件 CommonsChunkPlugin
。
按需加载
使用 Prepack
Prepack
在保持运行结果一致的情况下,改变源代码的运行逻辑,输出性能更高的 JavaScript
代码。 实际上 Prepack
就是一个部分求值器,编译代码时提前将计算结果放到编译后的代码中,而不是在代码运行时才去求值,从而优化代码在运行时的效率。
Prepack
还处于初期的开发阶段,局限性也很大。
开启 Scope Hoisting,又叫作用域提升
原理:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余。 因此只有那些被引用了一次的模块才能被合并。
输出分析
接入 webpack-bundle-analyzer
:
npm i -g webpack-bundle-analyzer
- 启动
Webpack
命令:webpack --profile --json > stats.json
- 在项目根目录中执行
webpack-bundle-analyzer
后,浏览器会打开对应网页看到分析结果。
又话总结
- 侧重优化开发体验的配置文件
webpack.config.js
const path = require('path');
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
const {AutoWebPlugin} = require('web-webpack-plugin');
const HappyPack = require('happypack');
// 自动寻找 pages 目录下的所有目录,把每一个目录看成一个单页应用
const autoWebPlugin = new AutoWebPlugin('./src/pages', {
// HTML 模版文件所在的文件路径
template: './template.html',
// 提取出所有页面公共的代码
commonsChunk: {
// 提取出公共代码 Chunk 的名称
name: 'common',
},
});
module.exports = {
// AutoWebPlugin 会为寻找到的所有单页应用,生成对应的入口配置,
// autoWebPlugin.entry 方法可以获取到生成入口配置
entry: autoWebPlugin.entry({
// 这里可以加入你额外需要的 Chunk 入口
base: './src/base.js',
}),
output: {
filename: '[name].js',
},
resolve: {
// 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
// 其中 __dirname 表示当前工作目录,也就是项目根目录
modules: [path.resolve(__dirname, 'node_modules')],
// 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件,使用 Tree Shaking 优化
// 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
mainFields: ['jsnext:main', 'main'],
},
module: {
rules: [
{
// 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
test: /\.js$/,
// 使用 HappyPack 加速构建
use: ['happypack/loader?id=babel'],
// 只对项目根目录下的 src 目录中的文件采用 babel-loader
include: path.resolve(__dirname, 'src'),
},
{
test: /\.js$/,
use: ['happypack/loader?id=ui-component'],
include: path.resolve(__dirname, 'src'),
},
{
// 增加对 CSS 文件的支持
test: /\.css$/,
use: ['happypack/loader?id=css'],
},
]
},
plugins: [
autoWebPlugin,
// 使用 HappyPack 加速构建
new HappyPack({
id: 'babel',
// babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
loaders: ['babel-loader?cacheDirectory'],
}),
new HappyPack({
// UI 组件加载拆分
id: 'ui-component',
loaders: [{
loader: 'ui-component-loader',
options: {
lib: 'antd',
style: 'style/index.css',
camel2: '-'
}
}],
}),
new HappyPack({
id: 'css',
// 如何处理 .css 文件,用法和 Loader 配置中一样
loaders: ['style-loader', 'css-loader'],
}),
// 提取公共代码
new CommonsChunkPlugin({
// 从 common 和 base 两个现成的 Chunk 中提取公共的部分
chunks: ['common', 'base'],
// 把公共的部分放到 base 中
name: 'base'
}),
],
watchOptions: {
// 使用自动刷新:不监听的 node_modules 目录下的文件
ignored: /node_modules/,
}
};
- 侧重优化输出质量的配置文件
webpack-dist.config.js
const path = require('path');
const DefinePlugin = require('webpack/lib/DefinePlugin');
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const {AutoWebPlugin} = require('web-webpack-plugin');
const HappyPack = require('happypack');
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
// 自动寻找 pages 目录下的所有目录,把每一个目录看成一个单页应用
const autoWebPlugin = new AutoWebPlugin('./src/pages', {
// HTML 模版文件所在的文件路径
template: './template.html',
// 提取出所有页面公共的代码
commonsChunk: {
// 提取出公共代码 Chunk 的名称
name: 'common',
},
// 指定存放 CSS 文件的 CDN 目录 URL
stylePublicPath: '//css.cdn.com/id/',
});
module.exports = {
// AutoWebPlugin 会找为寻找到的所有单页应用,生成对应的入口配置,
// autoWebPlugin.entry 方法可以获取到生成入口配置
entry: autoWebPlugin.entry({
// 这里可以加入你额外需要的 Chunk 入口
base: './src/base.js',
}),
output: {
// 给输出的文件名称加上 Hash 值
filename: '[name]_[chunkhash:8].js',
path: path.resolve(__dirname, './dist'),
// 指定存放 JavaScript 文件的 CDN 目录 URL
publicPath: '//js.cdn.com/id/',
},
resolve: {
// 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
// 其中 __dirname 表示当前工作目录,也就是项目根目录
modules: [path.resolve(__dirname, 'node_modules')],
// 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
mainFields: ['jsnext:main', 'main'],
},
module: {
rules: [
{
// 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
test: /\.js$/,
// 使用 HappyPack 加速构建
use: ['happypack/loader?id=babel'],
// 只对项目根目录下的 src 目录中的文件采用 babel-loader
include: path.resolve(__dirname, 'src'),
},
{
test: /\.js$/,
use: ['happypack/loader?id=ui-component'],
include: path.resolve(__dirname, 'src'),
},
{
// 增加对 CSS 文件的支持
test: /\.css$/,
// 提取出 Chunk 中的 CSS 代码到单独的文件中
use: ExtractTextPlugin.extract({
use: ['happypack/loader?id=css'],
// 指定存放 CSS 中导入的资源(例如图片)的 CDN 目录 URL
publicPath: '//img.cdn.com/id/'
}),
},
]
},
plugins: [
autoWebPlugin,
// 开启ScopeHoisting
new ModuleConcatenationPlugin(),
// 使用HappyPack
new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'babel',
// babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
loaders: ['babel-loader?cacheDirectory'],
}),
new HappyPack({
// UI 组件加载拆分
id: 'ui-component',
loaders: [{
loader: 'ui-component-loader',
options: {
lib: 'antd',
style: 'style/index.css',
camel2: '-'
}
}],
}),
new HappyPack({
id: 'css',
// 如何处理 .css 文件,用法和 Loader 配置中一样
// 通过 minimize 选项压缩 CSS 代码
loaders: ['css-loader?minimize'],
}),
new ExtractTextPlugin({
// 给输出的 CSS 文件名称加上 Hash 值
filename: `[name]_[contenthash:8].css`,
}),
// 提取公共代码
new CommonsChunkPlugin({
// 从 common 和 base 两个现成的 Chunk 中提取公共的部分
chunks: ['common', 'base'],
// 把公共的部分放到 base 中
name: 'base'
}),
new DefinePlugin({
// 定义 NODE_ENV 环境变量为 production 去除 react 代码中的开发时才需要的部分
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
new ParallelUglifyPlugin({
// 传递给 UglifyJS 的参数
uglifyJS: {
output: {
// 最紧凑的输出
beautify: false,
// 删除所有的注释
comments: false,
},
compress: {
// 在UglifyJs删除没有用到的代码时不输出警告
warnings: false,
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
},
}),
]
};