Webpack「v5.0」

Webpack「v5.0」

概念 | webpack 中文文档 (docschina.org)
webpack是一个现代JavaScript应用程序的静态模块打包工具。当webpack处理应用程序时,它会在内部构建一个依赖图(dependency [/dɪˈpendənsi/] graph [/ɡræf/]),此依赖图会映射项目所需的每个模块,并生成一个或多个bundle /ˈbʌndl/ 包!

0. 预备知识

0.1 为啥要学webpack?

0.1.1 性能优化

见性能优化

之前我们在讲前端的时候,我们可以从以下几个方面,去提高页面第一次渲染的速度,以及运行时的性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/* 
第一类:明显改善页面第一次加载的速度,减少白屏等待的时间
@1 使用骨架屏技术
+ 服务器骨架屏:页面首屏内容是基于服务器渲染的
+ 前端骨架屏:在真实内容渲染出来之前,我们给予相关位置用“色框”占位!(知乎?)
@2 减少HTTP请求的次数和大小
+ CSS/JS都要做合并压缩「尽可能合并为一个CSS和一个JS」
+ 图片的合并压缩,例如:雪碧图(CSS Sprit)
+ 使用图片BASE64来加快图片的渲染!
+ 图片一定要做延迟加载
+ 使用字体图片和矢量图,来代替位图!!
+ 音视频资源也要设置延迟加载「preload='none'」
+ 数据的异步加载
+ 开启服务器端的GZIP压缩,可以让访问的资源压缩60%+
@3 渲染页面中的优化
+ 因为<script>会阻碍GUI渲染,所以:把其放在页面末尾,或者设置async/defer!
+ 因为<link>是异步加载CSS资源,所以建议放在页面开始,尽快获取样式资源!!
+ 不要使用import导入CSS,因为其会阻碍GUI的渲染!
+ 样式代码较少的情况下,使用<style>内嵌式,尤其是移动端开发!
@4 在网络传输中的优化
+ 减少cookie的内容大小「因为只要向服务器发送请求,不论服务器是否需要,总会在请求头中把cookie传递给服务器,如果cookie比较大,会让所有的请求都变慢」
+ 资源分服务器部署「例如:web服务器、图片服务器、数据服务器」,这样可以降低服务器压力,提高服务器的并发,让资源能更加合理的被利用;但是也会导致DNS解析次数增加,此时可以基于DNS Prefetch来优化DNS解析!!
+ 基于HTTP/2.0来代替HTTP/1.1
+ 基于新的二进制格式,让传输的内容更加丰富健壮
+ header压缩,让传输速度更快(没改就不传)
+ 服务端自动推送,可以减少HTTP请求次数
+ 多路复用,让传输速度更快,避免线头阻塞(UDP)
+ 基于Connection:keep-alive保持TCP通道长链接「HTTP/1.1版本会自动开启」
+ 开启CDN「地域分布式服务器部署」
+ 对于静态资源文件采取“强缓存和协商缓存”,对于不经常更新的数据请求,设置数据缓存!

第二类:提高页面运行时候的性能
@1 减少循环嵌套,降低时间复杂度;避免出现死循环/死递归;
@2 使用事件委托来优化事件绑定,性能可以提高40%+
@3 减少DOM的回流(重排)和重绘
+ 样式分离读写「渲染队列机制」
+ 批量新增元素「字符串拼接、文档碎片」
+ 基于transform/opacity等方式操作样式
+ 把需要频繁修改样式的元素(例如:JS实现动画)脱离文档流
+ ...
@4 使用函数的防抖和节流处理高频触发操作
@5 合理使用闭包,避免内存泄漏,以及手动释放无用的内存!
@6 减少页面冗余代码,提高代码的重复使用率「也就是尽可能的做封装处理」
@7 尽可能不要使用for/in循环,因为其消耗性能
@8 避免使用eval/with等消耗性能的代码
@9 能用CSS3处理的动画坚决不用JS「JS动画尽可能基于requestAnimationFrame来代替定时器」
@10 避免使用CSS表达式,因为其非常消耗性能
@11 CSS选择器的前缀不要过长「因为CSS选择器渲染是从右到左」
@12 减少table布局「table的渲染是比较消耗性能的」
......
*/

我们

0.1.2为了兼容、预编译、开发预览

  • 平时开发的时候,一般会使用less/sass/stylus等CSS预编译器「需要基于vscode插件把其编译为CSS」;

  • 会直接使用ES6的语法「需要基于babel把其转换为ES5,以此兼容IE」;

  • 会给CSS3样式加很多的前缀,以此来处理兼容问题……

  • 我们还会基于vscode中的liveserver插件,创建一个本地web服务器,来预览我们的项目…..
    我们还要基于各种方案,来解决跨域访问的问题……

0.2 webpack可以帮我们干的事情

  • 代码转换:TypeScript编译成JavaScript、LESS/SCSS编译成CSS、ES6/7编译为ES5、虚拟DOM编译为真实的DOM等等…
  • 文件优化:压缩JS、CSS、HTML代码,压缩合并图片,图片BASE64等
  • 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码等
  • 模块合并:把模块分类合并成一个文件
  • 自动刷新:创建本地Web服务器,监听本地源代码的变化,自动重新构建,刷新浏览器
  • 代码校验:Eslint代码规范校验和检测、单元测试等
  • 自动发布:自动构建出线上发布代码并传输给发布系统
  • 跨域处理
  • ……

除了webpack外的其他:

  • grunt(淘汰)
  • gulp(少了、老了)
  • Parcel
  • vite(底层原理是rollup,比webpack快,但兼容不好)
  • rollup
  • Turbopack(据说比webpack快700倍,比vite快10倍)
  • ……

webpack问题

  • 项目“比较大”时,由于底层,打包速度比较慢。

1.webpack打包原理

2.webpack的基础操作

安装

为防止全局安装webpack导致版本冲突,真实项目中以本地安装为主
弊端:本地安装就需要配置webpack命令
npm比较慢建议使用yarn、pnmp

1
2
3
4
$ npm init -y
$ npm install webpack webpack-cli --save-dev
OR
$ yarn add webpack webpack-cli -D

零配置使用

1
2
3
4
5
6
/*
* 默认会打包SRC目录中的JS文件(入口默认index.js)
* 打包完成的目录默认是DIST/MAIN.JS
* webpack默认支持CommonJS和ES6 Module的模块规范,依此进行依赖打包
*/
$ npx webpack

自定义基础配置

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Node内置的路径处理模块
const path = require('path');

// 导出自定义配置项
module.exports = {
// 环境模式「生产环境:production 开发环境:development」
mode: 'production',
// 打包入口「相对路径」
entry: './src/index.js',
// 打包出口
output: {
// 生成的文件名. [hash]创建随机哈希值(方便缓存机制)
filename: 'bundle.[hash].js',
// 打包地址「绝对路径」
path: path.resolve(__dirname, 'dist')
}
};

html-webpack-plugin

https://www.webpackjs.com/plugins/html-webpack-plugin/
$ yarn add html-webpack-plugin -D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
// 指定视图模板
template: './public/index.html',
// 编译后视图的名字
filename: 'index.html',
// 是否压缩
minify: true
})
]
}

多入口 & 多出口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const HtmlWebpackPlugin = require('html-webpack-plugin');
// HtmlWebpackPlugin需要配置多套
const htmlPlugins = ['index', 'login'].map(chunk => {
return new HtmlWebpackPlugin({
template: `./public/${chunk}.html`,
filename: `${chunk}.html`,
// 指定导入的JS
chunks: [chunk],
minify: true
});
});
module.exports = {
mode: 'production',
// 配置多入口
entry: {
index: "./src/index.js",
login: "./src/login.js",
},
// 出口采用相同的规则
output: {
filename: "[name].[hash].js",
path: path.resolve(__dirname, "dist")
},
plugins: [
...htmlPlugins
]
};

clean-webpack-plugin

$ yarn add clean-webpack-plugin -D

1
2
3
4
5
6
7
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
...
plugins: [
new CleanWebpackPlugin()
]
};

webpack-dev-server

https://webpack.js.org/configuration/dev-server/
$ yarn add webpack-dev-server -D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* paclage.json */
"scripts": {
"start": "webpack server",
"build": "webpack"
}

/* webpack.config.js */
module.exports = {
...
devServer: {
// 域名
host: '127.0.0.1',
// 断口号
port: 3000,
// GZIP压缩
compress: true,
// 自动打开浏览器
open: true,
// 热更新
hot: true,
// 跨域代理
proxy: {
"/jian": {
target: "https://www.jianshu.com/asimov",
changeOrigin: true,
ws: true,
pathRewrite: { "^/jian": "" }
},
"/zhi": {
target: "https://news-at.zhihu.com/api/4",
changeOrigin: true,
ws: true,
pathRewrite: { "^/zhi": "" }
}
}
}
};
/*
测试接口
简书:
https://www.jianshu.com/asimov/subscriptions/recommended_collections
知乎:
https://news-at.zhihu.com/api/4/news/latest
*/

处理样式的loader「加载器」

$ yarn add css-loader style-loader less less-loader autoprefixer postcss-loader -D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = {
...
/* 配置模块加载器LOADER,执行顺序:从右向左、从下向上 */
module: {
rules: [{
test: /\.(css|less)$/, // 基于正则匹配哪些模块需要处理
use: [
"style-loader", // 把CSS插入到HEAD中
"css-loader", // 编译解析@import/URL()这种语法
"postcss-loader", // 设置前缀
"less-loader" // 把LESS编译为CSS
]
}]
}
};

postcss.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
plugins: [
require('autoprefixer')
]
};

// 或者在配置项中直接处理
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
require('autoprefixer')
]
}
}
},

package.json OR .browserslistrc

1
2
3
4
5
6
// https://github.com/browserslist/browserslist
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]

mini-css-extract-plugin 抽离CSS样式 /ˈekstrækt/

https://www.npmjs.com/package/mini-css-extract-plugin
$ yarn add mini-css-extract-plugin -D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...
plugins: [
...
new MiniCssExtractPlugin({
// 设置编译后的文件名字
filename: 'main.[hash].css'
})
],
...
module: {
rules: [{
test: /\.(css|less)$/,
use: [
// 使用插件中的LOADER代替STYLE方式
MiniCssExtractPlugin.loader,
...
]
}]
}
};

基于babel实现ES6的转换

$ yarn add babel babel-loader @babel/preset-env @babel/core -D

$ yarn add @babel/polyfill

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
...
module: {
rules: [{
test: /\.js$/,
use: ['babel-loader'],
// 设置编译时忽略的文件和指定编译目录
include: path.resolve(__dirname, 'src'),
exclude: /node_modules/
}]
}
};

babel.config.js

1
2
3
4
5
module.exports = {
presets: [
"@babel/preset-env"
]
};

index.js

1
import '@babel/polyfill';

设置优化项:压缩CSS/JS

$ yarn add css-minimizer-webpack-plugin terser-webpack-plugin -D

1
2
3
4
5
6
7
8
9
10
11
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
optimization: {
// 设置压缩方式
minimizer: [
new CssMinimizerWebpackPlugin(),
new TerserPlugin()
]
}
};

设置解析器:配置别名

1
2
3
4
5
6
7
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
}
};

图片的处理

$ yarn add file-loader url-loader -D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
module.exports = {
...
module: {
rules: [
...
{
test: /\.(png|jpe?g|gif)$/i,
type: 'javascript/auto',
use: [{
// 把指定大小内的图片BASE64
loader: 'url-loader',
options: {
limit: 200 * 1024,
esModule: false,
name: 'images/[name].[hash].[ext]'
}
}]
}]
},
// 设置打包的最大资源大小
performance: {
maxAssetSize: 100 * 1024 * 1024 * 1024,
maxEntrypointSize: 100 * 1024 * 1024 * 1024
}
};