webpack作为一款当下最流行的前端构建工具,一直以来以门槛过高而受人诟病,且没有一个通用的配置适合全部的项目,为此咱们不得不开心的(大误:smiley:)手动配置咱们的项目,开启咱们的配置工程师之路,本文主要是对webpack的一些基础配置的解释和咱们能作的一些优化工做,适合有使用过webpack的前端开发者阅读,各位在阅读过程当中不妨打开本身的项目来跟着我一块儿优化。javascript
dev prod
一般咱们的项目会有开发环境和生产环境,而开发环境咱们配置的目标是构建更快,模块热替换,能从chrome控制台报错信息对应的源码的错误处(source map)等。生产环境咱们更加关注chunk分离,缓存,安全,tree shaking等优化点。 固然对于开发和生产环境的配置文件确定是不彻底相同的,因此咱们将不一样环境的配置文件分离开,同时将共用部分抽出来,最后使用webpack-merge插件整合。css
//参考使用方法
const webpackMerge = require('webpack-merge');
const additionalConfigPath = {
development: './webpack.dev.config.js',
production: './webpack.prod.config.js'
};
const baseConfig = {};
module.exports = webpackMerge(
baseConfig,
require(additionalConfigPath[process.env.NODE_ENV])
);
复制代码
在下方的优化点中 我会标注这些点适用的环境 dev=开发环境 prod=生产环境html
prod
代码分离可以将工程代码分离到各个文件中,而后按需加载或并行加载这些文件,也用于获取更小的 bundle,以及控制资源加载优先级前端
webpack有三种经常使用的代码分离方法:java
//多入口配置 最终输出两个chunk
module.exports = {
entry: {
index: 'index.js',
module: 'module.js'
},
output: {
//对于多入口配置须要指定[name]不然会出现重名问题
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
复制代码
问题所在:入口chunks中若是包含重复的模块,如jquery,那些重复模块都会被引入到各个bundle中node
对于webpack4 < 4 咱们使用CommonsChunkPlugin插件react
entry: {
"app": "entry-client.js"
}
//首先将app入口的公共模块提取出来
new webpack.optimize.CommonsChunkPlugin({
name: 'common',
filename: '[name].[chunkHash:6].common.js',
chunks: ['app']
})
//将 vendor runtime分离出来
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor','runtime'],
filename: '[name].[chunkHash:6].bundle.js',
//这个配置保证没其它的模块会打包进 vendor chunk
minChunks: Infinity
}),
复制代码
chunks 选项表明了从哪一个入口分离公共文件,这里咱们使用咱们项目的主入口 app来分离自定义的公共模块jquery
首先将自定义的公共模块单独抽离出来,这样作的目的是方便咱们作缓存,当自定义模块更新时不会影响到vendor文件的hash值。webpack
而后将第三方库文件 和 webpack运行文件分离。web
这样咱们的vendor就是很是干净的了,只包含第三方库,能够作长效缓存。这个地方须要分离webpack运行文件runtime,由于不管咱们是否修改了项目文件,每次build项目时的runtime文件的hash值老是会发生变化的,须要单独打出来。
对于webpack4 咱们须要实现一样的目标
// 提早自定义公共模块
optimization: {
splitChunks: {
chunks: "all",
minSize: 20000,
//其余入口chunk引用的次数
minChunks: 1,
//默认使用name + hash生成文件名
name: true,
//使用自定义缓存组
cacheGroups: {
//公共模块
commons: {
name: 'common',
//缓存优先级设置
priority: 10,
//从入口chunk提取
chunks: 'initial'
},
//提取第三方库
vendors: {
//符合条件的放入当前缓存组
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all"
},
}
}
}
//提取webpack运行文件
runtimeChunk: {
name: 'manifest'
},
复制代码
上面的配置大概意思是对全部知足条件的chunk开启代码拆分。
优先级(priority):多个分组冲突时决定把代码放在哪块
经过splitChunks 和 runtimeChunk插件咱们能达到一样的拆分和缓存目的
webpack能够将异步加载的组件打包为chunk,同时按需加载此chunk。最为常见是咱们的路由组件,咱们能够将路由入口组件动态导入以达到异步加载的目的。
异步组件的导入主要有两种方式 1:ES6 stage3的提案 import() 2:webpack 特定的 require.ensure
//import 方式
const Login = () => {
import(/* webpackChunkName: 'Login' */'./Login')
}
//require.ensure 方式
require.ensure([], function(require){
require('./Login');
},"Login");
//异步组件打包的chunk命名 使用webpackChunkName 或者
//使用require.ensure的第三个参数
//对于webpack2/3须要配置chunkFilename来接收配置的name
output: {
//非入口 chunk 的名称
chunkFilename: '[name].[chunkHash:6].chunk.js'
}
//webpack4配置name为true(默认值)
optimization: {
splitChunks: {
//chunk name由块名和hash值自动生成
name: true
}
}
复制代码
require.ensure 如今依赖于原生的 Promise。若是在不支持 Promise 的环境里使用 require.ensure,你须要添加 polyfill。
既然咱们可使用import()和require.ensure来动态导入组件那其实在项目中咱们更是作到能够基于组件的异步加载。
好比咱们的首页是由20个组件组成,可是首屏只会显示15个组件,另外的5个组件多是一些弹窗,或底部才显示的这种组件,那么咱们彻底能够将之使用动态导入抽离出来,在须要的时候加载它,如:
//在异步事件中去导入组件
<div onClick={this.showPopUps}>
show-popUps
</div>
showPopUps = () => {
this.popUps = ()=> {
import(/*webpackChunkName: 'PopUps'*/'./popUps')
}
}
复制代码
这样处理的问题在于,咱们在点击按钮弹出浮层时会有必定的延时,由于在点击时咱们才从服务端去获取组件,用户体验可能不太优秀。
这时咱们可使用一种预测加载的技术prefetch。prefetch提示浏览器这个资源未来可能须要,浏览器会选择==空闲的时间==去下载此资源。prefetch经常用于加速下一次操做。
<link rel="prefetch">
复制代码
兼容性问题:从上述能够得知,prefetch时基于浏览器实现的,存在必定的兼容性问题,在safari上还没由获得支持。
在不支持的浏览器中并不会影响咱们页面的功能
使用:preload-webpack-plugin 使用条件:webpack > 2.2 且须要使用 html-webpack-plugin
plugins: [
new HtmlWebpackPlugin(),
new PreloadWebpackPlugin({
rel: "prefetch",
as: 'script',
//包含了哪些chunk,默认值为"asyncChunks"
include: 'asyncChunks'
})
]
//最终效果相似这样
<link rel="prefetch" as="script" href="chunk.d15e.js">
复制代码
对于.css结尾的文件 as=style,.woff2结尾的文件as=font,不然as=script,固然你能够像上方那样给一个肯定的值。
对于include的值,其实咱们并非须要全部的异步chunk都使用prefetch,因此咱们须要重设include的值,像这样:
//1 给定须要prefetch的块的名字(chunkName)
include: ["PopUps","Login"]
//2 或者在动态导入时指定Prefetch
this.popUps = () => {
import(/* webpackChunkName: 'PopUps', webpackPrefetch: true */'./PopUps')
}
复制代码
dev prod
为Loader指定做用范围能够加快咱们的构建速度,提高开发体验。
{
test: /\.jsx?$/,
use: [
"babel-loader"
],
exclude: /node_modules/
}
//or
{
test: /\.jsx?$/,
use: [
"babel-loader"
],
include: /src/
}
复制代码
dev
resolve的部分配置过多项,会致使webpack搜索范围变大,效率降低,如没有特殊需求,能够保持默认配置保持开发体验。
//解析模块时应该搜索的目录
modules: ["node_modules"] //default
//自动解析肯定的扩展,不宜过多
extensions: [".js", ".json"] //default
//解析目录时要使用的文件名,不宜过多
mainFiles: ["index"] //default
复制代码
dev prod
externals 配置选项提供了「从输出的 bundle 中排除依赖」的方法,什么意思呢?顾名思义,externals能够排除掉咱们打包结果中某些依赖:
//排除react 咱们打包后的bundle中没有react了
externals: {
//将react指向一个全局变量 react
react: "react"
}
//这里的 "react" 会直接去全局变量中寻找
import react from "react"
复制代码
这种设置一般用于==library==开发人员,你的library依赖了react,同时使用了你的库的开发人员也依赖了react,最终会造依赖你的库的用户bundle中react被重复打包。
你应该作的是在你的library中将外部依赖放到externals中排除掉,而后将你的依赖暴露给使用你的库的用户,最后你的library应该从外部获取这些扩展依赖,而不是一股脑打包进你本身的bundle中。固然这个依赖在用户的环境中必须存在且可用。
问题所在:对于从一个依赖目录中,调用的多个文件,使用externals 没法一次排除;
import one from 'react/one';
import two from 'react/two';
//上面这种状况只能一个个匹配,或者这经过正则匹配
externals: [
'react/one',
'react/two',
/^react\/.+$/
]
复制代码
这里不得不提的是npm依赖管理中的peerDependencies,它的做用正是将咱们library中依赖的库暴露给用户的,如果用户没有安装咱们库中的必需依赖,npm会抛出错误。
固然若是你不是一个library开发人员,你一样可使用externals来排除项目中的外部依赖,而后经过其余方式引入成为一个全局变量的方式来优化打包速度。
dev
webpack在每次build时都会将整个项目从新构建一遍,无论这个文件是否发生了改变(固然若文件未更改hash值不变),可是其实咱们所依赖的三方库更改并不频繁,如果将三方库抽离出来单独构建,将构建好的目标和构建生成的json对照文件引如咱们的项目,这样不用每次都去打包这些代码。
使用 DllPlugin 将更改不频繁的代码进行单独编译。这将改善引用程序的编译速度,即便它增长了构建过程的复杂性。
//新建 webpack.dll.conf.js
module.exports = {
entry: {
vendor: [react, ...]
},
output: {
filename: 'dll.[name].js'
},
plugins: [
new webpack.DllPlugin({
path: '[name]-manifest.json'
})
]
}
//webpack.base.conf.js
const manifest = require('./vendor-manifest.json')
plugins: [
new webpack.DllReferencePlugin({
manifest
})
]
复制代码
最后须要手动将生成的dll.[name].js引入到html文件中去,固然这一步骤能够用assets-webpack-plugin插件优化。