本文原创:linqiumei、huoyinghui
不同项目,使用webpack版本不同,对应的插件也不同。
如何查看项目中webpack的版本?
1.在package.json中的script脚本命令中,添加"webpack": "webpack --version"
,终端运行npm run webpack
2.项目目录的终端运行./node_modules/.bin/webpack –v
3.项目目录的终端运行npx webpack -v
npx会自动查找当前依赖包中的可执行文件,找不到会在PATH中寻找,找不到,就会自动安装。npm5.2起支持npx
webpack -v
查询的是全局环境中的webpack版本
项目中webpack配置文件结构
webpack的重要属性
entry: 定义入口文件
output: 定义输出文件
module: 设置项目中不同类型模块的处理方式
resolve: 设置模块解析方式
plugins: 使用插件自定义webpack构建过程
webpack.base.js
entry: 定义了webpack打包的入口文件
output:
-- path: 定义输出文件存储位置
-- publicPath: 用于配置异步加载/按需加载等线上资源的url前缀,默认为空字符串,为相对路径;const publicPath = path.join('/account/')
-- filename: 定义打包资源输出到path指定目录位置的文件名
其中,[name]指代entry中键值对的键值,[hash]为打包时会生成唯一的标志符,指代打包过程,同一打包过程的文件hash是一样的
-- chunkFilename: chunk是webpack进行模块依赖分析时,代码分割出来的代码块。用于配置没有入口起点的代码块输出的文件名
-- globalObject: 因为默认传入的参数是window,但是umd书写规范中,传入的参数应该是this
module:
-- loader在module.rules中配置,如使用vue-loader加载vue文件,使用css-loader加载.css文件。
resolve:
-- extensions: 如果有多个文件有相同的名字,但是后缀名不同,webpack会按数组顺序解析列在首位的文件,并跳过其他后缀
-- alias: 别名,简化模块引入的代码
-- symlinks: 是否将符号链接解析为真实路径,默认为true
plugins
-- vueLoaderPlugin: 必须插件,将定义的其他规则应用到.vue文件中相应语言块。
-- HtmlWebpackPlugin: 简化了HTML文件的创建
当使用webpack打包时,HtmlWebpackPlugin创建一个html文件,并把webpack打包后的静态文件自动插入到html文件中。默认会在output.path目录下创建一个index.html文件,并在文件中插入一个script标签,src为output.filename。css资源会包含在html头部的link标记中。
-- DllReferencePlugin: 配合DllPlugin使用,webpack.dll.js中生成manifest.json文件,DllReferencePlugin打包时,不会打包manifest.json中的文件,而是使用相应的全局函数处理,用于提高构建速度。如第三方库vue、vue-loader等打包
-- DefinePlugin: 可以在编译时定义全局变量,打包的时候对这些变量进行替换
webpack.dll.js
将第三方库依赖打包到一个单独的dll文件目录下,并生成manifest.json文件
optimization
-- minimize: 是否压缩
plugins
-- ProvidePlugin: 每当vue作为自由变量时,模块会自动用已加载vue的内容填充模块
-- CleanWebpackPlugin: 用于npm run dll时,清除之前打包的文件
-- AssetsPlugin: 生成一个记录版本号的文件
-- DllPlugin: 将第三方库代码分离,每次文件更改的时候,只打包项目自身的代码。将vue、vue-router第三方库单独打包
webpack.dev.js
plugins:
-- HotModuleReplacementPlugin: 热加载模块,不能用于生产环境,因为watch状态消耗性能,生产环境中,代码稳定,更新几率小
webpack.prod.js
devtool:
控制是否生成以及如何生成source map,有助于定位代码错误位置
performance:
展示性能提示,当加载影响性能的资源时,可以输出一个警告提醒。warning、error、 false
optimization:
-- minimizer: 用于压缩js文件,不支持es6语法
-- uglifyjs-webpack-plugin: 用于压缩js文件,不支持es6语法,可用terser-webpack-plugin替代
-- OptimizeCssAssetsPlugin: 用于优化css文件的输出,摈弃重复样式,去除样式规则中的多余参数,移除不必要的浏览器前缀等。autoprefixer是否去除浏览器前缀
plugins:
-- MiniCssExtractPlugin: 将css提取到单独的文件中,并支持按需加载
-- DefinePlugin: 定义全局变量
-- hashedModuleIdsPlugin: 根据模块的相对路径,生成四位数的hash作为模块id,用于生产环境。
-- CleanWebpackPlugin: 清除打包内容:verbose: 是否将日志输出到控制台,默认为false;dry: 默认为false,为true的时候,模拟删除,不会真的删除文件;exclude: 忽略不用删除的文件
-- CopyWebpackPlugin: 将单个文件或整个目录复制到构建目录中
webpack5
首先可以看一下当前webpack5的开发进度
相较于webpack4,webpack5有了很大的变化,先简单列举一下新旧版本的几个不同点:
- webpack5清除webpack4标记即将过期的功能
- 通过持久缓存提高构建性能cache
- 确定性chunkId和moduleId,优化长期缓存
- 更好的Tree Shaking
- 支持生成es6/es2015的代码
- SplitChunk配置优化
清除webpack4标记即将过期的功能点就先掠过,其余几点下面我们一一介绍。
通过持久缓存提高构建性能
首先说明cache在构建中的作用,cache在首次打包构建项目时,会将编译结构缓存起来,二次构建时,会根据缓存判断当前哪些已经构建过并且没有改动,这些文件就不会被重新编译,从而减少项目构建时间。
在webpack4中,cache是这样设置的:
cache: true // 或false
通过cache-loader将编译结构写入硬盘缓存或者采用babel-loader并配置option.cacheDirectory将编译结果写入磁盘
而在webpack5中对cache做了升级,缓存默认是开启状态,缓存的文件存于内存中,我们也可以配置缓存的类型及存储的位置:
cache: {
type: 'filesystem’,
cacheDirectory: path,
cacheLocation: path + name,
buildDependencies: {
config: [__filename],
},
}
以上几个属性的含义可以简单理解为:
type: 缓存类型,值为 'memory'或‘filesystem’,分别代表基于内存的临时缓存,以及基于文件系统的持久化缓存。
cacheDirectory: 缓存目录,默认目录为 node_modules/.cache/webpack,也可以自定义设置
name: 缓存名称,同时也是 cacheDirectory 中的子目录命名,默认值为 Webpack配置中 的{config.mode}
cacheLocation: 缓存真正的存放地址, path.resolve(cache.cacheDirectory, cache.name), 该属性在赋值情况下将忽略 cacheDirectory 和 name 属性
优化长期缓存
我们都知道,webpack基本配置会有入口文件,类似的设置如下:
entry:{
app: './index.js'// 入口
},
output:{
filename:'./bundle.js'// 出口
}
但是有很多文件是不会配置entry的,这些未从entry打包的chunk文件,都会以0,1,2,3…加chunkhash的文件命名方式输出。这样就引发了一些问题,举个例子:
假设我们打包了page0.js、page1.js、page2.js,打包后的文件名暂时称为1.js、2.js、3.js,这样使用是不会有任何问题的,但现在如果我们删除或者暂时不使用1.js后,原来的2.js会变为1.js,3.js会变为2.js,此时再引用2.js就不对了,这就是出现了缓存失效问题。
出现的这种问题,目前有解决方案,就是使用import(/* webpackChunkName: “home” */ ‘./home’)
这种魔术式注释来给打包文件命名,虽然命名问题解决了,可是打开文件内容会发现,chunkId仍然发生了变化。
page1.js第一次打包时的chuankId:
page1.js第二次打包后的chunkId:
那么webpack5是怎么解决的呢
在webpack5中,有确定的 chunkId、moduleId 以及导出名称,生产模式下,默认是启用chunkIds、 moduleIds的,具体是以确定性的方式为模块和分块分配短的(3 或 5 位)数字 ID,使得生成的缓存失效频率降低,配置可以这样写:
optimization: {
chunkIds: "deterministic", // 顾名思义,deterministic为确定性的
moduleIds: "deterministic"
}
当然,chunkIds和moduleIds还有其他的配置选项
moduleId:
optimization | 选项值 |
---|---|
false | 告诉webpack不应使用任何内置算法, 通过插件提供自定义算法 |
natural | 按使用顺序的数字ID,沿用v4数值命名 |
named | 方便调试的高可读性id,文件命名 |
deterministic | 根据<i>模块路径</i>生成简短的hash值 |
size | 根据模块大小生成的数字id,沿用v4 |
chunkId:
optimization | 选项值 |
---|---|
false | 告诉webpack不应使用任何内置算法, 通过插件提供自定义算法 |
natural | 按使用顺序的数字ID,沿用v4数值命名 |
named | 方便调试的高可读性id,文件命名 |
deterministic | 根据代码<i>块内容</i>决定,注意与moduleId的此选项区分 |
totalSize | 根据请求到的解析资源size计算的id |
减小Bundle体积
- 分析导入导出模块之间的依赖关系
webpack可以通过优化配置选项中的innerGraph启用模块依赖,具体如下:
optimization: {
innerGraph: true
}
依赖图的好处是可以理清各个模块之间从export到import是怎样的引用关系,webpack对模块中的符号进行分析以找出从export到import的依赖关系,举个🌰:(内部依赖图可以记录到当前文件引用了something模块)
import {something} from './something'
function usingSomething() {
return something;
}
export function test() {
return usingSomething();
}
- 嵌套tree-shaking
Tree-shaking,翻译过来就是树摇,一个文件就是一棵树,会有主干和分支,也会有叶子,如果树被晃动,就会有叶子落下。tree-shaking就是将文件中不需要的代码删除掉,用来减小文件的体积,在webpack5中,不仅可以做到摇无用代码,还可以嵌套查询是否有无用代码从而进行删除。
首先做这样的配置:
optimization: {
sideEffects: false,
usedExports: true
}
// inner.js
export const a = 1;
export const b = 2;
// module.js
import * as inner from './inner'
export {inner}
// user.js
import * as module from './module';
console.log(module.inner.a);
通过内部模块依赖图,检测到inner.js中的常量 b 没有被使用,就会将其删除掉,是不是很腻害:happy:
注意!tree-shaking虽然在减小文件体积方面做的很到位,但使用需谨慎,要保证被删除的代码没有副作用才行,因为只要是没有使用到的文件,都会被摇掉,那如果有些文件不使用也不想被优化呢,比如需要写一个组件,但在库里没有使用它,这就有可能在打包时被优化掉了。
SplitChunk配置优化
splitChunk
是webpack优化配置中的一个配置项,可以将Node Modules中代码单独打包成一个chunk最终输出,配置成功的话,可以自动分析多入口chunk中,有没有公共的文件,如果有,就会打包成一个单独的chunk。
先在index文件里引入jquery库,简单写两行代码:
import $ from 'jquery'
function sum(...args) {
return args.reduce((a, b) => a + b, 0)
}
console.log(sum(1,2,3,4))
看一下不配置splitChunk的打包情况:
看第一张图,index.js中只写了几行代码,但体积却有852KiB,从第二张图可以得到答案,就是webpack将jquery和index中的代码打包到了一个文件里。
接下来配置一下splitchunk:
optimization: {
splitChunks: {
chunks: 'all',
minSize: {// webpack5之前只可以设置 minSize: size
javaScript: 2000,
style: 10000
}
}
}
splitchunk不是webpack5才有的属性,但minSize的细化配置是webpack5升级的,可以在其中配置单独打包文件满足的条件,比如javaScript: 2000,如果文件体积小于2000,是不会单独抽离出来打包的。
再来看下同样代码的打包情况:
可以看到,第三方组件库被单独打包了,虽然一个文件引用了jquery库,表现的作用不明显,但如果多个文件都引用了这个库,作用就很明显了,一个是每个文件中都会打包jquery,一个是jquery只打包一份。
webpack使用遇到的问题
1.本地绑定localhost.jd.com域名,然后通过绑定域名访问项目,但是页面会报Invalid Host header,如下图所示
问题原因:新版的webpack-dev-server增加了安全验证,默认检查hostname,如果hostname不是配置内的,将中断访问。
解决方法:在webapck配置中找到devServer配置项,添加{disableHostCheck: true},然后重启项目即可,如图
2.debug模式启动项目报找不到localhost
问题原因: 本地代理时,没有指定本地host
解决方案:在host配置中添加上本地回环地址127.0.0.1 localhost的映射,例如: 127.0.0.1 localhost.test.com
3.设置div超出两行显示省略号,但因为css样式中有浏览器前缀,线上打包会将前缀去掉导致样式失效。
问题原因:webpack.prod.js文件配置压缩时,引入了OptimizeCSSAssetsPlugin插件,这个插件默认会将浏览器前缀去掉
解决方案:为该插件设置参数autoprefixer为false
欢迎计算机前端相关领域小伙伴加入我们,具体的招聘信息可进入公众号查看,欢迎关注。