由于前端的快速发展,相关工具的发展速度也是相当迅猛,各大框架例如 vue,react 都有自己优秀的脚手架工具来帮助我们快速启动一个新项目,也正式因为这个原因,我们对于脚手架中最关键的一环 webpack 相关的优化知之甚少,脚手架基本上已经为我们做好了相关的开发准备,但是当我们想要做一些定制化的优化操作时,对 webpack 的优化也需要有一定的了解,否则无从下手,接下来就让我们进入 webpack 的优化世界
构建速度提升
loader 提升
loader 是 webpack 中最重要的特性,由于 webpack 自身只支持 JavaScript,因此需要一系列的 loader 来处理那些非 JavaScript 模块,因此在我们用 webpack 建项目的时候一定会使用一系列的 loader,例如:vue-loader、sass-loader、babel-loader 等等,就以 babel-loader 为例,来看具体配置:
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: ‘babel-loader?cacheDirectory=true’,
options: {
presets: [‘@babel/preset-env’],
plugins: [‘@babel/transform-runtime’]
}
}]
}
对于 loader 来说最常用的就是 exclude 属性,用来避免不必要的转译,上面通过 exclude 来避免对 node_modules 中 js 中进行转译来提升构建速度,但是这样带来的提升效果有限。
cacheDirectory 是对 babel-loader 的转译结果进行缓存,之后的 webpack 进行构建时,都会去尝试读取缓存来避免高耗能的 babel 重新转译过程,cacheDirectory 可以指定一个缓存目录或者指定为 true,为 true 时将使用默认的缓存目录 node_modules/.cache/babel-loader。
babel 对一些公共方法使用了非常小的辅助代码,默认会注入到每一个需要的文件,这样就造成重复引入,这时候就需要像上面那样引入 transform-runtime 来告诉 babel 引入 runtime 来代替注入
第三方库优化
externals
externals 提高构建速度的方法就是在构建时不会将指定的依赖包打包到 bundle 中,而是在运行时再从外部获取依赖,具体是怎么用的呢?来看个例子:
externals : {
vue : “Vue”,
vueRouter : “VueRouter”,
vueResource : “VueResource”,
vuex : “Vuex”
},
<script type=”text/javascript” src=”https//xxxx/vue.famliy.1.1.0.min.js”></script>
上面的例子的将 vue 全家桶都配置在 externals 中,然后将压缩包合成一个 js 文件放在 cdn 上面,这样就不会在构建时将文件打包到 bundle 中,提升打包速度,同时 cdn 又可以做缓存,提高访问速度,美滋滋
DllPlugin
DllPlugin 是用来干什么的呢?DllPlugin 会将第三方包到一个单独文件,并且生成一个映射的 json 文件,打包的生成的文件就是一个依赖库,这个依赖不会随着你的业务代码改变而被重新打包,只有当它自身依赖的包发生变化时才会需要重新打包依赖库,接下来来看具体配置吧:
module.exports = {
entry: {
vendor: [‘vue’, ‘vue-router’, ‘vue-resource’, ‘vuex’]
},
output: {
path: path.join(__dirname, ‘dist’),
filename: ‘[name].js’,
library: ‘[name]_hash’,
},
plugins: [
new webpack.DllPlugin({
name: ‘[name]_[hash]’,
path: path.join(__dirname, ‘dist’, ‘[name]-manifest.json’),
context: __dirname
})
]
}
首先我们需要一个如上面例子那样的 dll 配置文件,然后编译这个配置文件,生成一个 vendor.js 和一个映射文件 vendor-manifest.json,然后再在我们的 webpack 配置文件中对进行配置:
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require(‘./dist/vendor-manifest.json’)
})
]
这样就完成配置了,是不是很简单呢?赶紧动手试试吧
happypack
happypack 这是个什么呢?我们都知道 webpack 是个单线程处理任务的,当又多个任务需要处理的时候,需要排队,那 happypack 就是用多线程来处理任务,通过并发处理来提高任务处理速度,那么这个需要怎么配置呢?来看具体例子:
const happypack = require(‘happypack’)
// 创建并发池
const threadPool = happypack.ThreadPool({size: os.cpus().length})
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: ‘happypack/loader?id=happyBabel’ // id 对应 happypack 插件 id
}]
},
plugins: [
new happypack({
id: ‘happyBabel’,
threadPool: threadPool,
loaders: [‘babel-loader?cacheDirectory’]
})
],
减小构建体积
webpack-bundle-analyzer
这个相信大家都很熟悉,就是一个可视化工具,用来查看各个包的大小以及相互之间的依赖关系,配置方法也很简单,就和插件的配置一样,来看具体例子:
const bundleAnalyzerPlugin = require(‘webpack-bundle-analyzer’).BundleAnalyzerPlugin
plugins: [
new bundleAnalyzerPlugin()
],
tree shaking
tree shaking 指的是什么呢?通常指的是 JavaScript 上下文中未引用的代码,怎么理解呢?比如你引用了 lodash 包,里面有许多和 JavaScript 相关的便利方法,但你实际只用了其中的一两个,此时打包时如果把所有的方法都打进去了,是不是很浪费呢?tree shaking 的概念就是去除多余代码。来看一个简单的例子:
import {plus} from ‘./count’
console.log(plus(1, 2))
function plus(x, y) {
return x + y
}
function minus(x, y) {
return x – y
}
export {
plus,
minus
}
const path = require(‘path’)
module.exports = {
entry: {
main: ‘./src/index.js’,
},
output: {
path: path.join(__dirname, ‘dist’),
filename: ‘[name].js’,
},
mode: ‘development’
}
如上例所示,在入口文件中我们引入 count.js 中 plus 方法,我们期望的当然是只会引入 plus 方法,而不是都引入,但往往不随人愿,来看结果:
你会发现编译后的代码中,整个 count.js 都被编译进去了,这时候你就需要 tree shaking 了,接下来看做 tree shaking 的具体方法
UglifyJsPlugin
这个插件大家一定都用过,使用 UglifyJsPlugin 就可以在构建的过程中对冗余的代码进行删除,在 webpack4 中只需要将上面 mode 的值改为 production,就会启用 UglifyJsPlugin,是不是很简单,或许你想知道 webpack4 中怎么自己配置 UglifyJsPlugin,那就来看具体配置吧:
const UglifyJsPlugin = require(‘uglifyjs-webpack-plugin’)
optimization: {
minimizer: [
new UglifyJsPlugin({
parallel: true,
cache: true,
uglifyOptions: {
compress: {
drop_console: true,
reduce_vars: true
},
output: {
comments: false,
beautify: false
}
}
})
]
}
是的在 webpack4 中的 UglifyJsPlugin 是配置在 optimization 中的 minimizer 中的,配置是不很简单呢?赶紧动手尝试吧
按需加载 (import)
这里的 import 是指 webpack 中的动态加载,它的语法和 ES6 中的动态加载语法一摸一样,这是官方推荐的按需加载的方式,还是上面 tree shaking 的例子,我们只想引入 plus 方法,我们来看具体怎么使用:
import(‘./count.js’).then((count) => {
console.log(count.plus(1, 2))
})
我们只需要将入口文件改成上面的形式,其他的都不要变就可以实现按需引入,是不是很简单呢?在 vue 中路由的按需加载也可以这么用,来看一个简单的例子:
function view (name) {
return new Promise((resolve, reject) => {
import(‘../views/’ + name + ‘.vue’)
.then((res) => {
resolve(res)
}).catch(e => {
reject(‘ 网络异常,请稍后再试 ’)
})
}).catch(err => {
throw new Error(‘err,组件加载失败 ’)
})
}
传入一个名字,动态引入对应目录的下的视图文件,这只是一个简单的例子,具体的使用形式还是依据具体的场景
总结
这篇文章简单的从构建速度和代码体积两个方面简单的介绍了 webpack 优化相关的方法,希望大家都能自己动手去写一写,毕竟只有实践出真知,更何况是编程。这篇文章如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞收藏