以下配置是在webpack 4.41.6+测试
目录
可用于生产环境:
- babel-loader缓存优化
- ignoreplugin
- noparse
- happyPack
- ParallelUglifyPlugin
不可用于生产环境的:
- 自动刷新
- 热更新
- DllPlugin
babel-loader的缓存优化
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory', // 开启缓存
include: path.resolve(__dirname, 'src'), // 明确范围
// 排除范围,include和exclude两者选一个就行
// exclude: path.resolve(__dirname, 'node_modules')
}
]
}
这里的?cacheDirectory
放在babel-loader
后面,把语法转换的代码缓存下来。只要ES6
代码没有改变的,第二次编译的时候,这些ES6
没有改动的部分就不会重新编译,直接使用缓存,编译速度更快。
或者这样写
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true
}
},
一般来说,一个loader
写成loader:"babel-loader"
这种字符串的形式,多个loader
写成use:["babel-loader", "eslint-loader"]
字符串数组的形式
happyPack多线程打包
HappyPack
是一个通过多线程来提升Webpack
打包速度的工具,不是多进程,很多博客写的多进程,为此我查阅github
的happypack插件说明明确说到是多线程。
在打包过程中有一项非常耗时的工作,就是使用loader
将各种资源进行转译处理
我们可以简单地将代码转译的工作流程概括如下:
1)从配置中获取打包入口;
2)匹配loader
规则,并对入口模块进行转译;
3)对转译后的模块进行依赖查找(如a.js
中加载了b.js
和c.js
);
4)对新找到的模块重复进行步骤2)和步骤3),直到没有新的依赖模块。
不难看出从步骤2)到步骤4)是一个递归的过程,Webpack
需要一步步地获取更深层级的资源,然后逐个进行转译。
这里的问题在于Webpack
是单线程的,假设一个模块依赖于几个其他模块,Webpack
必须对这些模块逐个进行转译。虽然这些转译任务彼此之间没有任何依赖关系,却必须串行地执行。HappyPack
恰恰以此为切入点,它的核心特性是可以开启多个线程,并行地对不同模块进行转译,这样就可以充分利用本地的计算资源来提升打包速度。
HappyPack
适用于那些转译任务比较重的工程,当我们把类似babel-loader
和ts-loader
迁移到HappyPack
之上后,一般都可以收到不错的效果,而对于其他的如sass-loader、less-loader
本身消耗时间并不太多的工程则效果一般。
每次webpack
解析模块时,HappyPack
都会获取它及其所有依赖项,并将这些文件分发到多个工作程序“线程”。如下图
在实际使用时,要用HappyPack
提供的loader
来替换原有loader
,并将原有的那个通过HappyPack
插件传进去。请看下面的例子
单个loader
的优化(一般不用这个方式,都是使用多个loader
的优化,多个loader
只写一个就是单个loader
)
// 初始Webpack配置(使用HappyPack前)
module.exports = {
//...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: ['react'],
},
}
],
},
};
// 使用HappyPack的配置
const HappyPack = require('happypack');
module.exports = {
//...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'happypack/loader',
}
],
},
plugins: [
new HappyPack({
loaders: [
{
loader: 'babel-loader',
options: {
presets: ['react'],
},
}
],
})
],
};
在module.rules
中,我们使用happypack/loader
替换了原有的babel-loader
,并在plugins
中添加了HappyPack
的插件,将原有的babel-loader
连同它的配置插入进去即可。
多个loader
的优化
提高构建速度,利用好多核CPU
1.安装happyPack
2.引入const HappyPack = require('happypack')
3.使用
在使用HappyPack
优化多个loader
时,需要为每一个loader
配置一个id
,否则HappyPack
无法知道rules
与plugins
如何一一对应。
module: {
rules: [
{
test: /\.js$/,
// 把对 .js 文件的处理转交给 id 为 babel123 的 HappyPack 实例
loader: 'happypack/loader?id=babel123',
include: path.resolve(__dirname, 'src'), // 明确范围
// 排除范围,include和exclude两者选一个就行
// exclude: path.resolve(__dirname, 'node_modules')
},
{
test: /\.ts$/,
include: path.resolve(__dirname, 'src'), // 明确范围
loader: 'happypack/loader?id=ts',
}
]
},
plugins: [
// ...省略其他代码
// happyPack 开启多线程打包
new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'babel123',
// 如何处理 .js 文件,用法和 Loader 配置中一样
use: ['babel-loader?cacheDirectory']
// 这里写成loaders: ['babel-loader?cacheDirectory']也可以
// 这里必须用数组形式
}),
new HappyPack({
id: 'ts',
use: [{
loader: 'ts-loader',
options: {}, // ts options
}],
})
],
如果你的happyPack
的id
对应不上就会报如下错误
AssertionError [ERR_ASSERTION]: HappyPack: plugin for the loader 'babel123' could not be found! Did you forget to add it to the plugin list?...
在使用多个HappyPack loader
的同时也就意味着要插入多个HappyPack
的插件,每个插件加上id
来作为标识。同时我们也可以为每个插件设置具体不同的配置项,如使用的线程数、是否开启debug
模式等。
ParallelUglifyPlugin多进程压缩JS
现在的webpack
内置Uglify
工具压缩js
,只要你是生产环境就会自动压缩js
(当然你webpack
版本太旧是不能自动在生产环境压缩的),因为JS
是单线程的,开启多线程会压缩的更快。
1.安装webpack-parallel-uglify-plugin
2.引入const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
plugins: [
// ...省略部分无关代码
new ParallelUglifyPlugin({
// 传递给 UglifyJS 的参数
// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
uglifyJS: {
output: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
}
})
],
热更新
热更新:新代码生效,网页不刷新,状态不丢失
自动网页刷新状态会丢失
自动刷新会用到devServer
1.引入const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');
2.在plugins
加入配置
plugins: [
// ...省略其他无关代码
new HotModuleReplacementPlugin()
],
3.在devServer
加入hot: true
devServer: {
port: 8080,
progress: true, // 显示打包的进度条
contentBase: distPath, // 根目录
open: true, // 自动打开浏览器
compress: true, // 启动 gzip 压缩
hot: true, // ======在这里加入热更新配置=============
// 设置代理
proxy: {
// 将本地 /api/xxx 代理转发到 http://localhost:3000/api/xxx
'/api': 'http://localhost:3000',
// 将本地 /api2/xxx 代理转发到 http://localhost:3000/xxx
'/api2': {
target: 'http://localhost:3000',
pathRewrite: {
'/api2': '' // 将路径中的'/api2'变为''空串
}
}
}
},
举例子:
这里开启devServer
,如果不是热更新,我们修改代码会自动刷新整个网页。如果每次刷新都会有网络请求,增加了后台负担;如果填写都表单有数据,网页刷新表单数据会丢失;如果你进了路由都子路由的子路由,层级比较深,而刷新后又回到了根路由…
开启热更新之后,需要热更新部分加上监听
// 增加,开启热更新之后的代码逻辑
if (module.hot) {
module.hot.accept(['./math.js'], () => {
const sumRes = sum(10, 30)
console.log('sumRes in hot', sumRes)
})
}
那么你只要修改了math.js
里面的代码,就只会热更新,执行这里module.hot.accept
的第二个参数----回调函数中的内容。
并且这里不会清空你在Console
中定义的变量值,不会清空你在input
框里面的值,因为它并不会刷新整个网页,仅仅只是针对math.js
里面的东西作出响应。
webpack在生产环境的常用优化思路
1.小图片base64编码
module: {
rules: [
// ...省略无关代码
// 图片 - 考虑 base64 编码的情况
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
// 小于 5kb 的图片用 base64 格式产出
// 否则,依然延用 file-loader 的形式,产出 url 格式
limit: 5 * 1024,
// 打包到 img 目录下
outputPath: '/img1/',
// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)
// publicPath: 'http://cdn.abc.com'
}
}
},
]
},
这个例子,小于5kb
以base64
产出,url-loader
处理,打包到了对应js
,这样就不会单独打包成图片,减少网络请求的耗时。
太大对图片就单独打包成图片,避免js文件过大,下载太耗时导致页面渲染卡住。
2.bundle加上hash值
output: {
// filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳
filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
path: path.join(__dirname, '..', 'dist'),
// publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
加上contentHash
是因为只要文件js内容不变,这个contentHash
值就不会变,这样上线之后用户发起请求可以命中缓存,直接取本地缓存,当内容变化之后contentHash
变化,缓存失效,再发起请求拉去新的文件。
为什么不用[hash]
而是[contentHash]
,因为webpack
每次打包都会有一个hash
,而且每次不一样,这样每次还是回去请求新的文件,没有利用到缓存,失去了意义。
后面对:8
是取contentHash
值的前8位。
CSS
操作也是一样,css-loader
是将css
文件变成commonjs
模块加载js中,里面内容是样式字符串,这样CSS
文件就放在了打包后的JS
文件中,当多个JS
引入相同的CSS
的时候,如果这样操作,每个打包出来的CSS
文件都放在不同的JS
文件中,而这些CSS
又是重复的样式,所以需要把CSS
提取出来减小JS
体积,我们一般会对CSS
文件命名,这里也是加上了[contentHash:8]
plugins: [
// ...省略无关代码
// 抽离 css 文件
new MiniCssExtractPlugin({
filename: 'css/main.[contentHash:8].css'
})
],
3.懒加载和预加载
比较大的文件用懒加载(异步加载)
document.getElementById('btn').onclick = function() {
// 懒加载~:当文件需要使用时才加载~
// 预加载 prefetch:会在使用之前,提前加载js文件
// 正常加载可以认为是并行加载(同一时间加载多个文件)
// 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
};
为什么选择懒加载呢?
这样可以提高代码覆盖率。也就是一个js
里面的代码使用率提高。我们可以在MAC
电脑使用快捷键command+shift+P
,输入coverage
–>选择Show Coverage
,然后如下图所示点击
如果不使用懒加载,你的代码属于Unused Bytes
,使用了之后,你的代码是是属于Used Bytes
,我们的目的就是提高Used Bytes
,这样就提高了代码覆盖率。优化首先考虑代码覆盖率再才会考虑缓存。
这里写了/* webpackChunkName: 'test', webpackPrefetch: true */
表示这里的回调函数的内容会打包到chunkName为test
到js中,默认entry
我们是单入口文件,比如
entry: './src/js/index.js',
实际上等同于
entry: {
main: './src/js/index.js' // 这个默认的main就是默认的webpackChunkName
}
webpackChunkName
是main
,当我们把/* webpackChunkName: 'test' */
之后就指定webpackChunkName
是test
,所以console.log(mul(4, 5));
会打包到test.[contentHash:8].js
中
当然,你的输出文件名仍然是可以在output修改的
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build'),
chunkFilename: 'js/[name].[contenthash:10]_chunk.js' // 这个[name]是你/* webpackChunkName: 'xxx'*/指定的,打包出来就是js/xxx.[contentHash:10]_chunk.js
// 如果你不指定webpackChunkName,这里就会输出js/[id].[contentHash:10]_chunk.js,以从0开始的数字往后命名,看你webpack打包日志的chunks这一项是什么数字,这个[id]就会显示多少
},
这个就不多说了,不然篇幅太长。
这里还提到了/* webpackPrefetch: true */
,懒加载是等用到的时候再去发起请求获取数据,而预加载是等网络带宽空闲时去加载,比如这里的test.[contentHash:8].js
是现在不需要的,后面可能会用到,但是这里是在onclick
监听事件里面才import()
,属于宏任务,宏任务一定会在尝试一次DOM渲染之后才执行, 所以在这个例子中是渲染一次完成了再去加载,然后当你点击触发获取test.[contentHash:8].js
的时候就不用再发起请求了,直接在本地加载,速度看起来更快。预加载目前在一些浏览器和移动端可能不支持。
举个例子:比如网页登录按钮点击之后弹出提示登录的操作,很显然我们需要懒加载这个登录界面,那么如果我点击按钮之后才去请求这个js
(创建DOM
结点操作显示界面),会不会有点慢,让人感觉会卡顿一下?那么这里的预加载就是很好的一种方案了。在网络带宽空闲的时候会去把这个预加载的js
下载下来,再次加载的时候之后从缓存请求这个js
,速度就非常快了。
有人可能会问了,这里在onlick
事件里面,我没去点击按钮,没触发这个回调你怎么知道我回调函数里面有个预加载或者懒加载?因为DOM
事件是宏任务,在你的同步代码执行完=>微任务=>尝试DOM
渲染=>宏任务,按照这样的执行顺序来的。如果你不了解JS
异步,可以看看这里JS 异步进阶【想要进大厂,更多异步的问题等着你】
4.提取公共代码(我想多说一点东西)
optimization: {
splitChunks: {
// initial 入口chunk,对于异步导入的文件不处理
// async 异步chunk,只对异步导入的文件处理
// all 全部chunk
chunks: 'all', // 默认是async
// 为什么默认是async呢?上面说过了,懒加载会提高代码覆盖率,而拆分同步代码只是利用缓存,优化十分有限,所以默认拆分懒加载的代码,为async!!!!
// 下面是默认值,可以不写~
minSize: 30 * 1024, // 分割的chunk最小为30kb
maxSize: 0, // 最大没有限制
minChunks: 1, // 要提取的chunk最少被引用1次
maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
maxInitialRequests: 3, // 入口js文件最大并行请求数量
automaticNameDelimiter: '~', // 名称连接符
name: true, // 可以使用命名规则
// === 以上为公共规则 ==========
cacheGroups: {
// 分割chunk的组的规则
// node_modules文件会被打包到 vendors 组的chunk中。--> vendors~xxx.js,这个~是名称链接符
// 满足上面的公共规则,如:大小超过30kb,至少被引用一次。比如vue、vue-router等等
vendors: {
test: /[\\/]node_modules[\\/]/,
// 优先级
priority: -10
},
default: {
// 要提取的chunk最少被引用2次
minChunks: 2,
// 优先级
priority: -20,
// 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
reuseExistingChunk: true
}
}
},
// 将当前模块的记录其他模块的hash单独打包为一个文件 runtime
// 解决:修改a文件导致b文件的contenthash变化,做代码分割一定要加上runtimeChunk,否则导致缓存失效
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
},
minimizer: [
// 配置生产环境的压缩方案:js和css,4.26以上的webpack压缩js使用terser-webpack-plugin
// 压缩js
new TerserWebpackPlugin({
// 开启缓存
cache: true,
// 开启多进程打包
parallel: true,
// 启动source-map
sourceMap: true
}),
// 压缩css
new OptimizeCSSAssetsPlugin({})
]
}
个人测试例子
// output配置
entry: path.join(__dirname, 'src/index.js'),
output: {
filename: 'test.js',
path: path.resolve(__dirname, '../dist'),
chunkFilename: '[name]_chunk.js'
},
optimization.splitChunks
就是默认配置,和上面一样。
optimization: {
splitChunks: {
chunks: 'all' // 这里要写,不然就是只分割异步代码
// 后面不写,都是默认配置
}
}
代码中自定义webpackChunkName
为vConsole
import _ from 'lodash'// 这个在node_modules里面,分割时属于vendors组的规则
import myjs from './lib/myjs' // 这个在我新建的lib文件夹下的目录
// promise方式
if (process.env.NODE_ENV === 'development') {
let VConsole;
import(/* webpackChunkName: 'vConsole' */'vconsole/dist/vconsole.min.js').then((module) => {
VConsole = module.default;
new VConsole();
});
}
// 或者async/await方式
async created() {
if (process.env.NODE_ENV === 'development') {
try {
const { default: VConsole } =
await import(/* webpackChunkName: 'vConsole' */ 'vconsole/dist/vconsole.min.js');
new VConsole();
} catch (err) {
console.log(err);
}
}
},
打包出来的vendors~vConsole_chunk.js
、main_chunk.js
、vendors~main_chunk.js
vendors~vConsole_chunk.js
的文件说明
vendors
是缓存组cacheGroups
的组名字,~
是默认的automaticNameDelimiter
名称链接符,vConsole _chunk.js就是output
中的chunkFilename
规则[name]_chunk.js,这里[name]
就是由于魔法注释(magic comment
)/* webpackChunkName: 'vConsole' */
变成vConosle
。这里如果打生产包是不会把vconsole
打进去的,因为process.env.NODE_ENV === 'development'
为false
main_chunk.js
的文件说明
入口文件默认
chunk
名为main
,这里面有lodash
的映射关系,但是lodash
库的js
不在这。
不过myjs
在这里,所以引入的js
或者库文件只要不是node_modules
目录下的js
都会打包在这里
vendors~main_chunk.js
的文件说明
默认
cacheGroups
里面的test: /[\\/]node_modules[\\/]/
, 所以node_modules
里面的文件都会满足这个拆分规则,[name]
为vendors
,所以node_modules
里的lodash
包拆分到这里来了,~
是默认的automaticNameDelimiter
名称链接符,因为入口文件默认chunkName
为main
,chunkFilename
规则[name]_chunk.js
,连起来就是main_chunk.js
默认配置的一些属性说明:
chunks: 'all'
, // 默认是async
为什么默认是async
呢?上面说过了,懒加载会提高代码覆盖率,而拆分同步代码只是利用缓存,优化十分有限,所以默认拆分懒加载的代码,为async
!!!!
注意:
terser-webpack-plugin
插件压缩js,而不是uglifyjs-webpack-plugin
,在webpack4.26+就用terser-webpack-plugin
去压缩js,因为uglifyjs-webpack-plugin
不再维护了。
缓存组cacheGroups
里面default
组里有一个reuseExistingChunk: true
,解释一下,比如文件c.js
里引入a.js
和b.js
,而a.js
里面又引入里b.js
,打包的时候设置reuseExistingChunk: true
,则会忽略第二次引入b.js
,这样就避免了重复引入b.js
从webpack 5
开始就不支持{cacheGroup}.name
了,即
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
- name: 'vendors', // 这里不支持
chunks: 'all'
}
}
}
}
这里块名称是commons
,那么分割出的包名就是commons.js
,name
命名无效,默认就是那个组名称。
这里为什么写/[\\/]node_modules[\\/]/
而不是/node_modules/
?
webpack
在处理文件路径时,默认在Unix
是/
,在Windows
是\
,[\\/]
避免在跨平台使用时出现问题
分割chunk
组规则里的优先级priority
有什么用?
当满足公共规则的时候,比如提取出引入的第三方jquery
,既满足vendors
组的规则(因为在node_modules
路径下),也满足default
组的规则的时候,谁的优先级高就匹配对应组的规则,这里-10 > -20,所以打包出来的[name]
是vendors
而不是default
我们建议webpack
都升级到4.0以上,如果你还是webpack 4.0
以下,那就不得不说一下runtimeChunk
,这是为了防止修改a文件导致b文件的contenthash
变化,做代码分割一定要加上runtimeChunk
,否则可能导致缓存失效。
我们先看现象,再讲解原因
// a.js
export function add(x, y) {
return x + y;
}
// main.js
import(/* webpackChunkName: 'a' */'./a.js').then(({ add }) => {
console.log(add(1, 2));
});
举个例子,还是上面的默认分割规则,没有配置runtimeChunk
的时候,打包出来如下
在main.[contentHash:10].js
中存在映射关系,包含了a.[contentHash:10].js
文件映射,在html
文件的<script>
标签只会引入main.e9bc442f61.js
文件,而main.e9bc442f61.js
会动态创建<script>
标签并引入a.ad13327f26_chunk.js
。
如果我修改a.js
文件的内容,打包后a.js
的contentHash
会变化,因为映射关系要对应,从而会导致main.js
的contentHash
会变化,那么客户端根据缓存发现哈希值不一致,会重新下载。所以我们需要把映射关系从main.[contentHash:10].js
提取出来,加上runtimeChunk
配置之后,打包如下
打包之后 html
文件中,<script>
只引入main.[contentHash:10]_chunk.js
和runtime-main.[contentHash:10].js
,映射关系跑到了runtime-main.[contentHash:10].js
里面去了,而打开runtime-main.[contentHash:10].js
会发现是管理着映射关系,会动态创建<script>
标签然后引入a.[contentHash:10]_chunk.js
。
所以再次修改a.js
,就只是runtime-main.[contentHash:10].js
和a.[contentHash:10]_chunk.js
去变化,main.[contentHash:10]_chunk.js
就不会改变。这里main
也能变成chunk
块了,匹配output.chunkFilename:[name]_chunk.js
规则。这样客户端只会拉取下载runtime-main.[contentHash:10].js
,main.[contentHash:10]_chunk.js
利用缓存还可以继续使用。
实际上,平时写代码的时候,main.[contenthash:10].js
是业务逻辑,vendors.[contenthash:10].js
放的是库文件,业务逻辑和库文件的映射关系代码叫做manifest
,默认manifest
存在于main.[contenthash:10].js
中,也存在于vendors.[contenthash:10].js
里面,manifest
在旧版webpack
中打包可能会有差异,正是这种差异导致在旧版中哪怕内容没改变,contenthash
值也会发生改变,原因在于包之间的关系或者js
之间的关系嵌套在main
和vendors
文件里面,打包的时候会发生变化。我们把manifest
里关联关系的代码抽离出来放在runtime
文件里去。这样的话,main
里面就是业务相关的代码,vendors
就是库文件代码,关联关系代码放在runtime
文件,这样打包后main
文件的contenthash
和vendors
文件的contenthash
都不会变,这样新旧版本都实现了每次打包只要内容不变,contenthash
就不改变的情况。
5.IgnorePlugin
在项目中可能有几处体积占用较大的库,其中一个便是moment.js
这个日期处理库。对于一个日期处理的功能,为何这个库会占用如此大的体积,仔细查看发现当引用这个库的时候,所有的locale
文件都被引入,而这些文件甚至在整个库的体积中占了大部分,因此当webpack
打包时移除这部分内容会让打包文件的体积有所减小。
webpack
自带的两个库可以实现这个功能:
IgnorePlugin
ContextReplacementPlugin
IgnorePlugin
的使用方法如下:
// 插件配置
plugins: [
// 忽略moment.js中所有的locale文件
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
// 使用方式
const moment = require('moment');
// 引入zh-cn locale文件
require('moment/locale/zh-cn');
moment.locale('zh-cn');
复制代码ContextReplacementPlugin
的使用方法如下:
// 插件配置
plugins: [
// 只加载locale zh-cn文件
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
],
// 使用方式
const moment = require('moment');
moment.locale('zh-cn');
复制代码通过以上两种方式,moment.js
的体积大致能缩减为原来的四分之一。
6.CDN加速
你要引入一个库,但是这个库的在线js比较慢,你可以放到CDN
。
如果你最终是在线页面,你会把这些资源包上传到公司的CDN
或者自己的CDN
,你可以这么写
output: {
// filename: 'bundle.[contentHash:8].js', // 打包代码时,加上 hash 戳
filename: '[name].[contentHash:8].js', // name 即多入口时 entry 的 key
path: path.join(__dirname, '..', 'dist'),
publicPath: 'http://cdn.abc.com' // 修改所有静态文件 url 的前缀(如 cdn 域名),这里暂时用不到
},
这里的publicPath
写为你公司的CDN
或者自己的CDN
,打包之后是这样的
如果不写,那么publicPath
默认是相对路径,相对于根目录
如果你最终是会变成下载下来的本地包加载,那么就不用写在线CDN
的URL
了,直接写上publicPath: '/'
或者publicPath: './'
,根据你的的资源最后打包出来的路径选择
这个publicPath
也可以写在loader
的options
里面,比如写在url-loader
里面,去解析图片,这样打包出来的东西大于指定范围limit
的东西会变成file-loader
处理输出,outputPath
决定输出路径,而publicPath
的可以改变在线CDN
的前缀路径。
7.使用production
- 会自动开启代码压缩
vue
、react
等会自动删掉调试代码(如开发环境的warning
)- 启动
Tree Shaking
(1. 必须使用ES6
模块化import
引入 2. 开启production
环境)
说一下Tree Shaking
摇树,如果是开发环境,如果JS中有很多函数,而我只import
了一个函数,打包的时候会把所有的函数代码打包进去,而生产环境,就只会引入你用到的那个函数。
形象比喻:树上很多果子代表函数,你只要一个果子,生产环境就是就会把整个树上无用的果子摇掉,简称“摇树Tree Shaking
”
为什么必须使用ES6模块化import
引入才能Tree Shaking
呢?
ES6 Module
是静态引入,编译时引入Commonjs
是动态引入,执行时引入- 只有
ES6 Module
才能静态分析,实现Tree Shaking
Commonjs
执行的时候才知道哪个函数需要哪个不需要,Commonjs
就不能实现编译的时候摇树
commonjs
可以加上条件判断去引入,因为动态执行的时候根据条件变化可以执行,而ES6 Module静态编译的时候无法确定条件,会直接报错告诉你Module parse failed: 'import' and 'export' may only appear at the top level
只能出现在最外层,外层不能再加条件判断了。
const flag = true
if (flag) {
import test from './test
} // 会直接报错
const flag = true
if (flag) {
require('./test')
} // 完全没问题
8.Scope Hosting
创建函数作用域更少,体积更小,可读性更好,现在的webpack
自动集成了这一功能
以前引入一个js
,默认打包的时候就会产生一个新的作用域,当引入文件比较多的时候就产生了很多作用域,现在的webpack
将这些代码优化在了一个作用域,减小了体积。
关注、留言,我们一起学习。
===============Talk is cheap, show me the code================