【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置

一、前言

本来想将webpack的内容整理成一篇到底,但太长了容易引起视觉疲劳。所以单独将内容和实践内容分开。

在了解到理论内容之后,脑子总会出现这么一个问题。
脑瓜子: 只看不写真的能让你学废吗?
我的回答是:会的!脑子暂时会了,手脚还没会!

所以本文偏向于实践,通过实践配置,结合测试结果图表。让你对webpack配置有个更清晰理解。

二、实践案例

实现 webpack 工程化打包的一些配置案例,完成案例将得到的收益:

基础入门案例:了解 webpack 的相关基本配置

搭建开发案例:脱离脚手架结合 React 框架实现开发案例配置,达到更了解工程开发的运作过程。

2.0.环境&版本

本文包管理采用pnpm
"webpack": "^5.88.2"
node:18.12.0 或 16.14.2

2.1.基础配置案例

新建一个干净的文件包赶紧开始吧!🐵

2.1.1.初始化项目

打开终端初始化项目配置文件 package.json

bash 复制代码
pnpm init

安装 Webpack 及脚手架

bash 复制代码
pnpm add -D webpack webpack-cli

新建文件夹src,并在其目录下添加一个文件 main.js 并写点内容:

javascript 复制代码
console.log("Hi! Webpack 你也会打篮球吗🤣");

测试是否初始化成功!🚀

配置package.json脚本

javascript 复制代码
"scripts": {
  "build": "webpack ./src/main.js"
},

执行脚本 ♻️

bash 复制代码
pnpm run build

webpack 开箱即用,可以无需使用任何配置文件。这时查看根目录发现 dist文件夹 dist/main.js

到这里说明已初始化成功!✅
官方在线案例预览


2.1.2.出入口基础配置

完成上一步,说明咱已经实现了 webpack 的第一次打包 😄

但其实我们还没有做任何配置,打包生成的内容只是 webpack 的默认配置。下面咱们来添加更丰富的配置。

新建build文件夹,并添加webpack.config.js文件

javascript 复制代码
/**
 * @fileName webpack.config.js
 * @description based.config|基础配置
 * @param mode|开发模式
 * @param entry|入口文件路径
 * @param output|打包输出配置
 */
const path = require("path");

module.exports = {
  mode: "development",
  entry: path.resolve(__dirname, "../src/main.js"),
  output: {
    filename: "[name].[hash:8].js",
    path: path.resolve(__dirname, "../dist"), // 打包后的目录
  },
};

filename: "[name].[hash:8].js" 中的
[name] 是占位符,表示输出文件的名称将根据入口文件的名称进行命名。
[hash:8] 也是占位符,表示在输出文件的名称中插入一个哈希值,:8表示使用哈希的前八位字符。

接下来测试配置是否成功!🚀

修改之前的package.json脚本配置

javascript 复制代码
"scripts": {
  "build": "webpack --config build/webpack.config.js"
}

根据特定情况使用不同的配置文件时,则可以通过在命令行中使用--config 标志修改。也可以用-c缩写形式

执行脚本 ♻️

bash 复制代码
pnpm run build

如图根目录下dist/main.js文件后缀多了插入的[hash:8]哈希值。

说明已配置成功!✅


2.1.3.配置 HTML

上步打包好的内容,可通过<script src='./main.xxx.js'> 引入html文档并使用。

但配置的动态命名哈希值,每次打包后都不一样,不可能每次都手动引入,所以引入插件来完成这个自动插入 <script > 标签的过程。

根目录下新建public文件夹,添加index.html文件,

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Webpack config Template</title>
</head>

<body>
  <div id="app"></div>
</body>

</html>

并安装插件

bash 复制代码
pnpm install -D html-webpack-plugin

继续添加配置

javascript 复制代码
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  // code...

  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
    }),
  ],
};

执行脚本测试配置是否成功!🚀

bash 复制代码
pnpm run build

如图根目录下dist/index.html会自动引入打包好的文件。

html 自动注入 js 配置成功!✅
官方插件详情


2.1.4.配置 CSS 解析

clean-webpack-plugin 配置

基础三件套还差css没有配置,在配置前先解决一个问题。

你可能会发现在修改main.js内容后,执行打包脚本时输出的 dist文件夹会有新的打包内容,并且之前打包的文件依然存在。如下图所示:

正常来讲这是不合理的,旧的内容应当被更新的内容替换。为了解决这个问题还需引入clean-webpack-plugin插件,在打包文件生成前清空输出的文件夹。

继续添加插件配置

webpack4

javascript 复制代码
// webpack.config.js
...
const {CleanWebpackPlugin} = require('clean-webpack-plugin')

module.exports = {
    ...
    plugins:[new CleanWebpackPlugin()]
}

上方是 webpack4 的配置方式,webpack5 的已经将该方式移入output配置项目中。官方详情

javascript 复制代码
// webpack.config.js
const path = require("path");
// ...

module.exports = {
  mode: "development",
  entry: path.resolve(__dirname, "../src/main.js"),
  output: {
    filename: "[name].[hash:8].js",
    path: path.resolve(__dirname, "../dist"),
    clean: true, //w4 - clean-webpack-plugin
  },
  // ...
};

执行脚本测试配置是否成功!🚀

如图在修改main.js内容后,执行打包脚本时输出的 dist文件夹不再出现之前的内容。

打包前清空输出文件夹目录配置成功!✅


css,less,sass 基本配置

接下来就是 CSS 的配置

安装样式解析器loader

bash 复制代码
pnpm add -D style-loader css-loader

如要使用lesssass则需要再安装

bash 复制代码
pnpm add -D less less-loader

#or
pnpm add -D sass sass-loader node-sass

sass 配置官方详情

继续添加配置

javascript 复制代码
// ...
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /\.s[ac]ss$/i,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
    ],
  },
};

测试配置是否成功!🚀

pulic/index.html中添加如下测试 div

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Webpack config Template</title>
<script defer src="./main.d269bcb0.js"></script></head>

<body>
  <div id="app"></div>
  <div class="demo-css">css demo</div>
  <div class="demo-less">less demo</div>
  <div class="demo-sass">sass demo</div>
</body>

</html>

src文件夹下新建 index.css index.less index.scss 或 .sass 样式文件,并在入口文件引入

css 复制代码
/* index.css */
.demo-css {
  width: 100vw;
  height: 100px;
  background-color: orangered;
}
css 复制代码
/* index.less */
.demo-less {
  width: 100vw;
  height: 100px;
  background-color: green;
}
css 复制代码
/* index.sass */
.demo-sass
  width: 100vw
  height: 100px
  background-color: orange
javascript 复制代码
// main.js
import "./index.css";
import "./index.less";
import "./index.sass";
console.log("hello webpack");

执行打包脚本测试 ♻️

按传统的加载资源方式来讲,此时 html 上应该会引入一个样式标签,<style>对 css 资源加载。

但查看打包好的dist/index.html文件你会发现,并没有<style>标签,浏览器打开发现运行。如下测试图所示:

运行打包后的 html 文件,发现<style>其实是有被引入的,这也说明了 webpack 并不是按传统的方式加载,它是以js 模块为主导,通过不同的loader 解析 js 中对应 import 的资源。

到这里说明你 Css 的基本配置已经成功!✅


css 添加浏览器厂商前缀配置

但还没有配置完,由于市面上浏览器类型众多,为了 Css 能兼容到不同浏览器,还需添加浏览器厂商前缀。此时需要使用到 autoprefixer前缀自动补全插件,并配合PostCss-loader 完成对样式的解析。

bash 复制代码
 pnpm add -D postcss-loader autoprefixer

继续添加配置

javascript 复制代码
module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"],
      },
      {
        test: /\.less$/,
        use: ["style-loader", "css-loader", "postcss-loader", "less-loader"],
      },
      {
        test: /\.s[ac]ss$/i,
        use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"],
      },
    ],
  },

在根目录下新建.postcssrc.jsonpostcss.config.js

javascript 复制代码
// .postcssrc.json
{
  "plugins": {
    "autoprefixer": {}
  }
}
javascript 复制代码
// .postcssrc.json
module.exports = {
  plugins: [
    require("autoprefixer"), // 直接引用该插件即可
  ],
};

两种形式使用那种都可以,同时还需在根目录子添加autoprefixer 插件,对不同浏览器盘本的限制的配置文件.browserslistrc

不做配置 autoprefixer 插件不起作用

json 复制代码
# 支持的不同浏览器厂商的版本
last 10 Chrome versions
last 10 Firefox versions
Safari >= 6
ie >= 10

也可以直接在 package.json 或 postcss-loader 条件中配置暂不展示。官方配置详情

测试配置是否成功!🚀

在 index.css 文件添加测试样式

css 复制代码
.demo-css {
  display: flex;
  transform: scale(0.5);
}

执行打包脚本测试 ♻️

浏览运行打包好的dist/index.html文件,结果如下图:

如图不同浏览器厂商前缀已经加上!

到这里说明你 Css 浏览器厂商前缀配置已经成功!✅


css 拆分配置

样式的配置其实到这里就差不多了,但是为性能提高性性能,需要将 CSS 提取到一个单独的文件中。

这样做的好处是:

  • 提高页面加载速度:将 CSS 文件与 JavaScript 文件分离,可以并行加载,加快页面的加载速度。
  • 缓存优化:当 CSS 文件发生变化时,浏览器可以只重新加载 CSS 文件,而不需要重新加载整个 JavaScript 文件。 代码分离:将 CSS 文件与 JavaScript 文件分离,可以更好地实现代码的分离与模块化,提高代码的可维护性和可读性。
  • 兼容性优化:某些浏览器对于内联的 CSS 样式有限制,将 CSS 提取到单独的文件中可以避免这些问题。

总的来说就是可以优化页面加载速度、提高用户体验,同时也有利于代码的维护和兼容性优化。所以这里需要用到mini-css-extract-plugin插件。 官方详情

bash 复制代码
pnpm add -D mini-css-extract-plugin

继续修改配置

javascript 复制代码
// # webpack.config.js
// ...
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  // ...

  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
      },
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "less-loader",
        ],
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "sass-loader",
        ],
      },
    ],
  },

  plugins: [
    //...
    new MiniCssExtractPlugin({
      filename: "static/css/[name].[contenthash:8].css",
    }),
  ],
};

这里输入的文件名使用contenthash 方式,可以防止第二次打包构建时 css 缓存被清除,也就是只有当 CSS 内容发生改变时,才对该文件进行清除缓存并更新。 官方详情

测试配置是否成功!🚀

再次修改 index.css 测试样式

css 复制代码
.demo-css {
  width: 100vw;
  height: 100px;
  background-color: orangered;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 30px;
}

执行打包脚本测试 ♻️

查看打包文件夹dist,发现 css 已经配单独取出到指定的文件,结果如下图:

浏览运行打包好的dist/index.html文件,结果如下图:

但需要注意的是独立抽取出来的.css一般使用于生产环境,而对于开发环境,为了方便热更新HRM使用 style-loader效果可能更好。所以后续优化,可针对不同环境配置不同的loader

到这里 Css 所以基础配置已经成功!✅


2.1.5.配置静态资源(图像、音视频)

关于静态资源文件导出的配置,在 webpack 5 之前,通常使用:

而在 Webpack5 中官方将上述方法内置化,推用资源模块(asset module) 方式替换。具体替换如下:

  • asset/resource : 发送一个单独的文件并导出 URL。替换 file-loader
  • asset/inline : 导出一个资源的 data URI。替换 url-loader
  • asset/source: 导出资源的源代码。替换 raw-loader
  • asset :在导出一个 data URI 和发送一个单独的文件之间自动选择,可配置资源体积限制实现。替换 url-loader

更多官方详情

在这里采用 asset方法,继续配置

javascript 复制代码
// # webpack.config.js
// ...

module.exports = {
  // ...

  module: {
    rules: [
      // ...
      //图像
      {
        test: /.(png|jpg|jepg|git|svg)$/,
        type: 'assets',
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, //限制在 10kb
          }
        },
        generator: {
          filename: 'static/images/[name][ext]'
        }
      },
      // 视频
      {
        test: /.(mp4|mp3|webm)$/,
        type: 'assets',
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024,
          }
        },
        generator: {
          filename: 'static/medias/[name][ext]'
        }
      }
      //字体
      {
        test: /.(woff2|eot|ttf|otf)$/,
        type: 'assets',
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024,
          }
        },
        generator: {
          filename: 'static/fonts/[name][ext]'
        }
      },

    ],
  },

  //...
}

测试配置是否成!🚀

使用 asset 时如果是未配置大小限制,webpack 将按照默认条件,自动地在 resource 和 inline 之间进行选择:小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。

在这里我们配置的最大限制是10K

同理如果图片大于 10K 测自动使用 asset/resource 处理,如果图片小于 10K 测自动 asset/inline 处理。

测试看看是否于预期相似。

新建静态资源文件src/assets,导入引入两张大于小于 10 的图片

public/index.html 中新增两个测试 dev,并在src/index.css添加样式

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Webpack config Template</title>
</head>

<body>
  <div id="app"></div>
  <div class="demo-css">css demo</div>
  <div class="demo-less">less demo</div>
  <div class="demo-sass">sass demo</div>
  <div class="demo-img1">img 小于10K </div>
  <div class="demo-img2">img 大于10K </div>
</body>

</html>
css 复制代码
/* index.css */
.demo-img1,
.demo-img2 {
  width: 100vw;
  height: 100px;
  background-color: gray;
}

并在main.js中引用

javascript 复制代码
// main.js
// ...
import "./index.css";

import vueSvg1K from "./assets/vue.svg";
import yyxJpg208K from "./assets/yyx.jpg";

document.querySelector(".demo-img1").style.background = `url(${vueSvg1K})`;
document.querySelector(".demo-img2").style.background = `url(${yyxJpg208K})`;

执行打包脚本测试 ♻️

浏览器打开查看dist/index.html,发现小于限制的图像被转化为Base64格式,说明配置的type: 'assets'方式自动使用了asset/inline模块进行处理。结果如下图:

到这里静态资源的配置已经成功完成!✅


2.1.6.配置 JS 语法解析

在完成上述内容的配置后,前端三件套还差 JS 未配置。

由于目前 ECMAScript 语法标准与过去有所区别,为了向后兼容的 JavaScript 语法,便于运行在当前和旧版本的浏览器或其他环境中。所以需要用到Babel 对 JS 进行编译解析。具体配置如下:

依赖安装

bash 复制代码
pnpm add -D babel-loader @babel/core @babel/preset-env core-js

webapck 在过去的版本中,为了确保 JS 最新的特性代码在不同浏览器和环境中兼容 ,通常是直接使用@babel/polyfill库,为了减小体积这里采用按需引入库core-js代替

在根目录新建.babelrc.json文件,并添加相关配置:

json 复制代码
//#.babelrc.json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ]
}

"useBuiltIns": 'usage' : 配置后 Babel 会根据代码中实际使用的 ES6+ 特性自动检测并按需引入 polyfills
"corejs": 3 : 提供了对 JS 新标准特性的支持和 polyfill 功能
polyfills 可以用于填充各种不同的功能缺失,比如 Promise、fetch API、ES6+ 新语法等。

javascript 复制代码
// # webpack.config
// ...其他的配置项

module.exports = {
  // ...其他的配置项

  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/, //排除内容不解析
        use: {
          loader: "babel-loader",
        },
      },
    ],
  },
};

测试是否配置成功!🚀

在 main.js 中添加一些 ES6 的语法特性

javascript 复制代码
// # main.js
// 数组去重
const uniqueArr = [...new Set([1, 1, 1, 2, 3])];
console.log(uniqueArr);

// 箭头函数
const es6Fn = () => "is es6Fn";
console.log(es6Fn());

其实如果你在未配置,babel 前 ES6 语法特性也是能解析的,但是为了支持和适配目标环境的兼容,以及更高级的 JavaScript 特性或 API 还是需要配置 babel 来进行解析的。

到这里所以的基本配置以及完成啦!✅

呱唧呱唧!🤗

在基础案例的基础上,需要达到开发目的我们还需要配置环境变量、结合开发架构、JS/TS 配置,以及性能优化配置等内容。当然如果你还想要继续,请看下一章节。


2.2.基于 React 搭建

开发配置章节!本章将结合 React 实现基础开发的基本配置。

2.2.1.初始化配置文件

在开始配置前,先修改一下基础案例的文件目录结构。

  • build文件名修改为scripts
  • 新增 App.jsx 文件,并将main.js改为main.jsx
  • 删除 public/index.html多余的 div,只保留
  • 删除不需要的文件index.sass,并将index.css改为App.css 或 .less

最终目录结构如下:

目录结构修改完成!✅

接下来可以开始基于 React 开发的相关配置了


2.2.2.配置 React JSX/TSX 解析

安装 react 依赖

bash 复制代码
pnpm add -s react react-dom
pnpm add -D @babel/preset-react

# or TS
pnpm add -s @types/react @types/react-dom
pnpm add -D @babel/preset-typescript

配置 react 预设解析.babelrc.json

json 复制代码
//# .babelrc.json

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ],
    "@babel/preset-react"
    //@babel/preset-typescript  //如使用ts需添加
  ]
}

修改webpack.config.js 文件名为webpack.base.js ,并添加 .jsx后缀的解析

javascript 复制代码
// # webpack.base.js
// ....
module: {
  rules: [
    {
      test: /.(js|jsx)$/,  // ts : /.(ts|tsx)$/
        exclude: /node_modules/, //排除内容不解析
        use: {
        loader: "babel-loader",
      },
    },
  ]

  /**
  * @description resolve|解析配置
  * @param {String} extensions |文件后缀扩展
  * @param {object} alias |别名配置
  */
  resolve: {
    extensions: [ '.jsx', '.js'], // 如使用Ts测加上'.tsx', '.ts'
    alias: {
      "@": path.join(__dirname, '../src')
    },
  },

  // ....
}

如需使用Ts进行开发,测安装对于包并更改如上对应配置即可。

就目前的配置,以实现基本的 JSX/TSX 解析,为了像脚手架一样,让浏览器可以实时响应修改内容。需要使用webpack-dev-server库,进行热更新(HRM)相关配置,具体如下。


2.2.3.配置 HRM 热更新&环境变量

启用 HMR 很容易,且在大多数情况下不需要任何配置。只需在devServer开启 host

javascript 复制代码
module.exports = {
  // ...
  devServer: {
    // 开启 HMR 特性
    hot: true,
  },
};

安装热更新依赖

bash 复制代码
pnpm add -D webpack-dev-server webpack-merge

webpack-dev-server 创建两个服务器:提供静态资源的服务(express)和 Socket 服务, 浏览器拿到两个新的文件后,通过 HMR runtime 机制实现热更新。
webpack-merge : 用于合并配置项。可实现环境变量区分配置。

在实际开发中,可能存在诸多模式,而热更新(HRM)通常用于开发环境,在生产环境中,测更倾向于使用代码打包。具体配置如下:

修改基础配置

javascript 复制代码
// # scripts/webapck.base.js
//...省略其它部分

module.exports = isDev => ({
  mode: isDev ? "development" : "production",

  //...省略其它部分
});

webpack 内置优化了三个模式, mode:' none' | 'development' | 'production' 默认值设置为 production。这里通过不同环境配置文件传入的值,对不同环境模式进行区分。

环境区分配置,新增开发环境 scripts/webapck.dev.js、结合webpack-merge获取基础配置。并开启热更新 hot: true

javascript 复制代码
/**
 * @fileName scripts/webapck.dev.js
 * @description development|开发环境
 * @param getBaseCfg |使用基础配置
 * @param {Object} merge |将多个 webpack 配置文件合并成一个
 */

const getBaseCfg = require("./webpack.base");
const { merge } = require("webpack-merge");
const path = require("path");

module.exports = merge(getBaseCfg(true), {
  devtool: "source-map",
  devServer: {
    port: 8297,
    compress: false, //|压缩
    hot: true, //|热更新
    historyApiFallback: true, //| 解决404的问题
    static: {
      directory: path.join(__dirname, "../public"),
    },
  },
});

新增生产环境scripts/webapck.prod.js配置文件,并结合webpack-merge获取基础配置。

javascript 复制代码
/**
 * @fileName scripts/webapck.prod.js
 * @description productions|生产环境配置
 */
const getBaseCfg = require("./webpack.base");
const { merge } = require("webpack-merge");
const path = require("path");

module.exports = merge(getBaseCfg(false), {
  /**
   * @description 优化方案配置
   * @param minimizer |压缩方案配置
   * @param TerserPlugin |压缩JS
   * @param CssMinimizerPlugin |压缩css
   * @param splitChunks |代码切片 (https://webpack.docschina.org/plugins/split-chunks-plugin)
   */
  optimization: {
    splitChunks: {
      // 缓存配置
      chunks: "async",
      minSize: 20000,
      minChunks: 1,
      cacheGroups: {
        vendors: {
          priority: 1,
          test: /node_modules/,
          name: "vendors",
        },
        commons: {
          name: "commons",
          minChunks: 3,
        },
      },
    },
  },
});

2.2.4.最终测试

分别在App.jsxApp.cssindex.less 添加一些内容,具体如下

jsx 复制代码
// # App.jsx
import React, { useState } from "react";
import reactLogo from "./assets/react.svg";
import "./App.css";

const App = () => {
  const [count, setCount] = useState(0);

  return (
    <>
      <div>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>webpack5 + React</h1>
      <div className="card">
        <button onClick={() => setCount(count => count + 1)}>
          count is {count}
        </button>
      </div>
    </>
  );
};

export default App;
css 复制代码
/* App.css */
#root {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
  filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
less 复制代码
/* index.less */
:root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;

  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
}

a {
  font-weight: 500;
  color: #646cff;
  text-decoration: inherit;
}
a:hover {
  color: #535bf2;
}

body {
  margin: 0;
  display: flex;
  place-items: center;
  min-width: 320px;
  min-height: 100vh;
}

h1 {
  font-size: 3.2em;
  line-height: 1.1;
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: #1a1a1a;
  cursor: pointer;
  transition: border-color 0.25s;
}
button:hover {
  border-color: #646cff;
}
button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }
  a:hover {
    color: #747bff;
  }
  button {
    background-color: #f9f9f9;
  }
}

最后修改入口文件src/main.jsx配置

jsx 复制代码
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.less";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

修改package.json 脚本配置

json 复制代码
"scripts": {
  "dev": "webpack-dev-server -c scripts/webpack.dev.js",
  "start:prod": "webpack-dev-server -c scripts/webpack.prod.js",
  "build": "webpack --config scripts/webpack.base.js"
},

其它环境变量配置方法:

  • cross-env : 在命令行设置环境变量值NODE_ENV=xxx,并通过 Node 全局变量 process.env.NODE_ENV获取对应的变量值.
  • dotenv : 使用.env 文件和 dotenv 插件,可以使用.env 文件来定义环境变量,并使用 dotenv 插件来加载这些环境变量。

执行 start 脚本! ♻️

浏览器打开终端显示的链接,结果如下:

到这里基于 react 开发的 webpack 基本配置就完成啦!✅

呱唧呱唧!🤗


2.2.5.配置第三方库

至此已完成 React 开发看来的基础配,在开发中除了这些配置,可能还会使用到第三方的 UI 组件库,页面路由,以及状态管理库等。具体安装配置如下:

2.2.5.1.配置原子化 CSS ( Tailwind )

在根目录下安装,并初始化配置文件

bash 复制代码
pnpm add -D tailwindcss postcss

#初始化
npx tailwindcss init

配置tailwind.config.js,定制化

javascript 复制代码
// # tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

根据需求定制化全局 CSS。

在全局样式文件中scr/index.less 引入tailwindcss配置

css 复制代码
/* index.lesss */
@tailwind base;
@tailwind components;
@tailwind utilities;

最后只需要在 CSS 解析器配置**.postcssrc.json** 插件中引入 tailwindcss 库即可

javascript 复制代码
// # .postcssrc.json
{
    "plugins":{
        "autoprefixer": {},
        "tailwindcss": {}
    }
}

测试配置是否成功!🚀

执行pnpm dev查看结果如下图所示:

配置 tailwindcss 完成!✅


2.3.优化打包性能

在实际开发中当项目较大时,如果打包性能不佳你可能会体验过这样的情况,项目运行与打包期间格外的长。为了提升开发与用户体验,需要对打包速度与包的体积做相关优化。

2.3.1.打包速度优化

2.3.1.0.按需选择解析方案

在基础案例 css 拆分配置中有用到 mini-css-extract-plugin 抽取独立的 CSS 文件,该方式可提升生产环境 CSS 的加载速度,但对于开发环境 style-loader方式更方便热更新HRM

所以可以结合环境变量进行不同环境的按需加载。具体配置如下:

javascript 复制代码
//# webpack.base.js
// ....省略其他

module.exports = isDev => ({
  // ....省略其他

  module: {
    rules: [
      {
        test: /.(css)$/,
        use: [
          isDev ? "style-loader" : MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
        ],
      },
      {
        test: /.(less)$/,
        use: [
          isDev ? "style-loader" : MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "less-loader",
        ],
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          isDev ? "style-loader" : MiniCssExtractPlugin.loader,
          "css-loader",
          "postcss-loader",
          "sass-loader",
        ],
      },
    ],
  },

  // ....省略其他
});

为了规范样式和防止命名重复,可以采用 CSS Module 的形式。具体如下配置:

结合 oneOf 属性默认使用第一个配置,可防止使用到css module样式重复渲染。

javascript 复制代码
//# webpack.base.js
// ....省略其他

module.exports = (isDev) => ({
  // ....省略其他

	module: {
  	rules: [

      // 默认使用第一个配置
      oneOf: [
       	{
          test: /.module.(less|css)$/,
          include: [path.resolve(__dirname, "../src")],
          use: [
            isDev ? "style-loader" : MiniCssExtractPlugin.loader,
            {
              loader: "css-loader",
              options: {
                importLoaders: 2,
                // 开启 css modules,动态定义样式名称
                modules: {
                  localIdentName: "[path][name]__[local]--[hash:base64:4]",
                },
              },
            },
            "postcss-loader",
            "less-loader",
          ],
        },
        {
          test: /.(css)$/,
          use: [
            isDev ? "style-loader" : MiniCssExtractPlugin.loader,
            "css-loader",
            "postcss-loader",
          ],
        },

				// ....省略less,sass
      ]
    ]
  },

  // ....省略其他

})

测试优化配置是否成功!🚀

新建src/app.module.css 或.less

css 复制代码
// app.module.css
.modtitle {
  background-color: orange;
}

在实例中使用

javascript 复制代码
// App.jsx
// ...
import styles from "./app.module.css";

const App = () => {
  return (
    <>
      // ...
      <div className={styles.modtitle}>CSS Module</div>
    </>
  );
};

export default App;

运行开发环境脚本 ♻️

Css 按需优化配置完成!✅

2.3.1.1.缩小文件解析范围

结合内置属性限制打包时的解析范围:

  • exclude/include : 缩小loader解析范围
javascript 复制代码
//# webpack.base.js
// ....省略其他

module.exports = (isDev) => ({
  module: {
      rules: [
          {
            test: /.(js|jsx|ts|tsx)$/,
            include:path.resolve(__dirname,'../src'),
            use: {
              loader: "babel-loader",
            },
            exclude: "/node_modules",
          },
      ]
  }
)}
  • resolve.extensions 尽可能减少后缀尝试的可能性
  • alias 定义文件别名
javascript 复制代码
module.exports = (isDev) => ({
  //...
  resolve: {
    extensions: [".jsx", ".js", ".tsx", ".ts"],
    alias: {
      "@": path.join(__dirname, "../src"),
    },
  },
};
  • noParse 对完全不需要解析的库进行忽略
javascript 复制代码
module.exports = (isDev) => ({
  //...
  module: {
    noParse: /jquery/,
  },
};

2.3.2.打包体积优化

对包的大小进行限制或减小处理:

  • CSS 代码压缩
  • JS 代码压缩
  • Html 文件代码压缩
  • 文件大小压缩
  • 图片压缩
  • Tree Shaking
  • 代码分离
  • 内联 chunk

CSS 压缩

将所有样式中多余的空格删除并压缩在一起,

bash 复制代码
pnpm add -D css-minimizer-webpack-plugin
javascript 复制代码
/**
 * @fileName scripts/webapck.dev.js
 * @description development|开发环境
 * @param getBaseCfg |使用基础配置
 * @param {Object} merge |将多个 webpack 配置文件合并成一个
 */
const getBaseCfg = require("./webpack.base");
const { merge } = require("webpack-merge");
const path = require("path");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = merge(getBaseCfg(false), {
  optimization: {
    minimizer: [
      new CssMinimizerPlugin({
        parallel: true, // 并行压缩
      }),
    ],
  },
});

压缩结果如图

JS 压缩

bash 复制代码
pnpm add -D terser-webpack-plugin
javascript 复制代码
/**
 * @fileName scripts/webapck.dev.js
 * @description development|开发环境
 * @param getBaseCfg |使用基础配置
 * @param {Object} merge |将多个 webpack 配置文件合并成一个
 */
const getBaseCfg = require("./webpack.base");
const { merge } = require("webpack-merge");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");

module.exports = merge(getBaseCfg(false), {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true, // 并行压缩
        terserOptions: {
          compress: {
            pure_funcs: ["console.log", "console.warn"], //去除
          },
        },
      }),
    ],
  },
});

压缩结果

代码分离

将代码分离到不同的 bundle 中,之后可以按需加载,或者并行加载这些文件, 官方详情

javascript 复制代码
/**
 * @fileName scripts/webapck.dev.js
 * @description development|开发环境
 * @param getBaseCfg |使用基础配置
 * @param {Object} merge |将多个 webpack 配置文件合并成一个
 */
const getBaseCfg = require("./webpack.base");
const { merge } = require("webpack-merge");
const path = require("path");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = merge(getBaseCfg(false), {
  optimization: {
    splitChunks: {
      // 缓存配置
      chunks: "async",
      minSize: 20000,
      minChunks: 1,
      cacheGroups: {
        vendors: {
          priority: 1,
          test: /node_modules/,
          name: "vendors",
        },
        commons: {
          name: "commons",
          minChunks: 3,
        },
      },
    },
  },
});

其他优化配置待后续更新...


总结

本文通过对 webpack 的基本配置了解到其Loader,Plugin的配置。

  • 通过clean-webpack-plugin 实现样式独立抽取;
  • 通过 html-webpack-plugin 将压缩的JS注入到独立的THML文档;
  • 同时结合webpack5 的最方法 assets 实现对静态资源的加载。为了确保JS最新的特性代码在不同浏览器和环境中兼容还配置了 loader-babel JS解析方法。

在了解的基础案例配置的基础上,结合了React框架实现了基本开发配置

  • 其实现了开发中常见的环境变量配置
  • 并通过环境变量优化了不同环境对CSS的运用
  • 于此同时针对打包速度与包的体积大小做了相关优化处理

参考附录

webpack.docschina.org/concepts/
www.babeljs.cn/
vue3js.cn/

相关推荐
0wioiw01 分钟前
Flutter基础(前端教程④-组件拼接)
前端·flutter
花生侠26 分钟前
记录:前端项目使用pnpm+husky(v9)+commitlint,提交代码格式化校验
前端
一涯33 分钟前
Cursor操作面板改为垂直
前端
我要让全世界知道我很低调40 分钟前
记一次 Vite 下的白屏优化
前端·css
1undefined242 分钟前
element中的Table改造成虚拟列表,并封装成hooks
前端·javascript·vue.js
蓝倾1 小时前
淘宝批量获取商品SKU实战案例
前端·后端·api
comelong1 小时前
Docker容器启动postgres端口映射失败问题
前端
花海如潮淹1 小时前
硬件产品研发管理工具实战指南
前端·python
用户3802258598241 小时前
vue3源码解析:依赖收集
前端·vue.js