likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 4

一、前言

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

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

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

二、实践案例

实现 webpack 工程化打包的一些配置案例,完成案例将得到的收益:基础入门案例:了解 webpack 的相关基本配置搭建开发案例:脱离脚手架结合 React 框架实现开发案例配置,达到更了解工程开发的运作过程。

2.0.环境&版本

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

2.1.基础配置案例

新建一个干净的文件包赶紧开始吧!🐵【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置

2.1.1.初始化项目

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

pnpm init

安装 Webpack 及脚手架

pnpm add -D webpack webpack-cli

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

console.log("Hi! Webpack 你也会打篮球吗🤣");

测试是否初始化成功!🚀配置package.json脚本

"scripts": {
  "build": "webpack ./src/main.js"
},

执行脚本 ♻️

pnpm run build

【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 webpack 开箱即用,可以无需使用任何配置文件。这时查看根目录发现 dist文件夹 dist/main.js到这里说明已初始化成功!✅官方在线案例预览


2.1.2.出入口基础配置

完成上一步,说明咱已经实现了 webpack 的第一次打包 😄但其实我们还没有做任何配置,打包生成的内容只是 webpack 的默认配置。下面咱们来添加更丰富的配置。新建build文件夹,并添加webpack.config.js文件

/**
 * @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脚本配置

"scripts": {
  "build": "webpack --config build/webpack.config.js"
}

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

pnpm run build

【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 如图根目录下dist/main.js文件后缀多了插入的[hash:8]哈希值。说明已配置成功!✅


2.1.3.配置 HTML

上步打包好的内容,可通过<script src='./main.xxx.js'> 引入html文档并使用。但配置的动态命名哈希值,每次打包后都不一样,不可能每次都手动引入,所以引入插件来完成这个自动插入 <script > 标签的过程。

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

<!DOCTYPE html>
<html lang="en">

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

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

</html>

并安装插件

pnpm install -D html-webpack-plugin

继续添加配置

// 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"),
    }),
  ],
};

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

pnpm run build

【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 如图根目录下dist/index.html会自动引入打包好的文件。html 自动注入 js 配置成功!✅官方插件详情


2.1.4.配置 CSS 解析

clean-webpack-plugin 配置

基础三件套还差css没有配置,在配置前先解决一个问题。你可能会发现在修改main.js内容后,执行打包脚本时输出的 dist文件夹会有新的打包内容,并且之前打包的文件依然存在。如下图所示:【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 正常来讲这是不合理的,旧的内容应当被更新的内容替换。为了解决这个问题还需引入clean-webpack-plugin插件,在打包文件生成前清空输出的文件夹。

继续添加插件配置

webpack4

// webpack.config.js
...
const {CleanWebpackPlugin} = require('clean-webpack-plugin')

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

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

// 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
  },
  // ...
};

执行脚本测试配置是否成功!🚀【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 如图在修改main.js内容后,执行打包脚本时输出的 dist文件夹不再出现之前的内容。打包前清空输出文件夹目录配置成功!✅


css,less,sass 基本配置

接下来就是 CSS 的配置安装样式解析器loader

pnpm add -D style-loader css-loader

如要使用lesssass则需要再安装

pnpm add -D less less-loader

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

sass 配置官方详情

继续添加配置

// ...
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

<!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 样式文件,并在入口文件引入

/* index.css */
.demo-css {
  width: 100vw;
  height: 100px;
  background-color: orangered;
}
/* index.less */
.demo-less {
  width: 100vw;
  height: 100px;
  background-color: green;
}
/* index.sass */
.demo-sass
  width: 100vw
  height: 100px
  background-color: orange


// main.js
import "./index.css";
import "./index.less";
import "./index.sass";
console.log("hello webpack");

执行打包脚本测试 ♻️按传统的加载资源方式来讲,此时 html 上应该会引入一个样式标签,<style>对 css 资源加载。但查看打包好的dist/index.html文件你会发现,并没有<style>标签,浏览器打开发现运行。如下测试图所示:【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 运行打包后的 html 文件,发现<style>其实是有被引入的,这也说明了 webpack 并不是按传统的方式加载,它是以js 模块为主导,通过不同的loader 解析 js 中对应 import 的资源。

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


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

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

 pnpm add -D postcss-loader autoprefixer

继续添加配置

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

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

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

不做配置 autoprefixer 插件不起作用

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

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

测试配置是否成功!🚀在 index.css 文件添加测试样式

.demo-css {
  display: flex;
  transform: scale(0.5);
}

执行打包脚本测试 ♻️浏览运行打包好的dist/index.html文件,结果如下图:【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 如图不同浏览器厂商前缀已经加上!到这里说明你 Css 浏览器厂商前缀配置已经成功!✅


css 拆分配置

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

这样做的好处是:

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

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

pnpm add -D mini-css-extract-plugin

继续修改配置

// # 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 测试样式

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

执行打包脚本测试 ♻️查看打包文件夹dist,发现 css 已经配单独取出到指定的文件,结果如下图:【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 浏览运行打包好的dist/index.html文件,结果如下图:【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 但需要注意的是独立抽取出来的.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方法,继续配置

// # webpack.config.js
// ...

module.exports = {
  // ...

  module: {
    rules: [
      // ...
      //图像
      {
        test: /.(png|jpg|jepg|git|svg)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, //限制在 10kb
          }
        },
        generator: {
          filename: 'static/images/[name][ext]'
        }
      },
      // 视频
      {
        test: /.(mp4|mp3|webm)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024,
          }
        },
        generator: {
          filename: 'static/medias/[name][ext]'
        }
      }
      //字体
      {
        test: /.(woff2|eot|ttf|otf)$/,
        type: 'asset',
        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 的图片【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 public/index.html 中新增两个测试 dev,并在src/index.css添加样式

<!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>
/* index.css */
.demo-img1,
.demo-img2 {
  width: 100vw;
  height: 100px;
  background-color: gray;
}

并在main.js中引用

// 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模块进行处理。结果如下图:【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 到这里静态资源的配置已经成功完成!✅


2.1.6.配置 JS 语法解析

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

依赖安装

pnpm add -D babel-loader @babel/core @babel/preset-env core-js

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

在根目录新建.babelrc.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+ 新语法等。

// # webpack.config
// ...其他的配置项

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

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

测试是否配置成功!🚀在 main.js 中添加一些 ES6 的语法特性

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

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

【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 其实如果你在未配置,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

最终目录结构如下:【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 目录结构修改完成!✅接下来可以开始基于 React 开发的相关配置了


2.2.2.配置 React JSX/TSX 解析

安装 react 依赖

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

//# .babelrc.json

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

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

// # 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

module.exports = {
  // ...
  devServer: {
    // 开启 HMR 特性
    hot: true,
  },
};

安装热更新依赖

pnpm add -D webpack-dev-server webpack-merge

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

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

修改基础配置

// # 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

/**
 * @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获取基础配置。

/**
 * @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 添加一些内容,具体如下

// # 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;
/* 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);
  }
}
/* 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配置

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 脚本配置

"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 脚本! ♻️浏览器打开终端显示的链接,结果如下:【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 到这里基于 react 开发的 webpack 基本配置就完成啦!✅呱唧呱唧!🤗


2.2.5.配置第三方库

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

2.2.5.1.配置原子化 CSS ( Tailwind )

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

pnpm add -D tailwindcss postcss

#初始化
npx tailwindcss init

配置tailwind.config.js,定制化

// # tailwind.config.js

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

根据需求定制化全局 CSS。在全局样式文件中scr/index.less 引入tailwindcss配置

/* index.lesss */
@tailwind base;
@tailwind components;
@tailwind utilities;

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

// # .postcssrc.json
{
    "plugins":{
        "autoprefixer": {},
        "tailwindcss": {}
    }
}

测试配置是否成功!🚀执行pnpm dev查看结果如下图所示:【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 配置 tailwindcss 完成!✅


2.3.优化打包性能

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

2.3.1.打包速度优化

2.3.1.0.按需选择解析方案

在基础案例 css 拆分配置中有用到 mini-css-extract-plugin 抽取独立的 CSS 文件,该方式可提升生产环境 CSS 的加载速度,但对于开发环境 style-loader方式更方便热更新HRM。所以可以结合环境变量进行不同环境的按需加载。具体配置如下:

//# 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样式重复渲染。

//# 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

// app.module.css
.modtitle {
  background-color: orange;
}

在实例中使用

// App.jsx
// ...
import styles from "./app.module.css";

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

export default App;

运行开发环境脚本 ♻️【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置 Css 按需优化配置完成!✅

2.3.1.1.缩小文件解析范围

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

  • exclude/include: 缩小loader解析范围
//# 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 定义文件别名

module.exports = (isDev) => ({
  //...
  resolve: {
    extensions: [".jsx", ".js", ".tsx", ".ts"],
    alias: {
      "@": path.join(__dirname, "../src"),
    },
  },
};
  • noParse 对完全不需要解析的库进行忽略
module.exports = (isDev) => ({
  //...
  module: {
    noParse: /jquery/,
  },
};

2.3.2.打包体积优化

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

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

CSS 压缩

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

pnpm add -D css-minimizer-webpack-plugin
/**
 * @fileName scripts/webapck.prod.js
 * @description productions|生产环境
 * @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, // 并行压缩
      }),
    ],
  },
});

压缩结果如图【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置

JS 压缩

pnpm add -D terser-webpack-plugin
/**
 * @fileName scripts/webapck.prod.js
 * @description productions|生产环境
 * @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"], //去除
          },
        },
      }),
    ],
  },
});

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

代码分离

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

/**
 * @fileName scripts/webapck.prod.js
 * @description productions|生产环境
 * @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 的 type: asset 方法实现对静态资源的加载。为了确保JS最新的特性代码在不同浏览器和环境中兼容还配置了 loader-babel JS解析方法。

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

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

案例模板地址

模板已经封装到脚手架中,如需要模板源码,可以选择局部安装: norush-cli

npm i norush-cli -g

# or

npm i norush-cli -D

安装脚手架后,终端执行如下命令

  • -g 时直接执行
norush 
  • -D 时需要在添加执行脚本
"scripts": {
    "norush": "npx norush",
}
# 执行
npm run norush

选择项目模板: webpack-React 【Webpack5 实践篇】Webpack 5 TypeScript React 开发实践案例配置

拿到模板后如果不需要脚手架,可直接删除:

npm rm -rf norush-cli

参考附录

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