- A+
[webpack中文文档](概念 | webpack 中文文档 | webpack中文文档 | webpack中文网 (webpackjs.com)):
本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。
这篇笔记主要记录的是Webpack 5
。
基本使用
安装
安装webpack的同时需要安装webpack-cli。
npm i webpack webpack-cli -D
启动
启动分为开发模式和生产模式。
- 开发模式
npx webpack /path/to/main.js --mode=development
- 生产模式
npx webpack /path/to/main.js --mode=production
其中:
npx webpack
:用于运行本地安装的webpack;/path/to/main.js
:指定webpack从main.js开始打包,会同时打包其依赖文件;--mode=xxx
:指定模式。
注:
如果终端输出webpack is not recognized as an internal or external command
,即无法被识别为指令。
那么可以考虑将webpack进行全局安装:
npm i -g webpack webpack-cli
输出
webpack会默认将文件打包输出到dist
文件夹,并且只能处理js
资源。
如果需要处理其它资源文件,或者需要更精细化地打包,则需要设置webpack的配置文件:webpack.config.js
。
Webpack的配置
webpack的配置文件命名为webpack.config.js
,置于项目文件的根路径下。
webpack是基于node运行的,所以webpack.config.js
采用CommonJS
模块化规范。
webpack的配置有五大核心概念:
- entry(入口):webpack从这个文件开始进行打包,并打包其依赖文件;
- output(输出):决定了webpack打包后的文件的文件名、地址等;
- loader(加载器):webpack只能处理js文件,其它文件比如css文件、图片资源、jsx文件,需要使用loader才能解析;
- plugins(插件):扩展webpack的功能,每一个插件如何配置取决于插件的提供者如何设计;
- mode(模式):主要有两种模式,开发模式(development)和生产模式(production)。
简单的配置文件样例:
// Node.js的核心模块,专门用来处理文件路径 const path = require("path"); module.exports = { // 入口 // 相对路径和绝对路径都行 entry: "./src/main.js", // 输出 output: { // path: 文件输出目录,必须是绝对路径 // path.resolve()方法返回一个绝对路径 // __dirname 当前文件的文件夹绝对路径 path: path.resolve(__dirname, "dist"), // filename: 输出文件名 filename: "main.js", }, // 加载器 module: { rules: [], }, // 插件 plugins: [], // 模式 mode: "development", // 开发模式 };
在上面基本使用那一栏里,由于没有配置文件,运行webpack的时候通常会带上后缀:npx webpack /path/to/main.js --mode=development
,用于指定入口文件、模式等信息。
添加配置文件之后,webpack会自动读取webpack.config.js
这个文件里的配置信息,因此可以直接运行:npx webpack
。
开发模式介绍
通常会创建两个配置文件,一个对应开发模式,另一个对应生产模式。
开发模式的webpack主要需要完成以下任务:
-
编译代码,使浏览器能识别运行
webpack只能处理js文件,而其它文件,例如:css文件、字体图标、图片资源、html资源等,webpack需要通过配置loader才能处理这些资源;
-
代码质量检查
使用例如eslint之类的代码检查工具,提前发现代码缺陷,也可以检查代码书写是否规范,统一团队编码风格。
处理样式资源
需要使用css-loader
和style-loader
才能处理css文件。
css-loader
:负责将CSS文件编译成webpack可以识别的模块。style-loader
:会动态地创建一个style标签,用于放置webpack 中的css模块。
先安装:
npm i css-loader style-loader -D
配置webpack:
const path = require("path"); module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "main.js", }, module: { rules: [ { // 用来匹配 .css 结尾的文件 test: /.css$/, // use 数组里面 Loader 执行顺序是从右到左 use: ["style-loader", "css-loader"], }, ], }, plugins: [], mode: "development", };
这里需要注意的是:use
字段指定需要应用的loader,顺序是从右到左的,即css文件先经由css-loader
变成webpack可以识别的模块,然后再由style-loader
将这些模块注入到html里。
这里提供案例的相关代码:
/src/assets/css/style.css
#app{ text-align: center; font-weight: bold; }
/src/js/show.js
export function show(dom, msg){ dom.innerText = msg; }
/src/main.js
import {show} from './js/show' import './assets/css/style.css' const app = document.getElementById('app'); show(app, 'Hello Webpack');
/public/index.html
<!DOCTYPE html> <html lang="en"> <head> <title>webpack-learning</title> </head> <body> <div id="app"></div> <script src="../dist/main.js"></script> </body> </html>
同时,可以在package.json
中设置常用的指令:
{ ... "script":{ "start": "webpack --config webpack.config.js" }, ... }
然后,只需要在终端运行:
npm run start
就会以webpack.config.js
为配置,运行webpack,将main.js
及其依赖文件,打包到/dist/main.js
。
此时,打开/public/index.html
,可以看到依赖的js文件show
函数的功能正常,且引入的style.css
样式也被打包并注入到style标签中:
常用的样式资源还有Less,Sass等,这里再介绍Less样式表的处理,其它样式资源的处理方式是类似的。
首先安装相应的loader:
npm i -D less-loader
配置webpack:在webpack.config.js
文件原有的基础上进行修改。
... module.exports = { ... module: { rules: [ ... { // 用来匹配 .less 结尾的文件 test: /.less$/, use: ['style-loader', 'css-loader', 'less-loader'] } ] }, ... }
上述代码中的...
表示原有的,但是这里为了突出重点而省略的代码片段。
配置less-loader
只需要在rules
数组中添加新的一项,注意use
的顺序是从右到左,先把less
代码转换成css
代码,然后依此类推。
这里我只是在上述css的代码中做了一些修改:
- 新增
/src/assets/less/style.less
文件
#app{ text-align: center; font-weight: bold; }
/src/main.js
中不引入css
文件,而是引入less
文件
import {show} from './js/show' import './assets/less/style.less' const app = document.getElementById('app'); show(app, 'Hello Webpack');
-
执行
npm run start
-
打开
/pubcli/index.html
,可以观察到less
也能被正确地处理并注入到最终的style标签中。
这里再浅浅地提一嘴关于Sass/Scss
的样式资源处理配置,他们的loader都是使用sass-loader
,因此,在配置test
的时候,正则表达式可以使用:/.s[ac]ss$/
去同时匹配这两种文件后缀。
处理图片资源
过去的Webpack4 ,处理图片资源需要使用file-loader
和url-loader
。
现在Webpack5已经将两个Loader功能内置到Webpack里了,只需要简单配置即可处理图片资源。
webpack.config.js
... module.exports = { ... module: { rules: [ ... { test: /.(png|jpe?g|gif|webp)$/, type: "asset", }, ], }, ... };
相关代码:
- 在代码中引入图片(记得需要出现在入口文件的依赖中,否则不会被webpack处理)
body{ background-image: url('../images/normal.png'); } #app{ text-align: center; font-weight: bold; background-image: url('../images/small.png'); }
图片可以随便照几张测试,这里我习惯性地将图片放在
/src/assets/images
目录下。
- 运行webpack之后,可以发现图片也被输出了。
上面样式资源打包后没有相应的样式资源出现是因为:经过
style-loader
的处理,样式资源被打包进了/dist/main.js
。
对图片资源进行优化:将小于某个大小的图片转换成Base64格式。
... module.exports = { ... module: { rules: [ ... { test: /.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024 // 小于10kb的图片会被base64处理 } } }, ], }, ... };
这里我用于测试的两张图片:normal.png和small.png,其中normal.png大于10kb,而small.png小于10kb。
再次打包并运行:
可以看到小于10kb的图片文件被转换成Base64格式。
- 优点:减少请求数量
- 缺点:
main.js
体积变得更大
修改输出资源的名称和路径
module.exports = { ... output: { path: path.resolve(__dirname, "dist"), filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中 }, module: { rules: [ ... { test: /.(png|jpe?g|gif|webp)$/, type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, // 小于10kb的图片会被base64处理 }, }, generator: { // 将图片文件输出到 static/imgs 目录中 // 将图片文件命名 [hash:8][ext][query] // [hash:8]: hash值取8位 // [ext]: 使用之前的文件扩展名 // [query]: 添加之前的query参数 filename: "static/imgs/[hash:8][ext][query]", }, }, ], }, ... };
上述配置在原来的基础上,修改了:
main.js
的输出目录;- 图片文件的输出目录以及名称。
修改了main.js
的输出路径之后,需要修改/public/index.html
中的<script>
标签的路径。
<script src="../dist/static/js/main.js"></script>
自动清空上次打包的资源
如果想要每次打包自动覆盖上一次打包生成的资源,使用clean: true
module.exports = { entry: "./src/main.js", output: { path: path.resolve(__dirname, "dist"), filename: "static/js/main.js", clean: true, // 自动将上次打包目录资源清空 }, module: { rules: [ ... ], }, ... };
处理字体图标资源
字体图标可以到阿里巴巴矢量图标库下载。
下载到本地后解压并添加到项目中的文件夹,这里只需要以下文件:
src/fonts/iconfont.ttf
src/fonts/iconfont.woff
src/fonts/iconfont.woff2
src/css/iconfont.css
将文件分开放置需要修改iconfont.css中对于字体文件的引用路径:
@font-face { font-family: "iconfont"; /* Project id 4000108 */ src: url('../fonts/iconfont.woff2?t=1695445577878') format('woff2'), url('../fonts/iconfont.woff?t=1695445577878') format('woff'), url('../fonts/iconfont.ttf?t=1695445577878') format('truetype'); }
然后在/public/index.html
中使用字体图标:
<div id="app"></div> <i class="iconfont icon-wode3"></i> <i class="iconfont icon-search"></i> <i class="iconfont icon-rollback"></i> <script src="../dist/static/js/main.js"></script>
配置webpack:
... module.exports = { ... module: { rules: [ ... { test: /.(ttf|woff2?)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, }, ], }, ... };
type: "asset/resource"
和type: "asset"
的区别:
type: "asset/resource"
相当于file-loader
, 将文件转化成 Webpack 能识别的资源,其他不做处理;type: "asset"
相当于url-loader
, 将文件转化成 Webpack 能识别的资源,同时小于某个大小的资源会处理成 data URI 形式。
处理音频视频资源
音频和视频资源和字体图标做同样的处理,使用type: "asset/resource"
。
... module.exports = { ... module: { rules: [ ... { test: /.(ttf|woff2?|map4|map3|avi)$/, type: "asset/resource", generator: { filename: "static/media/[hash:8][ext][query]", }, }, ], }, ... };
处理JS资源
webpack只能编译JS中的ES模块化语法,其它更高级的功能需要配置才能使用。
常用的工具有Babel
和Eslint
:
Babel
用于将高版本的JS语法转换成ES5语法,是针对兼容性的;Eslint
用于检查代码格式。
Eslint
Eslint可以用来检测JS和jsx语法。
使用Eslint需要写配置文件,标注各种rules,用于声明哪些代码规则需要检查。
在安装Eslint之前,先了解一下配置的写法:
- 新建配置文件
.eslintrc.*
:位于项目根目录.eslintrc
.eslintrc.js
.eslintrc.json
- 上面三种文件命名的区别在于文件类型不同,配置的格式不一样。
- 直接在
package.json
中的eslintConfig
进行配置。
基本配置
module.exports = { // 解析选项 parserOptions: {}, // 具体检查规则 rules: {}, // 继承其他规则 extends: [], };
-
parserOptions
:解析选项parserOptions: { ecmaVersion: 6, // ES 语法版本 sourceType: "module", // ES 模块化 ecmaFeatures: { // ES 其他特性 jsx: true // 如果是 React 项目,就需要开启 jsx 语法 } }
-
rules
具体规则off
或0
:关闭warn
或1
:警告error
或2
:报错
rules: { semi: "error", // 禁止使用分号 'array-callback-return': 'warn', // 强制数组方法的回调函数中有 return 语句,否则警告 'default-case': [ 'warn', // 要求 switch 语句中有 default 分支,否则警告 { commentPattern: '^no default$' } // 允许在最后注释 no default, 就不会有警告了 ], eqeqeq: [ 'warn', // 强制使用 === 和 !==,否则警告 'smart' // https://eslint.bootcss.com/docs/rules/eqeqeq#smart 除了少数情况下不会有警告 ], }
-
extends
继承直接引入已有的规则,常见的规则:
- Eslint官方的规则:
eslint:recommended
- Vue-cli官方的规则:
plugin:vue/essential
- React-cli官方的规则:
react-app
如果本地的
rules
和继承的规则出现相同的选项,则本地的规则覆盖继承的规则。 - Eslint官方的规则:
安装Eslint
npm i -D eslint-webpack-plugin eslint
定义配置文件:.eslintrc.js
module.exports = { // 继承 Eslint 规则 extends: ["eslint:recommended"], env: { node: true, // 启用node中全局变量 browser: true, // 启用浏览器中全局变量 }, parserOptions: { ecmaVersion: 6, sourceType: "module", }, rules: { "no-var": 2, // 不能使用 var 定义变量 }, };
配置webpack
... const ESLintWebpackPlugin = require("eslint-webpack-plugin"); module.exports = { ... plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "src"), }), ], ... };
Babel
Babel的配置文件可以是下面的文件名之一:
babel.config.js
babel.config.json
.babelrc
.babelrc.js
.babelrc.json
或者可以直接在package.json
里的babel
项进行配置。
以babel.config.js
为例,基础配置为:
module.exports = { // 预设 presets: [], };
预设可以理解为是Babel插件,扩展Babel的功能。
@babel/preset-env
: 一个智能预设,允许您使用最新的 JavaScript;@babel/preset-react
:一个用来编译 React jsx 语法的预设;@babel/preset-typescript
:一个用来编译 TypeScript 语法的预设。
安装
npm i babel-loader @babel/core @babel/preset-env -D
定义配置文件:babel.config.js
module.exports = { presets: ["@babel/preset-env"], };
webpack配置
... module.exports = { ... module: { rules: [ ... { test: /.js$/, exclude: /node_modules/, // 排除node_modules代码不编译 loader: "babel-loader", }, ], }, ... };
处理Html资源
之前没有处理html文件,所做的只是处理JS文件和其它资源,然后手动打开public/index.html
。(/public/index.html
需要手动引入打包后的dist/static/js/main.js
文件)
安装
npm i html-webpack-plugin -D
配置
... const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { ... plugins: [ ... new HtmlWebpackPlugin({ // 以 public/index.html 为模板创建文件 // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源 template: path.resolve(__dirname, "public/index.html"), }), ], ... };
修改/public/index.html
我们的目标是使用html-webpack-plugin
完成html文件的打包,现在/public/index.html
不需要手动引入dist/static/js/main.js
文件了,插件会自动完成这个任务,并把html文件也一并打包到/dist
文件夹中。
打包并检查结果
npm run start
可以看到index.html
也被打包了:
以下打包之后的/dist/index.html
,可以看到被自动添加了<script>
标签:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>webpack-learning</title> <script defer src="static/js/main.js"></script></head> <body> <div id="app"></div> <i class="iconfont icon-wode3"></i> <i class="iconfont icon-search"></i> <i class="iconfont icon-rollback"></i> </body> </html>
开发服务器与自动化
使用自动化代替原先的手动输入打包指令的操作。
安装依赖
npm i webpack-dev-server -D
配置:(webpack.config.js
文件)
... module.exports = { ... // 开发服务器 devServer: { host: "localhost", // 启动服务器域名 port: "3000", // 启动服务器端口号 open: true, // 是否自动打开浏览器 }, ... };
修改package.json
脚本
"scripts": { "start": "webpack --config webpack.config.js", "dev": "webpack serve --config webpack.config.js" },
新加一个dev
指令,用于启动本地服务。
运行指令
npm run dev
启动服务之后,会自动打开浏览器(可能会被电脑的安全软件拦截,点击允许就行)。
默认启动热加载功能,此时修改源代码,会自动更新。
注:使用开发服务器的时候,代码不会被打包到/dist
文件夹,而是编译打包在内存中。
生产模式介绍
开发完成代码之后,需要将代码打包给后端用于部署。
打包的配置需要考虑优化问题,主要从两个方面考虑:
- 优化代码运行性能
- 优化代码打包速度
注:
通常会使用两个配置文件来对应开发模式和生产模式,方便随时切换。
在项目根目录下创建文件夹:config
。
然后新建两个配置文件:
- 开发模式配置文件:
/config/webpack.dev.js
- 生产模式配置文件:
/config/webpack.prod.js
在上文已经写好的webpack.config.js
文件的基础上,稍作修改,拆分出两个模式的配置文件。
配置文件迁移的时候,由于目录变更,配置文件中的路径字符串需要做出相应的修改。同时,mode
字段需要区分development
或production
。
开发模式需要注意:
- 没有输出,不需要指定输出路径,也不需要清空输出结果。
- 需要启动开发服务器。
生产模式需要注意:
- 需要指定输出路径。
- 关闭开发服务器。
更新package.json
里的脚本
"scripts": { "start": "npm run dev", "dev": "webpack serve --config ./config/webpack.dev.js", "build": "webpack --config ./config/webpack.prod.js" },
- 启动开发模式使用:
npm run start
或npm run dev
- 启动生产模式使用:
npm run build
CSS处理
将css打包成单独文件
上文中的CSS处理是使用style-loader
,这样做的结果是:样式会被打包到js文件中,最终再由js生成style标签。
这样做可能会导致白屏现象,因为目前流行的前端框架vue和react主要做的都是单页面应用,需要加载一个很大的js文件来渲染页面,如果样式也由js文件来负责的话,在js解析加载完成之前,页面内容一片空白。这样的页面用户体验不好。
因此,更好的解决方案是将CSS打包成单独的.css
文件,然后使用<link>
引入。
安装相关的插件
npm i -D mini-css-extract-plugin
配置webpack.build.js
... // 引入插件 const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { ... module: { // css相关的rules,将style-loader替换成新插件的loader rules: [ { // 用来匹配 .css 结尾的文件 test: /.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] }, { // 用来匹配 .less 结尾的文件 test: /.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] }, ... ], }, plugins: [ ... // 提取css成单独文件 new MiniCssExtractPlugin({ // 定义输出文件名和目录 filename: "static/css/main.css", }), ], mode: "production", };
主要步骤:
- 引入插件;
- 修改rules中的loader;
- 新增plugins,并指定输出的文件名。
运行打包指令
npm run build
效果:
-
输出了
/dist/static/css/main.css
文件: -
输出的
/dist/index.html
中出现了<link>
标签,并引入了打包后的css文件。
css兼容性处理
css兼容性处理可以使用postcss,拥有许多插件,可以按需配置,可以与解决JS兼容性问题的Babel进行类比。
安装
需要安装:
postcss
:postcss本身postcss-loader
:为了webpack接入的loaderpostcss-preset-env
:postcss官方提供的预设配置
npm i -D postcss postcss-loader postcss-preset-env
配置webpack.prod.js
只需要在css-loader
之前新增一个postcss-loader
就可以:
rules: [ { // 用来匹配 .css 结尾的文件 test: /.css$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', // 在css-loader之前使用postcss-loader { loader: 'postcss-loader', options: { postcssOptions: { plugins: ['postcss-preset-env'] } } } ] }, { // 用来匹配 .less 结尾的文件 test: /.less$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', { loader: 'postcss-loader', options: { postcssOptions: { plugins: ['postcss-preset-env'] } } }, 'less-loader', ] }, ]
对于其它css扩展语法,例如less
,可以先使用less-loader
转成css
,然后使用postcss-loader
处理其兼容性,最后才是使用css-loader
处理成webpack可以处理的模块,以及打包用的MiniCssExtractPlugin.loader
。
兼容性控制
package.json
文件中可以写入browserslist
这个属性,指定兼容性的考虑程度。
这个属性是共用的,与兼容性问题相关的许多插件都会自动读取这个属性。
常用案例:
{ "browserslist": ["last 2 version", "> 1%", "not dead"] }
css压缩
减少打包后的体积。
安装
npm i -D css-minimizer-webpack-plugin
配置
... const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); module.exports = { ... plugins: [ ... // css压缩 new CssMinimizerPlugin() ], mode: 'production' }
html和js压缩
生产环境下,html和js是默认压缩的,即打包后的代码都是单行的。
总结
5个核心概念:
- entry(入口):webpack从这个文件开始进行打包,并打包其依赖文件;
- output(输出):决定了webpack打包后的文件的文件名、地址等;
- loader(加载器):webpack只能处理js文件,其它文件比如css文件、图片资源、jsx文件,需要使用loader才能解析;
- plugins(插件):扩展webpack的功能,每一个插件如何配置取决于插件的提供者如何设计;
- mode(模式):主要有两种模式,开发模式(development)和生产模式(production)。
2种模式
使用两个独立的配置文件。
- 开发模式:需要代码编译、语法检查、热加载。
- 生产模式:需要考虑性能与兼容性。