# webpack-study **Repository Path**: nicefree/webpack-study ## Basic Information - **Project Name**: webpack-study - **Description**: 2022年了,再不会webpack5敲得代码就不香了,持续更新中...... - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: https://gitee.com/nicefree/webpack-study - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 2 - **Created**: 2021-11-30 - **Last Updated**: 2023-05-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: webpack5, webpack, JavaScript ## README ![avatar](./src/assets/images/iu.jpg) > 参考: - https://juejin.cn/post/6844904031240863758#heading-1 - https://cloud.tencent.com/developer/article/1847715、 - https://jelly.jd.com/article/61179aa26bea510187770aa3 - https://juejin.cn/post/6990869970385109005#heading-3 > 注意:当前 node 版本要尽量用最新版本,目前 16.13.0 是没有问题的 ### 初始化 创建项目文件夹 webpack-study npm init -y npm i -D webpack webpack-cli - npm i -D 为 npm install --save-dev 的缩写 - npm i -S 为 npm install --save 的缩写 新建一个文件夹 src ,然后新建一个文件 main.js,写一点代码测试一下 `console.log('Hello World')` 配置 package.json 命令 ```javascript "scripts": { "build": "webpack src/main.js" } ``` 执行 npm run build 此时如果生成了一个 dist 文件夹,并且内部含有 main.js 说明已经打包成功了 ### 自定义配置 上面一个简单的例子只是 webpack 自己默认的配置,下面我们要实现更加丰富的自定义配置 新建一个 build 文件夹,里面新建一个 webpack.config.js ```javascript // webpack.config.js const path = require("path"); module.exports = { mode: "development", // 开发模式 entry: path.resolve(__dirname, "../src/main.js"), // 入口文件 output: { filename: "output.js", // 打包后的文件名称 path: path.resolve(__dirname, "../dist"), // 打包后的目录 }, }; ``` 更改我们的打包命令 ```javascript "scripts": { "build": "webpack --config build/webpack.config.js" } ``` 执行 npm run build 会发现生成了以下目录 其中 dist 文件夹中的 main.js 就是我们需要在浏览器中实际运行的文件 当然实际运用中不会仅仅如此,下面让我们通过实际案例带你快速入手 webpack ### 配置 html 模板 js 文件打包好了,但是我们不可能每次在 html 文件中手动引入打包好的 js > 这里可能有的朋友会认为我们打包 js 文件名称不是一直是固定的嘛(output.js)?这样每次就不用改动引入文件名称了呀?实际上我们日常开发中往往会这样配置: ```javascript module.exports = { // 省略其他配置 output: { filename: "[name].[hash:8].js", // 打包后的文件名称, hash已被启用,改fullhash, path: path.resolve(__dirname, "../dist"), // 打包后的目录 }, }; ``` 这时候生成的 dist 目录文件如下 main.8626b326.js 为了缓存,你会发现打包好的 js 文件的名称每次都不一样。webpack 打包出来的 js 文件我们需要引入到 html 中,但是每次我们都手动修改 js 文件名显得很麻烦,因此我们需要一个插件来帮我们完成这件事情 npm i -D html-webpack-plugin 新建一个 build 同级的文件夹 public,里面新建一个 index.html 具体配置文件如下 ```javascript // webpack.config.js const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { mode: "development", // 开发模式 entry: path.resolve(__dirname, "../src/main.js"), // 入口文件 output: { filename: "[name].[fullhash:8].js", // 打包后的文件名称 path: path.resolve(__dirname, "../dist"), // 打包后的目录 }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, "../public/index.html"), }), ], }; ``` 可以发现打包生成的 js 文件已经被自动引入 html 文件中 思考?为什么多了个 defer,查看博客文章https://blog.csdn.net/qq_43238599/article/details/115273893 查看官方插件配置选项https://github.com/jantimon/html-webpack-plugin#options 重新调整插件配置,再打包 ```javascript plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, "../public/index.html"), scriptLoading: "blocking", }), ]; ``` 打包结果的 html 内容: js 打包进入到了 body,如果不配置 scriptLoading 却在 head,配置了却在 body,再加个配置项 ```javascript plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, "../public/index.html"), inject: "head", scriptLoading: "blocking", }), ]; ``` 再打包就 ok #### 多入口文件如何开发 > 生成多个 html-webpack-plugin 实例来解决这个问题 public 目录下新建 header.html,新建 webpack.config.multi.js ```javascript // webpack.config.multi.js const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { mode: "development", // 开发模式 entry: { main: path.resolve(__dirname, "../src/main.js"), header: path.resolve(__dirname, "../src/header.js"), }, output: { filename: "[name].[fullhash:8].js", // 打包后的文件名称 path: path.resolve(__dirname, "../dist"), // 打包后的目录 }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(__dirname, "../public/index.html"), filename: "index.html", inject: "head", scriptLoading: "blocking", chunks: ["main"], // 与入口文件对应的模块名 }), new HtmlWebpackPlugin({ template: path.resolve(__dirname, "../public/header.html"), filename: "header.html", inject: "head", scriptLoading: "blocking", chunks: ["header"], // 与入口文件对应的模块名 }), ], }; ``` package.json 调整 ```javascript "scripts": { "build": "webpack --config build/webpack.config.js", "buildmul": "webpack --config build/webpack.config.multi.js" }, ``` 执行 npm run buildmul #### clean-webpack-plugin > 每次执行 npm run build 会发现 dist 文件夹里会残留上次打包的文件,这里我们推荐一个 plugin 来帮我们在打包输出前清空文件夹 clean-webpack-plugin npm i -D clean-webpack-plugin ```javascript const { CleanWebpackPlugin } = require("clean-webpack-plugin"); module.exports = { // ...省略其他配置 plugins: [new CleanWebpackPlugin()], }; ``` ### 引用 CSS #### loader loader 用于对模块的源代码进行转换 loader 都在 module 下的 rules 中配置 loader 配置项包括: - test 正则校验(必须) - loader 调用 loader 的名称 / use 链式调用 loader (二选一) - include/exclude 手动添加必修处理的文件/文件夹或屏蔽不需要处理的文件/文件夹(可选) - options 为 loaders 提供额外的设置选项(可选) tip:use 链式调用,都是从右向左解析,需注意调用 loader 的顺序。loader 要记住,面试经常被问到有哪些 loader 以及其作用 我们的入口文件是 js,所以我们在入口 js 中引入我们的 css 文件 ```css /* index.css */ * { margin: 0; padding: 0; } ``` ```css /* index.less */ .red { color: red; } ``` ```css /* index.scss */ .green { color: green; } ``` ```javascript import "./assets/index.css"; import "./assets/index.less"; import "./assets/index.scss"; console.log("Hello World"); ``` 同时我们也需要一些 loader 来解析我们的 css 文件 > npm i -D style-loader css-loader 如果我们使用 less 来构建样式,则需要多安装两个 > npm i -D less less-loader 如果我们使用 scss 来构建样式,则需要安装 > npm i -D sass-loader 配置文件如下 ```javascript // webpack.config.js module.exports = { // ...省略其他配置 module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader"], // 从右向左解析原则 }, { test: /\.less$/, use: ["style-loader", "css-loader", "less-loader"], // 从右向左解析原则 }, { test: /\.scss$/, use: ["style-loader", "css-loader", "sass-loader"], // 从右向左解析原则 }, ], }, }; ``` 执行编译的时候会报 scss 相关错误,还需安装 node-sass,但没有看到相关使用 node-sass 的字眼!!! npm i -D node-sass sass-loader 全局安装 live-server, npm i -g live-server cmd cd dist cmd live-server 打开浏览器,观察 html 内容 为了直观可以修改 public/index.html 的内容 ```html
Hello World
Hello World
``` 可以看到 css 都已经注入 #### 为 css 添加浏览器前缀 > npm i -D postcss-loader autoprefixer 给 index.css 添加内容 ```css .noselect { user-select: none; } ``` 模板 html ```html
Hello World
Hello World
``` 调整 webpack.config.js 的 rules 配置如下: ```javascript rules: [ { test: /\.css$/, use: ["style-loader", "css-loader", "postcss-loader"], // 从右向左解析原则 }, { test: /\.less$/, use: ["style-loader", "css-loader", "postcss-loader", "less-loader"], // 从右向左解析原则 }, { test: /\.scss$/, use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"], // 从右向左解析原则 }, ]; ``` 接下来,我们还需要引入 autoprefixer 使其生效,这里有两种方式 1,在项目根目录下创建一个 postcss.config.js 文件,配置如下: ```javascript module.exports = { plugins: [require("autoprefixer")], // 引用该插件即可了 }; ``` 在 index.css 修改为 ```css .noselect { user-select: none; transform: rotate(3deg); display: flex; } ``` 重新 npm run build,浏览器发现 transfrom 只有-webkit-前缀,如果兼容性更强有两种方法: 1. 在 package.json 追加 ```javascript "browserslist": [ "defaults", "not ie <= 8", "last 2 versions", "> 1%", "Safari >= 6", "Firefox > 3", "iOS >= 7", "Android >= 4.0" ] ``` 或者 2. 根目录下新建.browserslistrc ```javascript ie > 8 last 2 versions > 1% Safari >= 6 Firefox > 3 iOS >= 7 Android >= 4.0 ``` cmd npx browserslist 可以查看浏览器厂商前缀兼容的范围 2,直接在 webpack.config.js 里配置,由于高版本的 webpack 不兼容了,这里暂不研究 这时候我们发现 css 通过 style 标签的方式添加到了 html 文件中,但是如果样式文件很多,全部添加到 html 中,难免显得混乱。这时候我们想用把 css 拆分出来用外链的形式引入 css 文件怎么做呢?这时候我们就需要借助插件来帮助我们 #### 合并 css > npm i -D mini-css-extract-plugin > webpack 4.0 以前,我们通过 extract-text-webpack-plugin 插件,把 css 样式从 js 文件中提取到单独的 css 文件中。webpack4.0 以后,官方推荐使用 mini-css-extract-plugin 插件来打包 css 文件 配置文件如下 webpack.config.jminicss.js ```javascript const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { //...省略其他配置 module: { rules: [ { test: /\.less$/, use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"], }, ], }, plugins: [ new MiniCssExtractPlugin({ filename: "[name].[hash].css", chunkFilename: "[id].css", }), ], }; ``` 执行 npm run buildminicss 刚开始的的时候 rules 中使用了 style-loader,发现和 MiniCssExtractPlugin.loader 一起使用会出现 warning style-loader 的作用是头部用嵌入式引入, MiniCssExtractPlugin 插件的作用是提取 JS 中的 CSS 样式,用 link 外部引入,减少 JS 文件的大小,简称 CSS 样式分离 如果使用了 MiniCssExtractPlugin.loader 又要使用 webpack5 的内置资源打包静态资源 需加入 ```javascript // webpack.config.minicss.js rules: [ { test: /\.(jpe?g|png|gif)$/i, // 图片文件 type: "asset", // 解析 parser: { // 超过10kb将转成base64,优点:减少请求 缺点:文件体积变大 dataUrlCondition: { maxSize: 10 * 1024, }, }, generator: { // 与output.assetModuleFilename是相同的,这个写法引入的时候也会添加好这个路径 filename: "images/[name].[hash:6][ext]", // 打包后对资源的引入 // publicPath: '../' }, }, ]; ``` 一定不要使用 publicPath !!!不兼容,会报错 ### 拆分多个 css > 这里需要说的细一点,上面我们所用到的 mini-css-extract-plugin 会将所有的 css 样式合并为一个 css 文件。如果你想拆分为一一对应的多个 css 文件,我们需要使用到 extract-text-webpack-plugin,而目前 mini-css-extract-plugin 还不支持此功能。我们需要安装@next 版本的 extract-text-webpack-plugin > npm i -D extract-text-webpack-plugin ```javascript // webpack.config.js const path = require("path"); const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin"); let indexLess = new ExtractTextWebpackPlugin("index.less"); let indexCss = new ExtractTextWebpackPlugin("index.css"); module.exports = { module: { rules: [ { test: /\.css$/, use: indexCss.extract({ use: ["css-loader"], }), }, { test: /\.less$/, use: indexLess.extract({ use: ["css-loader", "less-loader"], }), }, ], }, plugins: [indexLess, indexCss], }; ``` 运行不成功,暂不研究 webpack4 以上版本不支持该插件,废弃 https://github.com/webpack-contrib/extract-text-webpack-plugin Since webpack v4 the extract-text-webpack-plugin should not be used for css. Use mini-css-extract-plugin instead. ### 打包 图片、字体、媒体、等文件 file-loader 就是将文件在进行一些处理后(主要是处理文件名和路径、解析文件 url),并将文件移动到输出的目录中 url-loader 一般与 file-loader 搭配使用,功能与 file-loader 类似,如果文件小于限制的大小。则会返回 base64 编码,否则使用 file-loader 将文件移动到输出的目录中 ```javascript // webpack.config.js module.exports = { // 省略其它配置 ... module: { rules: [ // ... { test: /\.(jpe?g|png|gif)$/i, //图片文件 use: [ { loader: "url-loader", options: { limit: 10240, fallback: { loader: "file-loader", options: { name: "img/[name].[hash:8].[ext]", }, }, }, }, ], }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, //媒体文件 use: [ { loader: "url-loader", options: { limit: 10240, fallback: { loader: "file-loader", options: { name: "media/[name].[hash:8].[ext]", }, }, }, }, ], }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体 use: [ { loader: "url-loader", options: { limit: 10240, fallback: { loader: "file-loader", options: { name: "fonts/[name].[hash:8].[ext]", }, }, }, }, ], }, ], }, }; ``` webpack5 内置资源模块 asset module 来替换 raw-loader、url-loader、file-loader, 上述配置仅提供参考 博客文章: https://www.jianshu.com/p/36e972b19b28 https://blog.csdn.net/QQ1443003435/article/details/121108377 官方文档: https://webpack.docschina.org/guides/asset-modules/ 一定要使用 url-loader 和 file-loader 须使用如下,这里也仅验证图片资源 ```javascript rules: [ // ...其他配置 { test: /\.(jpe?g|png|gif)$/i, loader: "url-loader", type: "javascript/auto", options: { limit: 10 * 1024, esModule: false, fallback: { loader: "file-loader", options: { esModule: false, name: "[name].[hash:8].[ext]", }, }, }, }, ]; ``` 这里 type 和 esModule 很重要!!! 在 src/assets/images 下存放 三张图片资源,其中一个小于 10kb index.css 添加 ```css #box1 { width: 200px; height: 100px; background-image: url("./images/logo.jpg"); background-repeat: no-repeat; background-size: 100% 100%; } #box2 { width: 200px; height: 100px; background-image: url("./images/web.jpeg"); background-repeat: no-repeat; background-size: 100% 100%; } #box3 { width: 400px; height: 600px; background-image: url("./images/iu.jpg"); background-repeat: no-repeat; background-size: 100% 100%; } ``` 在 index.html 中代码调整修改 ```html
Hello World
Hello World
``` npm run build 在 dist 目录下旧可以看到只有两张图片资源,其中有个小于 10kb 的图片被转成了 base64,校验此配置选项成功 由于 webpack5 已经使用 asset 来打包资源,现在使用如下最新配置选项 ```javascript rules: [ // ...其他配置 { test: /\.(jpe?g|png|gif)$/i, // 图片文件 type: "asset", // 解析 parser: { // 超过10kb将转成base64,优点:减少请求 缺点:文件体积变大 dataUrlCondition: { maxSize: 10 * 1024, }, }, generator: { // 与output.assetModuleFilename是相同的,这个写法引入的时候也会添加好这个路径 filename: "images/[name].[hash:6][ext]", // 打包后对资源的引入 publicPath: "./", }, }, ]; ``` 这里 filename 多生成了一个 images 目录 #### html 中图片资源处理 > npm i html-loader -D 在 index.html 的内容调整 ```html
Hello World
Hello World
``` rules 的配置 ```javascript rules: [ { test: /\.html$/, loader: "html-loader", }, ]; ``` npm run build 查看 html 的内容,其中一张是转成了 base64 一张是文件路径形式 #### 处理媒体文件 免费视频素材网站 - https://www.videezy.com - http://www.wedistill.io/ - https://mazwai.com/ - https://footagecrate.com/ - https://www.monzoom.com 下载个视频放到 media 目录下 video.mp4 index.html 内容调整 ```html
Hello World
Hello World
``` ```javascript // webpack.config.js rules: [ { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, // 媒体文件 type: "asset", parser: { dataUrlCondition: { maxSize: 10 * 1024, }, }, generator: { filename: "media/[name].[hash:6][ext]", publicPath: "./", }, }, ]; ``` cmd npm run build 查看 html 引入的视频路径和 dist/media 下的视频 #### 处理字体文件 添加字体 src/assets/fonts/Poppins-Regular.woff ```css // index.css追加如下 .font { font-family: "Poppins-Regular"; } @font-face { font-family: "Poppins-Regular"; src: url("./fonts/Poppins-Regular.woff") format("woff"); } ``` ```javascript // webpack.config.js rules: [ { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体 type: "asset/inline", // inline 的时候不需要指定文件名 }, ]; ``` inde.html 调整添加 font ```html
Hello World
``` npm run build 查看 index.html,动态去除 font 类能看到字体有变化,或者通过浏览器也能看到字体的加载说明成功 ### 用 babel 转义 js 文件 为了使我们的 js 代码兼容更多的环境我们需要安装依赖 > npm i -D babel-loader @babel/preset-env @babel/core 注意 babel-loader 与 babel-core 的版本对应关系 1. babel-loader 8.x 对应 babel-core 7.x 2. babel-loader 7.x 对应 babel-core 6.x 最新安装的都是高版本 配置如下 ```javascript // webpack.config.js rules = [ // ... 其他配置 { test: /\.js$/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env"], }, }, exclude: /node_modules/, }, ]; ``` 在 main.js 中添加如下 ES6 代码 ```javascript let arr = [1, 2, , 3, 4, 5, 6, 7, 8, 9]; arr.map((item) => { console.log(item); }); class Animal { constructor() { this.type = "animal"; } says(say) { console.log(this.type + " says " + say); } } let animal = new Animal(); animal.says("hello"); ``` npm run build 以后观察打包出来的 js 代码,可以看到相关转化成 ES5 的代码,不够明显,可以把转换的配置规则去掉,再打包可以看到打包出来的 js 是没有转化的 ES6 代码 如果 options 去掉 在项目的根目录下新建.babelrc 内容如下: ```javascript { "presets": ["@babel/preset-env"], "plugins": ["@babel/plugin-transform-runtime"] } ``` ```javascript // webpack.config.js rules: [ { test: /\.js$/, use: { loader: "babel-loader", }, exclude: /node_modules/, }, ]; ``` 还需安装一个 > npm i @babel/plugin-transform-runtime -D 打包观察 js 的是否转换成 es5 Babel 其实是几个模块化的包: - @babel/core:babel 核心库 - babel-loader:webpack 的 babel 插件,让我们可以在 webpack 中运行 babel - @babel/preset-env:将 ES6 转换为向后兼容的 JavaScript - @babel/plugin-transform-runtime:处理 async,await、import()等语法关键字的帮助函数 上面的 babel-loader 只会将 ES6/7/8 语法转换为 ES5 语法,但是对新 api 并不会转换 例如(promise、Generator、Set、Maps、Proxy 等) 此时我们需要借助 babel-polyfill 来帮助我们转换 如下未实验,仅供参考 > npm i @babel/polyfill ```javascript // webpack.config.js const path = require("path"); module.exports = { entry: ["@babel/polyfill", path.resolve(__dirname, "../src/index.js")], // 入口文件 }; ``` ### 增加编译进度条 进度条插件有两种,取其中一个使用就行 > 1. npm i progress-bar-webpack-plugin -D ```javascript const chalk = require("chalk"); const ProgressBarPlugin = require("progress-bar-webpack-plugin"); // ... plugins: [ // 进度条 new ProgressBarPlugin({ format: ` :msg [:bar] ${chalk.green.bold(":percent")} (:elapsed s)`, }), ]; ``` > 2. npm i webpackbar ```javascript const WebpackBar = require("webpackbar"); // ... plugin: [new WebpackBar()]; ``` 保留前面的 webpack.config.js 配置到 webpack.config.bak.js ```javascript // webpack.config.js const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const chalk = require("chalk"); const ProgressBarPlugin = require("progress-bar-webpack-plugin"); const WebpackBar = require("webpackbar"); module.exports = { mode: "development", // 开发模式 entry: path.resolve(__dirname, "../src/main.js"), // 入口文件 output: { filename: "[name].[fullhash:8].js", // 打包后的文件名称 path: path.resolve(__dirname, "../dist"), // 打包后的目录 }, plugins: [ // 进度条 new WebpackBar(), new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template: path.resolve(__dirname, "../public/index.html"), inject: "head", scriptLoading: "blocking", }), ], module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader", "postcss-loader"], // 从右向左解析原则 }, { test: /\.less$/, use: ["style-loader", "css-loader", "postcss-loader", "less-loader"], // 从右向左解析原则 }, { test: /\.scss$/, use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"], // 从右向左解析原则 }, { test: /\.(jpe?g|png|gif)$/i, // 图片文件 type: "asset", // 解析 parser: { // 超过10kb将转成base64,优点:减少请求 缺点:文件体积变大 dataUrlCondition: { maxSize: 10 * 1024, }, }, generator: { // 与output.assetModuleFilename是相同的,这个写法引入的时候也会添加好这个路径 filename: "images/[name].[hash:6][ext]", // 打包后对资源的引入 publicPath: "./", }, }, { test: /\.html$/, loader: "html-loader", }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, // 媒体文件 type: "asset/resource", generator: { filename: "media/[name].[hash:6][ext]", publicPath: "./", }, }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体 type: "asset/inline", // inline 的时候不需要指定文件名 }, { test: /\.js$/, use: { loader: "babel-loader", /* options: { presets: ["@babel/preset-env"] } */ }, exclude: /node_modules/, }, ], }, }; ``` ### 搭建本地服务器 > npm i webpack-dev-server -D 将 webpack.config.js 复制一份,改名称 webpack.common.js 添加选项: ```javascript devServer: { hot: true, // 热更新 open: false, // 编译完自动打开浏览器 compress: true,// 开启gzip压缩 port: 8088, // 开启端口号 client: { //在浏览器端打印编译进度 progress: true, }, } ``` 在 package.json 添加命令 ```javascript "dev": "webpack serve --config build/webpack.common.js" ``` npm run dev 浏览器输入 http://localhost:8088/ 使用了本地服务将不会打包到 dist 目录 ### 生产环境与开发环境 > npm i webpack-merge -D build 下新建 webpack.dev.js,webpack.prod.js webpack.dev.js 内容如下: ```javascript const { merge } = require("webpack-merge"); const common = require("./webpack.common.js"); module.exports = merge(common, { mode: "development", devServer: { hot: true, // 热更新 open: false, // 编译完自动打开浏览器 compress: true, // 开启gzip压缩 port: 8088, // 开启端口号 client: { //在浏览器端打印编译进度 progress: true, }, }, }); ``` webpack.prod.js 内容如下: ```javascript const { merge } = require("webpack-merge"); const common = require("./webpack.common.js"); module.exports = merge(common, { mode: "production", }); ``` webpack.common.js 内容如下: ```javascript // webpack.config.js const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const chalk = require("chalk"); const ProgressBarPlugin = require("progress-bar-webpack-plugin"); const WebpackBar = require("webpackbar"); module.exports = { entry: path.resolve(__dirname, "../src/main.js"), // 入口文件 output: { filename: "[name].[fullhash:8].js", // 打包后的文件名称 path: path.resolve(__dirname, "../dist"), // 打包后的目录 }, plugins: [ new WebpackBar(), new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template: path.resolve(__dirname, "../public/index.html"), inject: "head", scriptLoading: "blocking", }), ], module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader", "postcss-loader"], // 从右向左解析原则 }, { test: /\.less$/, use: ["style-loader", "css-loader", "postcss-loader", "less-loader"], // 从右向左解析原则 }, { test: /\.scss$/, use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"], // 从右向左解析原则 }, { test: /\.(jpe?g|png|gif)$/i, // 图片文件 type: "asset", // 解析 parser: { // 超过10kb将转成base64,优点:减少请求 缺点:文件体积变大 dataUrlCondition: { maxSize: 10 * 1024, }, }, generator: { // 与output.assetModuleFilename是相同的,这个写法引入的时候也会添加好这个路径 filename: "images/[name].[hash:6][ext]", // 打包后对资源的引入 publicPath: "./", }, }, { test: /\.html$/, loader: "html-loader", }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, // 媒体文件 type: "asset/resource", generator: { filename: "media/[name].[hash:6][ext]", publicPath: "./", }, }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体 type: "asset/inline", // inline 的时候不需要指定文件名 }, { test: /\.js$/, use: { loader: "babel-loader", /* options: { presets: ["@babel/preset-env"] } */ }, exclude: /node_modules/, }, ], }, }; ``` package.json 命令 ```javascript { "scripts": { "dev": "webpack serve --config build/webpack.dev.js", "prod": "webpack --config build/webpack.prod.js" } } ``` npm run prod 和 npm run dev 可以正常执行 ### 配置别名 在 index.html 添加内容 ```html ``` 注意:~@ 在 webpack.common.js 中添加配置选项 resolve 和 entry 同级 ```javascript resolve: { alias: { // 配置别名 "@": path.resolve(__dirname, "../src"), }, }, ``` npm run dev 编译通过,并且浏览器正常访问,如果把配置去掉,编译不通过会报错! ### 代码依赖分析 > npm i webpack-bundle-analyzer -D 只有打包才会分析所以在 webpack.prod.js 添加如下配置: ```javascript const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; // ... 其他配置 plugins: [ new BundleAnalyzerPlugin({ // 可以是`server`,`static`或`disabled`。 // 在`server`模式下,分析器将启动HTTP服务器来显示软件包报告。 // 在“静态”模式下,会生成带有报告的单个HTML文件。 // 在`disabled`模式下,你可以使用这个插件来将`generateStatsFile`设置为`true`来生成Webpack Stats JSON文件。 analyzerMode: "static", // 将在“服务器”模式下使用的主机启动HTTP服务器。 analyzerHost: "127.0.0.1", // 将在“服务器”模式下使用的端口启动HTTP服务器。 analyzerPort: 8888, // 路径捆绑,将在`static`模式下生成的报告文件。 // 相对于捆绑输出目录。 reportFilename: "report.html", // 模块大小默认显示在报告中。 // 应该是`stat`,`parsed`或者`gzip`中的一个。 // 有关更多信息,请参见“定义”一节。 defaultSizes: "parsed", // 在默认浏览器中自动打开报告 openAnalyzer: false, // 如果为true,则Webpack Stats JSON文件将在bundle输出目录中生成 generateStatsFile: false, // 如果`generateStatsFile`为`true`,将会生成Webpack Stats JSON文件的名字。 // 相对于捆绑输出目录。 statsFilename: "stats.json", // stats.toJson()方法的选项。 // 例如,您可以使用`source:false`选项排除统计文件中模块的来源。 // 在这里查看更多选项:https: //github.com/webpack/webpack/blob/webpack-1/lib/Stats.js#L21 statsOptions: null, logLevel: "info", // 日志级别。可以是'信息','警告','错误'或'沉默'。 }), ]; ``` npm run prod ### splitChunks(分离 chunks) webpack.prod.js 也只有打包才需要分离 ```javascript //webpack.prod.js //与plugins同级 optimization: { splitChunks: { chunks: "all", name: "vendor", cacheGroups: { "echarts.vendor": { name: "echarts.vendor", priority: 40, test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/, chunks: "all", }, lodash: { name: "lodash", chunks: "async", test: /[\\/]node_modules[\\/]lodash[\\/]/, priority: 40, }, "async-common": { chunks: "async", minChunks: 2, name: "async-commons", priority: 30, }, commons: { name: "commons", chunks: "all", minChunks: 2, priority: 20, }, }, }, }, ``` 安装 echarts > npm i echarts -S 新建 src/echart.js ```javascript import * as echarts from "echarts"; var myChart = echarts.init(document.getElementById("main")); var option = { title: { text: "ECharts 入门示例", }, tooltip: {}, legend: { data: ["销量"], }, xAxis: { data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"], }, yAxis: {}, series: [ { name: "销量", type: "bar", data: [5, 20, 36, 10, 10, 20], }, ], }; myChart.setOption(option); ``` 在 main.js 中引入 > import './echart' 修改 public/index.html ```html
``` 这个时候还需调整 HtmlWebpackPlugin 配置的 nject: 'body',思考为什么? 运行 npm run prod,在 dist 下会发现新增了 echarts.vendor.xxxx.js 这就是通过 splitChunks 分离出来 echarts 包 ### 动态导入(按需加载 chunks) 按需下载资源,如路由懒加载。可以提升首屏加载速度 > npm i lodash -S 通过 import()语法实现动态导入 ```javascript //在main.js添加 function getComponent() { // Lodash, now imported by this script return import("lodash") .then(({ default: _ }) => { const element = document.createElement("div"); element.innerHTML = _.join(["Hello", "webpack"], " "); return element; }) .catch((error) => "An error occurred while loading the component"); } const button = document.createElement("button"); button.innerHTML = "Click me "; button.onclick = () => { getComponent().then((component) => { document.body.appendChild(component); }); }; document.body.appendChild(button); ``` 在 webpack.prod.js 中 cacheGroups 下添加(在上面 splitChunks 中已经加过了) ```javascript lodash: { name: "lodash", chunks: "async", test: /[\\/]node_modules[\\/]lodash[\\/]/, priority: 40, }, ``` npm run prod 可以看到有生成 lodash.xxx.js cd dist cmd live-server 启动服务,点击底部的 Click me 按钮,可以看到动态加载的 js ### 定义编译时全局变量 > npm i cross-env -D package.json 添加两个命令 ```javascript "scripts": { "cdev": "cross-env NODE_ENV=development webpack serve --config build/webpack.dev.js", "cbuild": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js", } ``` 在 webpack.common.js 添加输出 console.log('process.env.NODE_ENV',process.env.NODE_ENV) cmd npm run cdev 或者 npm run cbuild 可以看到輸出的变量 ### 定义编译后全局变量 通过 DefinePlugin 实现 根目录下新建 config/dev.env.js ```javascript module.exports = { NODE_ENV: '"development"', APP_API: '"wwww.testapi.com"', }; ``` ```webpack.dev.js // webpck.dev.js const env = require("../config/dev.env") const webpack =require("webpack") module.exports = merge(common,{ plugins: [ new webpack.DefinePlugin({ "process.env": env, }), ], }) ``` // main.js try { console.log(process.env) } catch (error) { } ### externals 防止将外部资源包打包到自己的 bundle 中 示例:从 cdn 引入 jQuery,而不是把它打包 1. index.html 中添加 必须是前,如果放后就不行,思考? 2. main.js 添加 ```javascript import $ from "jquery"; console.log($("#main")); ``` 3. npm i jquery -S 这里主要满足 dev 环境使用, html 引入了 jq,main.js 中又引入,会出现引入两次的情况,后面再考虑如何 dev 只引入一次 4. webpack.prod.js 添加配置 ```javascript externals: { jquery: 'jQuery', , ``` npm run dev 和 npm run prod 查看结果 ### 打包入口是 CSS 思考后续是否可以打包组件相关的样式,只打包样式文件 新建 src\assets\main.css ```css * { margin: 0; padding: 0; } .red { color: red; } .green { color: green; } ``` 新建 webpack.config.css.js ```javascript const path = require('path') module.exports = { mode: 'development', // 模式 entry: path.resolve(**dirname, '../src/assets/main.css'), // 打包入口地址 output: { filename: 'bundle.css', // 输出文件名 path: path.join(**dirname, '../dist') // 输出文件目录 } } ``` package.json 添加命令 > "buildcss": "webpack --config build/webpack.config.css.js" npm run buildcss 编译错误 处理 css 文件需要使用 css-loader 调整配置 ```javascript // webpack.config.css.js const path = require("path"); module.exports = { mode: "development", // 模式 entry: path.resolve(__dirname, "../src/assets/main.css"), // 打包入口地址 output: { filename: "bundle.css", // 输出文件名 path: path.join(__dirname, "../dist"), // 输出文件目录 }, module: { rules: [ // 转换规则 { test: /\.css$/, //匹配所有的 css 文件 use: "css-loader", // use: 对应的 Loader 名称 }, ], }, }; ``` npm run buildcss ### 常用 loader 常用的 loader: - style-loader、css-loader、postcss-loader、less-loader, - 编译 sass 须使用 sass-loader node-sass 或 dart-sass - style-loader 和 MiniCssExtractPlugin.loader 不能共用 - file-loader:解决资源引入问题,并将资源 copy 到指定目录,默认为 dist - url-loader:解依赖 file-loader,当图片小于 limit 值的时候,会将图片转为 base64 编码,大于 limit 值的时候依然是使用 file-loader 进行拷贝 - img-loader:压缩图片 - html-loader: 处理 html 文件资源 - webpack5 新增资源模块(asset module),允许使用资源文件(字体,图标等)而无需配置额外的 loader。 - asset/resource 将资源分割为单独的文件,并导出 url,类似之前的 file-loader 的功能. - asset/inline 将资源导出为 dataUrl 的形式,类似之前的 url-loader 的小于 limit 参数时功能. - asset/source 将资源导出为源码(source code). 类似的 raw-loader 功能. - asset 会根据文件大小来选择使用哪种类型,当文件小于 8 KB(默认) 的时候会使用 asset/inline,否则会使用 asset/resource ### 缩小文件的搜索范围(配置 include exclude alias noParse extensions) - alias: 当我们代码中出现 import 'vue'时, webpack 会采用向上递归搜索的方式去 node_modules 目录下找。为了减少搜索范围我们可以直接告诉 webpack 去哪个路径下查找。也就是别名(alias)的配置。 - include exclude 同样配置 include exclude 也可以减少 webpack loader 的搜索转换时间。 - noParse 当我们代码中使用到 import jq from 'jquery'时,webpack 会去解析 jq 这个库是否有依赖其他的包。但是我们对类似 jquery 这类依赖库,一般会认为不会引用其他的包(特殊除外,自行判断)。增加 noParse 属性,告诉 webpack 不必解析,以此增加打包速度。 - extensions webpack 会根据 extensions 定义的后缀查找文件(频率较高的文件类型优先写在前面) ```javascript module.exports = { module: { noParse: /jquery/, rules: [ { test: /\.vue$/, loader: "vue-loader", include: [path.resolve(__dirname, "src")], exclude: /node_modules/, }, { test: '/\.(jep?g|png|gif)$/, use: { loader: 'url-loader', include: [path.resolve(__dirname, 'src/assets/icons)], exclude: /node_modules/ } } ], }, resolve: { alias: { 'vue$', 'vue/dist/vue.runtime.esm.js', '@': path.resolve(__dirname, '../src'), 'assets': resolve('src/assets'), 'components': resolve('src/components') }, extensions: ['*', '.js', '.json', '.vue'] } } ``` ### parallelUglifyPlugin 多进程压缩 js 适用生产环境 > npm i webpack-parallel-uglify-plugin -D ```javascript // webpack.prod.js const ParallelUglifyPlugin = require("webpack-parallel-uglify-plugin"); plugins: [ new ParallelUglifyPlugin({ // 压缩js的一些配置 uglifyJS: { output: { beautify: false, // 不需要格式化,以最紧凑的方式输出 comments: false, // 删除注释 }, warnings: false, // 删除未使用一些代码时引起的警告 compress: { drop_console: true, // 删除所有console.log // 是否内嵌虽定义,但只使用了一次的变量 // 比如var x = 2, y = 10, z = x + y 变成 z = 12 collapse_vars: true, // 提出多次出现但没定义的变量,将其变成静态值; // 比如x = 'xx', y = 'xx' 变成 var a = 'xx', x = a, y = a reduce_vars: true, }, }, }), ]; ``` npm run prod 后查看打包出来的 js 已经没有相关 console.log 等 ### happyPack 多进程打包 因为 js 是单线程的,如果引用的模块很多,且模块间引用的层级很深,那么 webpack 在递归解析依赖时,速度就会很慢。而使用 happyPack 可以开启多进程打包,会提高构建速度 它在开发或者生产环境都可以使用,不过对于小项目,使用这个优化空间不大,且开启进程可能消耗性能会更多;在大项目时,才会有较多的优化空间 > npm i happypack -D ```javascript // wepback.common.js 或webpack.prod.js const HappyPack = require('happypack') // 将原来babel的配置改下,改为使用happypack多进程打包 module: { rules: [ { test: /\.js$/, // use: ['babel-loader?cacheDirectory'], // 改为使用 happypack打包 use: ['happypack/loader?id=babel'], // 这个id是自定义命名的,要跟插件中id对应 // 排除 node_modules 目录下的文件 exclude: /node_modules/ } ] }, plugins: [ new HappyPack({ id: 'babel', // 唯一标识符 // 使用的loader配置改写到happypack的配置项中 use: ['babel-loader'] }) ] ``` npm run prod HappyPack 添加多个 loader,如下参考, 未实验: ```javascript // ... // 引入 happypack const HappyPack = require("happypack"); // 创建 happypack 共享进程池,其中包含 6 个子进程 const happyThreadPool = HappyPack.ThreadPool({ size: 6 }); module.exports = { // ... module: { rules: [ { test: /\.js$/, // use: ['babel-loader?cacheDirectory'] 之前是使用这种方式直接使用 loader // 现在用下面的方式替换成 happypack/loader,并使用 id 指定创建的 HappyPack 插件 use: ["happypack/loader?id=babel"], include: path.resolve(__dirname, "src"), }, { test: /\.(css|less)$/, // 之前是使用这种方式直接使用 loader // use: ['style-loader', // { // loader: 'css-loader', // options: { // sourceMap: true // } // }, // { // loader: 'postcss-loader', // options: { // plugins: () => [autoprefixer()] // } // }, // { // loader: 'less-loader', // options: { // javascriptEnabled: true, // } // }] // 现在用下面的方式替换成 happypack/loader,并使用 id 指定创建的 HappyPack 插件 use: ["happypack/loader?id=styles"], include: path.resolve(__dirname, "src"), }, ], }, plugins: [ // ... new HappyPack({ /* * 必须配置项 */ // id 标识符,要和 rules 中指定的 id 对应起来 id: "babel", // 需要使用的 loader,用法和 rules 中 Loader 配置一样 // 可以直接是字符串,也可以是对象形式 loaders: ["babel-loader?cacheDirectory"], // 使用共享进程池中的进程处理任务 threadPool: happyThreadPool, }), new HappyPack({ /* * 必须配置 */ // id 标识符,要和 rules 中指定的 id 对应起来 id: "styles", // 需要使用的 loader,用法和 rules 中 Loader 配置一样 // 可以直接是字符串,也可以是对象形式 loaders: [ "style-loader", { loader: "css-loader", options: { sourceMap: true, }, }, { loader: "postcss-loader", options: { plugins: () => [autoprefixer()], }, }, { loader: "less-loader", options: { javascriptEnabled: true, }, }, ], // 使用共享进程池中的进程处理任务 threadPool: happyThreadPool, }), ], }; ``` ### css 压缩 css-minimizer-webpack-plugin CSS 压缩之前会使用 optimize-css-assets-webpack-plugin 这个插件,在 webpack v5 之后推荐使用 css-minimizer-webpack-plugin 这个插件。 > npm i css-minimizer-webpack-plugin -D > 修改 webpack.config.minicss.js ```javascript const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); module.exports = { optimization: { minimizer: [new CssMinimizerPlugin()], }, }; ``` 忘了 mode: 'development', // 开发模式 改为 productions,相关 hash 改为 fullhash npm run buildminicss 观察添加配置前后的 css 变化 ### 拷贝静态资源 copy-webpack-plugin > npm i -D copy-webpack-plugin webpack.common.js 添加配置选项 ```javascript const CopyPlugin = require("copy-webpack-plugin"); module.exports = { plugins: [ new CopyPlugin({ patterns: [ { from: path.resolve(__dirname, "../public"), to: "./", globOptions: { ignore: ["**/*.html"], }, }, ], }), ], }; ``` public 下面必须需要有可拷贝的资源(除了 html),这里放了个 logo,否则会有警告信息 npm run dev / npm run prod