likes
comments
collection
share

实践!使用Webpack搭建【React + TS】开发环境

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

大家好我是来蹭饭,一个会点儿吉他和编曲,绞尽脑汁想傍个富婆的摸鱼大师。

回顾咱们webpack系列的历程,行程已经过半,前两篇文章是备受好评,今天总算来到了我们的实战篇——使用webpack搭建React的开发环境:

本篇将结合前两篇章的知识点进行实践,如果对webpack的 基础进阶 相关知识了解不多的话,建议先去阅读这两篇文章,再来进行本次实战。

一. 前言

本次实战将以 webpack 5.74.0,react 18.2.0 为地基,从 搭建基础开发环境优化环境配置 两个维度跟大家一起从0到1完成脚手架的搭建。还是老规矩,相关的案例代码已全部上传至 Git ,欢迎自取,不嫌麻烦的话欢迎点个star。接下来闲言少叙,大家坐稳扶好,我们发车!

二. 搭建基础开发环境

2.1 前置工作

本章节将按照 entry,output,loader,plugin,其他配置 的顺序带大家搭建一个基础的React开发环境。在正式开始之前我们先做好如下前置工作:

  • 步骤1:  安装 webpackwebpack-cli

yarn add webpack webpack-cli -D

  • 步骤2: 调整项目结构
├─ public (存放静态资源)
│  ├─ favicon.ico
│  └─ index.html
├─ src(存放项目资源)
│  ├─ App.tsx
│  └─ index.tsx
├─ webpack(存放webpack的配置)
│  ├─ webpack.config.js
├─ .gitignore
├─ package.json
└─ README.md
  • 步骤3: 配置 package.json 启动服务的脚本命令
"scripts": {
    "start": "webpack-dev-server --config ./webpack/webpack.config.js"
  },
  • 步骤4: 配置 public/index.html 的内容
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>webpack-react</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

到这里前置工作就准备完毕了,其他需要安装的依赖等具体场景分析之后进行相应的解决。待后续所有配置工作完成后,再启动服务查看页面是否渲染成功。

下面我们开始配置 webpack.config.js ,本篇默认大家对webpack有一定的了解,配置项不做过多赘述如有不了解处可查看 基础篇进阶篇 进行查漏补缺。

2.2 配置entry

由于后面我们会有大量路径的获取操作,这里我们引入node的path模块,封装一个转换路径的 resolvePath 函数后进行entry的配置。

const path = require('path')

// 相对路径转绝对路径
const resolvePath = _path => path.resolve(__dirname, _path)

module.exports = {
  entry: resolvePath('../src/index.tsx')
}

2.3 配置output

指定 dist 为bundle输出的路径。

module.exports = {
  // ...
  output: {
    path: resolvePath('../dist'),
    clean: true,
    filename: 'scripts/[name].js'
  }
}

2.4 配置loader

本小节我们通过安装和配置loader来解析 样式资源,图片/字体资源,js/ts,jsx/tsx

2.4.1 解析样式

本案例中我们需要webpack能识别 css,less,sass 等样式文件,因此需要安装如下loader:

yarn add less less-loader sass sass-loader css-loader style-loader -D

安装完成后对 webpack.config.js 进行配置:

module.exports = {
  // ...
  module: {
    rules: [{
      test: /\.css$/,
      use: [
        'style-loader',
        'css-loader'
      ]
    }, {
      test: /\.less$/,
      use: [
        'style-loader',
        'css-loader',
        'less-loader'
      ]
    },{
      test: /\.s[ac]ss$/,
      use: [
        'style-loader',
        'css-loader',
        'sass-loader'
      ]
    }]
  }
}

2.4.2 解析图片/字体

在webpack5之前,处理图片,字体类资源时通常需要使用如下3个loader:

名称作用
raw-loader将文件导入为字符串
url-loader将文件作为 data URI 内联到 bundle 中
file-loader将文件发送到输出目录

webpack5之后可以使用资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换这些 loader:

名称作用
asset/resource导出一个单独的文件到bundle中,并导出 URL。之前通过使用 file-loader 实现
asset/inline不导出单独的资源到bundle中。导出一个资源的 data URI。之前通过使用 url-loader 实现
asset/source导出资源的源代码到bundle中。之前通过使用 raw-loader 实现
asset在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现

这些配置已被内置在webpack5中,我们仅需激活配置即可完成对应的功能,下面我们对 webpack.config.js 进行配置:

module.exports = {
  // ...
  module: {
    rules: [
    // ...
    {
      // 处理图片
      test: /\.(jpe?g|png|gif|webp|svg)$/,
      type: 'asset',
      generator: {
        filename: 'assets/img/[hash:10][ext]'
      },
      parser: {
        dataUrlCondition: {
          // 小于60kb的图片会被base64处理
          maxSize: 60 * 1024 
        }
      }
    }, {
      // 处理字体资源
      test: /\.(woff2?|ttf)$/,
      type: 'asset/resource'
    }]
  }
}

2.4.3 解析jsx/tsx

这部分的内容有点多,我们理一下整个过程再分步操作。首先我们搭建的是react开发环境,这就少不了react相关的依赖 react,react-dom

其次我们要解析 ts,tsx 这就需要使用 babel-loader,因此配合babel-loader的配置文件 .babelrc 和配合 ts 的 tsconfig.json 文件也要被创建。

另外需要特别注意在写tsx函数组件时,我们会指定组件的类型React.FC,这个类型由 @types/react,@types/react-dom 提供,因此这些依赖也要被安装,否则后续编译会报错。

过程理顺后我们分步骤来操作。

  • 步骤1:  安装 react,react-dom

yarn add react react-dom

reactreact-dom
react 核心库用于支持 react 操作 DOM
  • 步骤2:  安装 @types/react,@types/react-dom

yarn add @types/react @types/react-dom -D

  • 步骤3:  安装 babel-loader 以及react的babel官方预设 @babel/preset-react 相关的依赖

yarn add @babel/core babel-loader babel-preset-react-app @babel/preset-react -D

  • 步骤4:  配置webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [
    // ...
    {
      test: /\.(js|jsx|ts|tsx)$/,
      // 只处理 src 下的文件,排除其他如 node_modules 的处理
      include: resolvePath('../src'),
      loader:'babel-loader',
      options: {
        // 开启 babel 缓存
        cacheDirectory: true,
        // 关闭缓存压缩
        cacheCompression: false
      }
    }]
  }
}
  • 步骤5:  调整目录结构新增babel和ts的配置文件 .babelrc,tsconfig.json
├─ public
│  ├─ favicon.ico
│  └─ index.html
├─ src
│  ├─ App.tsx
│  └─ index.tsx
├─ webpack
│  ├─ webpack.config.js
├─ .babelrc
├─ tsconfig.json
├─ .gitignore
├─ package.json
└─ README.md
  • 步骤6:  配置 .babelrc
{
  "presets": ["react-app"]
}
  • 步骤7:  配置 tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src"
  ]
}

到这里一个基本的开发环境所需的loader就安装并配置完毕了,接下来我们进入plugin的安装和配置。

2.5 配置plugin

配置一个基础的react开发环境,plugin我们只需要用到 html-webpack-plugin,eslint 即可。

2.5.1 配置HtmlWebpackPlugin

  • 步骤1:  安装 html-webpack-plugin

yarn add html-webpack-plugin -D

  • 步骤2:  配置 webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  //...
  plugins: [
    new HtmlWebpackPlugin({
      template: resolvePath('../public/index.html'),
    }),
  ]
}

2.5.2 配置EsLint

使用eslint除了安装依赖, 配置 webpack.config.js 以外,还需要创建eslint校验的配置文件下面我们开始逐步操作。

  • 步骤1:  安装 eslint,eslint-webpack-plugin

yarn add eslint eslint-webpack-plugin -D

  • 步骤2:  配置 webpack.config.js
const EsLintWebpackPlugin = require('eslint-webpack-plugin')

module.exports = {
  //...
  plugins: [
    //...
    new EsLintWebpackPlugin({
      context: resolvePath('../src'),
      exclude:'node_modules',
      cache: true,
      cacheLocation: resolvePath('../node_modules/.cache/.eslintCache')
    })
  ]
}
  • 步骤3:  调整目录结构,新增 .eslintrc.js 文件
├─ public
│  ├─ favicon.ico
│  └─ index.html
├─ src
│  ├─ App.tsx
│  └─ index.tsx
├─ webpack
│  ├─ webpack.config.js
├─ .babelrc
├─ .eslintrc.js
├─ tsconfig.json
├─ .gitignore
├─ package.json
└─ README.md
  • 步骤4:  配置 .eslintrc.js 文件
module.exports = {
  // 继承官方规则
  extends: ['react-app'],
  parserOptions:{
    babelOptions: {
      presets: [
        ['babel-preset-react-app', false],
        'babel-preset-react-app/prod'
      ]
    }
  }
}

2.6 配置其他选项

接下来我们配置下 resolve,mode,devServer 选项。

  • 步骤1:  安装 webpack-dev-server

yarn add webpack-dev-server -D

  • 步骤2:  配置 webpack.config.js
module.exports = {
  //...
  resolve: {
    alias: {
      '@': resolvePath('../src')
    },
    extensions: [".js", ".ts", ".jsx", ".tsx"]
  },
  
  mode: 'development',
  
  devtool: 'cheap-module-source-map',

  devServer: {
    host: 'localhost',
    port: 8080,
    open: true,
    hot: true,
  },
}

到这里一个基本的react开发环境已经搭建完毕,接下来我们把这个环境启动起来

2.7 启动项目

2.7.1 启动基础环境

启动环境前我们先创建一些tsx组件,并把react的入口文件写好。

  • 步骤1:  设置 react 入口文件 src/index.tsx,react 18开始入口文件内容有变化如下所示
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
)

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)
  • 步骤2:  设置 src/App.tsx 的内容
import React from 'react'

const App: React.FC<any> = () => {
  return(
    <div className='App'>
      <h2>hello react</h2>
    </div>
  )
}

export default App

好了前置工作准备完毕,接下来我们使用 yarn start 启动服务查看结果:

实践!使用Webpack搭建【React + TS】开发环境

真是炸了个茄子了,这是什么报错?搞这么半天都白忙活了?

实践!使用Webpack搭建【React + TS】开发环境

不着急我们看下报错信息: Using babel-preset-react-app requires that you specify NODE_ENV or BABEL_ENV environment variables

哦!原来是运行react官方预设时,预设的运行环境需要获取全局 NODE_ENVBABEL_ENV 的配置。这里的环境配置跟webpack中 mode 的配置不一样,需要我们单独设置后,官方预设才能获取到。我们更改下 package.json 中的 script 脚本,增加环境相关的配置。

"scripts": {
    "start": "NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.js"
  },

再次运行 yarn start 查看结果。

实践!使用Webpack搭建【React + TS】开发环境

坏菜了!直接报错,windows不支持这样的写法。

在设置整体环境变量时,windows,Linux,MAC 的设置方法都不相同。这里推荐安装并使用 cross-env,它支持跨平台设置和使用环境变量,安装和使用步骤如下:

  • 步骤1:  安装 cross-env

yarn add cross-env -D

  • 步骤2:  配置 package.json 中的 script 脚本
  "scripts": {
    "start": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.js"
  },

配置完成后启动服务查看结果,一个基本的react开发环境就启动起来了:

实践!使用Webpack搭建【React + TS】开发环境

2.7.2 增加项目内容

接下来按步骤我们稍微丰富一下业务逻辑,引入路由并渲染一些数据,实现一个单的路由切换效果:

  • 步骤1:  安装 react-router-dom

yarn add react-router-dom

yarn add @types/react-router-dom -D

  • 步骤2:  修改 src 目录结构,新增 components 文件夹,新增几个组件;新增 assets 文件夹,往里面存入一张图片

实践!使用Webpack搭建【React + TS】开发环境

├─ src
│  ├─ assets
│  │  ├─ img
│  │  │  └─ img.png
│  ├─ components
│  │  ├─ Computer
│  │  │  └─ index.tsx
│  │  ├─ Phone
│  │  │  └─ index.tsx
│  ├─ App.tsx
│  ├─ index.scss
│  ├─ App.tsx
│  └─ index.tsx
  • 步骤3:  往各个tsx文件里填充内容

src/index.tsx:

import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
)

root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
)

src/App.tsx:

import React, { Suspense, lazy } from 'react'
import { Link, Routes, Route } from 'react-router-dom'
import './index.scss'

const Computer = lazy(() => import(/* webpackChunkName:"Computer", webpackPrefetch: true*/ './components/Computer'))
const Phone = lazy(() => import(/* webpackChunkName:"Phone", webpackPrefetch: true */ './components/Phone'))

const App: React.FC = () => {
  return (
    <div className='App'>
      <h2>hello react</h2>
      <div className='App-img'></div>
      <div className='App-content'>
        <ul>
          <li>
            <Link to="/Computer">Computer</Link>
          </li>
          <li>
            <Link to="/Phone">Phone</Link>
          </li>
        </ul>
      </div>

      <hr/>

      <Suspense fallback={<div>loading...</div>}>
        <Routes>
          <Route path="/Computer" element={<Computer />}></Route>
          <Route path="/Phone" element={<Phone />}></Route>
        </Routes>
      </Suspense>
    </div>
  )
}

export default App

src/components/Computer/index.tsx:

import React from "react";

const Computer: React.FC = () => {
  return (
    <div className="App-route-component">
      I am Computer
    </div>
  )
}

export default Computer

src/components/Phone/index.tsx:

import React from "react";

const Phone: React.FC = () => {
  const hello = (str: String) => {
    return str
  }
  return (
    <div className="App-route-component">
      I am {hello('Phone')}
    </div>
  )
}

export default Phone
  • 步骤4:  给 index.scss 写一个简单的样式
$baseCls: 'App';

.#{$baseCls} {
  &-img{
    width: 200px;
    height: 200px;
    background: url(./assets/img/img.png) no-repeat center /contain;
    transition: all .3s;
    &:hover{
      transform: scale(1.5);
    }
  }
  &-route-component{
    color: #4285f4;
    font-weight: bold;
  }
}

再次启动服务查看效果,当点击 Computer 组件时,由于路由组件的懒加载,文件还未加载完成时,屏幕显示 loading。

实践!使用Webpack搭建【React + TS】开发环境

加载完成后,显示 Computer 组件的内容。

实践!使用Webpack搭建【React + TS】开发环境

到这里,我们的演示效果就完成了,接下来我们看看当前还存在哪些待解决的问题。

2.7.3 待解决的问题

 tsx文件没有热更新功能。

如果我们在样式中,将图片的大小更改,页面会被无感刷新(HMR),如下所示。

实践!使用Webpack搭建【React + TS】开发环境

但是如果我们尝试更改 src/App.tsx 文件,给 hello react 加几个标点符号,看看会发生什么。

实践!使用Webpack搭建【React + TS】开发环境

注意图片左上角,浏览器的角标正在转圈,页面会被刷新而不是直接进行热更新,这正是问题所在。

我们的 HMR 功能支持样式文件等。但像 tsx 这样的文件,如何支持热更新呢?这里需要安装 tsx 热更新相关的依赖并做好配置,具体步骤如下:

  • 步骤1:  安装依赖

yarn add @pmmmwh/react-refresh-webpack-plugin react-refresh -D

  • 步骤2:  配置 webpack.config.js
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
// ...
module.exports = {
  // ...
  module: {
    rules: [
    // ...
    {
      test: /\.(js|jsx|ts|tsx)$/,
      // 只处理 src 下的文件,排除其他如 node_modules 的处理
      include: resolvePath('../src'),
      loader:'babel-loader',
      options: {
        // 开启babel缓存
        cacheDirectory: true,
        // 关闭缓存压缩
        cacheCompression: false,
        plugins: [
          'react-refresh/babel'
        ]
      }
    }]
  },

  plugins: [
    // ...
    new ReactRefreshWebpackPlugin()
  ],
  
 // ...
}

当完成配置后我们修改tsx文件,查看打印的日志,tsx也支持热更新了。

实践!使用Webpack搭建【React + TS】开发环境

接下来我们看下一个待解决的问题 前端路由刷新404

 前端路由刷新404

这是什么问题呢,我们直接演示一下,这里将路由切换成 Computer 组件。

实践!使用Webpack搭建【React + TS】开发环境

之后我们在当前路由下进行页面的刷新,页面 404 了。

实践!使用Webpack搭建【React + TS】开发环境

原因是这样的,当访问 localhost:8080/Computer 这个地址时,devServer 会去对应的 Sources 寻找资源。可是依据我们在 webpack.config.js 中配置的 output 属性可知,在 devServer启动的开发服务器中 并不直接存在 Computer 这样的资源。它正确的匹配路径应该是 localhost:8080/script/Computer 。因此,当出现错误匹配时需要返回 index.html,它会自动匹配路径,从而渲染路由。

实践!使用Webpack搭建【React + TS】开发环境

webpack的 devServer 中有对应的配置 historyApiFallback 它能够用 index.html 替代所有 404 响应的资源,以此解决问题。配置方式如下:

module.exports = {
  //...

  devServer: {
    host: 'localhost',
    port: 8080,
    open: true,
    hot: true,
    // 使用 index.html 代替所有404页面,解决使用H5的history API刷新页面导致404的问题
    historyApiFallback: true,
  },
}

配置完成后,我们在 Computer 路由下重新刷新页面,直接访问 Computer 时,返回了 index.html 的内容,之后它帮我们做好了路由匹配,完成了这次渲染。因此也不会发生404的现象了。

实践!使用Webpack搭建【React + TS】开发环境

到这里开发环境的内容搭建就彻底完毕了,接下来我们针对不同的环境,对webpack的配置进行优化。

三. 优化环境配置

本章节我们通过调整 webpack.config.js 针对开发/生产环境进行不同的优化。在此之前,我们可以看看react官方是如何写 webpack.config.js 的。本地使用react安装命令,安装一个项目。之后使用 eject 命令弹出 webpack 的配置然后进行查看。

如果你恰巧比较懒可以直接移步react官方团队的 Git仓库 ,查看官方 webpack 的配置。

由于代码较多这里我大概说下思路,官方用 isEnvDevelopment,isEnvProduction 标识开发和生产环境,依据环境的不同通过 .filter() 方法,过滤生产环境使用不到的 webpack 配置参数。

顺着官方的思路,我们对自己的 webpack.config.js 分以下三大步进行优化。优化完毕后,打包生产环境代码再运行查看结果。

3.1 获取环境标识

由于配置了 cross-env 的缘故。我们可以在 webpack.config.js 中直接获取当前所处环境,再依据不同的环境去做优化。

这里我们只取生产环境的标识 isEnvProduction

// 获取 cross-env 环境变量
const isEnvProduction = process.env.NODE_ENV === 'production'

3.2 抽取公共配置

之前对样式相关的loader处理中有大量重复的地方,另外针对不同的环境我们希望启用不同的样式loader,因此我们将样式相关的loader进行封装并安装一些依赖,接下来我们开始分步操作。

  • 步骤1:  安装依赖

yarn add postcss-loader postcss-preset-env cache-loader mini-css-extract-plugin -D

依赖名称作用
postcss-loader处理css的兼容问题
cache-loader缓存解析的css文件
mini-css-extract-plugin将css抽取成单独文件
  • 步骤2:  配置 webpack.config.js,使用刚刚安装的loader和plugin,并封装 getStyleLoaders 函数,用以返回解析对应样式文件时,使用的loader数组。之后只用 oneOf 增加唯一匹配loader的功能。
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

// 获取cross-env环境变量
const isEnvProduction = process.env.NODE_ENV === 'production'

const getStyleLoaders = (prevLoader) => {
  return [
    // 生产环境将css单独抽取成文件,开发环境直接只用 style-loader
    isEnvProduction ? MiniCssExtractPlugin.loader : 'style-loader',
    // 开发环境缓存css文件
    !isEnvProduction && 'cache-loader',
    'css-loader',
    {
      // 处理css兼容问题
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins: ['postcss-preset-env']
        }
      }
    },
    prevLoader
  ].filter(Boolean)
}

module.exports = {
  // ...
  module: {
    rules: [{
      oneOf: [{
          test: /\.css$/,
          use: getStyleLoaders()
        }, {
          test: /\.less$/,
          use: getStyleLoaders('less-loader')
        }, {
          test: /\.s[ac]ss$/,
          use: getStyleLoaders('sass-loader')
        },
        // ...
      ]
    }]
  },

  plugins: [
    // ...
    new MiniCssExtractPlugin({
      filename: isEnvProduction ? 'css/[name].[contenthash:10].css' : 'css/[name].css',
      chunkFilename: isEnvProduction ? 'css/[name].[contenthash:10].chunk.css' : 'css/[name].chunk.css',
    }),
  ],
  // ...
}
  • 步骤3:  配置package.json,辅助postcss-lodaer做兼容处理:
{
  // ...
  "browserslist": [
    "last 2 version",
    "> 1%",
    "not dead"
  ]
}

3.3 优化其他配置

接下来我们针对生产环境优化其他相关配置。

3.3.1 修改输出文件名

在生产环境下,我们希望文件缓存更加持久,并且在被修改后浏览器也能及时获取最新的文件资源。此时需要为生产环境文件名增加 contenthash 值来达到上述目的。

// ...
module.exports = {
  entry: resolvePath('../src/index.tsx'),

  output: {
    path: isEnvProduction ? resolvePath('../dist') : undefined,
    filename: isEnvProduction ? 'scripts/[name].[contenthash:10].js' : 'scripts/[name].js',
    chunkFilename: isEnvProduction ? 'scripts/[name].[contenthash:10].chunk.js' : 'scripts/[name].chunk.js',
    // 静态资源输出位置
    assetModuleFilename: 'assets/img/[hash:10][ext][query]',
    clean: true
  },

  // ...
}

3.3.2 修改loader

tsx的热更新功能仅在开发环境保留,生产环境可以剔除。

module.exports = {
  // ...

  module: {
    rules: [{
      oneOf: [
        //...
        {
          test: /\.(js|jsx|ts|tsx)$/,
          // 只处理 src 下的文件,排除其他如 node_modules 的处理
          include: resolvePath('../src'),
          loader: 'babel-loader',
          options: {
            // 开启babel缓存
            cacheDirectory: true,
            // 关闭缓存压缩
            cacheCompression: false,
            // 开发环境激活TSX的HMR
            plugins: [
              !isEnvProduction && 'react-refresh/babel'
            ].filter(Boolean),
          }
        }
      ]
    }]
  },

  // ...
}

3.3.3 修改plugin

生产环境和开发环境需要使用到的plugin也不相同,这里我们做一个区分,使用 isEnvProduction 标识配合 .filter(Boolean) 方法过滤生产环境不需要的plugin。

module.exports = {
  // ...

  plugins: [
    new HtmlWebpackPlugin({
      template: resolvePath('../public/index.html'),
    }),
    isEnvProduction && new MiniCssExtractPlugin({
      filename: isEnvProduction ? 'css/[name].[contenthash:10].css' : 'css/[name].css',
      chunkFilename: isEnvProduction ? 'css/[name].[contenthash:10].chunk.css' : 'css/[name].chunk.css',
    }),
    new EsLintWebpackPlugin({
      context: resolvePath('../src'),
      exclude: 'node_modules',
      cache: true,
      cacheLocation: resolvePath('../node_modules/.cache/.eslintCache')
    }),
    !isEnvProduction && new ReactRefreshWebpackPlugin()
  ].filter(Boolean),

  // ...
}

3.3.4 添加optimization

生产模式下,需要对代码进行压缩和分割操作。先安装压缩css代码的依赖。

yarn add terser-webpack-plugin -D

配置optimization

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
// terser-webpack-plugin 在 webpack5 中已被内置,开箱即用无需安装
const TerserPlugin = require("terser-webpack-plugin")

module.exports = {
  // ...
  
  optimization: {
    splitChunks: {
      chunks: 'all'
    },

    // 运行时的chunk文件
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}.js`
    },

    // 是否需要开启压缩
    minimize: isEnvProduction,

    // 压缩css
    minimizer: [
      new CssMinimizerPlugin(),
      new TerserPlugin()
    ],
  },

  // ...
}

3.3.5 其他配置优化

剩下将mode和devtool也改成适应环境的配置

module.exports = {
  // ...

  mode: isEnvProduction ? 'production' : 'development',

  devtool: isEnvProduction ? false : 'cheap-module-source-map',

  // ...
}

3.3.6 修改package.json

package.json 中添加打包命令 build

{
  "scripts": {
    "start": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack/webpack.config.js",
    "build": "cross-env NODE_ENV=production webpack --config ./webpack/webpack.config.js"
  },
}

全部工作完成后终端输入 yarn build 打包代码查看打包结果。

实践!使用Webpack搭建【React + TS】开发环境

运行index.html,打包结果正常运行。

实践!使用Webpack搭建【React + TS】开发环境

3.3.7 代码分割

经过我们对webpack的配置后,项目已经能正常运行react的开发和生产环境代码了。但是打住!别着急开香槟,这里还有个问题需要我们优化,我们先看看打包后的各个文件的体积。

实践!使用Webpack搭建【React + TS】开发环境

这个名为 343 的 bundle 体积是业务代码的百倍以上,它是所有 node_modules 打包后产生的 bundle 。如果以后我们的项目添加各种第三方依赖。,bundle 的体积会越来越大,从而影响用户体验。因此我们需要在这里做下代码分割处理。

配置 webpack.config.js 中的 optimization

module.exports = {
  // ...

  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // react react-dom react-router-dom 一起打包
        react: {
          test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
          name:'chunk-react',
          // 优先级,打包 react 相关依赖时,不会被打入 node_modules 中的chunk
          priority: 10
        },
        // node_modules 单独打包
        lib: {
          test: /[\\/]node_modules[\\/]/,
          name:'chunk-libs',
          priority: 1
        }
      }
    },

    // ...
  },

  // ...

  // 关闭性能分析,提升打包速度
  performance: false
}

配置完成后重新打包查看打包结果,原来 343 的 bundle 变成了 chunk-react,chunk-lib

实践!使用Webpack搭建【React + TS】开发环境

运行打包后的页面,查看各资源大小:

实践!使用Webpack搭建【React + TS】开发环境

这样原先相对较大的资源就被拆成了2个较小的资源。后续随着项目复杂程度不断增加,第三方依赖越来越多时,也能避免所有资源被打进一个bundle从而导致应用首页加载资源过慢的问题。

到这里所有的优化工作就配置完毕了。一个能承载你react项目的脚手架就这样搭建完成了。

四. 尾巴

赶在“银十”的尾巴,webpack实战篇终于正式上菜了。最近工作属实繁忙,9月底写完的文章拖到了现在才发布,咱们系列的最终章——— 原理篇 在后续工作不忙时自会新建文件夹,咱们有缘下次再见!

希望通过本次的实战,能让你把咱们webpack系列 基础篇进阶篇 的知识点串联起来。如果本篇文章对你有帮助,欢迎点赞,评论,收藏,你们的支持是我创作的最大动力。

我是来蹭饭,一个会点儿吉他和编曲,绞尽脑汁想傍个富婆的摸鱼大师,希望本次的分享对你有帮助。

实践!使用Webpack搭建【React + TS】开发环境