还不会 webpack ? 一文带你用 webpack 打造溜溜的前端工具流 !

在这里插入图片描述

一、前言

1. webpack 是什么 ?

  • webpack 是一个静态模块打包工具,简单理解就是可以将你的项目中的静态资源(html、css、js、images、json等),按照既有的依赖关系构建起一张资源映射网,通过配置的 loader 和 plugin 的配合来最终输出 1 个或者多个 bundle。
  • 😂,哈哈,前面这句话对于新手理解起来并不友好。白话表述就是: 你的项目里面写的东西多了,会有很多的 html、js、css、图片等一些静态资源,文件资源太多如果再加上命名不规范最终你的项目会变得 “一塌糊涂” ,webpack 这个工具可以根据你的项目之中文件与文件、资源与资源之间相互引用的关系自动的将他们打包成 1 个或者几个文件。
  • 举个🌰:唐三要炼制暴雨梨花针,一开始炼制所需的原材料 深海沉银母发金 等这些相当于(html、js、css)等静态资源、铁匠炉 相当于 webpack、在炼制过程中添加的其它辅助材料特殊的锤炼技艺等相当于 loader 和 plugin,那么最终产出的暴雨梨花针的成品就相当于 webpack 最终的打包成的 bundle。

深海沉银母 + 发金 + … + 辅助材料 + 特殊的锤炼技艺 => 铁匠炉【产出暴雨梨花针】
html + css + js + images + … + loader + plugin => webpack 【产出 1 个或多个 bundle】

当然 webpack 的妙用绝不止于此,因为 webpack 底层是依赖 nodeJS 的能力,所以 webpack 有着及其丰富的 loader 和 plugin ,甚至你可以自己去写一个你需要的 loader 和 plugin。

2. 为什么要用 webpack 或者类似的静态资源打包工具 ?

  • 可以有效的整合项目中的静态资源最终打包输出 1 个或者几个 bundle。
  • 可以打包的过程中配合 loader 和 plugin 实现代码压缩和加密【可以免去自己去代码压缩网站手动压缩】
  • 由于新的技术的发展 less 和 scss 等预编译语言方便了开发但是编译是一个麻烦事,webpack 可以配合指定的 loader 在打包的过程中完成这些并且会对代码进行优化。
  • ES Next 规范不断的迭代,有很多 ECMAScript 新特性在当前的浏览器是不支持的,所以 webpack 可以配合 babel 实现这些高级语法的降维转换,转换成当前浏览器认识的代码(你可以用 ES 7、8、9的新特性来编写代码但是一点也不耽误最终打包出来的 bundle 在浏览器中的运行)【 这块最简单的 🌰 就是,浏览器不认识 export default、import 但是 webpack 中你依然可以使用这些语法,只不过在打包的时候 webpack 自己实现了一套方案将这个功能实现了而已 】。
  • 有了 webpack 的支持还可以进行代码分割,减少主 bundle 的体积以获得最优的用户体验。
  • node 生态及其广泛,其包管理工具 npm 有很多前端优秀的包,可能我们只能是通过 cdn 的方式在 html 中 通过 script 标签将其引入,但是借助 webpack 的能力,我们大可直接将包 npm 下来然后直接在想用的脚本中将其 import 进去即可,比较方便。
  • 一些其它的定制化的需求,webpack 借助对应的 loader 和 plugin 也都可以实现。

二、你能收获

👺    webpack 的基本 12 项配置。
🚀    体验到快速构建开发的快感。
🌈    感受到更加丝滑的编码体验。
🎉    一步步教你打造溜溜的前端工作流。
📦    利用 webpack 开箱即用,插件化的特点。学会通过各种配置来满足自己的业务需求。

🗣 建议大家拿起笔记本一步一步跟下去 !!!

三、步入正题

1. 安装 webpack 和 webpack cli 来开始构建我们自己的前端项目

  1. 先创建一个空的项目目录,然后用 vs code 打开。
    在这里插入图片描述
    在这里插入图片描述
  2. 初始化当前项目

~$ yarn init -y

此时项目里面会多一个 package.json 文件来管理项目依赖。
在这里插入图片描述

  1. 初始化 git

~$ git init

此时当前项目目录多了一个 .git 隐藏目录。
在这里插入图片描述

  1. 先安装 webpack 和 webpack-cli

~$ yarn add webpack@4.46.0 webpack-cli@3.3.12 -D --save

执行完这个命令后会发现当前工程多了一个 node_modules 以后项目中装配的所有依赖的包都自动放在 node_modules 下面管控着,与此同时 package.json 也会记录我们安装的开发依赖和生产依赖。
在这里插入图片描述
小结 :
到此时我们已经通过 webpack 和 webpack-cli 来将我们的项目构建起来了,还是比较简单的。剩下的工作就是通过了解如何使用 webpack 来帮助我们构建一个溜溜的前端工作流。
并且我们也顺便初始化好了我们的 git,之后可以选择一个远程仓库进行关联。
ok,这一步我们顺利的完成了。

2. webpack 是可以 0 配置使用的

什么是 0 配置使用 ? 就是 webpack 是可以做的开箱即用的不需要任何配置也可以实现它的一些基本功能,例如打包。

  1. 我们先新建一个 src 目录。
  2. 然后分别新建 index.jstest.js

test.js

const util = function util() {
    console.log("TODO ...");
};
module.exports = util;

index.js

const util = require("./test");
util();

然后运行一下 index.js,发现打印了 TODO ...。接下来我们要利用 webpack 的打包能力了。
运行命令 :

~$ npx webpack

你会发现当前项目的根目录多出了一个 dist 文件夹,这就是 webpack 打包出来之后的结果。这里面有个压缩过的 main.js。先运行下 main.js 发现也打印了 TODO …。
在这里插入图片描述
但是我有 2 个疑问 :

  • webpack 是怎么将我这两个文件给打包成一个文件了 ?
    在 0 配置的环境下,webpack 会自动的将 src 目录下的 index.js 作为入口,其里面引用的任何资源 webpack 都会将其打包进去。上述 index.js 依赖了 test.js 所以打包的时候将两个文件一起打包了。
  • 明明两个文件的代码加起来才那么点为什么打包出来的代码反而看上去多了呢 ?
    因为浏览器是不认识 module.exports 和 require 语法的,但是我们写代码的时候使用这种语法却是很方便的,所以呀为了让打包出来之后的文件也可以在浏览器中正常运行 webpack 自己实现了一套方案来兼容 module.exports 和 require 语法,所以代码看上去显得多了。
    我们可以验证下这一点的,首先在 dist 目录下新建一个 index.html 然后在里面分别引用 src 下的 index.js 和当前目录下的 main.js。做个对比一看便知。
    首先引 src 下的 index.js,由于浏览器不认识 module.exports 和 require 语法所以报错了。
    在这里插入图片描述
    然后引 index.html 当前目录的 main.js,由于 webpack 在进行打包的时候对这种语法兼容过了,所以可以正常产出结果。
    在这里插入图片描述

小结 :
我们已经初步认识了 webpack 的打包能力,也尝到了 webpack 0 配置的甜头,但是这种 0 配置是比较弱的,要想我们的前端工作流妥妥的强大, 0 配置是远远不能满足需求的。

3. 利用 webpack.config.js 手动定制自己的 webpack

比如我们想自己指定 webpack 的入口,自己指定打包出来的文件名叫什么 …

  1. 在项目根目录下创建 webpack.config.js
  2. 我们可以通过配置 webpack 的 entery 属性和 outputfilename 属性、path 。属性来指定当前 webpack 的入口和 webpack 打包产出的目录以及文件名。

webpack.config.js

const path = require("path");
module.exports = { // 配置 webpack
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录】
    },
};

然后我们删除掉 dist 目录再次执行 webpack 打包命令。
会发现生成的 dist 目录下的打包后的文件由原来的 mian.js 变成了 bundle.js
在这里插入图片描述
我们都知道,有些时候浏览器对于两次同样的请求会走到缓存里面,为了消除这个问题我们可以配置下,在打包出来的 bundle.js 后面加上 1 个 8 位的 hash 值。
修改 webpack.config.js 中的 output 中的 filename 中的属性值,将 bundle.js 改为 bundle.[hash:8].js。再次删除掉根目录的 dist 文件夹重新执行 webpack 打包命令。
你会发现 bundle 后面有了一串 hash 值。

webpack.config.js

const path = require("path");
module.exports = { // 配置 webpack
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
};

在这里插入图片描述

  1. 我们可以通过配置 mode 属性来指定当前 webpack 打包的模式。
    因为在开发的时候我们可能会去查阅打包后的文件,但是目前打包后的文件已经被压缩所以查阅起来不是特别方便,鉴于此我们可以指定 webpack 的打包模式为开发环境模式 development

webpack.config.js

const path = require("path");
module.exports = { // 配置 webpack
    mode: "development", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
};

删除根目录下的 dist 目录并重新 webpack 打包命令。
你会发现打包后的文件不是压缩过得了。
在这里插入图片描述
小结 :
这里我们已经可以通过 webpack.config.js 来配置自己的 webpack。指定入口文件的位置、指定打包产出文件目录的位置以及文件名名称、指定当前的打包模式。
这就完了吗 ?不还没有 🎶 …

4. 通过配置 html-webpack-plugin 插件来自动打包创建 html 并将打包后的资源自动引入

我们使用 webpack 打包出来的文件最终都是要放到浏览器中运行的。我们也可以每次在打包出来的 dist 目录中手动创建 html 并将打包后的资源手动引入就像一开始我们那样,但是这样会耗费很多精力,我们可以通过在 webpack 中配置 html-webpack-plugin 插件来自动帮助我们完成这些事情。

  1. 安装 html-webpack-plugin 插件

~$ yarn add html-webpack-plugin@4.5.2 -D --save

  1. 将其导入 webpack.config.js
  2. 在 plugins 这个插件数组中配置使用 html-webpack-plugin 插件。
  3. html-webpack-plugin 插件有几个常用配置项
    -1). template【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
    -2). filename【指定打包后的 html 文件的名称】
    -3). hash【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
    -4). minify【压缩打包后的 html 配置项】(removeAttributeQuotes 删除所有双引号、collapseWhiteSpace 删除所有折叠行)

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = { // 配置 webpack
    mode: "development", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
        }),
    ],
};

删除根目录下的 dist 目录并重新执行 webpack 打包命令。
你会发现在 dist 目录下有个 index.html,index.html 里面已经自动的引入了 bundle,我们可以运行下这个 index.html。
在这里插入图片描述

在这里插入图片描述
此时你发现没有任何问题。
这个时候我们再把 minify 属性配好,删除下打包后的 index.html 中的引号与折行

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = { // 配置 webpack
    mode: "development", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
    ],
};

删除根目录下的 dist 目录并执行 webpack 打包命令。
此时你会看见 dist 目录下生成的 index.html 是已经经过删除折行和双引号之后的结果了,再次运行也是可以正常运行的。
在这里插入图片描述
在这里插入图片描述
小结 :
这里我们实现了通过配置 webpack 的 html-webpack-plugin 插件在打包时自动生成 html 文件并引入 bundle。
👍 坚持下去 …

5. 通过配置 webpack-dev-server 来实现浏览器的自动刷新和代码热更新功能。

其它的 Vue 和 React 脚手架都有这种功能,就是代码一改变,浏览器自动刷新。这种功能在开发时是非常有用的,我们也可以通过配置 webpack-dev-server 来实现类似功能。

  1. 安装 webpack-dev-server

~$ yarn add webpack-dev-server -D --save

  1. 在 webpack.config.js 中配置 webpack-dev-server 来实现功能。
    配置 webpack 的 devServer 属性的时候有几个属性需要我们知道的 :
    -1). contentBase【指定静态服务目录】
    -2). progress【启动静态服务器的时候显示进度条】
    -3). port【指定端口号】
    -4). open【自动打开浏览器】
    -5). compress【为所有服务启动 gzip 压缩】

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "development", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3000, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
    ],
};

webpack-dev-server

小结 :
通过配置 webpack-dev-server(WDS) 我们实现了浏览器自动刷新以及代码的热更新。注意这里面有个点是需要我们注意的 :

  • webpack-dev-server 就是 webpack 的静态服务实际上在任何时候访问的都是打到内存中的文件,而不是真实的物理磁盘上的文件。
  • 当我们指定了 contentBase 的时候,是否配置启用 html-webpack-plugin 插件决定了 webpack-dev-server 将以何种方式将需要的内容打到内存中。
    • 如果未启用 html-webpack-plugin 插件则 webpack-dev-server 会按照设置的 contentBase 设置的目录去寻找 “bundle.js” 与 “index.html” 将其打到内存,然后运行静态服务访问内存中的文件。
    • 如果启用了 html-webpack-plugin 插件则 webpack-dev-server 会按照 html-webpack-plugin 的相关配置(template 属性)去找到对应的 “index.html” 以及找到 “bundle.js” 将其打进内存中,然后运行静态服务访问内存中的文件。
  • 所以才会有这个现象 => 当启用 html-webpack-plugin 插件的时候,即使删掉根目录下的 dist 打包目录,程序依然可以正常运行。

用 2 个视频来配合上述的描述 :
-未启用 html-webpack-plugin 插件

未启用 html-webpack-plugin

-启用 html-webpack-plugin 插件

已启用 html-webpack-p

6. 通过配置 package.json 中的 scripts 来指定我们操作的命令

我们在执行构建打包命令和运行 webpack 的静态服务的命令都是用的默认的命令,人家有的是这样运行的 => yarn buildyarn dev ,这样既显得高大上又简化了操作。下面我们也来搞一下。

  1. 添加 scripts 属性并配置 build 命令。
  "scripts": {
    "build": "webpack --config webpack.config.js",
  },
  1. 添加配置 dev 命令。
  "scripts": {
    "build": "webpack --config webpack.config.js",
    "dev": "webpack-dev-server"
  },

package.json

{
  "name": "test-webpack",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "build": "webpack --config webpack.config.js",
    "dev": "webpack-dev-server"
  },
  "devDependencies": {
    "html-webpack-plugin": "4.5.2",
    "webpack": "4.46.0",
    "webpack-cli": "3.3.12",
    "webpack-dev-server": "^3.11.2"
  }
}

这个时候我们就可以通过执行 yarn build 来运行打包构建指令、执行 yarn dev 来运行 webpack 静态服务指令了。
此时 yarn build “等于” npx webpack、yarn build “等于” webpack-dev-server。

update package.json scripts

小结 :
通过在 package.json 中配置 scripts 属性,现在终于可以美观的大方的通过 yarn build 和 yarn dev 命令来构建和运行 webpack 静态服务了,后面如果大家还有其它的命令也可以使用类似的方式去配置,还是比较快捷的。

7. 通过配置 loader 来处理 css 并插入到页面上

  1. 为什么要需要单出处理 css ? 是因为 webpack 默认在打包的时候不会打包 css 文件,所以就导致最终 css 不会输出到 dist 目录中所以也就无法通过在 html 中通过 link 方式来引用 css 资源。所以我们需要在 js 文件中引入 css 并对 css 进行处理将其输出到 html 上,这样浏览页面的时候就可以看到样式了。
  • 安装 css-loader 和 style-loader

~$ yarn add css-loader style-loader -D --save

  • css-loader 主要是处理 @import (“xx.css”) 这种语法的【将两个 css 文件处理成一个】、style-loader 主要是将处理好的 css 内容插入到 head 标签中。
  • 更改 webpack 配置,添加 module 属性,以及 module 属性下的 rules 属性并配置。
    -1). rules 下的每一个对象都对应 webpack 模块文件中的一条处理规则。
    -2). 每一个处理规则可以使用多个 loader。
    -3). 每一条规则中可以有 test 、use、loader 三个属性,test 的作用主要是规定哪种文件适用此规则、use 的作用是标名当前规则使用的 loader。其中 use 的值可以是【字符串、数组】。当为字符串时表使用哪个 loader、当为数组时表为使用哪些 loader。
    rules: [
            { test: /\.css$/, use: ["style-loader", "css-loader"], }
        ],
    },

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "development", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
    ],
    module: {
        rules: [
            { test: /\.css$/, use: ["style-loader", "css-loader"], }
        ],
    },
};

创建 index.css 和 test.css 并在 index.css 中导入 test.css,在 index.js 中导入 index.css。

index.css

@import "./test.css";
body {
    background-color: green;
}

test.css

body {
    color: hotpink;
}

index.js

require("./index.css");
const util = require("./test");
util();

运行 yarn dev 命令查看效果。
在这里插入图片描述
在这里插入图片描述
由此可见我们实现了将 css 插入到 html,并如实的看到了效果。

  1. 如果我突然想在模板 html 中手动写样式呢 ?虽然这种需求极少但是有些时候迫不得已还是会这么干的。
    自己先加上试试看 :

src/index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            background-color: aqua;
        }
    </style>
</head>
<body>
    99
</body>
</html>

再运行 yarn dev 命令查看效果,可以看到我们在模板 html 中新加的样式被放到了最上面,所以导致被下面的样式覆盖,通常我们自己在 html 写的样式的优先级都应该是最高的,也就是说正常我们期望我们在 html 写的样式应该在最下方,界面现在应该呈现出蓝色才对。
在这里插入图片描述
所以下面需要我们更改 css 的规则, use 属性里面的 loader 呢使用对象形式来满足定制化的需求。

module: {
    rules: [
        { test: /\.css$/, use: [{
            loader: "style-loader",
            options: {
                insert: function insertAtTop(element) {
                    var parent = document.querySelector('head');
                    // eslint-disable-next-line no-underscore-dangle
                    var lastInsertedElement =
                        window._lastElementInsertedByStyleLoader;

                    if (!lastInsertedElement) {
                        parent.insertBefore(element, parent.firstChild);
                    } else if (lastInsertedElement.nextSibling) {
                        parent.insertBefore(element, lastInsertedElement.nextSibling);
                    } else {
                        parent.appendChild(element);
                    }

                    // eslint-disable-next-line no-underscore-dangle
                    window._lastElementInsertedByStyleLoader = element;
                },
            },
        }, "css-loader"], }
    ],
},

options 属性可以提供更加精确的控制,比如我们为了实现上述需求可以实现一个 insert 方法放在这里。
然后再运行 yarn dev 命令,即可看见我们在 html 中写的样式已经生效了。
在这里插入图片描述

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "development", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
    ],
    module: {
        rules: [
            { test: /\.css$/, use: [{
                loader: "style-loader",
                options: {
                    insert: function insertAtTop(element) {
                        var parent = document.querySelector('head');
                        // eslint-disable-next-line no-underscore-dangle
                        var lastInsertedElement =
                            window._lastElementInsertedByStyleLoader;

                        if (!lastInsertedElement) {
                            parent.insertBefore(element, parent.firstChild);
                        } else if (lastInsertedElement.nextSibling) {
                            parent.insertBefore(element, lastInsertedElement.nextSibling);
                        } else {
                            parent.appendChild(element);
                        }

                        // eslint-disable-next-line no-underscore-dangle
                        window._lastElementInsertedByStyleLoader = element;
                    },
                },
            }, "css-loader"], }
        ],
    },
};

小结 :
通过配置 css-loader 和 style-loader 我们现在已经可以处理 css 并将其插入到 html 中使其生效了。也就是说我们完全可以用现在的架构去构建页面了,但是目前的配置还是较弱,需要后续的配置来增强当前项目的扩展能力,比如当我们使用 css 预编译语言时 webpack 在打包构建时会进行自动编译然后打包构建,当然类似这种功能需要后续的配置来完成实现。

8. 通过配置 less-loader 来处理 less 文件

现今有很多的 css 预编译语言,我们使用起来也是非常的顺手,但是在上线之前都需要编译成 css ,现在我们可以不用自己手动去编译,我们直接可以通过 webpack 的配置能力来帮助我们完成这个编译的工作。

  1. 安装 less-loader

~$ yarn add less less-loader@6.2.0 -D --save

  1. webpack.config.js 中添加处理 less 文件的规则
module: {
        rules: [
            { test: /\.css$/, use: [{
                loader: "style-loader",
                options: {
                    insert: function insertAtTop(element) {
                            var parent = document.querySelector('head');
                            // eslint-disable-next-line no-underscore-dangle
                            var lastInsertedElement =
                                window._lastElementInsertedByStyleLoader;

                            if (!lastInsertedElement) {
                                parent.insertBefore(element, parent.firstChild);
                            } else if (lastInsertedElement.nextSibling) {
                                parent.insertBefore(element, lastInsertedElement.nextSibling);
                            } else {
                                parent.appendChild(element);
                            }

                            // eslint-disable-next-line no-underscore-dangle
                            window._lastElementInsertedByStyleLoader = element;
                        },
                },
            }, "css-loader"], },
            { test: /\.less$/, use: [{
                loader: "style-loader",
                options: {
                    insert: function insertAtTop(element) {
                            var parent = document.querySelector('head');
                            // eslint-disable-next-line no-underscore-dangle
                            var lastInsertedElement =
                                window._lastElementInsertedByStyleLoader;

                            if (!lastInsertedElement) {
                                parent.insertBefore(element, parent.firstChild);
                            } else if (lastInsertedElement.nextSibling) {
                                parent.insertBefore(element, lastInsertedElement.nextSibling);
                            } else {
                                parent.appendChild(element);
                            }

                            // eslint-disable-next-line no-underscore-dangle
                            window._lastElementInsertedByStyleLoader = element;
                        },
                },
            }, "css-loader", "less-loader"], }
        ],
    },

再新建 index.less 文件书写样式
index.less

body {
    div {
        width: 100px;
        height: 100px;
        background-color: sandybrown;
    }
}

更改模板 html 的 dom 元素

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            background-color: aqua;
        }
    </style>
</head>
<body>
    <div>
        99
    </div>
</body>
</html>

index.js 导入 index.less

require("./index.less");
require("./index.css");

const util = require("./test");
util();

再运行 yarn dev 命令,查看效果。发现 less 书写的样式已经生效。页面上的 div 有了自己的背景色。
在这里插入图片描述
到这步有人会迷惑,不要迷惑。有人会想为什么要这么配 loader ?use 属性里面 loader 的顺序会不会影响项目 ?答案时肯定的,use 属性中 loader 的放置顺序必须严格按照 => 【先右后左,先下后上】的原则,即最先用到的 loader 放在最下最右边,后续用到的依次往上往左放置。

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "development", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [{ // 配置解析处理 css 模块的规则
                loader: "style-loader", // 使用 style-loader
                options: {
                    insert: function insertAtTop(element) { // 保证项目中的 css 放在 header 标签的字标签中的最前面
                            var parent = document.querySelector('head');
                            // eslint-disable-next-line no-underscore-dangle
                            var lastInsertedElement =
                                window._lastElementInsertedByStyleLoader;

                            if (!lastInsertedElement) {
                                parent.insertBefore(element, parent.firstChild);
                            } else if (lastInsertedElement.nextSibling) {
                                parent.insertBefore(element, lastInsertedElement.nextSibling);
                            } else {
                                parent.appendChild(element);
                            }

                            // eslint-disable-next-line no-underscore-dangle
                            window._lastElementInsertedByStyleLoader = element;
                        },
                },
            }, "css-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [{ // 配置解析处理 less 模块的规则
                loader: "style-loader",
                options: {
                    insert: function insertAtTop(element) {
                            var parent = document.querySelector('head');
                            // eslint-disable-next-line no-underscore-dangle
                            var lastInsertedElement =
                                window._lastElementInsertedByStyleLoader;

                            if (!lastInsertedElement) {
                                parent.insertBefore(element, parent.firstChild);
                            } else if (lastInsertedElement.nextSibling) {
                                parent.insertBefore(element, lastInsertedElement.nextSibling);
                            } else {
                                parent.appendChild(element);
                            }

                            // eslint-disable-next-line no-underscore-dangle
                            window._lastElementInsertedByStyleLoader = element;
                        },
                },
            }, "css-loader", "less-loader"], } // 使用 less-loader
        ],
    },
};

小结 :
通过配置 less 和 less-loader 我们已经可以在项目中正常的使用 less 了,当然如果你想在自己的项目中使用其它的 css 预编译语言,也按照这个逻辑去配置就也没问题的。

9. 通过使用 mini-css-extract-plugin 将 css 进行抽离

虽然我们实现将 css 插入 html 并最终看到了想要的效果,但这有个问题,就是因为我们是通过 style-loader 将处理好的 css 以 style 标签的形式进行插入的,如果你的 css 过多就会导致 html 最终打包的体积过大,也不美观。所以我们要对 css 进行抽离,最好是通过 link 标签的形式进行引入。当然通过 webpack 的配置能力我们只需要借助 mini-css-extract-plugin 插件就可以帮助我们自动完成这件事情。

  1. 安装 mini-css-extract-plugin 插件

~$ yarn add mini-css-extract-plugin -D --save

  1. 更改 weback.config.js 配置文件
  • -1). 在 plugins 下增加此插件,并指定打包后产出的 css 名称
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
// ...
plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],	
  • -2). 将 css 规则和 less 规则中的 style-loader 的配置干掉,然后使用插件提供的 loader 属性,这样就可以将 style-loader 的内嵌式插入变成 mini-css-extract-plugin 插件 loader 属性的 link 引入式。
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "less-loader"], } // 使用 less-loader
        ],
    },

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");

module.exports = { // 配置 webpack
    mode: "development", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "less-loader"], } // 使用 less-loader
        ],
    },
};

再次运行 yarn dev 命令查看效果。
在这里插入图片描述
可以看到效果没啥问题,并且确实也是 link 形式引入的我们的 bundle.css。
小结 :
当通过配置 minli-css-extract-plugin 插件将 css 抽离出来,再借助 html-webpack-plugin 插件的能力将抽离出来的 css 通过 link 标签的方式进行引入,就越来越接近常规的开发模式了。

10. 通过配置 postcss-loader 和 autoprefixer 插件实现为 css3 属性自动添加兼容性浏览器前缀

在开发中类似一些 css3 属性我们通常会在前面加上各种前缀以兼容不同浏览器。
举个🌰

div {
	transform: rotate(90deg);
	-webkit-transform: rotate(90deg);
}

当然以前都是开发者自己手写,但是同样代码写了好几遍,有时候就感觉很烦。所以我们依然可以借助 webpack 的配置能力使用 postcss-loader 和 autoprefixer 插件来自动添加兼容性浏览器前缀。

  1. 安装 postcss-loader 和 autoprefixer

~$ yarn add postcss-loader@4.0.4 autoprefixer@9.8.0 -D --save

  1. 更改 webpack.config.js,在 css 规则和 less 规则中添加 postcss-loader
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], } // 使用 less-loader
        ],
    },
  1. 在项目根目录新增 postcss.config.js 文件,然后里面配置下 autoprefixer 插件。
module.exports = {
    plugins: [require("autoprefixer")],
};
  1. 在 package.json 中添加 browserslist 属性。
  "browserslist": [
    "> 1%",
    "last 2 versions"
  ]

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");

module.exports = { // 配置 webpack
    mode: "development", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], } // 使用 less-loader
        ],
    },
};

postcss.config.js

module.exports = {
    plugins: [require("autoprefixer")],
};

package.json

{
  "name": "test-webpack",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "build": "webpack --config webpack.config.js",
    "dev": "webpack-dev-server"
  },
  "devDependencies": {
    "autoprefixer": "9.8.0",
    "css-loader": "^5.2.1",
    "html-webpack-plugin": "4.5.2",
    "less": "^4.1.1",
    "less-loader": "6.2.0",
    "mini-css-extract-plugin": "^1.4.1",
    "postcss-loader": "4.0.4",
    "style-loader": "^2.0.0",
    "webpack": "4.46.0",
    "webpack-cli": "3.3.12",
    "webpack-dev-server": "^3.11.2"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions"
  ]
}

运行 yarn build 命令,在生成的 dist 目录下查看打包的 bundle.css 中已经添加了前缀。
在这里插入图片描述
小结 :
通过配置 postcss-loader 和 autoprefixer 插件我们实现了自动为 css3 属性添加兼容性浏览器前缀。免去了手工添加的苦楚,但要切记最后一步在 package.json 中配置 browserslist 属性不能省略,否则不会有效果的。

11. 通过配置 optimize-css-assets-webpack-plugin 插件来压缩 css

css 这边的 webpack 配置基本差不多了,但是还差一步就是代码压缩。
因为 js webpack 默认是压缩的,但是 css 则不会,所以必须借助 optimize-css-assets-webpack-plugin 插件来压缩 css,但是如果一旦使用该插件则 webpack 默认压缩 js 的功能就会被取消掉,所以我们可以借助 uglifyjs-webpack-plugin 插件来压缩 js。

  1. 安装 optimize-css-assets-webpack-pluginuglifyjs-webpack-plugin 插件。

~$ yarn add optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin -D --save

  1. 更改 webpack.config.js ,添加 optimization 配置项,使用 optimize-css-assets-webpack-plugin 插件优化 css 打包

先更改 webpack.config.js 中的 mode 属性值为 production,因为如果是 development 模式设置优化项是没有任何效果的。

mode: "production", // 指定 webpack 的打包模式【 development、production 】

优化 css 之前 : 【 js 是 webpack 默认压缩,css 是未被压缩 】
在这里插入图片描述
在这里插入图片描述
优化 css 之后 : 【 js 的 webpack 默认压缩被取消,css 已被压缩 】

const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
optimization: {
     minimizer: [
         new OptimizeCSSAssetsWebpackPlugin(),
     ],
 },

在这里插入图片描述
在这里插入图片描述

  1. 使用 uglifyjs-webpack-plugin 插件优化 js 打包

优化 js 之后 : 【 js 已被压缩,css 已被压缩 】

const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
 optimization: {
     minimizer: [
         new UglifyJsWebpackPlugin({
             cache: true, // 是否开启缓存
             parallel: true, // 是否并行压缩
             sourceMap: true, // 是否开启源码映射
         }),
         new OptimizeCSSAssetsWebpackPlugin(),
     ],
 },

在这里插入图片描述
在这里插入图片描述
小结 :
通过配置 optimize-css-assets-webpack-plugin 插件和 uglifyjs-webpack-plugin 插件我们实现了 css 和 js 压缩。目前打包的 html、css、js 文件都已经处于已经打包压缩的状态了,这在一定程度上减少了包的体积,提高了网页的访问速度。

12. 通过配置 babel 来将高级语法转换为兼容性更强的低级语法

目前虽然谷歌等浏览器对 ES6 的新特性支持的还不错,甚至 chrome 都支持了 ES7 等更高级的语法,但是有些浏览器可能还没有支持这些 ES6、ES7、ES Next 的高级语法,就比如最某些手机浏览器中虽然是 webkit 内核但是就是没有支持 ES6 语法,这让我一度很头疼,只能用那些原来的又臭又长的低版本语法去做兼容。实际上通过 webpack 我们可以配置 babel 来帮助我们将这件事情实现自动化,就是说自动帮助我们将高级语法转换为兼容性更强的低级语法。
在讲如何配置之前先科普下 babel 帮助我们转换语法的大致过程(导播上图 …)。
在这里插入图片描述

是这样的,如果我们单纯的下载 babel 的相关包不做任何配置的话,webpack 是不会鸟我们的,所以我们需要通过 loader 来告诉 webpack 针对 js 文件我们要做特殊处理,所以我们需要一个 babel-loader。但是这还不够上图大家都可以看懂,就是高级语法先被解析编译成对应的 AST 语法树,然后再调用我们指定的预设模块进行 AST 层面的语法转换,转换完毕后再将转换后的 AST 语法树进行重组生成新的转换后的代码。但是这一系列工作总得有人做呀 ?所以我们还需要 @babel/core(babel 的核心模块) 和 @babel/preset-env (babel 的预设模块),当 babel-loader 调用 @babel/core 处理 js 文件的时候,@babel/core 就会调用自身的 transform 方法进行处理,但是我们如果指定了预设模块的话,就会按照我们指定的预设模块中的规则进行语法转换,而这个预设模块可以理解为是所有高级语法转换为低级语法的插件集合,因为如果我们自己一个一个装的话成本太大,索性 babel 直接定义了几个对应环境的预设模块,模块里面就包含这各种需要的转换规则。
了解了这些前置知识我们就可以说具体的配置步骤啦。

  1. 通过配置 webpack 的 babel-loader、@babel/core、@babel/preset-env 来将 ES6 高级语法转换为低级语法
  • 安装 babel-loader、@babel/core、@babel/preset-env

~$ yarn add babel-loader @babel/core @babel/preset-env -D --save

  • 配置 webpack.config.js,在 module 的 rules 中再加一条处理 js 的规则
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use:{
                loader: "babel-loader",
                options: {
                    presets: ["@babel/preset-env"],
                },
            } },
        ],
    },

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use:{
                loader: "babel-loader",
                options: {
                    presets: ["@babel/preset-env"],
                },
            } },
        ],
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

index.js

require("./index.less");
require("./index.css");
const util = require("./test");
util();
const arrowFunc = () => {
    console.log("我是箭头函数");
};
arrowFunc();

执行 yarn build 命令查看打包后的 js bundle 文件,发现确实将箭头函数转换为普通函数了。
在这里插入图片描述
执行 yarn dev,查看效果发现没什么问题。
在这里插入图片描述
但是即使这样我们也是只是可以将一部分的高级语法转换为兼容性更好的低级语法,如果后面我们遇到了预设模块里面没有包含的高级语法转换规则我们怎么办?
这个时候我们就可以一个一个手动去安装这些插件来使我们的 babel 转换功能充实强大起来。

  1. 通过使用 @babel/plugin-proposal-class-properties@babel/plugin-proposal-decorators 插件来处理 class 语法以及装饰器语法
    其中 class 语法和装饰器语法就是 babel 预设模块里不曾包含的插件,所以对于 class 和装饰器转换的插件需要我们自行安装。
  • 安装 @babel/plugin-proposal-class-properties@babel/plugin-proposal-decorators 插件

~$ yarn add @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators -D --save

  • 更改 webpack.config.js 配置,添加 plugins 属性并使用这两个插件来增强 babel。
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use:{
                loader: "babel-loader",
                options: {
                    presets: ["@babel/preset-env"],
                    plugins: [
                        ["@babel/plugin-proposal-decorators", { legacy: true, }],
                        ["@babel/plugin-proposal-class-properties", { loose: true, }],
                    ],
                },
            } },
        ],
    },

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use:{ // 使用 babel-loader 进行 js 高级语法转换
                loader: "babel-loader",
                options: {
                    presets: ["@babel/preset-env"], // 使用 babel 预设模块进行转换
                    plugins: [ // 补缺 babel 预设模块
                        ["@babel/plugin-proposal-decorators", { legacy: true, }], // 处理装饰器语法
                        ["@babel/plugin-proposal-class-properties", { loose: true, }], // 处理 class 语法
                    ],
                },
            } },
        ],
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

index.js

require("./index.less");
require("./index.css");
const util = require("./test");
util();
const arrowFunc = () => {
    console.log("我是箭头函数");
};
arrowFunc();
@log
class Confirm {
    bgColor = "#000";
}
console.log(new Confirm().bgColor);
function log(target) {
    console.log(target, "装饰器");
}

运行 yarn build 命令,查看打包后的 bundle.js 发现确实帮助我们将 class 转换为了普通函数,装饰器语法也进行了转换。
在这里插入图片描述
运行 yarn dev 命令查看效果,确实打印了我们需要打印的结果。
在这里插入图片描述

  1. 通过使用 @babel/plugin-transform-runtime 来处理更高级的 Generator 语法以及转换过程中进行一些优化
    实际上 @babel/plugin-transform-runtime 包是依赖 @babel/runtime 包的,因为比如我们使用了一些高级一点的语法比如 Generator 语法的时候 @babel/plugin-transform-runtime 会到 @babel/runtime 包里面去寻找对应的帮助函数,并将其打进最终输出的 bundle.js 中,因为 babel 只是进行语法层面的转换,api 层面的转换大都是由 @babel/runtime 和 @babel/polyfill 包来做的,所以这种 api 层面的转换都需要将一些帮助函数的补丁代码注入到我们的 bundle.js 中(所以这两个包【@babel/runtime 和 @babel/polyfill】安装的时候都需要装进生产依赖中)。
    同时 @babel/plugin-transform-runtime 在 babel 工作运行时也对其流程进行了一定优化,比如 Babel可能会在输出中注入一些跨文件相同的代码,像 function _classCallCheck(instance, Constructor) { /* ... */ } 这种代码多了也是资源上的浪费,使用 @babel/plugin-transform-runtime,它可以将上面的 _classCallCheck 函数实体替换成引用,这样在使用到的地方直接引用一下即可就没有必要各处写 _classCallCheck 函数实体了。
  • 安装 @babel/plugin-transform-runtime 插件

~$ yarn add @babel/plugin-transform-runtime -D --save

  • 安装 @babel/runtime 补丁包

~$ yarn add @babel/runtime --save

  • 配置 webpack.config.js,添加 @babel/plugin-transform-runtime 插件。
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use:{ // 使用 babel-loader 进行 js 高级语法转换
                loader: "babel-loader",
                options: {
                    presets: ["@babel/preset-env"], // 使用 babel 预设模块进行转换
                    plugins: [ // 补缺 babel 预设模块
                        ["@babel/plugin-proposal-decorators", { legacy: true, }], // 处理装饰器语法
                        ["@babel/plugin-proposal-class-properties", { loose: true, }], // 处理 class 语法
                        "@babel/plugin-transform-runtime",
                    ],
                },
            } },
        ],
    },

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use:{ // 使用 babel-loader 进行 js 高级语法转换
                loader: "babel-loader",
                options: {
                    presets: ["@babel/preset-env"], // 使用 babel 预设模块进行转换
                    plugins: [ // 补缺 babel 预设模块
                        ["@babel/plugin-proposal-decorators", { legacy: true, }], // 处理装饰器语法
                        ["@babel/plugin-proposal-class-properties", { loose: true, }], // 处理 class 语法
                        "@babel/plugin-transform-runtime",
                    ],
                },
            } },
        ],
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

index.js

require("./index.less");
require("./index.css");
const util = require("./test");
util();
const arrowFunc = () => {
    console.log("我是箭头函数");
};
arrowFunc();
@log
class Confirm {
    bgColor = "#000";
}
console.log(new Confirm().bgColor);
function log(target) {
    console.log(target, "装饰器");
}
function * getStatus() {
    yield 1;
}
console.log(getStatus().next(), "generator");

运行 yarn build 命令,发现打包后的文件已经将 Generator 语法转换并且已经打好补丁
在这里插入图片描述
在这里插入图片描述
运行 yarn dev 命令查看效果,发现报错了。
在这里插入图片描述
但这个鸟错误显然不是咱们自己代码的错误,多半是 node_modules 中存在问题,所以我们可以在处理 js 的babel-loader 中加上 include 和 exclude 属性来将 node_modules 目录排除出去。

 module: { // 配置解析模块的众多规则
     rules: [
         { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
         { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
         { test: /\.js$/, 
             use:{ // 使用 babel-loader 进行 js 高级语法转换
             loader: "babel-loader",
             options: {
                 presets: ["@babel/preset-env"], // 使用 babel 预设模块进行转换
                 plugins: [ // 补缺 babel 预设模块
                     ["@babel/plugin-proposal-decorators", { legacy: true, }], // 处理装饰器语法
                     ["@babel/plugin-proposal-class-properties", { loose: true, }], // 处理 class 语法
                     "@babel/plugin-transform-runtime",
                 ],
             },
         },
         include: path.resolve(__dirname, "src"),
         exclude: /node_modules/,
      },
     ],
 },

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, 
                use:{ // 使用 babel-loader 进行 js 高级语法转换
                loader: "babel-loader",
                options: {
                    presets: ["@babel/preset-env"], // 使用 babel 预设模块进行转换
                    plugins: [ // 补缺 babel 预设模块
                        ["@babel/plugin-proposal-decorators", { legacy: true, }], // 处理装饰器语法
                        ["@babel/plugin-proposal-class-properties", { loose: true, }], // 处理 class 语法
                        "@babel/plugin-transform-runtime",
                    ],
                },
            },
            include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
            exclude: /node_modules/,
         },
        ],
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

然后再运行 yarn dev 再次查看效果,发现已经正常了。
在这里插入图片描述

  1. 通过配置 @babel/runtime-corejs3 来替换 @babel/runtime
    实际上还有很多的 @babel/runtime 包 polyfill 不了的 api,比如还有这些【Promise、Array.prototype.includes、Object.assign、Array.prototype.map …】等。这些 api 的转换就需要依赖 @babel/polyfill 包,但是目前在 babel 官网上看到,明确表示 @babel/polyfill 包已经被废弃,所以这种方式就不推荐使用了,我们其实还是可以借助 @babel/plugin-transform-runtime 包来解决当前面临的窘境的,这个插件有个属性是 corejs ,默认值是 fasle ,但是我们可以指定 corejs 的值为其它的值 【2、3】。
    在这里插入图片描述
    当其值为默认值 false 的时候默认是使用 @babel/runtime 进行 polyfill 的,通过图片中的英文描述我们可以知道 core-js2 可以 polyfill 那些非实例属性的所有,但是对于实例属性还是无能为力,而 core-js3 则是在可以 polyfill 包含实例属性在内的所有,所以我们直接将 corejs 的属性值指定为 3 。然后安装 @babel/runtime-corejs3 即可。
    所以前面用的 @babel/runtime 包就可以 remove 掉了,因为 core-js 3 包含了原来的 @babel/runtime@babel/polyfill
  • 安装 @babel/runtime-corejs3

~$ yarn add @babel/runtime-corejs3 --save

  • 修改 webpack.congfig.js 添加 corejs 属性并将其值指定为 3。
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, 
                use:{ // 使用 babel-loader 进行 js 高级语法转换
                loader: "babel-loader",
                options: {
                    presets: ["@babel/preset-env"], // 使用 babel 预设模块进行转换
                    plugins: [ // 补缺 babel 预设模块
                        ["@babel/plugin-proposal-decorators", { legacy: true, }], // 处理装饰器语法
                        ["@babel/plugin-proposal-class-properties", { loose: true, }], // 处理 class 语法
                        ["@babel/plugin-transform-runtime", { "corejs": 3, }], // 替代 @babel/runtime 和 @babel/polyfill
                    ],
                },
            },
            include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
            exclude: /node_modules/,
         },
        ],
    },

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, 
                use:{ // 使用 babel-loader 进行 js 高级语法转换
                loader: "babel-loader",
                options: {
                    presets: ["@babel/preset-env"], // 使用 babel 预设模块进行转换
                    plugins: [ // 补缺 babel 预设模块
                        ["@babel/plugin-proposal-decorators", { legacy: true, }], // 处理装饰器语法
                        ["@babel/plugin-proposal-class-properties", { loose: true, }], // 处理 class 语法
                        ["@babel/plugin-transform-runtime", { "corejs": 3, }], // 替代 @babel/runtime 和 @babel/polyfill
                    ],
                },
            },
            include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
            exclude: /node_modules/,
         },
        ],
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};
  • 移除 @babel/runtime 包。

~$ yarn remove @babel/runtime --save

  • index.js 中新增 includes 和 Promise 测试代码

index.js

require("./index.less");
require("./index.css");
const util = require("./test");
util();
const arrowFunc = () => {
    console.log("我是箭头函数");
};
arrowFunc();
@log
class Confirm {
    bgColor = "#000";
}
console.log(new Confirm().bgColor);
function log(target) {
    console.log(target, "装饰器");
}
function * getStatus() {
    yield 1;
}
console.log(getStatus().next(), "generator");
console.log("aa".includes("a"), ": 是否包含");
const promise = new Promise((resolve) => {
    resolve("哈哈");
});
promise.then(res => {
    console.log("promise 123", res);
});

运行 yarn build 指令查看输出结果,发现确实已经将 Promise 、includes、Generator 转换了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
运行 yarn dev 命令查看效果,发现均可以正常打印。
在这里插入图片描述

  1. 新建 .babelrc babel 配置文件(注意这是一个隐藏文件,文件名前面有个点)并将 babel 的相关配置从 webpack.config.js 中进行抽离。
    webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
            include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
            exclude: /node_modules/,
         },
        ],
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

.babelrc

{
    "presets": ["@babel/preset-env"], // 使用 babel 预设模块进行转换
    "plugins": [ // 补缺 babel 预设模块
        ["@babel/plugin-proposal-decorators", { "legacy": true }], // 处理装饰器语法
        ["@babel/plugin-proposal-class-properties", { "loose": true }], // 处理 class 语法
        ["@babel/plugin-transform-runtime", { "corejs": 3 }] // 替代 @babel/runtime 和 @babel/polyfill
    ]
}

再次运行命令 yarn build 和 yarn dev 查看都是没有问题的。

小结 :
这一小节我们实现了 babel 对高级语法的转换,并对 babel 的转换机制与用法有个简单的了解,熟悉了 babel 在 webpack 的配置情况,到此我们就可以实现语法转换功能了。再梳理下我们这个小结用到的包 :

1 babel-loader
2 @babel/core
3 @babel/preset-env
4 @babel/plugin-proposal-class-properties
5 @babel/plugin-proposal-decorators
6 @babel/plugin-transform-runtime
7 @babel/runtime-corejs3

13. 通过配置 ESLint 来添加语法校验

  1. ESLint 规则校验需要根据需求到 ESLint 官网勾选需要的校验规则并下载对应的 json 文件到自己的工程项目中
    因为 ESLint 官网中的选项有很多,本文这里就不做其它勾选了,直接选取它默认的示例 json 配置做案例即可。读者可以自行到官网定制下载 传送门
    因为这个 eslintrc.json 文件是一个隐藏文件所以下载到我们的项目根目录后要重命名一下就在前面加个点变成 .eslintrc.json 即可。
    在这里插入图片描述
  2. 需要在 webpack.config.js 中进行配置
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
            include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
            exclude: /node_modules/,
         },
         { test: /\.js$/, use: {
            loader: "eslint-loader",
            options: {
                enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
            },
        },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
            include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
            exclude: /node_modules/,
         },
         { test: /\.js$/, use: {
            loader: "eslint-loader",
            options: {
                enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
            },
        },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

运行 build 命令发现 eslint 提示语法有错误,我们基于提示一个一个去更改即可,这无疑算得上是我们的一大助力,在一定程度上减少了 bug 的产生。
在这里插入图片描述
小结 :
这一节我们实现了 eslint js 的语法规则校验,这样我们在日后开发一直遵守我们制定好的 eslint 规范即可。这会使得代码变得更加可维护,bug 也更少。但是为了后面我们可以顺利演示其它的配置,这个 eslint 的配置暂时就注释了。
webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
            include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
            exclude: /node_modules/,
         },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

14. 全局变量引入的问题

在我们的日常项目中肯定会用到一些包,这些包暴露出来的对象都是挂载 window 上的,我们也成这类对象叫全局对象或者全局变量,那么还有一些其它的三方包也会依赖 window 上挂载好的那个全局对象。最典型的就是 jQuery,有很多其它的库是依赖 jQuery 的所以就会有一个问题,我们如何将 jQuery 的 $ 挂载到 window 上呢 ?
再说具体实现方案之前先来看下问题 :

  • 先安装 jQuery

~$ yarn add jquery --save

  • 然后更改 index.js
    index.js
import $ from 'jquery';
console.log($, window.$);

运行命令 yarn dev ,发现 $ 可以打印出来 jQuery 函数,但是 window.$ 打印的是 undefined。
怎么解决这个问题呢 ? 怎么能将 $ 也可以挂载到 window 上呢 ?

  1. 使用 expose-loader

安装 expose-loader

~$ yarn add expose-loader -D --save

webpack.config.js 中配置,在 module 中再加一条规则

    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
            include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
            exclude: /node_modules/,
         },
         { test: require.resolve("jquery"), use: "expose-loader?$", }
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
            include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
            exclude: /node_modules/,
         },
         { test: require.resolve("jquery"), use: "expose-loader?$", }
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

再次运行 yarn dev 命令发现 $window.$ 都打印了。
在这里插入图片描述

  1. 使用 cdn 配合 webpack.config.js 中的 externals 属性的方式

先将 cdn 在 html 中引入

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            background-color: aqua;
        }
    </style>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
    <div>
        99
    </div>
</body>
</html>

然后在 webpack.config.js 中添加 externals 属性

externals: {
     jquery: "$",
},

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { test: /\.js$/, use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
            include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
            exclude: /node_modules/,
         },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", }
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: {
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

运行 yarn dev,查看效果发现 $window.$ 都是打印了 jQuery 函数的。
在这里插入图片描述
其实 externals 也可以不配置,因为 jQuery 是通过 cdn 的方式引入的,这样不论是 $ 还是 window.$ 肯定都是可以拿到 jQuery 的,但是如果在 index.js 代码中的最前面加上这样的导入语句就会导致最终将 jQuery 打包进自己的 bundle 中,所以为了防止这种情况才使用的 externals 属性,指明不打包 jQuery。有问会问,谁会这么蠢都已经引入 cdn 了还在代码里面自己 import ? 须知一个项目是需要多人合作的若其它的合作者不知道外面已经引入 cdn 了呢?

小结 :
通过这一节我们了解了解决第三方全局模块怎么全局引入的问题,记住两种方式即可 :

expose-loader 或者 cdn + externals 属性

15. 通过配置 url-loader、file-loader、html-loader 来处理图片

问题 : 就是如果还按照原来的使用方式,图片默认最终打包的时候不会被打包进 dist 目录。

  • html 中通过 img 标签引入
  • js 动态创建图片
  • css 引入背景图片

解决方案 :

  1. 解决 js 中动态创建图片问题
    因为 js 直接按照原来的写法去直接赋值 src 图片相对路径是不行的,因为最终图片不会打包到 dist 目录中,所以我们需要使用 url-loader 来处理这个情况,url-loader 依赖 file-loader 所以我们安装的时候要将这两个 loader 都装下来。同时 url-loader 还可以通过配置 limit 属性来控制图片的输出,比如我们可以设定图片不足 20k 的直接 base64 处理,大于等于 20k 的直接借助 file-loader 的能力输出真实图片。

  2. 解决 css 中引入背景图片的问题
    css 这里面不需要安装其它任何的 loader 了,因为 css-loader 就已经对此进行了处理了,所以在 background 中使用图片是丝毫没有问题的。

  3. 解决 html 中通过 img 标签引入图片的问题
    html 中 img 标签引入 src 属性的时候也会有路径的问题,我们可以借助 html-withimg-loader 来处理但是这个 loader 已经超过 1 年没有开发者维护了,所以还是不建议使用它了,我们还可以使用 html-loder 通过配置 attrs 参数来指定 html 中哪些标签的哪些属性可以经过 html-loader 处理,这样一来我们就可以在 html 中和往常一样书写代码了。

首先 安装 url-loaderfile-loaderhtml-loader

~$ yarn add file-loader url-loader html-loader@0.5.5 -D --save

然后 更改 webpack.config.js 中的配置

    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ MiniCSSExtractPlugin.loader, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: { 
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

接着 我们分别使用 html、js、css 的方式引用图片看看效果是否正常。
index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            background-color: aqua;
        }
    </style>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
    <div>
        99
        <img src="./dva.jpg">
    </div>
    <div class="container">
        
    </div>
</body>
</html>

index.less

body {
    // div {
    //     width: 100px;
    //     height: 100px;
    //     background-color: sandybrown;
    //     transform: rotate(45deg);
    // }
    .container {
        width: 100px;
        height: 100px;
        background: url('./dva.jpg');
        background-size: cover;
    }
}

index.js

import $ from "jquery";
import dvaLogo from "./dva.jpg";
import "./index.less";

console.log($, window.$);
const image = new Image();
image.src = dvaLogo;
document.body.appendChild(image);

然后运行 yarn dev 命令,查看效果发现三种方式引用的图片均正常显示
在这里插入图片描述
小结 :
这一节我们通过配置 webpack的 url-loader、file-loader、html-loader 实现了图片的处理,现在就可以和往常一样正常的使用图片了。

16. 配置打包文件分类

目前我们打包出来的静态资源全部在 dist 目录下,但是我们想像 css 和 image 这种单独输出到一个目录中,也就是把打包出来的文件分下类 :

  1. 打包分类处理
  • css 分类
    css 分类比较容易就是在使用 mini-css-extract-plugin 插件的 filename 属性上加上 css/ 即可。
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "css/bundle.css",
        }),
    ],

在这里插入图片描述

  • 图片分类
    图片分类就在 url-loader 中加上 outputPath 属性指明打包后将产出的路径
{ 
    test: /\.(png|jpg|gif|webp|jpeg)$/, 
    use: { 
        loader: "url-loader", 
        options: { 
            esModule:false,
            limit: 1 * 1024,
            outputPath: "./img/",
        },
    } 
},

在这里插入图片描述
2. 介绍个小 skill ,怎么在 webpack 使用 iconfont ?

首先到 阿里巴巴矢量图标库 去找个图标然后下载
在这里插入图片描述
然后将相关文件 copy 一份到咱们的 src 下的 iconfont/right 的目录中
在这里插入图片描述
在这里插入图片描述
然后 webpack.config.js 需要重新加一下 url-loader 来单独处理下这些奇怪的文件。将是这些文件结尾的【eot、svg、ttf、woff、woff2】,将这些文件打到 dist/iconfont/right 目录中

{
    test: /\.(svg|eot|ttf|woff|woff2)\??.*$/, 
    use: { 
        loader: "url-loader", 
        options: { 
            esModule:false,
            limit: 20 * 1024,
            outputPath: "./iconfont/right/",
            // publicPath: "https://www.baidu.com",
        },
    } 
},

在这里插入图片描述
然后我们就可以正常使用了。

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            background-color: aqua;
        }
    </style>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
    <div>
        99
        <img src="./dva.jpg">
    </div>
    <div class="container">
    </div>
    <span class="iconfont">&#xe618;</span>
</body>
</html>

index.css

@import "./test.css";

body {
    background-color: green;
}

@font-face {
    font-family: 'iconfont1';
    src: url(./iconfont/right/iconfont.eot);
    src: url(./iconfont/right/iconfont.eot?#iefix) format('embedded-opentype'),
        url(./iconfont/right/iconfont.woff2) format('woff2'),
        url(./iconfont/right/iconfont.woff) format('woff'),
        url(./iconfont/right/iconfont.ttf) format('truetype'),
        url(./iconfont/right/iconfont.svg#iconfont) format('svg');
  }

.iconfont {
    font-family: "iconfont1" !important;
    font-size: 16px;
    font-style: normal;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

这里需要注意的是,人家让我们复制下面这段代码

@font-face {
  font-family: 'iconfont';
  src: url('iconfont.eot');
  src: url('iconfont.eot?#iefix') format('embedded-opentype'),
      url('iconfont.woff2') format('woff2'),
      url('iconfont.woff') format('woff'),
      url('iconfont.ttf') format('truetype'),
      url('iconfont.svg#iconfont') format('svg');
}

但是在 webpack 里面我们要把 url 里面的路径指向我们的 iconfont/right 目录下且去掉引号。就变成了上面我们贴出的 index.css 中的那段代码了。

贴下 webpack.config.js 代码

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "css/bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp|jpeg)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./img/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            {
                test: /\.(svg|eot|ttf|woff|woff2)\??.*$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./iconfont/right/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: { 
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

运行 yarn dev ,发现出现了小图标。
在这里插入图片描述
至于为什么图标不是网站上呈现的蓝色而是粉色,是因为还记得咱们在 test.css 中设置的 color: hotpink; 呢吗 ?

实际上使用 iconfont 有 3 种方式,我们这里面只是介绍最复杂的使用方式,剩下两种一个是引入 css 一个是引入 js,都比较简单,所以介绍了下比较复杂的实现。
在这里插入图片描述
不过更多的时候我们工程项目中的静态资源都是单独放在一台 cdn 服务器上,比如图片都存放在七牛云或者自己的 cdn 节点。这个时候 html、js、css 中引用的图片地址一定要变成对应的 cdn 的地址,这个可以在 webpack.config.js 中配置一下,可以更改处理图片的 url-loader 中使用 publicPath 属性指向 cdn 资源地址即可。
比如我们的 cdn 地址为 https://www.baidu.com 我们可以这样设定。

{ 
     test: /\.(png|jpg|gif|webp|jpeg)$/, 
     use: { 
         loader: "url-loader", 
         options: { 
             esModule:false,
             limit: 1 * 1024,
             outputPath: "./img/",
             publicPath: "https://www.baidu.com",
         },
     } 
 },

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    entry: "./src/index.js", // 指定 webpack 入口
    output: {
        filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
        }),
        new MiniCSSExtractPlugin({
            filename: "css/bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp|jpeg)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./img/",
                        publicPath: "https://www.baidu.com",
                    },
                } 
            },
            {
                test: /\.(svg|eot|ttf|woff|woff2)\??.*$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./iconfont/right/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: { 
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

这样再运行 yarn build 命令。发现 html 和 css 引用的图片地址都变成了 cdn 的资源地址。
在这里插入图片描述
在这里插入图片描述
由于这个是模拟的给大家看效果的,就不用 yarn dev 去看页面效果了。
模拟的效果看完我们可以将 publicPath 先注释掉。

小结 :
这一节我们实现了打包文件分类,同时也介绍了我们使用 iconfont 的时候最复杂的那种方法在 webpack 中的使用方式,也顺便说明了当我们引 cdn 服务器上的图片资源该怎么配置。

17. 打包多页应用

上面介绍的是打包单页应用,那如果是打包多页应用呢 ? 比如我们希望最终可以打包出来 2 个页面,一个是 index.html 另一个是 other.html ,index.html 引自己的 index.js,other.html 引自己的 other.js。

  1. 新建 other.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    other.html
</body>
</html>
  1. 新建 other.js
console.log("other");
  1. webpack.config.js 需要更改一下配置, entry 要换成对象形式来指定多个入口、output 中的 filename 要由 “bundle.[hash:8].js” 换成 “[name].[hash:8].js”、html-webpack-plugin 要使用两次并且要借助 chunks 属性为各自的页面引入对应的 js 资源。
entry: {
    home: "./src/index.js",
    other: "./src/other.js",
},
 ...
output: {
    // filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
    filename: "[name].[hash:8].js", // 指定 webpack 打包出来的文件的名称
    path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
},
...
new HtmlWebpackPlugin({
    template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
    filename: "index.html", //【指定打包后的 html 文件的名称】
    hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
    minify: { // 【压缩打包后的 html 配置项】
        removeAttributeQuotes: true, // 删除所有双引号
        collapseWhitespace: true, // 删除所有折叠行
    },
    chunks: ["home"],
}),
new HtmlWebpackPlugin({
    template: "./src/other.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
    filename: "other.html", //【指定打包后的 html 文件的名称】
    hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
    minify: { // 【压缩打包后的 html 配置项】
        removeAttributeQuotes: true, // 删除所有双引号
        collapseWhitespace: true, // 删除所有折叠行
    },
    chunks: ["other"],
}),

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    // entry: "./src/index.js", // 指定 webpack 入口
    entry: {
        home: "./src/index.js",
        other: "./src/other.js",
    },
    output: {
        // filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        filename: "[name].[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["home"],
        }),
        new HtmlWebpackPlugin({
            template: "./src/other.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "other.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["other"],
        }),
        new MiniCSSExtractPlugin({
            filename: "css/bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp|jpeg)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./img/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            {
                test: /\.(svg|eot|ttf|woff|woff2)\??.*$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./iconfont/right/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: { 
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
};

然后运行 yarn build 查看打包结果。
在这里插入图片描述
在这里插入图片描述
然后运行 yarn dev 查看效果,发现两个页面均正常。
在这里插入图片描述
在这里插入图片描述
小结 :
这一节我们实现了打包多页应用,配置起来还是比较简单的 👊。

18. 配置调试利器【source-map】

因为我们最终打包出来的 bundle 是一个压缩后的文件,如果里面有代码报错是极为不好调试的,所以我们需要配置下 source-map,即使有的代码错了,我们直接可以定位到源代码出错的位置,而不是定位到压缩后的代码某处。

  1. 配置 source-map 之前呈现的状态
    先故意伪造个错误将 console.log 写成 console.lo

index.js

import $ from "jquery";
import dvaLogo from "./dva.jpg";
import "./index.less";
import "./index.css";

console.log($, window.$);
const image = new Image();
image.src = dvaLogo;
document.body.appendChild(image);
class Confirm {
    constructor() {
        console.lo("故意伪造的错误");
    }
}
new Confirm();

运行命令 yarn dev 发现报错并不明显。
在这里插入图片描述
2. 配置 soucre-map 之后呈现的状态

  • 我们只需要在 webpack.config.js 中配置 devtool 属性,值为 "source-map" 即可。
  • 再次运行命令 yarn dev ,发现报错信息更加明显。
    在这里插入图片描述
    小结 :
    这一节我们知道了通过配置 source-map 我们就可以更加方便、精准的调试错误了。

19. 通过配置 watch 实现实时打包

如果我们有个需求就是每次代码变动的时候都可以进行实时打包而不是通过我们自己输入命令 yarn build 去手动打包,我们就需要去配置 webpack 的 watch 属性了。

  1. 配置 watch 属性实现实时打包
    更改 webpack.config.js 的配置,添加 watch 属性与 watchOptions 属性。
watch: true,
watchOptions: {
    poll: 1000,
    aggregateTimeout: 500,
},

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    // entry: "./src/index.js", // 指定 webpack 入口
    entry: {
        home: "./src/index.js",
        other: "./src/other.js",
    },
    output: {
        // filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        filename: "[name].[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["home"],
        }),
        new HtmlWebpackPlugin({
            template: "./src/other.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "other.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["other"],
        }),
        new MiniCSSExtractPlugin({
            filename: "css/bundle.css",
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp|jpeg)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./img/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            {
                test: /\.(svg|eot|ttf|woff|woff2)\??.*$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./iconfont/right/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: { 
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
    devtool: "source-map",
    watch: true, // 开启监听
    watchOptions: {
        poll: 1000, // 每秒监听一次变化
        aggregateTimeout: 500, // 代码输入 500ms 后代码无变化的时候执行打包命令
    },
};

先运行下 yarn build,然后在文件中做一些改动。发现确实是实时打包了,因为产生了好多 js bundle 文件,产生这么多文件的原因就是代码更改后 500 毫秒自动执行了打包命令。
在这里插入图片描述
但是这样太丑了,每次打包后上次打包的结果依然还留着,所以我们需要借助 clean-webpack-plugin 插件来在打包的时候自动删除上次打包的文件目录,使得我们看到的始终是最新打包的。

  1. 解决打包后 dist 目录依然存留上次打包的文件的问题

首先安装 clean-webpack-plugin

~$ yarn add clean-webpack-plugin -D --save

接着在 webpack.config.js 进行配置使用(因为最新版的 clean-webpack-plugin 插件已经和过去的用法不一样了,具体用法请移步官网)

...
plugins: [
	...
	new CleanWebpackPlugin(),
],
...

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    // entry: "./src/index.js", // 指定 webpack 入口
    entry: {
        home: "./src/index.js",
        other: "./src/other.js",
    },
    output: {
        // filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        filename: "[name].[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["home"],
        }),
        new HtmlWebpackPlugin({
            template: "./src/other.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "other.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["other"],
        }),
        new MiniCSSExtractPlugin({
            filename: "css/bundle.css",
        }),
        new CleanWebpackPlugin(), // 删除 dist 目录【默认就是删除 dist 目录所以不用传参数】
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp|jpeg)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./img/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            {
                test: /\.(svg|eot|ttf|woff|woff2)\??.*$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./iconfont/right/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: { 
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
    devtool: "source-map",
    watch: true, // 开启监听
    watchOptions: {
        poll: 1000, // 每秒监听一次变化
        aggregateTimeout: 500, // 代码输入 500ms 后代码无变化的时候执行打包命令
    },
};

此时我们的 clean-webpack-plugin 插件就配好了,现在就可以实现每次代码输入改变 500ms 之后会自动删除上一次 build 的结果然后自动重新生成 build 的结果。
比如当前的 index.js 内容如下
index.js

import $ from "jquery";
import dvaLogo from "./dva.jpg";
import "./index.less";
import "./index.css";

console.log($, window.$);
const image = new Image();
image.src = dvaLogo;
document.body.appendChild(image);
class Confirm {
    constructor() {
        console.log("故意伪造的错误jkl");
    }
}
new Confirm();

运行命令 yarn build
然后再度更改 index.js 文件中 console 中的内容,将 console.log(故意伪造的错误jkl) 变成 故意伪造的错误asd
index.js

import $ from "jquery";
import dvaLogo from "./dva.jpg";
import "./index.less";
import "./index.css";

console.log($, window.$);
const image = new Image();
image.src = dvaLogo;
document.body.appendChild(image);
class Confirm {
    constructor() {
        console.log("故意伪造的错误asd");
    }
}
new Confirm();

然后查看自动打包的结果,发现已经将上一次 build 的结果删除生成了最新 build 的结果。
在这里插入图片描述
小结 :
这一节我们完美实现了通过配置 webpack 的 watch 属性实现了实时打包构建功能,并借助 clean-webpack-plugin 插件成功删除上一次构建的结果。

20. 使用 2 个小插件来如虎添翼

这一节主要介绍两个小插件的使用【copy-webpack-plugin、“banner-plugin”】,日常

  1. 项目中可能有一些目录或者文件不需要走 webpack 打包但是最后也想在 build 的时候输出到打包后的目录,比如项目的 doc 目录,这个时候我们可以借助 copy-webpack-plugin 来实现。
  • 安装 copy-webpack-plugin 插件

~$ yarn add copy-webpack-plugin@6.4.1 -D --save

  • webpack.config.js 中添加配置使用该插件
...
plugins: [
	...
    new CopyWebpackPlugin({
       patterns: [
           { from: "doc", to: "./doc", },
       ],
   }),
]
...

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    // entry: "./src/index.js", // 指定 webpack 入口
    entry: {
        home: "./src/index.js",
        other: "./src/other.js",
    },
    output: {
        // filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        filename: "[name].[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["home"],
        }),
        new HtmlWebpackPlugin({
            template: "./src/other.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "other.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["other"],
        }),
        new MiniCSSExtractPlugin({
            filename: "css/bundle.css",
        }),
        new CleanWebpackPlugin(), // 删除 dist 目录【默认就是删除 dist 目录所以不用传参数】
        new CopyWebpackPlugin({
            patterns: [
                { from: "doc", to: "./doc", },
            ],
        }),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp|jpeg)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./img/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            {
                test: /\.(svg|eot|ttf|woff|woff2)\??.*$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./iconfont/right/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: { 
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
    devtool: "source-map",
    watch: true, // 开启监听
    watchOptions: {
        poll: 1000, // 每秒监听一次变化
        aggregateTimeout: 500, // 代码输入 500ms 后代码无变化的时候执行打包命令
    },
};
  • 项目根目录新建 doc 文件夹,然后新建 doc.md 里面写上内容
    在这里插入图片描述
    然后运行命令 yarn build ,就看到了项目根目录中的 doc 文件夹完整的输出到了 dist 目录下。
    在这里插入图片描述
  1. 有些时候我们可能通过 webpack 来构建我们自己的 npm 包。这个时候我们的代码文件最好可以加上版权声明,因为其它的 npm 包都是版权声明的。所以我们可以借助 webpack 自带插件 banner 插件来实现。
  • 修改 webpack.config.js ,导入 webpack 并配置使用 banner 插件。
...
plugins: [
	...
	new Webpack.BannerPlugin("make 2021 by FruitJ"),
],
...

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const Webpack = require("webpack");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    // entry: "./src/index.js", // 指定 webpack 入口
    entry: {
        home: "./src/index.js",
        other: "./src/other.js",
    },
    output: {
        // filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        filename: "[name].[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["home"],
        }),
        new HtmlWebpackPlugin({
            template: "./src/other.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "other.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["other"],
        }),
        new MiniCSSExtractPlugin({
            filename: "css/bundle.css",
        }),
        new CleanWebpackPlugin(), // 删除 dist 目录【默认就是删除 dist 目录所以不用传参数】
        new CopyWebpackPlugin({
            patterns: [
                { from: "doc", to: "./doc", },
            ],
        }),
        new Webpack.BannerPlugin("make 2021 by FruitJ"),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp|jpeg)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./img/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            {
                test: /\.(svg|eot|ttf|woff|woff2)\??.*$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./iconfont/right/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: { 
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
    devtool: "source-map",
    watch: true, // 开启监听
    watchOptions: {
        poll: 1000, // 每秒监听一次变化
        aggregateTimeout: 500, // 代码输入 500ms 后代码无变化的时候执行打包命令
    },
};

接下来再运行 yarn build 命令发现打包出来的 js bundle 文件中的内容前面有我们加的版权说明。
在这里插入图片描述
小结 :
这一节我们通过配置使用 copy-webpack-plugin 插件实现了非打包 文件/目录 的复制。通过配置使用 webpack 的 banner 插件实现了在打包出来的 js bundle 中添加版权说明。

21. 通过配置 Proxy 解决 Webpack 跨域问题

为什么会有跨域问题嘞 ? 这个问题多出现在开发环境中,后端服务和前端不在同一【协议、域名、端口号】内,就会出现这种跨域问题,这也是在开发过程中不可避免的问题。当然这个通常做法就是后端同学设置一下就好了,但是如果不想麻烦后端的话,前端自己通过配置 devServer 的代理也是可以解决的。

先看看问题所在
index.js

import $ from "jquery";
import dvaLogo from "./dva.jpg";
import "./index.less";
import "./index.css";

fetch('http://localhost:3005/user')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(myJson);
  });

毫无疑问的出现了跨域问题就是因为同时本地服务,协议与域名都一样但是端口号不一样,从 3010 端口向 3005 端口的服务发送请求所以必然跨域。
在这里插入图片描述
通过配置 webpack 的 devServer 的 Proxy 来解决跨域问题

  1. 新建一个 server.js 文件,在里面通过 node 启个服务。
    server.js
const express = require("express");

const app = express();
app.get("/api/user", (req, res) => {
    res.json({
        name: "FruitJ",
    });
});
app.listen(3005);

代码的意思就是开启一个本地服务,端口是 3005 ,当我们访问本地服务 3005 端口的 /user 这个 api 地址的时候就会返回一条 json 数据。

  1. 配置 webpack.config.js ,在 devServer 中添加 Proxy 配置。
proxy: { // 【 处理跨域问题 】
    "/api": "http://localhost:3005",
    pathRewrite: { "/api": "", }, // 重写请求地址,将 api 替换成空
},

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const Webpack = require("webpack");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    // entry: "./src/index.js", // 指定 webpack 入口
    entry: {
        home: "./src/index.js",
        other: "./src/other.js",
    },
    output: {
        // filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        filename: "[name].[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
        proxy: { // 【 处理跨域问题 】
            "/api": "http://localhost:3005",
            pathRewrite: { "/api": "", }, // 重写请求地址,将 api 替换成空
        },
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["home"],
        }),
        new HtmlWebpackPlugin({
            template: "./src/other.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "other.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["other"],
        }),
        new MiniCSSExtractPlugin({
            filename: "css/bundle.css",
        }),
        new CleanWebpackPlugin(), // 删除 dist 目录【默认就是删除 dist 目录所以不用传参数】
        new CopyWebpackPlugin({
            patterns: [
                { from: "doc", to: "./doc", },
            ],
        }),
        new Webpack.BannerPlugin("make 2021 by FruitJ"),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp|jpeg)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./img/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            {
                test: /\.(svg|eot|ttf|woff|woff2)\??.*$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./iconfont/right/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: { 
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
    devtool: "source-map",
    watch: true, // 开启监听
    watchOptions: {
        poll: 1000, // 每秒监听一次变化
        aggregateTimeout: 500, // 代码输入 500ms 后代码无变化的时候执行打包命令
    },
};
  1. index.js 中通过 fetch api 发出一条请求 http://localhost:3005/user 的请求。
    index.js
import $ from "jquery";
import dvaLogo from "./dva.jpg";
import "./index.less";
import "./index.css";

fetch('/api/user')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(myJson);
  });

先运行命令开启服务

~$ node server.js

再运行命令 yarn dev 查看效果,发现成功的解决了跨域的问题
在这里插入图片描述
那么 webpack 是怎么解决跨域问题的呢 ?
在这里插入图片描述
就是页面发送的请求先发送给当前自己同【协议、域名、端口号】的 webpack devServer 服务器,然后由这个 devServer 服务器向 server.js 启的 3005 端口的服务转发请求,因为服务器与服务器之间的通信是没有跨域这一说的,所以通过设置代理服务器就可以解决这个跨域的问题,在咱们的 webpack 中这个 devServer 就充当了代理服务器。
小结 :
这一节我们通过配置 webpack 的 Proxy 代理,完美的解决了跨域问题,极大的提升了开发效率。

22. 通过配置 webpack 的 resolve 来实现自定义导入

  1. 自定义指定导入资源的包的查找顺序
    首先我们知道当前工程中某个文件引入资源的查找路径是当前工程的 node_modules ,如果没有找到就到上一层寻找。
    如果我们有个需要就是只允许在当前目录的 node_modules 中查找该怎么配置 ?
    可以通过 webpack 的 resolve 属性的 modules 属性来配置(查找顺序是从左向右)
...
resolve: {
    modules: [path.resolve("node_modules")], // 自定义指定导入资源的包的查找顺序
},
...

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const Webpack = require("webpack");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    // entry: "./src/index.js", // 指定 webpack 入口
    entry: {
        home: "./src/index.js",
        other: "./src/other.js",
    },
    output: {
        // filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        filename: "[name].[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
        proxy: { // 【 处理跨域问题 】
            "/api": "http://localhost:3005",
            pathRewrite: { "/api": "", }, // 重写请求地址,将 api 替换成空
        },
        // before(app) { // 钩子【 mock 数据的时候可以使用 】
        //     app.get("/api/user", (req, res) => {
        //         res.json({
        //             name: "FruitJ",
        //         });
        //     });
        // },
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["home"],
        }),
        new HtmlWebpackPlugin({
            template: "./src/other.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "other.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["other"],
        }),
        new MiniCSSExtractPlugin({
            filename: "css/bundle.css",
        }),
        new CleanWebpackPlugin(), // 删除 dist 目录【默认就是删除 dist 目录所以不用传参数】
        new CopyWebpackPlugin({
            patterns: [
                { from: "doc", to: "./doc", },
            ],
        }),
        new Webpack.BannerPlugin("make 2021 by FruitJ"),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp|jpeg)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./img/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            {
                test: /\.(svg|eot|ttf|woff|woff2)\??.*$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./iconfont/right/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: { 
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
    devtool: "source-map",
    watch: true, // 开启监听
    watchOptions: {
        poll: 1000, // 每秒监听一次变化
        aggregateTimeout: 500, // 代码输入 500ms 后代码无变化的时候执行打包命令
    },
    resolve: {
        modules: [path.resolve("node_modules")], // 自定义指定导入资源的包的查找顺序
    },
};
  1. 自定义指定导入资源省略后缀的查找顺序
    就是当我们想引入某个 js 的时候我们希望这样导入 import utils from "./utils"; 而不是这样导入 import utils from "./utils.js"; 通俗说就是想引入 js 的时候不加后缀。
...
resolve: {
...
    extensions: [".js", ".css"], // 自定义指定导入资源省略后缀的查找顺序
},
...

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const Webpack = require("webpack");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    // entry: "./src/index.js", // 指定 webpack 入口
    entry: {
        home: "./src/index.js",
        other: "./src/other.js",
    },
    output: {
        // filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        filename: "[name].[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
        proxy: { // 【 处理跨域问题 】
            "/api": "http://localhost:3005",
            pathRewrite: { "/api": "", }, // 重写请求地址,将 api 替换成空
        },
        // before(app) { // 钩子【 mock 数据的时候可以使用 】
        //     app.get("/api/user", (req, res) => {
        //         res.json({
        //             name: "FruitJ",
        //         });
        //     });
        // },
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["home"],
        }),
        new HtmlWebpackPlugin({
            template: "./src/other.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "other.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["other"],
        }),
        new MiniCSSExtractPlugin({
            filename: "css/bundle.css",
        }),
        new CleanWebpackPlugin(), // 删除 dist 目录【默认就是删除 dist 目录所以不用传参数】
        new CopyWebpackPlugin({
            patterns: [
                { from: "doc", to: "./doc", },
            ],
        }),
        new Webpack.BannerPlugin("make 2021 by FruitJ"),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp|jpeg)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./img/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            {
                test: /\.(svg|eot|ttf|woff|woff2)\??.*$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./iconfont/right/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: { 
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
    devtool: "source-map",
    watch: true, // 开启监听
    watchOptions: {
        poll: 1000, // 每秒监听一次变化
        aggregateTimeout: 500, // 代码输入 500ms 后代码无变化的时候执行打包命令
    },
    resolve: {
        modules: [path.resolve("node_modules")], // 自定义指定导入资源的包的查找顺序
        extensions: [".js", ".css"], // 自定义指定导入资源省略后缀的查找顺序
    },
};

修改 index.js,引入 test.js 但是不加后缀 .js

import $ from "jquery";
import dvaLogo from "./dva.jpg";
import "./index.less";
import "./index.css";
import util from "./test";

util();

运行 yarn dev 命令,发现 test.js 中的 util 方法中的打印语句执行了。
在这里插入图片描述

  1. 自定义指定导入资源的别名
    某些时候例如我们想导入 bootstrap.css ,可能我们会这么写 import "bootstrap/dist/css/bootstrap.min.css"; ,但是这么写太长了,所以我们可以设置一个别名,引入的时候引入别名就好了。
...
resolve: {
	...
    alias: { // 自定义指定导入资源的别名
        "bootstrap-css": "bootstrap/dist/css/bootstrap.min.css",
    },
},
...

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin");
const Webpack = require("webpack");
const OptimizeCSSAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = { // 配置 webpack
    mode: "production", // 指定 webpack 的打包模式【 development、production 】
    // entry: "./src/index.js", // 指定 webpack 入口
    entry: {
        home: "./src/index.js",
        other: "./src/other.js",
    },
    output: {
        // filename: "bundle.[hash:8].js", // 指定 webpack 打包出来的文件的名称
        filename: "[name].[hash:8].js", // 指定 webpack 打包出来的文件的名称
        path: path.resolve(__dirname, "dist"), // 指定 webpack 打包的产出路径【这块的路径必须是一个绝对路径,这里规定为项目根目录的 dist 目录。可以添加 [hash] 每次 build 的时候都会生成新的文件,来防止浏览器缓存问题、冒号8 表示只显示 8 位的 hash 值 】
    },
    devServer: {
        contentBase: "./dist", // 指定 dev-server 的服务器目录
        progress: true, // 在启动静态服务器时显示进度条
        port: 3010, // 指定端口号
        open: true, // 自动打开浏览器
        compress: true, // 为所有服务启用 gzip 压缩
        proxy: { // 【 处理跨域问题 】
            "/api": "http://localhost:3005",
            pathRewrite: { "/api": "", }, // 重写请求地址,将 api 替换成空
        },
        // before(app) { // 钩子【 mock 数据的时候可以使用 】
        //     app.get("/api/user", (req, res) => {
        //         res.json({
        //             name: "FruitJ",
        //         });
        //     });
        // },
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "index.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["home"],
        }),
        new HtmlWebpackPlugin({
            template: "./src/other.html", //【 指定以哪个 html 文件为模板自动生成打包后的 html 文件 】
            filename: "other.html", //【指定打包后的 html 文件的名称】
            hash: true, //【在引入 bundle 的时候后面加上 hash 值来防止浏览器的缓存问题】
            minify: { // 【压缩打包后的 html 配置项】
                removeAttributeQuotes: true, // 删除所有双引号
                collapseWhitespace: true, // 删除所有折叠行
            },
            chunks: ["other"],
        }),
        new MiniCSSExtractPlugin({
            filename: "css/bundle.css",
        }),
        new CleanWebpackPlugin(), // 删除 dist 目录【默认就是删除 dist 目录所以不用传参数】
        new CopyWebpackPlugin({
            patterns: [
                { from: "doc", to: "./doc", },
            ],
        }),
        new Webpack.BannerPlugin("make 2021 by FruitJ"),
    ],
    module: { // 配置解析模块的众多规则
        rules: [
            { test: /\.css$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader"], }, // 使用 css-loader
            { test: /\.less$/, use: [ 
                {
                    loader: MiniCSSExtractPlugin.loader,
                    options: {
                        publicPath: "../",
                    },
                }, "css-loader", "postcss-loader", "less-loader"], }, // 使用 less-loader
            { 
                test: /\.js$/, 
                use: ["babel-loader"], // 使用 babel-loader 进行 js 高级语法转换
                include: path.resolve(__dirname, "src"), // 只处理 src 下的 js 文件
                exclude: /node_modules/,
            },
            { 
                test: /\.(png|jpg|gif|webp|jpeg)$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./img/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            {
                test: /\.(svg|eot|ttf|woff|woff2)\??.*$/, 
                use: { 
                    loader: "url-loader", 
                    options: { 
                        esModule:false,
                        limit: 1 * 1024,
                        outputPath: "./iconfont/right/",
                        // publicPath: "https://www.baidu.com",
                    },
                } 
            },
            { 
                test: /\.html$/, 
                use: {
                    loader: "html-loader",
                    options: {
                        attrs: ['img:src', 'img:data-src', 'audio:src'],
                    }
                } 
            },
        //  { test: require.resolve("jquery"), use: "expose-loader?$", },
        //  { test: /\.js$/, use: {
        //     loader: "eslint-loader",
        //     options: {
        //         enforce: "pre", // pre 代表在下面的 loader 之前执行、post 相反
        //     },
        // },include: path.resolve(__dirname, "src"),exclude: /node_modules/, },
        ],
    },
    externals: { 
        jquery: "$",
    },
    optimization: {
        minimizer: [
            new UglifyJsWebpackPlugin({
                cache: true, // 是否开启缓存
                parallel: true, // 是否并行压缩
                sourceMap: true, // 是否开启源码映射
            }),
            new OptimizeCSSAssetsWebpackPlugin(),
        ],
    },
    devtool: "source-map",
    watch: true, // 开启监听
    watchOptions: {
        poll: 1000, // 每秒监听一次变化
        aggregateTimeout: 500, // 代码输入 500ms 后代码无变化的时候执行打包命令
    },
    resolve: {
        modules: [path.resolve("node_modules")], // 自定义指定导入资源的包的查找顺序
        extensions: [".js", ".css"], // 自定义指定导入资源省略后缀的查找顺序
        alias: { // 自定义指定导入资源的别名
            "bootstrap-css": "bootstrap/dist/css/bootstrap.min.css",
        },
    },
};

index.js

import $ from "jquery";
import dvaLogo from "./dva.jpg";
import "./index.less";
import "./index.css";
import util from "./test";
import "bootstrap-css";

util();

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            background-color: aqua;
        }
    </style>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
    <div>
        99
        <img src="./dva.jpg">
    </div>
    <div class="container">

    </div>
    <span class="iconfont">&#xe618;</span>

    <button class="btn btn-success">success</button>
</body>
</html>

然后运行 yarn dev 命令,查看 success 按钮确实已经应用上了 bootstrap 的样式。
在这里插入图片描述
至于为什么 body 的背景色由绿色变成了白色,是因为被 bootstrap 的 body 样式覆盖了,这个我们可以在靠前的位置引入 bootstrap。

index.js

import $ from "jquery";
import dvaLogo from "./dva.jpg";
import "bootstrap-css";
import "./index.less";
import "./index.css";
import util from "./test";

util();

再看效果就是绿色背景了
在这里插入图片描述
小结 :
经过这一节我们了解了,可以通过配置 webpack 的 resolve 属性来实现自定义导入。

四、后语

本文是从 0 到 1 开始一点一点搭建 webpack 帮助大家打造自己的前端工作流。到这里我们基于 webpack 构建的项目就具备基本开发能力了。我们总结下 :
webpack可以配置哪些项 ?

  • mode【 webpack 模式配置 】
  • entry【 入口相关配置 】
  • output【 出口相关配置 】
  • devServer【 静态服务相关配置 】
  • module【 规则(loaders)相关配置 】
  • plugins【 插件相关配置 】
  • optimization【 优化项相关配置 】
  • resolve【 自定义资源查找和引入相关配置 】
  • externals【 指明不打包某个资源的配置 】
  • devtool【 开发工具相关配置(比如 sorce-map) 】
  • watch【 开启实时监听的开关 】
  • watchOptions【 实时监听实时打包相关配置 】

有很多人认为这个玩意配置这么多东西有什么用 ?
实际上问这个问题的同学应该是前端方面的初学者,因为这个问题以前我也会问。为什么用 ? 因为这套工具可以极大程度上提高我们的编码速率,而且就算费工夫配置也只是一开始费点功夫而已,后面使用起来直接爽到飞起,况且现在很多的脚手架基本这些配置都帮你弄好了,你上去直接开箱即用即可(比如 react 脚手架、vue 脚手架、antd、antd-pro 、umi、dva 等)。一句话我们可以通过配置 webpack 可以将以前我们手动干的活变成插件帮助我们自动完成,甚至可以获得一些良好另样的开发体验就像 devServer 的实时刷新等,最重要的是当我们的重复代码太多我们也可以通过配置进行公共代码抽离,代码压缩、加密等这些插件帮助我们做了,它不放心、不安心、不香吗 ? 😘

本文到此就截止啦,谢谢一路跟下来的朋友们,也希望大家可以收获一些东西,本文只是介绍了一些基本的用法和功能,原理尚未进行深入探索,全当是帮助不太了解 webpack 的前端同学引路吧。文章中虽然经过审阅但是难免会有错字、代码片段错误、贴图错误、甚至 npm 包不断升级可能跟到某处就卡了,如果出现这些问题请在博文下方留言,笔者发现第一时间进行处理。

本文是笔者一点点构建出来的绝对可以放心食用 🐮。

git 仓库

🎉 欢迎大家观看本篇文章的姊妹篇 : 还不了解 webpack 优化 ? 一文带你快速优化 webpack !

👊 下一篇文章关于 webpack 配置代码提交(git commit)时自动校验 eslint、自动代码格式化、自动校验 git commit 规范已经提上日程,后续将会与大家见面 !

五、参考

  • 20
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值