Skip to main content

webpack 搭建开发环境: React、TSX、Antd

webpack 是一个模块打包工具,它引入各种模块(包括但不限于 js),经过依赖关系处理、优化和压缩后,能生成特定平台运行的代码包(兼容浏览器的代码)。

本实验从 0 开始配置 webpack,搭建一个可用的 React+TSX+Antd 界面应用。

首先用 yarn 初始化一个项目。

yarn init

webpack CLI

webpack CLI 提供核心的打包功能,它会读取当前工作目录的配置文件开始打包的工作。

webpack 可以读取多种格式的配置文件,在这里我推荐用 TypeScript 编写配置文件,可以提供代码提示,减少出错。

安装 webpack

  • webpack 打包工具 webpack 的核心包
  • webpack-cli 打包工具 webpack CLI
  • ts-node 类似 node,但可以直接运行 .ts 代码
  • typescript TS 编译器
  • @types/node nodejs 类型声明,提供 nodejs 代码提示
  • @types/webpack webpack 类型声明,提供 webpack 配置代码提示
yarn add -D webpack webpack-cli ts-node typescript @types/node @types/webpack

安装完成后,初始化 TS,并新建配置文件 webpack.config.ts

yarn tsc --init

Entry: 配置入口

入口文件 Entry 类似于 go、c 中带 main() 的文件,执行 entry 应该启动运行整个项目。

在项目根目录新建入口文件 index.entry.js:

window.onload = () => console.log('hello')

编写文件 webpack.config.ts 配置 webpack:

  • clean 每次打包前清理上次结果,这里的输出结果存放在 dist
import path from 'path'
import webpack from 'webpack'

const config: webpack.Configuration = {
  entry: {
    index: { import: './index.entry.js' },
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  },
}

export default config

执行 yarn webpack -c webpack.config.ts 后,可以在 dist 看到打包结果。

Loader: 引入其他格式的代码

webpack 本身只支持解析 js,为了能读取并引入 .ts.tsx.css 的代码,webpack 提供了 Loader 的概念。

配置 Loader 需要告诉 webpack 读取哪些文件(以正则判断,哪些文件用哪些 Loader)

加载 CSS 样式模块

  • style-loader 可以处理样式,然后把css写到js里面,用js处理css。不会解析任何样式文件。
  • mini-css-extract-plugin 可以处理样式,然后直接输出到指定的css文件。不会解析任何样式文件。
  • css-loader 可以引入并解析 .css 模块文件。

mini-css-extract-plugin 可以把样式单独存放成一个文件,推荐使用。

style-loader 把CSS以字符串形式写到JS里面,再调用 CSS API 动态添加样式。

yarn add -D mini-css-extract-plugin css-loader

配置文件 webpack.config.ts 添加 CSS Loader:

+import MiniCssExtractPlugin from 'mini-css-extract-plugin'

const config: webpack.Configuration = {
  entry: {...},
  output: {...},
+ module: {
+   rules: [
+     {
+       test: /\.css$/i,
+       use: [{ loader: MiniCssExtractPlugin.loader }, { loader: 'css-loader' }],
+     },
+   ],
+ },
}

加载 JS(X)、TS(X) 模块

JS 的代码格式受到浏览器的影响,如 箭头函数 在一些浏览器无法运行。

() => console.log();

Babel 就是用来解决这个问题的,它可以把这个箭头函数编译成:

function() { console.log() }

Babel 支持插件扩展和整合包,一般开发环境可以直接引入 preset(预设、整合包),需要一些扩展的JS功能时,可以配置插件。

  • babel-loader 给 webpack 专用的 Babel Loader。
  • @babel/core Babel 核心包
  • @babel/preset-env Babel 整合包,包含常用的兼容性插件,Babel 项目基本都装
  • @babel/preset-typescript Babel TS整合包,能让 Babel 解析 TS 文件。
  • @babel/preset-react Babel React整合包,能让 Babel 解析 React 组件。
yarn add -D babel-loader @babel/core @babel/preset-env @babel/preset-typescript @babel/preset-react

安装完毕后,根目录新建文件 babel.config.js,Babel 会在运行时读取。

module.exports = {
  presets: [
    "@babel/preset-env",
    "@babel/preset-typescript",
    "@babel/preset-react"
  ]
}

配置文件 webpack.config.ts 添加 Babel Loader:

Babel Loader 需要 exclude 去掉 node_modules 第三方包的代码,因为第三方包在分发时,js 代码就已经是编译过的,不需要再用 Babel 再次编译,否则会拖慢编译速度。

const config: webpack.Configuration = {
  entry: {...},
  output: {...},
  module: {
    rules: [
      { test: /\.css$/i, ... },
+     {
+       test: /\.(m?js|jsx?|tsx?)$/,
+       exclude: /node_modules/,
+       use: { loader: 'babel-loader' },
+     },
    ],
  },
}

加载图片、文档资源

webpack 5 起开箱自带资源管理,如引入图片,会在打包结果附上 URL。

配置文件 webpack.config.ts:

const config: webpack.Configuration = {
  entry: {...},
  output: {...},
  module: {
    rules: [
      { test: /\.css$/i, ... },
      { test: /\.(m?js|jsx?|tsx?)$/, ... },
+     {
+       test: /\.(png|svg|jpg|jpeg|gif)$/i,
+       type: 'asset/resource',
+     },
    ],
  },
}

Plugin: 扩展 webpack 功能

webpack 的官方文档已解释 Plugin 的重要性,插件可以用于实现辅助代码注入、环境变量定义、目标代码压缩等很多的功能。

这里用到的插件如下:

  • [自带] ProgressPlugin 让 webpack 编译的时候可以显示进度
  • [自带] DefinePlugin 可以注入变量,如注入 ENV="prod",则生成目标代码时替换:
    - console.log(ENV)
    + console.log("prod")
    
  • [terser-webpack-plugin] TerserPlugin 生成目标代码时,执行 Terser 压缩
  • [html-webpack-plugin] HtmlWebpackPlugin 以模板 HTML 生成目标 HTML
  • @types/html-webpack-plugin 提供 HtmlWebpackPlugin 配置的代码提示
yarn add -D terser-webpack-plugin html-webpack-plugin @types/html-webpack-plugin

HTML 模板

安装完成后,根目录新建 HTML 模板文件 index.template.html:

body 这里,预定了一个 div 占位符,下文会描述一段 React 代码,把组件渲染在这个 div 上。

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>LuoyzHouseReactApp</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
  <div id="app"></div>
</body>
</html>

增加 webpack Plugins 配置

配置文件 webpack.config.ts 新增插件配置:

+ import HtmlWebpackPlugin from 'html-webpack-plugin'

const config: webpack.Configuration = {
  entry: {...},
  output: {...},
  module: {...},
+ plugins: [
+   new webpack.ProgressPlugin(),
+   new webpack.DefinePlugin({
+     'process.env.NODE_ENV': JSON.stringify(
+       process.env.NODE_ENV ?? 'production'
+     ),
+   }),
+   new HtmlWebpackPlugin({
+     template: './index.template.html', // 模板文件
+     filename: '[name].html', // name 等同Entry名称,这里入口为 index,则生成 index.html
+   }),
+   new MiniCssExtractPlugin(), // 除了添加它的 Loader,还要应用它的插件
+ ],
}

现在执行 yarn webpack -c webpack.config.ts 编译打包,可以在 dist/ 目录看到模板 HTML 输出,并在 script 标签中引用了入口 Entry 的 JS 代码,且这个 JS 代码也已经编译过了。

devServer: 配置开发服务器

devServer 是 webpack 的热更新服务器,当文件保存时,可以触发重新编译。相比静态命令行编译,它有这些优点:

  • 常驻: 常驻端口,浏览器可以直接访问
  • 热更新: 代码修改后,会触发部分模块的重新编译
  • 及时反应: 重编译后,会通过 websocket 通知浏览器更新页面

综上,借助 devServer 可以把页面和代码模块的改动实时显示出来。

devServer 是 webpack 的一个独立的功能,这里额外安装以下包:

  • webpack-dev-server devServer 功能包
  • @types/webpack-dev-server devServer 类型声明,提供代码提示
yarn add -D webpack-dev-server @types/webpack-dev-server

配置文件 webpack.config.ts 新增 devServer (wds) 配置:

+import wds from 'webpack-dev-server'

const config: webpack.Configuration = {
  entry: {...},
  output: {...},
  module: {...},
  plugins: [...],
+ devServer: {
+   hot: true, // 启用部分模块的热重载后,浏览器不需要每次都刷新页面了
+   port: 9000, // 端口号 9000
+ } as wds.Configuration
}

配置完成,现执行 webpack serve 指令可以启动开发服务器。浏览器打开 localhost:9000 即可看到编译结果。

yarn webpack serve -c webpack.config.ts

编写 React 应用

现在开发环境准备好了,开始编写一个 React 的 web 应用。选型蚂蚁金服的 UI 框架 Ant Design

yarn add react @types/react antd

编写界面 App.tsx

新建一张图片 src/mineavatar.jpeg

新建文件 src/App.module.css,编写标题样式。

.title {
  font-weight: bold;
  font-style: italic;
}

新建文件 src/App.tsx,编写界面代码。

import React, { useState } from 'react'
import { Button, Space, Row } from 'antd'
import mineavatar from './mineavatar.jpeg' // 引入图片模块,解释成一个 URL
import styles from './App.module.less' // 引入样式模块,解释成 { title: string }

const App = () => {
  const Greetings = ['Hello', '你好', 'Bonjour', 'Bon不jour了']
  const [index, setIndex] = useState(0)
  return (
    <Row justify="center">
      <Space direction="vertical" align="center">
        <h1 className={styles['title']}>LuoyzHouse, {Greetings[index % Greetings.length]}</h1>
        <img src={mineavatar} />
        <br />
        <Button onClick={() => setIndex(index + 1)}>打招呼</Button>
      </Space>
    </Row>
  )
}
export default App

定义好了一个 React 组件,现在在入口代码处把组件挂载到 DOM 上。

编写入口代码

index.entry.js 改名为 index.entry.tsx,因为在 Loader 配置阶段,已经让 webpack Babel 具备识别 TypeScript 代码的功能了。

引入 React DOM,让组件挂载到浏览器的 DOM 树上。

yarn add react-dom @types/react-dom

重新编写入口代码。

import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './src/App'
import 'antd/dist/antd.min.css' // 全局 css

const root = createRoot(document.getElementById('app')!)
root.render(<App />) // 挂载 App 组件

自动引用 React

Babel 会把 JSX 渲染的逻辑改写成 JS 调用的方式:

<div style={{ width: 100 }} />
// Babel 转译后
// React.createElement('MyComponent', { style: 100 })

转译后的代码会引用 React,所以在渲染 JSX 的同时,必须显式引入 React:

// 不显式引入会导致报错: React not defined
import React from 'react'

const elem = <div style={{ width: 100 }} />

Babel 插件 babel-plugin-auto-import 可以给每个模块自动引入 React。

yarn add -D babel-plugin-auto-import

安装完毕后,babel.config.js 增加 auto-import 插件:

module.exports = {
  presets: [...],
+  plugins: [
+    ["babel-plugin-auto-import", {
+      declarations: [
+        { default: "React", "path": "react" }
+      ]
+    }]
+  ]
}

webpack 修改入口配置

配置文件 webpack.config.ts 修改入口配置:

const config: webpack.Configuration = {
  entry: {
-    index: { import: './index.entry.js' },
+    index: { import: './index.entry' },
  },
  output: {...},
  module: {...},
  plugins: [...],
  devServer: {...},
}

Resolve: 让 webpack 尝试其他后缀的文件

在上方的 React 代码,它引入了 src/App

import App from './src/App'

但是 src/App 实际上并不存在,而应该引入 src/App.tsx

webpack 如果找不到 src/App,它会尝试找这些文件或目录:

  • src/App.js
  • src/App.json
  • src/App.wasm
  • src/App/

修改 webpack 配置的 resolve,可以让 webpack 尝试搜索其他后缀的文件,从而使 src/App 能找到 src/App.tsx

配置文件 webpack.config.ts 修改 Resolve 配置:

设置 .js.json.jsx.tsx.ts 后缀搜索,对于 src/App,能找到以下文件:

  • src/App.js
  • src/App.json
  • src/App.jsx
  • src/App.tsx
  • src/App.ts
const config: webpack.Configuration = {
  entry: {...},
  output: {...},
  module: {...},
  plugins: [...],
  devServer: {...},
+ resolve: {
+   extensions: ['.js', '.json', '.jsx', '.tsx', '.ts'],
+ },
}

启动 webpack 开发服务器,即可看到页面的效果。

webpack + React 热重载效果

打包应用

webpack 配置文件的 optimization 提供优化定制,目标代码的其中一个特点是压缩,在这里可以用 TerserPlugin(高效率JS压缩混淆工具) 进行压缩。

同时为了避免启用了压缩影响开发环境速度,可以引入一个环境变量控制压缩开关。

配置文件 webpack.config.ts 修改压缩配置:

+import TerserPlugin from 'terser-webpack-plugin'

const config: webpack.Configuration = {
  entry: {...},
  output: {...},
  module: {...},
  plugins: [...],
  devServer: {...},
  resolve: {...},
+ optimization: {
+   minimize: process.env.NODE_ENV === 'production' && true,
+   minimizer: [new TerserPlugin()],
+ },
}

用 cross-env 可以消除跨平台的环境变量语法差异:

yarn add -D cross-env

带环境变量 NODE_ENV 执行编译

yarn cross-env NODE_ENV=production webpack -c webpack.config.ts

整合项目

经过上方的一通配置,现在 webpack 已经分为开发环境和线上环境:

  • 开发环境 特点:不启用压缩,启用热更新,增量编译
  • 线上环境 特点:编译一次,生成尽量小的目标代码

编写 package.json

{
  ...
+ "scripts": {
+   "dev": "cross-env NODE_ENV=development webpack serve -c webpack.config.ts",
+   "prod": "cross-env NODE_ENV=production webpack -c webpack.config.ts"
+ },
  ...
}

配置文件 webpack.config.ts 指定 webpack 模式:

webpack 会根据 mode(none、production、development)的值进行模块处理和优化。

const config: webpack.Configuration = {
+ mode: process.env.NODE_ENV === "production" ? "production" : "development",
  entry: {...},
  output: {...},
  module: {...},
  plugins: [...],
  devServer: {...},
  resolve: {...},
  optimization: {...},
}

搭建结果

至此已顺利搭建了一个 webpack 的开发环境。

# 启动开发环境
yarn dev
# 打包
yarn prod

项目打包后,结果存放于 dist/ 目录,可以使用 npm包 serve 启动 HTTP 文件服务,给浏览器访问。

# 安装
yarn add -D serve

# 运行
yarn serve dist/

附录

webpack Official (https://webpack.js.org/)

Ant Design Official (https://ant.design/)

下载开发环境

Github Repo