Webpack入门系列(三)
本文主要是对之前配置的本地webpack再次进行优化。没有阅读过前面两篇文章的建议先阅读入门系列一和入门系列二
传送门: Webpack入门系列(一) ,Webpack入门系列(二)
一、Eslint
eslint
想必大家很熟悉了,他可以格式化我们的代码,并且还可以在开发过程中对我们的代码进行检查,看是否有使用错的地方,大大减少了线上bug的产生。
首先安装eslint
npm i -D eslint
,安装好依赖后我们要在根目录创建一个eslint配置文件名为.eslintrc.js,当然,其他名称也行如.eslintrc.json等,我习惯了用js来命名配置文件。
module.exports = {
env: {
browser: true,
node: true,
es2021: true,
},
// 解析选项
parserOptions: {
ecmaVersion: 6, // ES 语法版本
sourceType: "module", // ES 模块化
ecmaFeatures: {
// ES 其他特性
jsx: true, // 如果是 React 项目,就需要开启 jsx 语法
},
},
// 具体检查规则
extends: ["eslint:recommended"],
// 具体检查规则
rules: {},
};
配置好之后,我们要指定eslint检测的文件目录,这就要用到eslint-webpack-plugin
eslint-webpack-plugin
首先安装npm i -D eslint-webpack-plugin
,然后使用插件,指定eslint需要检测的目录
new EslintWebpackPlugin({
context: path.resolve(__dirname, "./src"),
})
好了,我们可以build一下看一下效果。
可以看到eslint检测有效,成功检除了3个问题。
但是我们发现有的是引入的字体js代码,eslint也检测了。我们需要配置eslint忽略检测的目录。
可以在根目录下创建.eslintignore
文件,配置上要忽略检测的目录,如下
dist
node_modules
public
src/static/*
再一次build,成功减少报错
那对于这eslint报错,我们怎么解决呢?
如果你觉得这个报错是eslint的问题,而不是你的问题,好,你可以在eslint配置文件的rules中将这条校验规则关闭,如下。
cache
在项目越大的时候,eslint的代码检测反而月越慢,这时,我们应该开启缓存,将每一次的eslint结果缓存到本地,然后提升构建速度。开启方法如下
new EslintWebpackPlugin({
context: path.resolve(__dirname, "./src"),
cache: true, // 开启缓
// 缓存文件
cacheLocation: path.resolve(__dirname, "./node_modules/.cache/.eslintcache"),
}),
启动一下本地服务,发现生成缓存文件
二、 SourceMap
通常,在开发过程中,如果代码中有报错,我们如何去定位报错位置。我们需要借助一个工具,sourcemap
。他可以生成一个map文件,并且将代码位置的映射记录在map文件中,当代码报错时可以精准的定位到报错位置。
但是sourcemap也有缺点,就是会生成多余的map文件,如果sourcemap设置的不合理,还有可能造成项目代码的泄漏,所以,我们得小心配置。
根据webpack官方文档来看,webpack推荐在生产模式下不启动sorucemap,在开发模式下可以选择启用sorucemap,不同的类型,对于项目的启动编译也有影响,如下图
也可以直接前往webpakc官网查看,传送门
我们配置一下devtool
devtool: "eval-cheap-module-source-map"
三、HotModuleReplacement
对于css代码,如果我们想在改代码的时候就自动更新呢?只需要在devServer中开启热更新即可,如下
devServer: {
// 自动打开浏览器
open: true,
// 端口
port: "3000",
// 地址
host: "127.0.0.1",
// 热更新
hot: true,
// 静态目录
static: {
directory: path.resolve(__dirname, "./dist"),
},
},
但是对于js代码,默认是不支持热更新的。 我们可以手动增加热更新js文件
比如,我们新建一个sum方法,如下
const sum = (...args) => {
return args.reduce((pre, current) => {
return pre + current;
}, 0);
};
export { sum };
在入口文件main.js中引入。
我们启动本地服务
npm run dev
,打开控制面板
然后保存sum.js文件,发现控制台刷新了,说明js是没有热更新的,需要我们手动配置,如下
import { sum } from "./utils/sum";
console.log(sum(1, 2, 3));
// 判断是否支持热更新
if (module.hot) {
module.hot.accept("./utils/sum.js");
}
在启动服务后,修改sum.js文件后保存,发现控制台没有刷新,则热更新实现
那可能会有以下疑问,一个项目中有很多的js文件,每一个都需要手动引入吗?当然不是,vue和react都帮我们实现了。
四、oneOf
打包时每个文件都会经过所有 loader 处理,虽然因为 test
正则原因实际没有处理上,但是都要过一遍。比较慢。为了提高打包时的效率,我们只需要一种文件类型被一个loader处理即可,则需要配置oneOf,则需要在配置loader外层再包一层数组,如下
module: {
rules: [
{
oneOf: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
},
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"],
},
{
test: /\.s[ac]ss$/i,
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader", "sass-loader"],
},
{
test: /\.(png|jpe?g|gif)$/i,
type: "asset/resource",
generator: {
filename: "static/[hash][ext][query]",
},
},
{
test: /\.(ttf|woff|woff2)$/i,
type: "asset/resource",
generator: {
filename: "static/[hash][ext][query]",
},
},
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: "babel-loader",
options: {
cacheDirectory: true,
},
},
],
},
],
},
],
},
五、include/exclude
在需要大量编译操作的loader时,我们可以配置指定loader的处理文件夹,或者是指定不处理哪些文件夹,提升编译速度。
babel-loader
对于js的编译,我们可以指定需要babel处理的文件路径,如下
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: "babel-loader",
options: {
cacheDirectory: true,
include: path.resolve(__dirname, "./src"),
},
},
],
},
eslint
对于eslint需要检测的目录,我们可以排除需要代码检测的目录,如下
new EslintWebpackPlugin({
context: path.resolve(__dirname, "./src"),
exclude: "node_modules",
cache: true, // 开启缓
cacheLocation: path.resolve(__dirname, "./node_modules/.cache/.eslintcache"),
}),
六、thread多线程
当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码。这个速度是比较慢的。
我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。
而对 js 文件处理主要就是 eslint 、babel、Terser 三个工具,所以我们要提升它们的运行速度。
我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了。
需要注意:请仅在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右开销。 下面介绍一下怎么开启多线程打包
首先安装npm install --save-dev thread-loader
,然后我们在耗时的babel-loader中加入
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: [
"thread-loader",
{
loader: "babel-loader",
include: path.resolve(__dirname, "./src"),
options: {
cacheDirectory: true,
},
},
],
},
我们打包一下,发现确实变慢了,文件太少了,而开启进程的耗时太长了。需谨慎使用。
七、代码分割code split
目前我们在入口mian.js中引入的代码最终都被打包到一个js文件下了,这就造成了打包后main.js文件越来越大,在加载时就会出现白屏。我们想把模块都分开,我们可以在webpack.config.js中增加优化器配置
// 优化
optimization: {
splitChunks: {
chunks: "all", // 对所有模块都分割
},
},
然后,我们继续打包看一下,果然多出一个文件553.js
当然,我们也可以指定同类文件打包到一个js文件中,配置如下
cacheGroups: {
"vue":{
test: /[\\/]node_modules[\\/]vue(.*)?/,
name: 'vue-chunk.js',
priority: 40
},
libs:{
priority: 30,
test: /[\\/]node_modules[\\/]$/,
name: 'libs-chunk.js'
}
}
八、懒加载
通常在vue中,我们经常使用路由懒加载来优化加载速度,就是为了在首次加载的时候不一次性加载所有路由,在用户点击的时候才去请求相应的页面。 我们简单实现一下
首先在html文件中写一个按钮
<button class="btn">懒加载</button>
然后在main.js中写一些原生dom操作
const btn = document.querySelector(".btn");
btn.addEventListener("click", () => {
import(/* webpackChunkName:"count" */ "./utils/count").then((fn) => {
console.log(fn.default());
});
});
此时发现eslint报错了
我们安装个插件解决这个报错npm i eslint-plugin-import -D
然后在eslint配置文件中引入插件,如下
module.exports = {
// 继承 Eslint 规则
extends: ["eslint:recommended"],
env: {
node: true, // 启用node中全局变量
browser: true, // 启用浏览器中全局变量
es2020: true,
},
plugins: ["import"], // 解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的
parserOptions: {
ecmaVersion: 11,
sourceType: "module",
allowImportExportEverywhere: true,
},
rules: {
"no-var": 2, // 不能使用 var 定义变量
},
};
然后重新启动项目,打开f12,点击按钮,发现有个js的网络请求
懒加载成功
九、 Preload / Prefetch
为了进一步提高用户体验,有时懒加载的文件太大了会导致白屏,我们可不可以在浏览器空闲的时候去下载这些js资源呢?这里我们就需要用上 Preload
或 Prefetch
技术。
首先Preload / Prefetch是什么?
Preload
:告诉浏览器立即加载资源。Prefetch
:告诉浏览器在空闲时才开始加载资源。
他们的区别呢?
Preload
加载优先级高,Prefetch
加载优先级低。Preload
只能加载当前页面需要使用的资源,Prefetch
可以加载当前页面资源,也可以加载下一个页面需要使用的资源。
他们存在的问题
- 兼容性都不是好
如何使用?
安装插件
pnpm i -D @vue/preload-webpack-plugin
启动插件
new PreloadWebpackPlugin({
rel: "preload", // preload兼容性更好
as: "script",
// rel: 'prefetch' // prefetch兼容性更差
}),
我们打包一下,发现之前的懒加载count.js文件已经被标上了preload标签了
在页面首次加载时也是直接下载了count.js文件
十、PWA
我们想在用户没有网路的时候也能操作网址,这个要怎么实现呢?
首先安装插件pnpm i -D workbox-webpack-plugin
使用plugin
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true,
}),
然后在入口js文件中添加下面这一行代码
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('SW registered: ', registration);
}).catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}
build一下,然后起一个本地的静态服务serve dist
打开控制台发现
说明服务server work注册成功,接下来,我们关闭serve,然后重新刷新浏览器,发现页面还在。
如果刷新后页面不见了,需要把浏览器的停用缓存取消,如下。
十一、Network Cache
将来开发时我们对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了,速度很快。
但是这样的话就会有一个问题, 因为前后输出的文件名是一样的,都叫 main.js,一旦将来发布新版本,因为文件名没有变化导致浏览器会直接读取缓存,不会加载新资源,项目也就没法更新了。
所以我们从文件名入手,确保更新前后文件名不一样,这样就可以做缓存了。
webpack5中有以下几种类型的hash值
- fullhash(webpack4 是 hash)
每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。
- chunkhash
根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们 js 和 css 是同一个引入,会共享一个 hash 值。
- contenthash
根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的。 这我们使用contenthash来命名文件
output: {
filename: "js/[name].[contenthash:10].js",
path: path.resolve(__dirname, "./dist"),
assetModuleFilename: "static/[name][hash][ext][query]",
// 动态加载路径
chunkFilename: "js/[name].[contenthash:10].chunk.js",
// 打包前清空dist目录
clean: true,
},
目前我们更新一下count里的代码,发现main.js和count.js都变了
这是因为count里的代码发生了变化,进而contenthash值也发生了变化,导致main文件也发生了变化。这样main.js的缓存就失效了。
可不可以把对应的hash值存在一个文件里呢?保证main.js在不修改时不变。
我们需要做以下步骤,在优化器中添加runtimeChunk配置
// 优化
optimization: {
splitChunks: {
chunks: "all", // 对所有模块都分割
},
runtimeChunk: {
name: (entryPoint) => `runtime~${entryPoint.name}`,
},
},
我们再修改一下count.js文件,然后打包。这次发现只有count.js和runtime文件被修改了。
转载自:https://juejin.cn/post/7202891949379436600