玩转webpack
# webpack 简介
# 为什么选择 webpack
- 社区生态丰富
- 官方插件多
- 配置灵活
# 初识 webpack
- 默认配置文件
webpack.config.js
可以通过webpack --config
指定配置文件
# 配置组成
- entry 打包的入口文件
- output 打包的输出
- mode 环境
- module
- rules Loader 配置
- plugins 插件
零配置 webpack 只包含 entry output
# 安装 webpack
webpack 4 将 webpack 和 webpack-cli 做了分离 所以在开发时需要同时安装两者
mkdir xx
cd xx
npm init -y
npm install webpack webpack-cli --save-dev
./node_modules/.bin/webpack -v
2
3
4
5
# webpack 初体验
// webpack.config.js
"use strict";
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
path: path.join(__dirname, "dist"),
filename: "bundle.js"
}
};
2
3
4
5
6
7
8
9
10
11
12
配置好后在终端运行./node_modules/.bin/webpack
即可打包
每次在终端运行太过麻烦,可以通过npm run build
运行构建
在package.json
中加入
"scripts": {
"build": "webpack"
},
2
3
原理:模块局部安装会在node_modules/.bin
目录创建软连接
# webpack 基础用法
# 核心概念 - entry
- 单入口 entry 是一个字符串
- 多入口 entry 是一个对象
# 核心概念 - output
output 用来告诉 webpack 如何将编译后的文件输出到磁盘
- 单入口 只需指定 filename, path 即可
- 多入口 通过占位符确保文件名称的唯一
filename:'[name].js'
# 核心概念 - loaders
webpack 本身只支持 JS 和 JSON 两种文件类型,通过 loader 去支持其他文件类型并把它们转化成有效的模块添加到依赖图中
Loaders 本身是一个函数 接收源文件作为参数 返回转换结果
# 常见 Loaders 有哪些?
名称 | 描述 |
---|---|
babel-loader | 转换 ES6、ES7 等新特性语法 |
css-loader | .css 文件的加载和解析 |
less-loader | less 文件转换成 css |
ts-loader | ts 转换成 js |
file-loader | 进行图片、字体等打包 |
raw-loader | 将文件以字符串的形式导入 |
thread-loader | 多进程打包 JS CSS |
# Loaders 用法
test
指定匹配规则use
指定使用的 loader 名称
module: {
rules: [{ test: /\.txt$/, use: "raw-loader" }];
}
2
3
# 核心概念 - plugins
用于 bundle 文件的优化,资源管理和环境变量注入,作用于整个构建过程。
由于插件可以携带参数/选项,你必须在 webpack 配置中,向 plugins 属性传入 new 实例。
用 loader 完成不了的事情可以用 plugins 完成
# 常见 Loaders 有哪些?
名称 | 描述 |
---|---|
CommonsChunkPlugin | 将 chunks 相同的模块代码提取成公告 js |
CleanWebpackPlugin | 清理构建目录 |
ExtractTextWebpackPlugin | 将 CSS 从 bundle 里提取成一个独立 CSS 文件 |
CopyWebpackPlugin | 将文件或者文件夹拷贝到构建的输出目录 |
HtmlWebpackPlugin | 创建 html 文件去承载输出的 bundle |
UglifyjsWebpackPlugin | 压缩 JS |
ZipWebpackPlugin | 将打包出的资源生成一个 zip 包 |
# 核心概念 - mode
用来指定当前的构建环境是 production
development
还是 none
# 解析 ES6
- 使用 babel-loader 配置文件为
.babelrc
npm i @babel/core @babel/preset-env babel-loader -D
// .babelrc
{
"preset":["@babel/preset-env"]
}
2
3
4
# 解析 CSS
- css-loader 用于加载.css 文件并转换成 commonjs 对象
- style-loader 将样式通过
<style>
标签插入到 head 中
因为 loader 是链式调用(从右往左)所以要先写 style 后写 css
{
test:/.css$/,
use:[
'style-loader',
'css-loader'
]
}
2
3
4
5
6
7
# 解析 Less 和 Sass
{
test:/.sass$/,
use:[
'style-loader',
'css-loader',
'sass-loader'
]
}
2
3
4
5
6
7
8
# 解析图片 字体
使用 file-loader
- 图片:
test:/.(png|jpg|gif|jpeg)$/
- 图片:
使用 url-loader
- 可以设置较小资源自动 base64
- 内部是使用 file-loader
- 传递参数
options:{limit:10240}
小于 10kb 自动 base64
# webpack 中的文件监听
文件监听是在源码发生变化时自动重新构建出新的输出文件
两种开启监听模式的方法:
- 启动 webpack 命令时带上
--watch
参数- script 中加入
"watch":"webpack --watch"
- 唯一缺陷:每次需要手动刷新浏览器
- script 中加入
- 配置 webpack.config.js 中设置
watch:true
# 原理分析
轮询判断文件最后编辑时间是否发生变化
如发生变化并不会立刻告诉监听者,而是缓存等待 aggregateTimeout
modelu.export = {
watch: true,
watchOptions: {
ignored: /node_modules/, //可提高性能
aggregateTimeout: 300, //监听到变化时等300ms再去执行
poll: 1000 //轮询 每秒1000次
}
};
2
3
4
5
6
7
8
# 热更新 webpack-dev-server
- WDS 不需要刷新浏览器
- WDS 不输出文件 而是放在内存中
- 和 HotModuleReplacementPlugin 配合使用
脚本:"dev":"webpack-dev-server --open"
plugins: [
new webpack.HotModuleReplacementPlugin()
],
devServer: {
contentBase:'./dist',
hot:true
}
2
3
4
5
6
7
使用 webpack-dev-middleware 也可以进行热更新但是需要设置 node 服务器
# 原理分析
# 文件指纹
文件指纹就是打包后输出的文件名的后缀
# 如何生成文件指纹
- Hash:和整个项目的构建相关,只要项目有修改,整个项目的 hash 值就会改变
- Chunkhash:和 webpack 打包的 chunk 有关,不同的 entry 会生成不同的 chunkhash 值
- Contenthash:根据文件内容来定义 hash,文件内容不变则不变
# JS 的文件指纹设置
output: {
filename: "[name]_[chunkhash:8].js";
}
2
3
# CSS 的文件指纹设置
该 plugin 会把 css 提取成独立文件 与 style-loader 冲突
plugins: [
new MiniCssRxtractPlugin({
filename: `[name]_[contenthash:8].css`
})
];
2
3
4
5
# 图片 的文件指纹设置
use: [
{
loader: "file-loader",
options: {
name: "img/[name]_[hash:8].[ext]"
}
}
];
2
3
4
5
6
7
8
将生产和开发环境配置分开分别为 webpack.prod.js 和 webpack.dev.js
并在脚本处添加 --config [配置文件]
# 代码压缩
# JS 文件的压缩
内置了 uglifyjs-webpack-plugin
# CSS 文件的压缩
- 使用 optimize-css-assets-webpack-plugin
- 同时使用 cssnano
# html 文件的压缩
修改 html-webpack-plugin 设置压缩参数
通常一个页面对应一个 plugin
# webpack 进阶用法
# 自动清理构建目录产物
每次构建的时候不会清理目录,造成构建的输出目录 output 文件越来越多
通过 npm scripts 清理目录
rm -rm ./dist && webpack
rimraf ./dist && webpack
使用 clean-webpack-plugin ✔️
# PostCSS 插件 autoprefixer 自动补齐 CSS3 前缀
# 为什么 CSS3 属性需要前缀
浏览器内核不统一 如-moz -webkit
# 如何自动补齐
使用 autoprefixer 插件(后置处理)根据 caniuse.com 原则
通常和 postcss-loader 一起使用
# 移动端 CSS px 自动转 rem
# 浏览器的分辨率
- 之前使用媒体查询实现响应式布局 缺陷:需要编写多套适配样式代码
- 现在使用 rem:font-size of the root element
使用 px2rem-loader 配合 lib-flexible 库(根据分辨率自动计算根元素 font-size)
# 静态资源内联
# 资源内联的意义
代码层面
- 页面框架的初始化脚本
- 上报相关打点
- css 内联避免页面闪动
请求层面:减少 HTTP 网络请求数
- 小图片或者字体内联 url-loader
# JS 和 HTMl 内联
- raw-loader
<head>
${require('raw-loader!./meta.html')}
<title>document</title>
<script>
${require('raw-loader!babel-loader!../../node_modules/lib-flexible/flexible.js')}
</script>
</head>
2
3
4
5
6
7
# CSS 内联
- style-loader options 设置
singleton:true
- html-inline-css-webpack-plugin ✔️
# 多页面应用打包通用方案
# 多页面应用(MPA)概念
每一次页面跳转时后台服务器都会给返回一个新的 html 文档
- 每个页面解耦
- 对 SEO 更加友好
# 多页面打包基本思路
每个页面对应一个 entry ➕ 一个 html-webpack-plugin 缺点:每次新增或删除页面需要修改 webpack 配置
# 多页面打包通用方案 ✔️
动态获取 entry 并设置 html-webpack-plugin 数量
- 利用 glob.sync
- 命名遵循: ./src/页面名/index.js
entry:glob.sync(path.join(__dirname,'./src/*/index.js'))
# 使用 sourceMap
module.exports = {
plugins:[],
devtool: "source-map";
}
2
3
4
通过 sourceMap 定位到源码
- 开发环境开启,线上环境关闭
- 线上排查问题的时候可以将 sourcemap 上传到错误监控系统
# sourceMap 关键字
eval
使用 eval 包裹模块代码source map
产生.map 文件cheap
报错定位 不包含列信息inline
将.map 作为 DataURI 嵌入,不单独生成.map 文件 内联到 JS 文件module
包含 loader 的 sourceMap
# 提取公共资源
# 基础库分离 react/vue
- 思路:将 react/react-dom 基础包通过 cdn 引入 不打入 bundle 中
- 方法:使用 html-webpack-externals-plugins
# 利用 SplitChunksPlugin 进行公共脚本分离
- wp4 内置 代替 CommonsChunkPlugin
- 参数说明
async
异步引入的库进行分离(默认)initial
同步引入的库进行分离all
所有引入的库进行分离
# 利用 SplitChunksPlugin 分离基础包
test
匹配需要分离的包
# 利用 SplitChunksPlugin 分离页面公共文件
minChunks
设置最小引用次数minSize
分离的包体积大小
# tree shaking 摇树优化
- 概念:只把用到的方法打入 bundle,没用到的方法会在 uglify 阶段被擦出掉
- 使用:webpack 默认支持,在.babelrc 里设置 modules:false 即可
- production mode 默认开启
- 要求:必须是 ES6 语法
# DCE
- 代码不会被执行
- 代码执行的结果不会被用到
- 代码只会影响死变量(只写不读)
# tree shaking 原理
利用 ES6 模块特点
- 只能作为模块顶层语句出现
- import 的模块名只能是字符串常量
- import binding 是 immutable 的
uglify 阶段删除无用代码
# scope hoisting
构建后的代码存在大量闭包,将导致:
- 大量闭包包裹代码,体积增大
- 运行代码时创建的函数作用域变多,内存开销变大
# 模块转换分析
# scope hoisting 原理
- 原理:将所有模块代码按照引用顺序放在一个函数作用域里,然后适当重命名一些变量以防止变量名冲突
- 对比:通过 scope hoisting 可以减少函数声明代码和内存开销
# scope hoisting 使用
- production mode 默认开启
- 必须是 ES6 语法
# 代码分割和动态 import
# 代码分割的意义
对于大的 web 应用来说,将所有的代码都放在一个文件中显然是不够有效的,特别适合当你的某些代码块是在某些特殊的时候才会被用到。webpack 有一个功能就是将你的代码库分割成 chunks(语块)当代码运行到需要他们的时候在进行加载。
- 适用的场景:
- 抽离相同代码到一个共享块
- 脚本懒加载,使得初始下载的代码更小
# 懒加载 JS 脚本的方式
- CommonJS :
require.ensure
- ES6 : 动态 import(需要 babel 转换)
- 安装 babel 插件
npm install @babel/plugin-syntax-dynamic-import --save-dev
- babelrc 中新增 plugin
- 安装 babel 插件
# 使用 ESLint
# ESLint 如何执行落地
- webpack 与 CI/CD 集成
在 build 前增加 lint pipeline