前言
前面几篇文章,简单介绍了html,js,css,一些常用loader的加载和一些常用babel的加载,这一篇是项目搭建的最后内容,webpack的优化
babel的优化
babel的转译速度
在 options 里配置cacheDirectory 为true,开启缓存babel-loader执行的结果。
module.exports = {
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__driname, 'src'),
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
}
]
}
}
抽离公共转换代码
babel在每个文件都插入了辅助代码,使代码体积过大。babel对一些公共方法使用了非常小的辅助代码,比如_extend,默认情况下会被添加到每一个需要它的文件中
为了抽离这些公共转换代码,需要安装插件@babel/plugin-transform-runtime,其功能是可重复使用babel注入的代码以节省代码量
需要将其作为开发依赖,同时还需要安装@babel/runtime作为生产依赖
{
"presets": [
[
"@babel/env",
]
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
webpack优化
简单介绍一些优化点:打包编译时间和代码体积,特别是使用了大量的loader和第三方库时,那简直了。
mode模式选择
mode选择会影响到实际的打包体积,如:production模式下会自动开启tree-shaking(删除js无用代码)、TerserPlugin(压缩js代码)等等。根据不同的环境去选择合适的mode,将有利于优化打包体积(其实对构建速度也有一定的影响)。
plugin/loader合理使用
尽可能少的使用plugins/loader,或者使用test,exclude,include配置项来缩减其使用范围
rules: [
{
test: /\.css$/,
include: path.resolve(__dirname, "./src"),
use: ["style-loader", "css-loader"],
},
{
test: /\.js$/,
include: path.resolve(__dirname, "./src"),
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
},
],
同时,也根据mode去使用相匹配的loader,比如开发环境和生产环境下,生产环境下需要压缩代码,而在开发环境下不需要
代码压缩
之前的文章中,对html,js,css的压缩,也能加快编译速度和减少打包代码体积,production模式下默认是开启了js代码压缩的
代码分割code splitting
如果多个页面引用了一些公共模块,那么可以讲这些公共模块抽离出来,单独打包。公共代码只需要下载一次就缓存起来,避免重复下载
optimization: {
splitChunks: {
chunks: "all", // 所有的 chunks 代码公共的部分分离出来成为⼀个单独的⽂件
},
},
优化resolve.modules
这个属性告诉webpack解析模块时应该搜索的目录,绝对路径和相对路径都能使用。使用绝对路径之后,将只在给定目录中搜索,从而减少模块的搜索层级
resolve: {
modules: [
path.resolve(__dirname, 'node_modules'), // 指定当前目录下的 node_modules 优先查找
'node_modules', // 将默认写法放在后面
]
}
使用别名resolve.alias
多线程构建
由于运行在Node.js之上的webpack是单线程模型的,所以webpack需要处理的事情需要一件一件的做,不能多件事一起做。
如果webpack能同一时间处理多个任务,发挥多核CPU电脑的威力,那么对其打包速度的提升肯定是有很大的作用的。
Thread-loader
使用Thread-loader,webpack官方提供的插件。用法上只有一个单一的loader,比较简单,但兼容性较差。
需要安装依赖:thread-loader,其次要将其配置在loader首位确保其优先执行:
module.exports = {
module: {
rules: [{
test: /.js$/,
use: [
'thread-loader',
'babel-loader',
'eslint-loader'
],
}, ],
},
};
其缺点如下:
- Loader 中不能调用
emitAsset
等接口,这会导致style-loader
这一类 Loader 无法正常工作,解决方案是将这类组件放置在thread-loader
之前,如['style-loader', 'thread-loader', 'css-loader']
- Loader 中不能获取
compilation
、compiler
等实例对象,也无法获取 Webpack 配置
使用HappyPack
HappyPack 是一个使用多进程方式运行文件加载器 —— Loader 序列,从而提升构建性能的 Webpack 组件库,算得上 Webpack 社区内最先流行的并发方案,不过作者已经明确表示不会继续维护
安装依赖:happypack
const HappyPack = require('happypack');
module.exports = {
// ...
module: {
rules: [{
test: /.js$/,
use: 'happypack/loader',
// use: [
// {
// loader: 'babel-loader',
// options: {
// presets: ['@babel/preset-env']
// }
// },
// 'eslint-loader'
// ]
}]
},
plugins: [
new HappyPack({
loaders: [
{
loader: 'babel-loader',
option: {
presets: ['@babel/preset-env']
}
},
'eslint-loader'
]
})
]
};
为不同的文件配置多个相应的加载器数组:
const HappyPack = require('happypack');
module.exports = {
// ...
module: {
rules: [{
test: /.js?$/,
use: 'happypack/loader?id=js'
},
{
test: /.less$/,
use: 'happypack/loader?id=styles'
},
]
},
plugins: [
new HappyPack({
id: 'js',
loaders: ['babel-loader', 'eslint-loader']
}),
new HappyPack({
id: 'styles',
loaders: ['style-loader', 'css-loader', 'less-loader']
})
]
};
js
、less
资源都使用 happypack/loader
作为唯一 loader,并分别赋予 id = 'js' | 'styles'
参数;其次,示例中创建了两个 HappyPack
插件实例并分别配置了用于处理 js 与 css 的 loaders
数组,happypack/loader
与 HappyPack
实例之间通过 id
值关联起来,以此实现多资源配置
默认情况下,HappyPack 插件实例各自管理自身所消费的进程,导致整体需要维护一个数量庞大的进程池,反而带来新的性能损耗。
为此,HappyPack 提供了一套简单易用的共享进程池功能,使用上只需创建 HappyPack.ThreadPool
实例并通过 size
参数限定进程总量,之后将该实例配置到各个 HappyPack 插件的 threadPool
属性上即可
const os = require('os')
const HappyPack = require('happypack');
const happyThreadPool = HappyPack.ThreadPool({
size: os.cpus().length - 1
});
module.exports = {
// ...
plugins: [
new HappyPack({
id: 'js',
threadPool: happyThreadPool,
loaders: ['babel-loader', 'eslint-loader']
}),
new HappyPack({
id: 'styles',
threadPool: happyThreadPool,
loaders: ['style-loader', 'css-loader', 'less-loader']
})
]
};
sourceMap的合理使用
source map 即是一种映射文件,我们打包出来的 js 文件里面的代码都是各种 laoder 转换出来的代码,不是源码(原来代码的样子),source map 的作用就是将源码映射出来,就是解决控制台打印出来的代码应对应源码哪里,方便程序员进行调试。
在development模式里,建议使用eval-source-map和eval-cheap-module-source-map,前者可以追踪到源码的行列,后者只能追踪到行,当然后者的构建速度会块一点。
在production模式里,使用nosources-source-map,用这个进行调试程序是看不到任何的源码信息,只能看到信息来源与源码的第几行。
module.exports = merge(common, {
...
devtool: 'nosources-source-map',
...
}
按需加载路由
vue或者react项目中,首次加载时,由于存在很多的路由,这就导致了首屏加载时间的过长,一定程度上影响到了用户体验。实现路由的按需加载方式:
const B = () => import('@/pages/business/b.vue')
按需引入第三方库
比如在使用element-ui或者antd这种UI库时,只需要使用库中的某些组件,却要引入全部的组件及样式文件,这会造成打包体积巨大,这就需要在文件中去单独引用某个插件来解决了,UI库中也有提到按需引入的方式,可以在实际项目中使用。
静态图片资源压缩
总结
参考了很多资料,所以大家可能会发现很多相似的内容,因为有部分我懒得敲就直接复制了,文末附上了原文链接。至此,创建项目的基本结构便是搭建完成了。