它会以一个或多个文件作为打包的入口,将我们整个项目所有文件编译组合成一个或多个文件输出出去。输出的文件就是编译好的文件,就可以在浏览器段运行了。我们将 Webpack 输出的文件叫做 bundle
基本使用 Webpack 本身功能是有限的:
开发模式:仅能编译 JS 中的 ES Module 语法
生产模式:能编译 JS 中的 ES Module 语法,还能压缩 JS 代码
资源目录 1 2 3 4 5 6 webpack_code // 项目根目录(所有指令必须在这个目录运行) └── src // 项目源码目录 ├── js # js文件目录 │ ├── count.js │ └── sum.js └── main.js # 项目主文件
创建文件 count.js 1 2 3 export default function count (x, y ) { return x - y; }
sum.js 1 2 3 export default function sum (...args ) { return args.reduce ((p, c ) => p + c, 0 ); }
main.js 1 2 3 4 5 import count from "./js/count" ;import sum from "./js/sum" ;console .log (count (2 , 1 ));console .log (sum (1 , 2 , 3 , 4 ));
下载依赖 打开终端,来到项目根目录。运行以下指令:
此时会生成一个基础的 package.json
文件。需要注意的是 package.json
中 name 字段不能叫做 webpack , 否则下一步会报错
1 npm i webpack webpack-cli -D
启用 Webpack
1 npx webpack ./src/main.js --mode=development
1 npx webpack ./src/main.js --mode=production
npx webpack
:是用来运行本地安装 Webpack 包的
./src/main.js
:指定 Webpack 从 main.js 文件开始打包,不但会打包 main.js,还会将其依赖也一起打包进来
--mode=xxx
:指定模式(环境)
默认 Webpack 会将文件打包输出到 dist 目录下,我们查看 dist 目录下文件情况就好了。Webpack 本身功能比较少,只能处理 js 资源,一旦遇到 css 等其他资源就会报错。所以我们学习 Webpack,就是主要学习如何处理其他资源
基本配置 在开始使用 Webpack 之前,我们需要对 Webpack 的配置有一定的认识
entry(入口):指示 Webpack 从哪个文件开始打包
output(输出):指示 Webpack 打包完的文件输出到哪里去,如何命名等
loader(加载器):webpack 本身只能处理 js、json 等资源,其他资源需要借助 loader,Webpack 才能解析
plugins(插件):扩展 Webpack 的功能
mode(模式):主要由两种模式
开发模式:development
生产模式:production
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 const path = require ("path" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "dist" ), filename : "main.js" , }, module : { rules : [], }, plugins : [], mode : "development" , };
Webpack 将来都通过 webpack.config.js
文件进行配置,来增强 Webpack 的功能
处理样式资源 Webpack 本身是不能识别样式资源的,所以我们需要借助 Loader 来帮助 Webpack 解析样式资源。我们找 Loader 都应该去官方文档中找到对应的 Loader,然后使用。官方文档找不到的话,可以从社区 Github 中搜索查询 Webpack 官方 Loader 文档
处理 Css 资源 下载包 1 npm i css-loader style-loader -D
注意:需要下载两个 loader
功能介绍
css-loader
:负责将 Css 文件编译成 Webpack 能识别的模块
style-loader
:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容
此时样式就会以 Style 标签的形式在页面上生效
配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const path = require ("path" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "dist" ), filename : "main.js" , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, ], }, plugins : [], mode : "development" , };
处理 Less 资源 下载包
功能介绍 less-loader
:负责将 Less 文件编译成 Css 文件
配置 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 const path = require ("path" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "dist" ), filename : "main.js" , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, ], }, plugins : [], mode : "development" , };
处理 Sass 和 Scss 资源 下载包 1 npm i sass-loader sass -D
注意:需要下载两个
功能介绍
sass-loader
:负责将 Sass 文件编译成 css 文件
sass
:sass-loader 依赖 sass 进行编译
配置 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 const path = require ("path" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "dist" ), filename : "main.js" , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, ], }, plugins : [], mode : "development" , };
处理 Styl 资源 下载包
功能介绍 stylus-loader
:负责将 Styl 文件编译成 Css 文件
配置 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 const path = require ("path" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "dist" ), filename : "main.js" , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : ["style-loader" , "css-loader" , "stylus-loader" ], }, ], }, plugins : [], mode : "development" , };
处理图片资源 过去在 Webpack4 时,我们处理图片资源通过 file-loader 和 url-loader 进行处理。现在 Webpack5 已经将两个 Loader 功能内置到 Webpack 里了,我们只需要简单配置即可处理图片资源
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 const path = require ("path" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "dist" ), filename : "main.js" , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : ["style-loader" , "css-loader" , "stylus-loader" ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , }, ], }, plugins : [], mode : "development" , };
修改输出资源的名称和路径 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 const path = require ("path" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "dist" ), filename : "static/js/main.js" , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : ["style-loader" , "css-loader" , "stylus-loader" ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, ], }, plugins : [], mode : "development" , };
自动清空上次打包资源 可以通过在配置文件中的 output 选项中指定 clean 属性为 true 来启用自动清空上次打包资源的功能
1 2 3 4 5 6 7 8 9 module .exports = { entry : './src/index.js' , output : { path : path.resolve (__dirname, 'dist' ), filename : 'bundle.js' , clean : true , }, mode : 'production' , };
处理 js 资源 有人可能会问,js 资源 Webpack 不能已经处理了吗,为什么我们还要处理呢?原因是 Webpack 对 js 处理是有限的,只能编译 js 中 ES 模块化语法,不能编译其他语法,导致 js 不能在 IE 等浏览器运行,所以我们希望做一些兼容性处理。其次开发中,团队对代码格式是有严格要求的,我们不能由肉眼去检测代码格式,需要使用专业的工具来检测:
针对 js 兼容性处理,我们使用 Babel 来完成
针对代码格式,我们使用 Eslint 来完成
我们先完成 Eslint,检测代码格式无误后,在由 Babel 做代码兼容性处理
Eslint 可组装的 JavaScript 和 JSX 检查工具。这句话意思就是:它是用来检测 js 和 jsx 语法的工具 ,可以配置各项功能。我们使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查
配置文件 配置文件由很多种写法:
.eslintrc.*
:新建文件,位于项目根目录
.eslintrc
.eslintrc.js
.eslintrc.json
区别在于配置格式不一样
package.json
中 eslintConfig:不需要创建文件,在原有文件基础上写
ESLint 会查找和自动读取它们,所以以上配置文件只需要存在一个即可
具体配置 我们以 .eslintrc.js 配置文件为例:
1 2 3 4 5 6 7 8 9 10 module .exports = { parserOptions : {}, rules : {}, extends : [], };
parserOptions 解析选项:
1 2 3 4 5 6 7 parserOptions : { ecmaVersion : 6 , sourceType : "module" , ecmaFeatures : { jsx : true } }
rules 具体规则:
"off" 或 0 - 关闭规则
"warn" 或 1 - 开启规则,使用警告级别的错误:warn(不会导致程序退出)
"error" 或 2 - 开启规则,使用错误级别的错误:error(当被触发的时候,程序会退出)
1 2 3 4 5 6 7 8 9 10 11 12 rules : { semi : "error" , 'array-callback-return' : 'warn' , 'default-case' : [ 'warn' , { commentPattern : '^no default$' } ], eqeqeq : [ 'warn' , 'smart' ] }
更多规则详见:规则文档 。开发中一点点写 rules 规则太费劲了,所以有更好的办法,继承现有的规则。现有以下较为有名的规则:
1 2 3 4 5 6 7 8 9 module .exports = { extends : ["react-app" ], rules : { eqeqeq : ["warn" , "smart" ], }, };
在 Webpack 中使用
1 npm i eslint-webpack-plugin eslint -D
定义 Eslint 配置文件:.eslintrc.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 module .exports = { extends : ["eslint:recommended" ], env : { node : true , browser : true , }, parserOptions : { ecmaVersion : 6 , sourceType : "module" , }, rules : { "no-var" : 2 , }, };
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 52 53 54 55 56 57 58 59 60 61 62 63 64 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "dist" ), filename : "static/js/main.js" , clean : true , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : ["style-loader" , "css-loader" , "stylus-loader" ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "src" ), }), ], mode : "development" , };
Babel JavaScript 编译器。主要用于将 ES6 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中
配置文件 配置文件由很多种写法:
babel.config.*
:新建文件,位于项目根目录
babel.config.js
babel.config.json
.babelrc.*
:新建文件,位于项目根目录
.babelrc
.babelrc.js
.babelrc.json
package.json
中 babel:不需要创建文件,在原有文件基础上写
Babel 会查找和自动读取它们,所以以上配置文件只需要存在一个即可
具体配置 我们以 babel.config.js
配置文件为例:
1 2 3 4 module .exports = { presets : [], };
presets 预设。简单理解:就是一组 Babel 插件, 扩展 Babel 功能
@babel/preset-env
: 一个智能预设,允许您使用最新的 JavaScript
@babel/preset-react
:一个用来编译 React jsx 语法的预设
@babel/preset-typescript
:一个用来编译 TypeScript 语法的预设
在 Webpack 中使用
1 npm i babel-loader @babel/core @babel/preset-env -D
定义 Babel 配置文件:babel.config.js
1 2 3 module .exports = { presets : ["@babel/preset-env" ], };
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "dist" ), filename : "static/js/main.js" , clean : true , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : ["style-loader" , "css-loader" , "stylus-loader" ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , exclude : /node_modules/ , loader : "babel-loader" , }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "src" ), }), ], mode : "development" , };
处理 Html 资源 下载包 1 npm i html-webpack-plugin -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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "dist" ), filename : "static/js/main.js" , clean : true , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : ["style-loader" , "css-loader" , "stylus-loader" ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , exclude : /node_modules/ , loader : "babel-loader" , }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "src" ), }), new HtmlWebpackPlugin ({ template : path.resolve (__dirname, "public/index.html" ), }), ], mode : "development" , };
开发服务器 & 自动化 每次写完代码都需要手动输入指令才能编译代码,太麻烦了,我们希望一切自动化
下载包 1 npm i 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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "dist" ), filename : "static/js/main.js" , clean : true , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : ["style-loader" , "css-loader" , "stylus-loader" ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , exclude : /node_modules/ , loader : "babel-loader" , }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "src" ), }), new HtmlWebpackPlugin ({ template : path.resolve (__dirname, "public/index.html" ), }), ], devServer : { host : "localhost" , port : "3000" , open : true , }, mode : "development" , };
注意运行指令发生了变化:npx webpack serve
。当你使用开发服务器时,所有代码都会在内存中编译打包,并不会输出到 dist 目录下
生产模式 生产模式是开发完成代码后,我们需要得到代码将来部署上线。这个模式下我们主要对代码进行优化,让其运行性能更好。优化主要从两个角度出发:
优化代码运行性能
优化代码打包速度
我们分别准备两个配置文件来放不同的配置
1 2 3 4 5 6 7 8 9 10 11 12 ├── webpack-test(项目根目录) ├── config(Webpack配置文件目录) │ ├── webpack.dev.js(开发模式配置文件) │ └── webpack.prod.js(生产模式配置文件) ├── node_modules(下载包存放目录) ├── src(项目源码目录,除了html其他都在 src 里面) │ └── 略 ├── public(项目 html 文件) │ └── index.html ├── .eslintrc.js(Eslint 配置文件) ├── babel.config.js(Babel 配置文件) └── package.json(包的依赖管理配置文件)
webpack.dev.js 因为文件目录变了,所以所有绝对路径需要回退一层目录才能找到对应的文件
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );module .exports = { entry : "./src/main.js" , output : { path : undefined , filename : "static/js/main.js" , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : ["style-loader" , "css-loader" , "stylus-loader" ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , exclude : /node_modules/ , loader : "babel-loader" , }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "../src" ), }), new HtmlWebpackPlugin ({ template : path.resolve (__dirname, "../public/index.html" ), }), ], devServer : { host : "localhost" , port : "3000" , open : true , }, mode : "development" , };
运行开发模式的指令:npx webpack serve --config ./config/webpack.dev.js
webpack.prod.js 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "../dist" ), filename : "static/js/main.js" , clean : true , }, module : { rules : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : ["style-loader" , "css-loader" , "stylus-loader" ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , exclude : /node_modules/ , loader : "babel-loader" , }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "../src" ), }), new HtmlWebpackPlugin ({ template : path.resolve (__dirname, "../public/index.html" ), }), ], mode : "production" , };
运行生产模式的指令:npx webpack --config ./config/webpack.prod.js
配置运行指令 为了方便运行不同模式的指令,我们将指令定义在 package.json
中 scripts 里面
1 2 3 4 5 6 7 8 { "scripts" : { "start" : "npm run dev" , "dev" : "npx webpack serve --config ./config/webpack.dev.js" , "build" : "npx webpack --config ./config/webpack.prod.js" } }
以后启动指令:
开发模式:npm start
或 npm run dev
生产模式:npm run build
Css 处理 提取 Css 成单独文件 Css 文件目前被打包到 js 文件中,当 js 文件加载时,会创建一个 style 标签来生成样式。这样对于网站来说,会出现闪屏现象,用户体验不好。我们应该是单独的 Css 文件,通过 link 标签加载性能才好
下载包 1 npm i mini-css-extract-plugin -D
配置 webpack.prod.js 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );const MiniCssExtractPlugin = require ("mini-css-extract-plugin" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "../dist" ), filename : "static/js/main.js" , clean : true , }, module : { rules : [ { test : /\.css$/ , use : [MiniCssExtractPlugin .loader , "css-loader" ], }, { test : /\.less$/ , use : [MiniCssExtractPlugin .loader , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : [MiniCssExtractPlugin .loader , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : [MiniCssExtractPlugin .loader , "css-loader" , "stylus-loader" ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , exclude : /node_modules/ , loader : "babel-loader" , }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "../src" ), }), new HtmlWebpackPlugin ({ template : path.resolve (__dirname, "../public/index.html" ), }), new MiniCssExtractPlugin ({ filename : "static/css/main.css" , }), ], mode : "production" , };
Css 兼容性处理 下载包 1 npm i postcss-loader postcss postcss-preset-env -D
配置 webpack.prod.js 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );const MiniCssExtractPlugin = require ("mini-css-extract-plugin" );module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "../dist" ), filename : "static/js/main.js" , clean : true , }, module : { rules : [ { test : /\.css$/ , use : [ MiniCssExtractPlugin .loader , "css-loader" , { loader : "postcss-loader" , options : { postcssOptions : { plugins : [ "postcss-preset-env" , ], }, }, }, ], }, { test : /\.less$/ , use : [ MiniCssExtractPlugin .loader , "css-loader" , { loader : "postcss-loader" , options : { postcssOptions : { plugins : [ "postcss-preset-env" , ], }, }, }, "less-loader" , ], }, { test : /\.s[ac]ss$/ , use : [ MiniCssExtractPlugin .loader , "css-loader" , { loader : "postcss-loader" , options : { postcssOptions : { plugins : [ "postcss-preset-env" , ], }, }, }, "sass-loader" , ], }, { test : /\.styl$/ , use : [ MiniCssExtractPlugin .loader , "css-loader" , { loader : "postcss-loader" , options : { postcssOptions : { plugins : [ "postcss-preset-env" , ], }, }, }, "stylus-loader" , ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , exclude : /node_modules/ , loader : "babel-loader" , }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "../src" ), }), new HtmlWebpackPlugin ({ template : path.resolve (__dirname, "../public/index.html" ), }), new MiniCssExtractPlugin ({ filename : "static/css/main.css" , }), ], mode : "production" , };
控制兼容性 我们可以在 package.json
文件中添加 browserslist 来控制样式的兼容性做到什么程度
1 2 3 4 { "browserslist" : [ "ie >= 8" ] }
想要知道更多的 browserslist 配置,查看 browserslist 文档 。以上为了测试兼容性所以设置兼容浏览器 ie8 以上。实际开发中我们一般不考虑旧版本浏览器了,所以我们可以这样设置
1 2 3 4 { "browserslist" : [ "last 2 version" , "> 1%" , "not dead" ] }
合并配置 webpack.prod.js 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );const MiniCssExtractPlugin = require ("mini-css-extract-plugin" );const getStyleLoaders = (preProcessor ) => { return [ MiniCssExtractPlugin .loader , "css-loader" , { loader : "postcss-loader" , options : { postcssOptions : { plugins : [ "postcss-preset-env" , ], }, }, }, preProcessor, ].filter (Boolean ); }; module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "../dist" ), filename : "static/js/main.js" , clean : true , }, module : { rules : [ { test : /\.css$/ , use : getStyleLoaders (), }, { test : /\.less$/ , use : getStyleLoaders ("less-loader" ), }, { test : /\.s[ac]ss$/ , use : getStyleLoaders ("sass-loader" ), }, { test : /\.styl$/ , use : getStyleLoaders ("stylus-loader" ), }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , exclude : /node_modules/ , loader : "babel-loader" , }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "../src" ), }), new HtmlWebpackPlugin ({ template : path.resolve (__dirname, "../public/index.html" ), }), new MiniCssExtractPlugin ({ filename : "static/css/main.css" , }), ], mode : "production" , };
Css 压缩 下载包 1 npm i css-minimizer-webpack-plugin -D
配置 webpack.prod.js 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );const MiniCssExtractPlugin = require ("mini-css-extract-plugin" );const CssMinimizerPlugin = require ("css-minimizer-webpack-plugin" );const getStyleLoaders = (preProcessor ) => { return [ MiniCssExtractPlugin .loader , "css-loader" , { loader : "postcss-loader" , options : { postcssOptions : { plugins : [ "postcss-preset-env" , ], }, }, }, preProcessor, ].filter (Boolean ); }; module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "../dist" ), filename : "static/js/main.js" , clean : true , }, module : { rules : [ { test : /\.css$/ , use : getStyleLoaders (), }, { test : /\.less$/ , use : getStyleLoaders ("less-loader" ), }, { test : /\.s[ac]ss$/ , use : getStyleLoaders ("sass-loader" ), }, { test : /\.styl$/ , use : getStyleLoaders ("stylus-loader" ), }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , exclude : /node_modules/ , loader : "babel-loader" , }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "../src" ), }), new HtmlWebpackPlugin ({ template : path.resolve (__dirname, "../public/index.html" ), }), new MiniCssExtractPlugin ({ filename : "static/css/main.css" , }), new CssMinimizerPlugin (), ], mode : "production" , };
SourceMap 开发时我们运行的代码是经过 webpack 编译后的。所有 css 和 js 合并成了一个文件,并且多了其他代码。此时如果代码运行出错那么提示代码错误位置我们是看不懂的。一旦将来开发代码文件很多,那么很难去发现错误出现在哪里。所以我们需要更加准确的错误提示,来帮助我们更好的开发代码
SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的文件的方案 。它会生成一个 xxx.map
文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过 xxx.map
文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源
通过查看 Webpack DevTool 文档 可知,SourceMap 的值有很多种情况。但实际开发时我们只需要关注两种情况即可:
开发模式:cheap-module-source-map
。优点:打包编译速度快,只包含行映射。缺点:没有列映射
1 2 3 4 5 module .exports = { mode : "development" , devtool : "cheap-module-source-map" , };
生产模式:source-map
。优点:包含行 / 列映射。缺点:打包编译速度更慢
1 2 3 4 5 module .exports = { mode : "production" , devtool : "source-map" , };
HotModuleReplacement 开发时我们修改了其中一个模块代码,Webpack 默认会将所有模块全部重新打包编译,速度很慢。所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快
HotModuleReplacement(HMR / 热模块替换):在程序运行中,替换、添加或删除模块,而无需重新加载整个页面
1 2 3 4 5 6 7 8 9 module .exports = { devServer : { host : "localhost" , port : "3000" , open : true , hot : true , }, };
此时 css 样式经过 style-loader 处理,已经具备 HMR 功能了。 但是 js 还不行
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 import count from "./js/count" ;import sum from "./js/sum" ;import "./css/iconfont.css" ;import "./css/index.css" ;import "./less/index.less" ;import "./sass/index.sass" ;import "./sass/index.scss" ;import "./styl/index.styl" ;const result1 = count (2 , 1 );console .log (result1);const result2 = sum (1 , 2 , 3 , 4 );console .log (result2);if (module .hot ) { module .hot .accept ("./js/count.js" , function (count ) { const result1 = count (2 , 1 ); console .log (result1); }); module .hot .accept ("./js/sum.js" , function (sum ) { const result2 = sum (1 , 2 , 3 , 4 ); console .log (result2); }); }
上面这样写会很麻烦,所以实际开发我们会使用其他 loader 来解决。比如:vue-loader 、react-hot-loader
OneOf 因为 test
正则原因实际没有处理上,但是都要过一遍。比较慢。OneOf 顾名思义就是只能匹配上一个 loader, 剩下的就不匹配了
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );module .exports = { entry : "./src/main.js" , output : { path : undefined , filename : "static/js/main.js" , }, module : { rules : [ { oneOf : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : ["style-loader" , "css-loader" , "stylus-loader" ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , exclude : /node_modules/ , loader : "babel-loader" , }, ], }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "../src" ), }), new HtmlWebpackPlugin ({ template : path.resolve (__dirname, "../public/index.html" ), }), ], devServer : { host : "localhost" , port : "3000" , open : true , hot : true , }, mode : "development" , devtool : "cheap-module-source-map" , };
Include / Exclude 开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。所以我们在对 js 文件处理时,要排除 node_modules 下面的文件
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );module .exports = { entry : "./src/main.js" , output : { path : undefined , filename : "static/js/main.js" , }, module : { rules : [ { oneOf : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : ["style-loader" , "css-loader" , "stylus-loader" ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , include : path.resolve (__dirname, "../src" ), loader : "babel-loader" , }, ], }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "../src" ), exclude : "node_modules" , }), new HtmlWebpackPlugin ({ template : path.resolve (__dirname, "../public/index.html" ), }), ], devServer : { host : "localhost" , port : "3000" , open : true , hot : true , }, mode : "development" , devtool : "cheap-module-source-map" , };
Cache 每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );module .exports = { entry : "./src/main.js" , output : { path : undefined , filename : "static/js/main.js" , }, module : { rules : [ { oneOf : [ { test : /\.css$/ , use : ["style-loader" , "css-loader" ], }, { test : /\.less$/ , use : ["style-loader" , "css-loader" , "less-loader" ], }, { test : /\.s[ac]ss$/ , use : ["style-loader" , "css-loader" , "sass-loader" ], }, { test : /\.styl$/ , use : ["style-loader" , "css-loader" , "stylus-loader" ], }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , include : path.resolve (__dirname, "../src" ), loader : "babel-loader" , options : { cacheDirectory : true , cacheCompression : false , }, }, ], }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "../src" ), exclude : "node_modules" , cache : true , cacheLocation : path.resolve ( __dirname, "../node_modules/.cache/.eslintcache" ), }), new HtmlWebpackPlugin ({ template : path.resolve (__dirname, "../public/index.html" ), }), ], devServer : { host : "localhost" , port : "3000" , open : true , hot : true , }, mode : "development" , devtool : "cheap-module-source-map" , };
Thead 当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码。这个速度是比较慢的。我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。而对 js 文件处理主要就是 eslint 、babel、Terser 三个工具,所以我们要提升它们的运行速度。我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了
注意:请仅在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右开销
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 const os = require ("os" );const path = require ("path" );const ESLintWebpackPlugin = require ("eslint-webpack-plugin" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );const MiniCssExtractPlugin = require ("mini-css-extract-plugin" );const CssMinimizerPlugin = require ("css-minimizer-webpack-plugin" );const TerserPlugin = require ("terser-webpack-plugin" );const threads = os.cpus ().length ;const getStyleLoaders = (preProcessor ) => { return [ MiniCssExtractPlugin .loader , "css-loader" , { loader : "postcss-loader" , options : { postcssOptions : { plugins : [ "postcss-preset-env" , ], }, }, }, preProcessor, ].filter (Boolean ); }; module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "../dist" ), filename : "static/js/main.js" , clean : true , }, module : { rules : [ { oneOf : [ { test : /\.css$/ , use : getStyleLoaders (), }, { test : /\.less$/ , use : getStyleLoaders ("less-loader" ), }, { test : /\.s[ac]ss$/ , use : getStyleLoaders ("sass-loader" ), }, { test : /\.styl$/ , use : getStyleLoaders ("stylus-loader" ), }, { test : /\.(png|jpe?g|gif|webp)$/ , type : "asset" , parser : { dataUrlCondition : { maxSize : 10 * 1024 , }, }, generator : { filename : "static/imgs/[hash:8][ext][query]" , }, }, { test : /\.(ttf|woff2?)$/ , type : "asset/resource" , generator : { filename : "static/media/[hash:8][ext][query]" , }, }, { test : /\.js$/ , include : path.resolve (__dirname, "../src" ), use : [ { loader : "thread-loader" , options : { workers : threads, }, }, { loader : "babel-loader" , options : { cacheDirectory : true , }, }, ], }, ], }, ], }, plugins : [ new ESLintWebpackPlugin ({ context : path.resolve (__dirname, "../src" ), exclude : "node_modules" , cache : true , cacheLocation : path.resolve ( __dirname, "../node_modules/.cache/.eslintcache" ), threads, }), new HtmlWebpackPlugin ({ template : path.resolve (__dirname, "../public/index.html" ), }), new MiniCssExtractPlugin ({ filename : "static/css/main.css" , }), ], optimization : { minimize : true , minimizer : [ new CssMinimizerPlugin (), new TerserPlugin ({ parallel : threads }) ], }, mode : "production" , devtool : "source-map" , };
Tree Shaking 开发时我们定义了一些工具函数库,或者引用第三方工具函数库或组件库。如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上极小部分的功能。这样将整个库都打包进来,体积就太大了。Tree Shaking 是一个术语,通常用于描述移除 JavaScript 中的没有使用上的代码。注意:它依赖 ES Module
。Webpack 已经默认开启了这个功能,无需其他配置
Babel Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。你可以将这些辅助代码作为一个独立模块,来避免重复引入。
@babel/plugin-transform-runtime
:禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime
并且使所有辅助代码从这里引用
1 npm i @babel/plugin-transform-runtime -D
1 2 3 4 5 6 7 8 { loader : "babel-loader" , options : { cacheDirectory : true , cacheCompression : false , plugins : ["@babel/plugin-transform-runtime" ] } }
Image Minimizer 开发如果项目中引用了较多图片,那么图片体积会比较大,将来请求速度比较慢。我们可以对图片进行压缩,减少图片体积。注意:如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩
image-minimizer-webpack-plugin
:用来压缩图片的插件
1 npm i image-minimizer-webpack-plugin imagemin -D
还有剩下包需要下载,有两种模式:
1 npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
1 npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -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 new ImageMinimizerPlugin ({ minimizer : { implementation : ImageMinimizerPlugin .imageminGenerate , options : { plugins : [ ["gifsicle" , { interlaced : true }], ["jpegtran" , { progressive : true }], ["optipng" , { optimizationLevel : 5 }], [ "svgo" , { plugins : [ "preset-default" , "prefixIds" , { name : "sortAttrs" , params : { xmlnsOrder : "alphabetical" , }, }, ], }, ], ], }, }, })
Code Split 打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。代码分割(Code Split)主要做了两件事:
分割文件:将打包生成的文件进行分割,生成多个 js 文件
按需加载:需要哪个文件就加载哪个文件
下载包:
1 npm i webpack webpack-cli html-webpack-plugin -D
配置 webpack.config.js
:
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 const path = require ("path" );const HtmlWebpackPlugin = require ("html-webpack-plugin" );module .exports = { entry : { main : "./src/main.js" , app : "./src/app.js" , }, output : { path : path.resolve (__dirname, "./dist" ), filename : "js/[name].js" , clear : true , }, plugins : [ new HtmlWebpackPlugin ({ template : "./public/index.html" , }), ], mode : "production" , };
Preload / Prefetch 我们前面已经做了代码分割,同时会使用 import 动态导入语法来进行代码按需加载(我们也叫懒加载,比如路由懒加载就是这样实现的)但是加载速度还不够好,比如:是用户点击按钮时才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。我们想在浏览器空闲时间,加载后续需要使用的资源。我们就需要用上 Preload 或 Prefetch 技术
Preload:告诉浏览器立即加载资源
Prefetch:告诉浏览器在空闲时才开始加载资源
它们共同点:
它们区别:
Preload 加载优先级高,Prefetch 加载优先级低。
Preload 只能加载当前页面需要使用的资源,Prefetch 可以加载当前页面资源,也可以加载下一个页面需要使用的资源
下载包:
1 npm i @vue/preload-webpack-plugin -D
配置 webpack.prod.js
:
1 2 3 4 5 new PreloadWebpackPlugin ({ rel : "preload" , as : "script" , })
Network Cache 将来开发时我们对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了,速度很快。但是这样的话就会有一个问题, 因为前后输出的文件名是一样的,都叫 main.js,一旦将来发布新版本,因为文件名没有变化导致浏览器会直接读取缓存,不会加载新资源,项目也就没法更新了。所以我们从文件名入手,确保更新前后文件名不一样,这样就可以做缓存了。它们都会生成一个唯一的 hash 值
fullhash(webpack4 是 hash):每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效
chunkhash:根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们 js 和 css 是同一个引入,会共享一个 hash 值
contenthash:根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的
1 2 3 4 5 6 7 8 9 10 11 module .exports = { entry : "./src/main.js" , output : { path : path.resolve (__dirname, "../dist" ), filename : "static/js/[name].[contenthash:8].js" , chunkFilename : "static/js/[name].[contenthash:8].chunk.js" , assetModuleFilename : "static/media/[name].[hash][ext]" , clean : true , }, }
当我们修改 math.js 文件再重新打包的时候,因为 contenthash 原因,math.js 文件 hash 值发生了变化(这是正常的)但是 main.js 文件的 hash 值也发生了变化,这会导致 main.js 的缓存失效。明明我们只修改 math.js, 为什么 main.js 也会变身变化呢?
原因:
更新前:math.xxx.js
,main.js 引用的 math.xxx.js
更新后:math.yyy.js
,main.js 引用的 math.yyy.js
, 文件名发生了变化,间接导致 main.js 也发生了变化
解决:
将 hash 值单独保管在一个 runtime 文件中。我们最终输出三个文件:main、math、runtime。当 math 文件发送变化,变化的是 math 和 runtime 文件,main 不变。runtime 文件只保存文件的 hash 值和它们与文件关系,整个文件体积就比较小,所以变化重新请求的代价也小
1 2 3 4 runtimeChunk : { name : (entrypoint ) => `runtime~${entrypoint.name} ` }
Core-js 过去我们使用 babel 对 js 代码进行了兼容性处理,其中使用 @babel/preset-env
智能预设来处理兼容性问题。它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理。所以此时我们 js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。所以我们想要将 js 兼容性问题彻底解决
core-js
是专门用来做 ES6 以及以上 API 的 polyfill。polyfill 翻译过来叫做垫片 / 补丁。就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性
1 npm i @babel/eslint-parser -D
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 module .exports = { extends : ["eslint:recommended" ], parser : "@babel/eslint-parser" , env : { node : true , browser : true , }, plugins : ["import" ], parserOptions : { ecmaVersion : 6 , sourceType : "module" , }, rules : { "no-var" : 2 , }, };
此时观察打包输出的 js 文件,我们发现 Promise 语法并没有编译转换,所以我们需要使用 core-js 来进行 polyfill
1 2 3 4 5 6 7 8 9 10 module .exports = { presets : [ [ "@babel/preset-env" , { useBuiltIns : "usage" , corejs : { version : "3" , proposals : true } }, ], ], };
此时就会自动根据我们代码中使用的语法,来按需加载相应的 polyfill 了
PWA 开发 Web App 项目,项目一旦处于网络离线情况,就没法访问了。我们希望给项目提供离线体验。渐进式网络应用程序(progressive web application - PWA):是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。其中最重要的是,在 离线(offline) 时应用程序能够继续运行功能。内部通过 Service Workers 技术实现的
1 npm i workbox-webpack-plugin -D
1 2 3 4 5 6 new WorkboxPlugin .GenerateSW ({ clientsClaim : true , skipWaiting : true , })
Loader 原理 帮助 webpack 将不同类型的文件转换为 webpack 可识别的模块。主要分为:
pre: 前置 loader
normal: 普通 loader
inline: 内联 loader
post: 后置 loader
4 类 loader 的执行优级为:pre > normal > inline > post 。相同优先级的 loader 执行顺序为:从右到左 ,从下到上
同步 Loader 1 2 3 module .exports = function (content, map, meta ) { return content; };
this.callback
方法则更灵活,因为它允许传递多个参数,而不仅仅是 content
1 2 3 4 5 6 module .exports = function (content, map, meta ) { this .callback (null , content, map, meta); return ; };
异步 Loader 1 2 3 4 5 6 7 module .exports = function (content, map, meta ) { const callback = this .async (); setTimeout (() => { callback (null , result, map, meta); }, 1000 ); };
由于同步计算过于耗时,在 Node.js 这样的单线程环境下进行此操作并不是好的方案,我们建议尽可能地使你的 loader 异步化。但如果计算量很小,同步 loader 也是可以的
Raw Loader 默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw 为 true,loader 可以接收原始的 Buffer
1 2 3 4 5 module .exports = function (content ) { return content; }; module .exports .raw = true ;
Pitching Loader 1 2 3 4 5 6 module .exports = function (content ) { return content; }; module .exports .pitch = function (remainingRequest, precedingRequest, data ) { console .log ("do somethings" ); };
webpack 会先从左到右执行 loader 链中的每个 loader 上的 pitch 方法(如果有),然后再从右到左执行 loader 链中的每个 loader 上的普通 loader 方法。在这个过程中如果任何 pitch 有返回值,则 loader 链被阻断。webpack 会跳过后面所有的的 pitch 和 loader,直接进入上一个 loader
Loader API
方法名
含义
用法
this.async
异步回调 loader 返回 this.callback
const callback = this.async()
this.getOptions(schema)
获取 loader 的 options
this.getOptions(schema)
this.callback
可以同步或者异步调用的并返回多个结果的函数
this.callback(err, content, sourceMap?, meta?)
this.emitFile
产生一个文件
this.emitFile(name, content, sourceMap)
this.utils.contextify
返回一个相对路径
this.utils.contextify(context, request)
this.utils.absolutify
返回一个绝对路径
this.utils.absolutify(context, request)
更多文档,请查阅 webpack 官方 loader api 文档
手写 style-loader 作用:动态创建 style 标签,插入 js 中的样式代码,使样式生效
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 52 53 54 const styleLoader = ( ) => {};styleLoader.pitch = function (remainingRequest ) { const relativeRequest = remainingRequest .split ("!" ) .map ((part ) => { const relativePath = this .utils .contextify (this .context , part); return relativePath; }) .join ("!" ); const script = ` import style from "!!${relativeRequest} " const styleEl = document.createElement('style') styleEl.innerHTML = style document.head.appendChild(styleEl) ` ; return script; }; module .exports = styleLoader;
Plugin 原理 通过插件我们可以扩展 webpack,加入自定义的构建行为,使 webpack 可以执行更广泛的任务,拥有更强的构建能力
webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。 ——「深入浅出 Webpack」
站在代码逻辑的角度就是:webpack 在编译代码过程中,会触发一系列 Tapable
钩子事件,插件所做的,就是找到相应的钩子,往上面挂上自己的任务,也就是注册事件,这样,当 webpack 构建的时候,插件注册的事件就会随着钩子的触发而执行了
为了方便我们直接介入和控制编译过程,webpack 把编译过程中触发的各类关键事件封装成事件接口暴露了出来。这些接口被很形象地称做:hooks
(钩子)。开发插件,离不开这些钩子
Tapable Tapable
为 webpack 提供了统一的插件接口(钩子)类型定义,它是 webpack 的核心功能库。webpack 中目前有十种 hooks
,在 Tapable
源码中可以看到,他们是:
1 2 3 4 5 6 7 8 9 10 11 12 13 exports .SyncHook = require ("./SyncHook" );exports .SyncBailHook = require ("./SyncBailHook" );exports .SyncWaterfallHook = require ("./SyncWaterfallHook" );exports .SyncLoopHook = require ("./SyncLoopHook" );exports .AsyncParallelHook = require ("./AsyncParallelHook" );exports .AsyncParallelBailHook = require ("./AsyncParallelBailHook" );exports .AsyncSeriesHook = require ("./AsyncSeriesHook" );exports .AsyncSeriesBailHook = require ("./AsyncSeriesBailHook" );exports .AsyncSeriesLoopHook = require ("./AsyncSeriesLoopHook" );exports .AsyncSeriesWaterfallHook = require ("./AsyncSeriesWaterfallHook" );exports .HookMap = require ("./HookMap" );exports .MultiHook = require ("./MultiHook" );
Tapable
还统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:
tap
:可以注册同步钩子和异步钩子
tapAsync
:回调方式注册异步钩子
tapPromise
:Promise 方式注册异步钩子
Compiler compiler 对象中保存着完整的 Webpack 环境配置,每次启动 webpack 构建时它都是一个独一无二,仅仅会创建一次的对象。这个对象会在首次启动 Webpack 时创建,我们可以通过 compiler 对象上访问到 Webpack 的主环境配置,比如 loader、 plugin 等等配置信息。它有以下主要属性:
compiler.options
可以访问本次启动 webpack 时候所有的配置文件,包括但不限于 loaders、 entry、 output、 plugin 等等完整配置信息
compiler.inputFileSystem
和 compiler.outputFileSystem
可以进行文件操作,相当于 Nodejs 中 fs
compiler.hooks
可以注册 tapable 的不同种类 Hook,从而可以在 compiler 生命周期中植入不同的逻辑
Compilation compilation 对象代表一次资源的构建,compilation 实例能够访问所有的模块和它们的依赖。一个 compilation 对象会对构建依赖图中所有模块,进行编译。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、分块(chunk)、哈希(hash)和重新创建(restore)。它有以下主要属性:
compilation.modules
可以访问所有模块,打包的每一个文件都是一个模块
compilation.chunks
chunk 即是多个 modules 组成而来的一个代码块。入口文件引入的资源组成一个 chunk,通过代码分割的模块又是另外的 chunk
compilation.assets
可以访问本次打包生成所有文件的结果
compilation.hooks
可以注册 tapable 的不同种类 Hook,用于在 compilation 编译模块阶段进行逻辑添加以及修改
InlineChunkWebpackPlugin
作用:webpack 打包生成的 runtime 文件太小了,额外发送请求性能不好,所以需要将其内联到 js 中,从而减少请求数量
我们需要借助 html-webpack-plugin 来实现
在 html-webpack-plugin 输出 index.html 前将内联 runtime 注入进去
删除多余的 runtime 文件
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 const HtmlWebpackPlugin = require ("safe-require" )("html-webpack-plugin" );class InlineChunkWebpackPlugin { constructor (tests ) { this .tests = tests; } apply (compiler ) { compiler.hooks .compilation .tap ("InlineChunkWebpackPlugin" , (compilation ) => { const hooks = HtmlWebpackPlugin .getHooks (compilation); hooks.alterAssetTagGroups .tap ("InlineChunkWebpackPlugin" , (assets ) => { assets.headTags = this .getInlineTag (assets.headTags , compilation.assets ); assets.bodyTags = this .getInlineTag (assets.bodyTags , compilation.assets ); }); hooks.afterEmit .tap ("InlineChunkHtmlPlugin" , () => { Object .keys (compilation.assets ).forEach ((assetName ) => { if (this .tests .some ((test ) => assetName.match (test))) { delete compilation.assets [assetName]; } }); }); }); } getInlineTag (tags, assets ) { return tags.map ((tag ) => { if (tag.tagName !== "script" ) return tag; const scriptName = tag.attributes .src ; if (!this .tests .some ((test ) => scriptName.match (test))) return tag; return { tagName : "script" , innerHTML : assets[scriptName].source (), closeTag : true }; }); } } module .exports = InlineChunkWebpackPlugin ;