webpack是一个现代JavaScript应用程序的静态模块打包器。当webpack处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle.
本文着重讲述webpack基础,先来绘制一个大概的知识体系,接下来会根据该知识体系进行讲述:
1. 简单配置
第一部分,我们先从“会配置”入手,去了解webpack简单配置以及简单配置所涉及到的知识点。该部分重点需要掌握以下几点:
:::info
1、webpack的常规配置项;
2、常用的Loader与其配置;
3、常用的Plugin与其配置;
4、Babel的配置与使用;
:::
1.1 安装
在本地安装webpack以及webpack-cli
npm install webpack webpack-cli -D // 注意区分环境 -D -S
1.2 工作模式
webpack在4之后就支持0配置打包。我们在此处新建一个./src/index.js文件,简单测试一下:
const a = 'btqf';console.log('btqf');module.exports = a
当我们直接运行npx webpack,启动打包,会出现以下结果:
此时提示我们没有配置模式(mode),我们都知道mode的作用是告知webpack使用相应模式的内置优化,其默认值为production。
| 选项 | 描述 |
|---|---|
| development | 开发模式,打包更为快速,节省代码优化步骤 |
| production | 生产模式,打包较慢,会开启tree-shaking 和压缩代码 |
| none | 不使用任何默认的优化选项 |
因此对于上面的警告提示,我们可以采用以下两种方法:
在配置中提供
mode选项module.exports = {mode: 'production'};
从
cli参数中传递:webpack --mode=production
1.3 配置文件
虽然有0配置打包,但是在实际工作中。我们很多时候采用配置文件的方式来满足不同项目的需求,基本的配置步骤如下:
根路径下新建一个配置文件
webpack.config.js- 新增基本配置信息 ```javascript const path = require(‘path’)
module.exports = { mode: ‘production’, // 模式 entry: ‘./src/index.js’, // 打包入口地址 output: { filename: ‘bundle.js’, // 输出文件名 path: path.join(__dirname, ‘dist’) // 输出文件目录 } }
<a name="uAPKu"></a>### 1.4 Loader首先我们需要了解,**webpack默认支持处理 JS 和 JSON 文件,无法处理其他类型的文件,所以这里我们可以使用Loader来将webpack不认识的内容转化为认识的内容。**<br />我们先创建`./src/main.css`文件,在把**打包入口修改为**`./src/main.css`后直接运行`npx webpack`.```javascript// ./src/main.cssbody {margin: 10px auto;background: cyan;max-width: 800px;font-size: 46px;font-weight: 600;color: white;position: fixed;left: 50%;transform: translateX(-50%);}
此时我们来看看运行结果:<br /><br />如我们所料一般,根据提示我们需要安装`css-loader`来对 css 文件进行处理:
npm install css-loader -D
在安装过后,我们需要在`webpack.config.js`中配置资源加载模块:
const path = require('path')module.exports = {mode: 'development', // 模式entry: './src/main.css', // 打包入口地址output: {filename: 'bundle.css', // 输出文件名path: path.join(__dirname, 'dist') // 输出文件目录},module: {rules: [ // 转换规则{test: /\.css$/, //匹配所有的 css 文件use: 'css-loader' // use: 对应的 Loader 名称}]}}
在修改过后就可以成功打包了:<br />
1.5 Plugins(插件)
与 Loader 用于转换特定类型的文件不同,插件可以贯穿webpack打包的生命周期,执行不同的任务。
如果想要使用一个插件,我们只需要require()它,探后把它添加到plugins数组中,多事插件可以通过选项(option)自定义。这么说可能不太清楚,下面用一个例子来详细讲述:
第一步,我们新建一个./src/index.html文件:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>ITEM</title></head><body></body></html>
如果我们想要打包后的资源文件,例如 js 或者 css 文件可以自动引入到 html 文件中,就需要使用插件 `html-webpack-plugin`来帮助完成该操作。所以第二步为安装插件:
npm install html-webpack-plugin -D
第三步就是在`webpack.config.js`中配置插件即可:
const path = require('path')const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {mode: 'development', // 模式entry: './src/index.js', // 打包入口地址output: {filename: 'bundle.js', // 输出文件名path: path.join(__dirname, 'dist') // 输出文件目录},module: {rules: [{test: /\.css$/, //匹配所有的 css 文件use: 'css-loader' // use: 对应的 Loader 名称}]},plugins:[ // 配置插件new HtmlWebpackPlugin({template: './src/index.html'})]}
当我们运行后,打开`./dist/index.html`文件,可以看到它自动引入的打包好的`bundle.js`,非常方便:<br />
1.6 自动清空打包目录
在每次进行打包的时候,打包目录总是会遗留上次打包的文件,为了保持打包目录的纯净,我们需要在打包前将打包目录清空。这里我们可以使用插件clean-webpack-plugin来实现。
1.安装
npm install clean-webpack-plugin -D
2.配置
const path = require('path')const HtmlWebpackPlugin = require('html-webpack-plugin')// 引入插件const { CleanWebpackPlugin } = require('clean-webpack-plugin')module.exports = {// ...plugins:[ // 配置插件new HtmlWebpackPlugin({template: './src/index.html'}),new CleanWebpackPlugin() // 引入插件]}
1.7 区分环境
我们都清楚,本地开发和部署线上,肯定有不同的需求。
本地环境
- 需要更快的构件速度
- 需要打印 debug 信息
- 需要 live reload 或 hot reload 功能
- 需要 sourceMap 方便定位问题
生产环境
- 需要更小的包体积、代码压缩+tree-shaking
- 需要进行代码分割
- 需要压缩图片体积
- …..
针对不同的需求,我们得要做好环境的区分。
本地安装 cross-env
npm install cross-env -D
配置启动命令,打开
./package.json"scripts": {"dev": "cross-env NODE_ENV=dev webpack serve --mode development","test": "cross-env NODE_ENV=test webpack --mode production","build": "cross-env NODE_ENV=prod webpack --mode production"},
在webpack配置文件中获取环境变量 ```javascript const path = require(‘path’) const HtmlWebpackPlugin = require(‘html-webpack-plugin’)
console.log(‘process.env.NODE_ENV=’, process.env.NODE_ENV) // 打印环境变量
const config = { entry: ‘./src/index.js’, // 打包入口地址 output: { filename: ‘bundle.js’, // 输出文件名 path: path.join(__dirname, ‘dist’) // 输出文件目录 }, module: { rules: [ { test: /.css$/, //匹配所有的 css 文件 use: ‘css-loader’ // use: 对应的 Loader 名称 } ] }, plugins:[ // 配置插件 new HtmlWebpackPlugin({ template: ‘./src/index.html’ }) ] }
module.exports = (env, argv) => { console.log(‘argv.mode=’,argv.mode) // 打印 mode(模式) 值 // 这里可以通过不同的模式修改 config 配置 return config; }
4. 测试执行```javascript// 执行 npm run buildprocess.env.NODE_ENV= prodargv.mode= production// 执行 npm run testprocess.env.NODE_ENV= testargv.mode= production// 执行 npm run devprocess.env.NODE_ENV= devargv.mode= development
1.8 启动 devServer
1.安装 webpack-dev-server
npm intall webpack-dev-server@3.11.2 -D// 注意:当devServer版本 >= 4.0.0 时,需要使用devServer.static进行配置,不再有// devServer.contentBase 配置项
2.配置本地服务
// webpack.config.jsconst config = {// ...devServer: {contentBase: path.resolve(__dirname, 'public'), // 静态文件目录compress: true, //是否启动压缩 gzipport: 8080, // 端口号// open:true // 是否自动打开浏览器},// ...}module.exports = (env, argv) => {console.log('argv.mode=',argv.mode) // 打印 mode(模式) 值// 这里可以通过不同的模式修改 config 配置return config;}
在这里先说明一下,**为什么要配置 contentBase?**<br />原因在于webpack在进行打包的时候,对静态文件的处理,例如图片,都是直接copy到dist目录下,但对于本地开发来说,该过程费时也没有必要,所以在设置contentBase之后,就直接到对应的静态目录下面去读取文件,而不需要对文件进行任何改动,**节省了时间和性能开销。**<br />3.启动本地服务`npm run dev`,打开`http://localhost:8080/`即可看到效果
1.9 引入CSS
在上面的Loader中,我们通过使用css-Loader来处理css,但是单靠css-Loader是没有办法将样式加载到页面上的,此时我们可以再安装一个style-loader来完成。style-loader就是将处理好的css通过style标签的形式添加到页面上。值得注意的是,loader的执行顺序是固定从后往前的。
1.安装
npm install style-loader -D
2.配置
const config = {// ...module: {rules: [{test: /\.css$/, //匹配所有的 css 文件use: ['style-loader','css-loader']}]},// ...}
3.引入样式文件:在入口文件`./src/index.js`中引入样式文件`./src/main.css`
// ./src/index.jsimport './main.css';const a = 'Hello ITEM'console.log(a)module.exports = a;
4.重启本地服务,访问`http://localhost:8080/`<br />
1.10 CSS兼容性
我们可以通过使用postcss-loader,自动添加 css3 部分属性的浏览器前缀。
就像之前用到的transform: translateX(-50%),需要加上不同的浏览器前缀。
1.安装postcss-loader
npm install postcss postcss-loader postcss-preset-env -D
2.在webpack.config.js文件中添加postcss-loader
3.创建postcss配置文件postcss.config.js
// postcss.config.jsmodule.exports = {plugins: [require('postcss-preset-env')]}
4.创建 postcss-preset-env 配置文件 .browserslistrc
# 换行相当于 andlast 2 versions # 回退两个浏览器版本> 0.5% # 全球超过0.5%人使用的浏览器,可以通过 caniuse.com 查看不同浏览器不同版本占有率IE 10 # 兼容IE 10
1.11 引入Less或者Sass
less 和 sass 文件都是webpack无法识别的,需要对应的 loader 来处理。
| 文件类型 | loader |
|---|---|
| Less | less-loader |
| Sass | sass-loader node-sass或dart-sass |
1.安装
npm install sass-loader -D// 推荐用淘宝镜像下载,成功率比较高npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
2.新建`./src/sass.scss`
$color: rgb(190, 23, 168);body {p {background-color: $color;width: 300px;height: 300px;display: block;text-align: center;line-height: 300px;}}
3.在入口文件中引入Sass文件后,修改webpack.config.js配置
const config = {// ...rules: [{test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件use: ['style-loader','css-loader','postcss-loader','sass-loader',]},]},// ...}
1.12 分离样式文件
在前面我们通过style-loader将样式通过style标签的形式添加到页面上,但是我们可以通过mini-css-extract-plugin将css文件的形式引入到页面上。
1.安装
npm install mini-css-extract-plugin -D
2.修改配置
// ...// 引入插件const MiniCssExtractPlugin = require('mini-css-extract-plugin')const config = {// ...module: {rules: [// ...{test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件use: [// 'style-loader',MiniCssExtractPlugin.loader, // 添加 loader'css-loader','postcss-loader','sass-loader',]},]},// ...plugins:[ // 配置插件// ...new MiniCssExtractPlugin({ // 添加插件filename: '[name].[hash:8].css'}),// ...]}
3.查看打包结果<br />
1.13 图片和字体文件
在开发环境中,我们可以通过设置contentBase直接读取图片类的静态文件。但是当在生产环境中无法识别图片文件,所以可以用下面集中loader进行处理:
| Loader | 说明 |
|---|---|
| file-loader | 解决图片引入问题,并将图片 copy 到指定目录,默认为 dist |
| url-loader |
| 解依赖 file-loader,当图片小于 limit 值的时候,会将图片转为 base64 编码,大于 limit 值的时候依然是使用 file-loader 进行拷贝 | | img-loader | 压缩图片 |
1.安装file-loader。<br />值得一提的是,在webpack5中,内置了资源处理模块,`file-loader`和`url-loader`都可以不用安装。
npm install file-loader -D
2.修改配置
const config = {//...module: {rules: [{// ...},{test: /\.(jpe?g|png|gif)$/i, // 匹配图片文件use:['file-loader' // 使用 file-loader]}]},// ...}
3.引入图片
<!-- ./src/index.html --><!DOCTYPE html><html lang="en">...<body><p></p><div id="imgBox"></div></body></html>/* ./src/main.css */...#imgBox {height: 400px;width: 400px;background: url('../public/logo.png');background-size: contain;}
4.刷新运行,结果如下:
至于url-loader的配置基本与上述无异,只是多了一个 limit 的限制,这里就不再详细叙述了。
1.14 资源模块的使用
webpack5 新增资源模块,允许使用资源文件而不用配置额外的loader。资源模块支持以下四个配置: :::info
- asset/resource 将资源分割为单独的文件,并导出 url,类似之前的 file-loader 的功能.
- asset/inline 将资源导出为 dataUrl 的形式,类似之前的 url-loader 的小于 limit 参数时功能.
- asset/source 将资源导出为源码(source code). 类似的 raw-loader 功能.
- asset 会根据文件大小来选择使用哪种类型,当文件小于 8 KB(默认) 的时候会使用 asset/inline,否则会使用 asset/resource ::: 修改配置,执行打包后的结果一致。 ```javascript // ./src/index.js
const config = { // … module: { rules: [ // … { test: /.(jpe?g|png|gif)$/i, type: ‘asset’, generator: { // 输出文件位置以及文件名 // [ext] 自带 “.” 这个与 url-loader 配置不同 filename: “[name][hash:8][ext]” }, parser: { dataUrlCondition: { maxSize: 50 1024 //超过50kb不转 base64 } } }, { test: /.(woff2?|eot|ttf|otf)(\?.)?$/i, type: ‘asset’, generator: { // 输出文件位置以及文件名 filename: “[name][hash:8][ext]” }, parser: { dataUrlCondition: { maxSize: 10 * 1024 // 超过100kb不转 base64 } } }, ] }, // … }
<a name="iEr1a"></a>### 1.15 JS兼容性(Babel)在开发中,如果我们想要使用最新的 JS 特性,但有些新特性的兼容性支持并不是很好,所以我们需要做兼容处理,常见的就是将ES6语法转化为ES5。<br />1.在配置前,我们可以先写一点ES6语法。```javascriptimport './main.css'class Author {name = 'btqf'age = 18email = 'xxx@outlook.com'info = () => {return {name: this.name,age: this.age,email: this.email}}}module.exports = Author
为了方便看源码,我们先将mode设置为none,以最原始的形式打包。打包后发现代码变化不大,只是对图片的地址进行了替换,接下来我们看看配置babel后的打包结果有啥子变化。
2.安装依赖
npm install babel-loader @babel/core @babel/preset-env -D
babel-loader使用 Babel 加载 ES2015+ 代码并将其转换为 ES5@babel/core Babel编译的核心包@babel/preset-env Babel编译的预设,可以理解为 Babel 插件的超集
3.为了避免webpack.config.js过于臃肿,新建文件.babelrc.js配置Babel预设,
// ./babelrc.jsmodule.exports = {presets: [["@babel/preset-env",{// useBuiltIns: false 默认值,无视浏览器兼容配置,引入所有 polyfill// useBuiltIns: entry 根据配置的浏览器兼容,引入浏览器不兼容的 polyfill// useBuiltIns: usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加useBuiltIns: "entry",corejs: "3.9.1", // 是 core-js 版本号targets: {chrome: "58",ie: "11",},},],],};
配置执行打包,发现ES6 class写法已经转换成 ES5 的构造函数形式。
4.对于还未进入 ECMA 规范中的新特性, Babel 是无法进行处理的,必须要安装对应的插件,例如:
@babel/plugin-proposal-decorators@babel/plugin-proposal-class-properties
安装后打开.babelrc.js加上插件的配置
module.exports = {presets: [["@babel/preset-env",{useBuiltIns: "entry",corejs: "3.9.1",targets: {chrome: "58",ie: "11",},},],],plugins: [["@babel/plugin-proposal-decorators", { legacy: true }],["@babel/plugin-proposal-class-properties", { loose: true }],]};
这样就可以打包了,在bundle.js中已经转化为浏览器支持的JS代码。
2.SourceMap配置选择
SourceMap 是一种映射关系,当项目运行后,如果出现错误,我们可以利用 SourceMap 反向定位到源码位置。
2.1 devtool 配置
const config = {entry: './src/index.js', // 打包入口地址output: {filename: 'bundle.js', // 输出文件名path: path.join(__dirname, 'dist'), // 输出文件目录},devtool: 'source-map',module: {// ...}
执行打包后,dist 目录下会生成以 .map 结尾的 SourceMap 文件。除了 source-map 这种类型之外,还有很多种类型可以用,例如:
evaleval-source-mapcheap-source-mapinline-source-mapcheap-module-source-mapinline-cheap-source-mapcheap-module-eval-source-mapinline-cheap-module-source-maphidden-source-mapnosources-source-map
2.2 配置项差异
这里就不一一就行分析了,直接上图:
| devtool | build | rebuild | 显示代码 | SourceMap 文件 | 描述 |
|---|---|---|---|---|---|
| (none) | 很快 | 很快 | 无 | 无 | 无法定位错误 |
| eval | 快 | 很快(cache) | 编译后 | 无 | 定位到文件 |
| source-map | 很慢 | 很慢 | 源代码 | 有 | 定位到行列 |
| eval-source-map | 很慢 | 一般(cache) | 编译后 | 有(dataUrl) | 定位到行列 |
| eval-cheap-source-map | 一般 | 快(cache) | 编译后 | 有(dataUrl) | 定位到行 |
| eval-cheap-module-source-map | 慢 | 快(cache) | 源代码 | 有(dataUrl) | 定位到行 |
| inline-source-map | 很慢 | 很慢 | 源代码 | 有(dataUrl) | 定位到行列 |
| hidden-source-map | 很慢 | 很慢 | 源代码 | 有 | 无法定位错误 |
| nosource-source-map | 很慢 | 很慢 | 源代码 | 无 | 定位到文件 |
| 关键字 | 描述 |
|---|---|
| inline | 代码内通过 dataUrl 形式引入 SourceMap |
| hidden | 生成 SourceMap 文件,但不使用 |
| eval | eval(…) 形式执行代码,通过 dataUrl 形式引入 SourceMap |
| nosources | 不生成 SourceMap |
| cheap | 只需要定位到行信息,不需要列信息 |
| module | 展示源代码中的错误位置 |
2.3 推荐配置
- 本地开发:
推荐:eval-cheap-module-source-map
理由:
- 本地开发首次打包慢点没关系,因为 eval 缓存的原因,rebuild 会很快
- 开发中,我们每行代码不会写的太长,只需要定位到行就行,所以加上 cheap
- 我们希望能够找到源代码的错误,而不是打包后的,所以需要加上 module
- 生产环境:
推荐:(none)
理由:
- 就是不想别人看到我的源代码
3. 三种 hash 值
Webpack 文件指纹策略是将文件名后面加上 hash 值。特别在使用 CDN 的时候,缓存是它的特点与优势,但如果打包的文件名,没有 hash 后缀的话将会很麻烦。
例如我们在基础配置中用到的:filename: "[name][hash:8][ext],这里里面 [ ] 包起来的,就叫占位符,它们都是什么意思呢?请看下表:
| 占位符 | 解释 |
|---|---|
| ext | 文件后缀名 |
| name | 文件名 |
| path | 文件相对路径 |
| folder | 文件所在文件夹 |
| hash | 每次构建生成的唯一 hash 值,任意文件改动,整个项目的 hash 都会改变 |
| chunkhash | 根据 chunk 生成 hash 值,文件的改动只会影响其所在 chunk 的 hash 值 |
| contenthash | 根据文件内容生成hash 值,文件的改动只会影响自身的 hash 值 |
好了,关于webpack的基础篇就暂时讲到这里了,如有错误,欢迎指出,谢谢!
