0%

webpack爬坑之路

打包文件资源问题

filename 指列在 entry 中,打包后输出的文件的名称。
chunkFilename 指未列在 entry 中,却又需要被打包出来的文件的名称。

项目缓存问题

当项目上线新版本的时候,用户浏览器可能仍然使用的是之前 html 和 js 文件的缓存。通过以下几个方法可以解决这个问题。

  • 文件命名使用哈希值,这样当发布新版本就会请求新版本的资源文件。vue-cli 中默认有这个功能,webpack 在每次构建的时候都会生成 hash,contenthash,chunkhash 三个哈希值
    • hash 这是工程级别的,即每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。
    • chunkhash 根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。在生产环境里把一些公共库和程序入口文件区分开,单独打包构建,接着我们采用 chunkhash 的方式生成哈希值,那么只要我们不改动公共库的代码,就可以保证其哈希值不会受影响。并且 webpack4 中支持了异步 import 功能,chunkhash 也作用于此。
    • contenthash 一般来说,样式是作为模块 import 到 js 文件中,他们的 chunkhash 是一致的,这导致只要对应的 css 或者 js 改变,chunkhash 就会改变。而 contenthash 是针对文件内容级别的,只有自身模块的内容变了,hash 值才改变
  • html 加上
1
2
3
4
<meta
http-equiv="cache-control"
content="no-cache, no-store, must-revalidate"
/>
  • 配置 nginx,禁止 html 入口文件的缓存
1
2
3
4
5
6
location = /index.html {
add_header Cache-Control "no-cache, no-store";
}
location = /admin_index.html {
add_header Cache-Control "no-cache, no-store";
}
  • 使用 HtmlWebpackPlugin 插件,一个典型的配置如下
1
2
3
4
5
6
7
8
9
10
11
12
new HtmlWebpackPlugin({
filename: 'admin_index.html',
template: 'web-admin/admin_index.html',
inject: true,
hash: false,
chunks: ['adminApp', 'vendor~adminApp', 'vendor~adminApp~userApp'], // 这个数组是html要引入的打包生成的chunk文件,chunkname 在 webpack 打包输出信息中
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
}
}),

chunkname
图中最右边一列就是资源的 chunkname

MiniCssExtractPlugin

之前项目使用的是 ExtractTextPlugin 对 css 进行抽出并打包,然而 ExtractTextPlugin 在 4.0 版本后不再支持对 css 进行打包 (会在生成的 css 文件末尾附加 webpack 运行时的代码),后学习得知现在应使用 MiniCssExtractPlugin 对 css 进行抽离

CleanWebpackPlugin

这个插件的作用是每次构建的时候,清理重复构建的文件,因为使用哈希方式构建的时候,文件名不同,更改的文件不会直接替换。个人感觉使用 rm -rf dist/* 清理也可以 当然这可以避免你神志不清时误把整个项目文件删了

1
new CleanWebpackPlugin(["dist"]);

optimization.splitChunks

非常实用的功能,抽出代码中的公共组件到独立的文件,举个例子

1
2
3
4
5
6
7
8
9
cacheGroups: {
vendor: {
test: /node_modules/,
chunks: "initial",
// name:"vendor",
priority: 10,
enforce: true
}
}

如果没有 name 属性,a 入口文件引用了 vue 和 axios, b 入口文件引用了 react 和 axios,那么 webpack 就会把 axios 单独抽离出来为独立的 chunk,同时 vue 和 react 在各自的独立 chunk 中
而如果有 name 属性,那么所有依赖文件都会打包到命名的 chunk 文件中,这个原理同样适用于 CSS 的打包。

1
2
3
4
5
optimization: {
splitChunks: {
chunks: "all",
}
}

可以将 node_modules 中代码单独打包一个 chunk 最终输出

通过 js 代码让文件打包成单独 chunk

1
2
3
4
5
import(/*webpackChunkName:'test',webpackPrefetc:true*/,"./test")
.then(module => {
// 返回 es6 module
})
.catch(() => {});

懒加载 当文件需要时才加载
预加载 prefetch 会在使用之前,提前加载 js 文件,等其他资源加载完毕了,再偷偷加载
正常加载认为是并行加载

OptimizeCssAssetsPlugin

我在打包时发现 node_modules 中的 css 文件无法被压缩。css loader 中的 minimize 选项早已被废弃,尝试使用了这个插件后解决了问题。

devserver

  • proxy
    代理的只能是本地 如果本身请求的目的地址已经指定不是本地,那么这个请求不会被 webpack 代理
  • HMR
    • 样式文件 可以使用 因为 style-loader 内部实现了 HMR
    • js 文件 默认不能使用 HMR 功能
    • html 文件不能热更新 将 html 文件 加入 entry 这样会重新加载页面 不需要作 HMR 功能

sourcemap

类型 描述
source-map 外部 错误代码的准确信息访问到源代码的准确位置
inline-source-map 内联 只生成一个 sourcemap
hidden-source-map 外联 错误代码错误原因,但是没有错误位置,不能追踪源代码错误,只能提示到构建后代码的错误位置
eval-source-map 内联 每一个文件都生成对应的 source-map,都在 eval 错误代码准确信息,和源代码的错误位置
nosources-source-map 外部 错误代码准确信息,但是没有任何源代码信息
cheap-source-map 外部 错误代码准确信息和源代码的错误位置 只能精确到行
cheap-module-source-map 外部 错误代码准确信息 和 源代码错误位置

module 会将 loader 的 source map 加入

开发环境:速度快,调试更友好
速度快(eval>inline>cheap)
eval-cheap-source-map
eval-source-map
调试更友好
source-map
cheap-moudule-source-map
cheap-source-map

eval-cheap-source-map

  • 内联和外部的区别 1. 外部生成了文件,内联没有 2. 内联构建速度更快

开发环境:速度快,调试更友好
eval>inline>cheap
source-map
cheap-module-source-map
cheap-source-map
eval-source-map / eval-cheap-moudule-source-map

生产环境:源代码要不要隐藏?调试友好程度
内联会让代码变大,所以再生产环境不用内联
nosources-source-map 全部隐藏
hidden-source-map 只隐藏代码,会提示构建代码错误信息

babel-loader

babel 缓存 cacheDirectory: true

tree shakin 去除无用代码

  • 必须使用 ES6 模块化

  • 开启 production 环境

  • 在 package.json 中配置 “sideEffects”:false 所有的代码都没有副作用,都可以进行 tree shaking 可能会把 css/ @bable/polyfill 文件干掉

  • 定义 nodejs 环境变量:决定使用 browserslist 的哪个环境 process.env.NODE_ENV = ‘production’

PWA

workbox->workbox-webpack-plugin

1
2
3
4
5
new WorkboxWebpackPlugin.GenerateSW {
// 生成 serverworker.js
clientClaim: true,
skipWaiting:true
}

eslint 不认识 window,navigator 全局变量,需要修改

1
2
3
env: {
browser: true;
}

多进程打包 thread-loader

开启多进程打包,进程启动大概 600ms,进程通信也有开销
多用在 babel-loader

1
2
3
4
5
6
{
loader:'thread-loader',
options:{
workers:2
}
}

externals 忽略打包

1
2
3
4
externals: {
// 忽略库名 -- npm 包名
jQuery: "jQuery";
}

dll

打包 dll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const { resolve } = require("path");
module.exports = {
entry: {
jquery: ["jquery"],
},
output: {
filename: "[name].js",
path: resolve(__dirname, "dll"),
library: "[name]_[hash]",
},
plugins: [
// 打包生成一个 manifest.json --> 提供和jquery映射
new webpack.DLLPlugin({
name: "[name]_[hash]",
path: resolve(__dirname, "dll/manifest.json"),
}),
],
};

定义 dll

1
2
3
new webpack.DLLReferencePlugin({
manifest: resolve(__dirname, "dll/manifest.json"),
});

引用 dll

1
2
3
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, "dll/jquery.js"),
});

配置详解

  • module

    • enforce:’pre’, // 优先执行
    • enforce:’post’, // 延后执行
  • resolve 解析模块的规则

    • alias 配置解析模块的别名
    • extensions:[‘.js’,’json’] 配置省略文件路径的后缀名
    • modules:[resolve(__dirname,’../../node_modules’),’node_modules’] 解析模块去找哪个目录
  • optimization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
optimization: {
// 将当前模块记录的其他模块的hash单独打包为一个文件 runtime 一定要加上 保证未改动模块缓存不失效
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`;
},
minimize:{
// 配置生产环境的压缩方案: js和css
new TerserWebpackPlugin({
// 开启缓存
cache:true,
// 开启多进程打包
parallel:true,
// 启动source-map
sourceMap:true
})
}
}