1. 提升开发体验
1.1 SourceMap
1.1-1 为什么
运行后,所有的CSS和JS都合并成了一个文件,并且解决了其他代码,此时如果代码运行出错,那么提示代码错误的位置我们是看不懂的,那么我们及很难得去发现错误在哪里。
所以我们需要更加准确的错误提示,来帮助我们更好的开发代码。
1.1-2 是什么
SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的方案。他会生成XXX.map文件,里面包含源代码和构建后代码每一行,每一列得映射关系。当构建后代码出错了,会通过XXX.map文件,从构建后的代码出错位置找到映射后源代码出错的位置,从而让浏览器提示源代码文件出错的位置,帮助开发者能够更快的找到错误的根源。
1.1-3 怎么用
虽然SourceMap的值有很多种情况,但是实际情况开发的时候,我们一般关注两种即可:
开发模式: cheap-module-source-map
优点:打包编译速度快,只包含行映射
缺点:没有列映射
module.exports = {
// 其他省略号
mode: "development",
devtool: "cheap-module-source-map",
}
生产模式: source-map
优点:包含行/列映射(生产环境代码会被压缩到只有一行)
缺点:打包编译速度更慢
module.exports = {
// 其他省略号
mode: "production",
devtool: "source-map",
}
提高打包构建速度
2.1 Hot Module Replacement
2.1-1 为什么
开发时我们修改了其中一个模块代码,webpack默认会将所有模块全部重新打包编译,速度很慢,所以我们需要做到修改某个模块代码,就只有这个模块代码需要打包编译,其他模块不变,提高打包速度。
2.1-2 是什么
Hot Module Replacement(HMR/热模块替换)在程序运行中 替换 添加或删除模块 而无需重新加载整个页面。
2.1-3 怎么用
//webpack.config.js配置
devServer: {
// 其他省略
hot:false,//关闭HMR
},
//js 文件修改需要在main.js里去每一个js都做判断
if(module.hot) {
// 判断是否支持热模块替换功能
module.hot.accept("./js/count")//接收这个文件
}
//以上这么些在文件较多的时候会比较麻烦 所以需要借助loader
2.2 oneOf
2.2-1 为什么
打包时每个文件都会经过所有loader处理,虽然因为test正则原因实际没有处理上,但是都要过一遍速度比较慢。
2.2-2 是什么
oneOf会提高loader匹配效率,匹配到一个loader后,后面的就不会再继续匹配了。
2.2-3 怎么用
module:{
rules:[
{
test:/\.js$/,
exclude:/node_modules/,
// //优先执行
enforce:'pre',
loader:'eslint-loader',
options:{
fix:true
}
},
{
//下面的loader只会匹配一个,处理性能更好
//注意:不能有两个配置处理同一种类型文件
oneOf:[
{
test:/\.css$/,
use:[...commenCssLoader]
},
{
test:/\.less$/,
use:[...commenCssLoader,'less-loader']
},
]
}
]
},
2.3 include/exclude
2.3-1 为什么
webpack配置时,为了提高解析速度,需要指定需要处理的文件。
有三种配置可以指定需要处理的文件:
. test . include . exclude
2.3-2 是什么
include 包含 只处理xxx文件 exclude 排除 除了xxx文件以外其他文件都处理 优先级最高
2.3-3 怎么用
module:{
rules: [
{
test: /\.js$/,
// 只能二者存在其一
// include:path.resolve(__dirname,"../src"),//只处理src下的文件,其他文件不处理
exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
loader: "babel-loader",
},
],
},
2.4 cache
2.3-1 为什么
每次打包时js文件都要经过ESLint检查和Babel编译,速度比较慢,我们可以缓存之前得EsLint检查和babel编译结果,这样二次打包速度就会更快了(提升第一次以后得打包速度)。
2.3-2 是什么
对ESLint检查和babel编译结果进行缓存
2.3-3 怎么用
{
test: /\.js$/,
// 只能二者存在其一
// include:path.resolve(__dirname,"../src"),//只处理src下的文件,其他文件不处理
exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
loader: "babel-loader",
options:{
cacheDirectory:true,//开启babel缓存
cacheCompression:false,//关闭缓存文件压缩
}
},
2.5 Thead
2.5-1 为什么
开启多个线程同时来处理js(eslint babel Terser这三个工具从而提升他们的运行速度)
2.5-2 是什么
多进程打包:开启电脑得多个进程同时干一件事 速度更快 需要主意:请仅在特别耗时得操作中使用,因为每个进程启动大约有600ms左右开销
2.5-3 怎么用
1. 获取cpu核数
const os=require("os");
const threads=os.cpus().length;
2. 下载依赖
npm i thread-loader -D
{
test: /\.js$/,
{
loader:"thread-loader",//放在babel-loader之前
options:{
works:threads,//开启多进程对babel进行处理
}
},
{
loader: "babel-loader",
options:{
cacheDirectory:true,//开启babel缓存
cacheCompression:false,//关闭缓存文件压缩
}
}
},
3. js压缩
const TerserWebpackPlugin = require("terser-webpack-plugin");
new TerserWebpackPlugin({
parallel: threads, // 开启多进程和设置进程数量
})
或者放在
optimization: {
// 压缩的操作
minimizer: [
new TerserWebpackPlugin({
parallel: threads, // 开启多进程和设置进程数量
}),
],
},
3.减少代码体积
3.1 Babel
3.1-1 为什么
babel为编译的每个文件插入了辅助代码,使得代码体积过大 Babel对一些共共方法使用了非常小的辅助代码比如_extend,默认情况下会被添加到每一个需要他的文件中 你可以将这些辅助代码作为一个独立模块来避免重复引入
3.1-2 是什么
@babel/plugin-transform-runtime:禁用了babel自动对每个文件的runtime注入,而是引入 @babel/plugin-transform-runtime 并且所有辅助代码从这里引入
3.1-3 怎么用
//下载依赖
npm i @babel/plugin-transform-runtime -D
//配置文件
{
loader: "babel-loader",
options:{
plugins:["@babel/plugin-transform-runtime"]//减少代码体积
}
}
3.2 Tree Shaking
3.2-1 为什么
函数库或者第三方工具库或组件库不做处理就会把所有的打包,我们用到什么就打包什么
3.2-2 是什么
Tree Shaking是一个术语 通常用于描述移除JavaScript肿的没有使用上的代码 注意:他依赖ES moudle
3.2-3 怎么用
默认开启了
3.3 Image Minimizer
3.3-1 为什么
开发项目引用了较多的图片,那么图片体积会比较大,将来请求需求比较慢,可以对图片进行压缩,减少图片体积(在线链接图片就不需要,只有本地静态图片才需要压缩)
3.3-2 是什么
Image Minimizer-webpack-plugin:用来压缩图片的插件
3.3-3 怎么用
npm install image-minimizer-webpack-plugin --save-dev
**推荐用于无损优化的 imagemin 插件**
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev
**推荐用于有损优化的 imagemin 插件**
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev
对于`imagemin-svgo`v9.0.0+ 需要使用 svgo[配置](https://github.com/svg/svgo#configuration)
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const { extendDefaultPlugins } = require("svgo");
module.exports = {
module: {
rules: [
{
test: /.(jpe?g|png|gif|svg)$/i,
type: "asset",
},
],
},
plugins: [
new ImageMinimizerPlugin({
minimizerOptions: {
// Lossless optimization with custom option
// Feel free to experiment with options for better result for you
plugins: [
["gifsicle", { interlaced: true }],
["jpegtran", { progressive: true }],
["optipng", { optimizationLevel: 5 }],
// Svgo configuration here https://github.com/svg/svgo#configuration
[
"svgo",
{
plugins: extendDefaultPlugins([
{
name: "removeViewBox",
active: false,
},
{
name: "addAttributesToSVGElement",
params: {
attributes: [{ xmlns: "http://www.w3.org/2000/svg" }],
},
},
]),
},
],
],
},
}),
],
};
4.优化代码运行性能
4.1 Code Split
4.1-1 为什么
打包代码时会将所有js文件打包到一个文件中去,体积太大了,我们如果只要渲染首页,就应该只加载首页的js文件,其他文件不应该加载。 所以我们需要将打包生成的文件进行代码分割,生成多个js文件,渲染哪个页面就只加载某个js文件,这样加载的资源就少,速度更快。
4.1-2 是什么
代码分割主要做了两件事:
1、分割文件:将打包生成的文件进行分割,生成多个js文件
2、按需加载:需要哪个文件加载哪个文件
4.1-3 怎么用
代码分割实现方式有不同的方式
1、多入口
module.exports = {
entry: {
// 多入口
index: './index.js',
home: './home.js',
},
output: {
filename: '[name].[contenthash:4].js'
}
}
方式二 防止重复
作用
- 将node_modules中的代码单独打包一个chunk最终输出
- 自动分析多入口chunk中,如果有公共的问题件,会打包成单独一个chunk
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
方式三 按需加载,动态导入
import动态引入语法,能将文件单独打包
import(/* webpackChunkName: 'test' */ './test.js').then(()=>{
// 加载成功
}).catch(()=>{
// 加载失败
})
4.2 Preload/Prefetch
4.2-1 为什么
想在浏览器空闲时,加载后续使用的资源,我们就需要用上preload或prefetch技术
4.2-2 是什么
Preload:告诉浏览器立即加载资源
Prefetch:告诉浏览器在空闲时才开始加载资源
4.2-3 怎么用
1、懒加载 npm install --save-dev preload-webpack-plugin
当文件需要时才加载 new PreloadWebpackPlugin({ rel:'preload', as:'script', })
2、预加载
prefetch 会在使用前,提前加载js文件
正常加载可以认为是并行加载(同一时间加载多个文件) 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
NetWork Cache
filename: "[name].[contenthash:10].css", 可以看到,我们的main.js还是之前的包,所以这样一来,我们再更新版本的时候,没有修改到的文件缓存仍然存在 contenthash顾名思义,就是取的文件内容生成的hash值,内容变了hash就变了;而hash是每次webpack打包生成的hash值,所以,我们在提取分类的时候,所有提取出去的文件名都应该用contenthash
4.3 Core-js
4.3-1 为什么
能将es6得一些语法进行编译转换,比如箭头函数 ...运算符等,对于async函数 promise对象 数组的一些方法(includes)他没办法处理,所以对于js代码(Babel处理过后)仍然存在兼容性,一旦遇到低版本浏览器会直接报错,我们就需要将js得姜戎i选哪个问题彻底解决
4.3-2 是什么
core-js是专门用来做es6以及以上得api得polyfill
polyfill翻译过来叫做垫片/补丁,就是用社区上提供的一段代码,让我们在不兼容某些特性的浏览器上使用该特性
4.3-3 怎么用
npm i core-js
//手动全部引入 会将所有将兼容代码全部引入 体积太大
import 'core-js';
//手动按需引入
import 'core-js/es/promise';
//自动按需引入在babel配置文件里配置
module.exports = {
// 智能预设:能够编译ES6语法
presets: [
["@babel/preset-env",
{
useBuiltIns:'usage',//按需加载自动引入
corejs:3,
}
]
],
};
4.4 PWA
4.4-1 为什么
开发web app项目 项目一旦处于网路离线情况就没法访问 我们希望给项目提供离线体验
4.4-2 是什么
渐进式网络应用程序(progressive web application - PWA)是一种可以提供类似于native app(原生应用程序)体验的web app的技术 其中最重要的是 在离线时应用程序能够继续运行功能 内部通过Service Workers技术实现的
4.4-3 怎么用
npm install workbox-webpack-plugin --save-dev
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
+ const WorkboxPlugin = require('workbox-webpack-plugin');
module.exports = {
plugins: [
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true,
}),
],
};
index.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);
});
});
}