webpack5+react18+typescript脚手架搭建

1.项目创建

这里将使用pnpm

pnpm 使用独特的方法来安装包,避免重复包,从而加快安装和更新时
间。特别是,当安装共享依赖的多个包时, pnpm 只会为每个依赖安装一次,
除此之外它的优点还有: 更少的磁盘空间使用、更好地支持 monorepos、更好地支持对等依赖、更清晰的依赖树
# 初始化package.json文件
pnpm init

# 我的 pnpm 版本
pnpm -v
7.27.1
初始化完了会在根目录生成一个 package.json 文件
2.基本项目结构

在根目录新建基本

的项目结构:

├── build
| ├── webpack.base.ts # 公共配置
| ├── webpack.dev.ts # 开发环境配置
| └── webpack.prod.ts # 打包环境配置
├── public
│ └── index.html # html模板
├── src
| ├── App.tsx
| ├── App.css
│ └── index.tsx # react应用入口页面
└── package.json
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>webpack5-react-ts</title>
</head>

<body>
    <!-- 容器节点 -->
    <div id="root"></div>
</body>

</html>
3.引入react
安装依赖:
pnpm i react react-dom
# 声明依赖
pnpm i @types/react @types/react-dom -D
先将入口文件 src/index.tsx 写好:
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

// const root = document.getElementById('root');
const root = document.querySelector('#root')

if(root) {
    createRoot(root).render(<App />)
}
App.css
h2 {
    color: red;
}
以及 App.tsx
  import React from 'react'
    import './App.css'
    function App() {
        return <h2>Hello East_White</h2>
    }
    export default App
4.引入typescript
为什么要使用 typescript ?
它的优点如下:
1、 更好的代码质量: TypeScript 的静态类型系统可以帮助开发人员在编写代码时捕获错误,这可以 提高代码质量和稳定性。
2、 更好的可读性和可维护性: TypeScript 的静态类型系统和类可以提高代码的可读性和可维护性,特别是在大型项目中。这可以使代码更易于理解和修改。
3、 更好的 IDE 支持: TypeScript 具有出色的 IDE 支持,包括自动完成,语法突出显示和类型检查。这可以提高开发人员的生产力和准确性。
4、更好的可扩展性: TypeScript 支持面向对象编程,可以帮助开发人员创建复杂的数据类型和接
口,并使代码更易于扩展和维护。
5、更好的性能:由于 TypeScript 可以在编译时捕获错误,因此可以减少运行时错误并提高性能。
我们在项目中引入 typescript ,先安装依赖:
pnpm i typescript -D
pnpm i babel-loader ts-node @babel/core @babel/preset-react @babel/presettypescript @babel/preset-env core-js -D
初始化 tsconfig.json
npx tsc --init
# 如果全局安装了typescript,也可以通过下面的命令创建
tsc --init
初始化完了会在根目录生成一个 tsconfig.json 文件
5.webpack配置
安装依赖:
pnpm i webpack webpack-cli -D
配置 webpack.base.ts 文件:
import { Configuration } from 'webpack';
    const path = require("path");
    const baseConfig: Configuration = {
        entry: path.join(__dirname, "../src/index.tsx"), // 入口文件
        // 打包出口文件
        output: {
            filename: "static/js/[name].js", // 每个输出js的名称
            path: path.join(__dirname, "../dist"), // 打包结果输出路径
            clean: true, // webpack4需要配置clean-webpack-plugin来删除dist文件,webpack5内置了
            publicPath: "/", // 打包后文件的公共前缀路径
        },
        // loader 配置
        module: {
            rules: [],
        },
        resolve: {
            extensions: [".tsx", ".ts", ".jsx", ".js"],
        },
        // plugins 的配置
        plugins: []
    };

配置完了还会出现报红,那么我们就需要安装 @types/node 这个依赖:

pnpm i @types/node -D
另外因为我们在 App.tsx 中引入了 css 文件,所以还需要安装相关的 loader
pnpm i style-loader css-loader html-webpack-plugin -D
完善 webpack.base.ts
import { Configuration } from "webpack";
    import HtmlWebpackPlugin from "html-webpack-plugin";
    const path = require("path");
    const baseConfig: Configuration = {
        entry: path.join(__dirname, "../src/index.tsx"), // 入口文件
        // 打包出口文件
        output: {
            filename: "static/js/[name].js", // 每个输出js的名称
            path: path.join(__dirname, "../dist"), // 打包结果输出路径
            clean: true, // webpack4需要配置clean-webpack-plugin来删除dist文件,webpack5内置了
            publicPath: "/", // 打包后文件的公共前缀路径
        },
        // loader 配置
        module: {
            rules: [
                {
                    test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
                    use: {
                        loader: "babel-loader",
                        options: {
                            // 预设执行顺序由右往左,所以先处理ts,再处理tsx
                            presets: [
                                [
                                    "@babel/preset-env",
                                    {
                                        // 设置兼容目标浏览器版本,也可以在根目录配置.browserslistrc文件, babel- loader会自动寻找上面配置好的文件.browserslistrc
                                        targets: {
                                            browsers: ["> 1%", "last 2 versions", "not ie<= 8"] },
                                            useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加
                                            corejs: 3, // 配置使用core-js使用的版本
                                            loose: true,
                                    },
                                ],
                                // 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime":"automatic" 添加到配置中。
                                // 否则可能会出现错误:Uncaught ReferenceError: React is not
                                defined
                                ["@babel/preset-react", { runtime: "automatic" }],
                                "@babel/preset-typescript",
                            ],
                        },
                    },
                },
                {
                    test: /.css$/, //匹配 css 文件
                    use: ["style-loader", "css-loader"],
                },
            ],
        },
        resolve: {
            extensions: [".tsx", ".ts", ".jsx", ".js"],
        },
        // plugins
        plugins: [
            new HtmlWebpackPlugin({
                // 复制 'index.html' 文件,并自动引入打包输出的所有资源(js/css)
                template: path.join(__dirname, "../public/index.html"),
                // 压缩html资源
                minify: {
                    collapseWhitespace: true, //去空格
                    removeComments: true, // 去注释
                },
            }),
        ],
    };
    export default baseConfig
因为 webpack.base.ts 文件承载了基本的配置,随着 webpack 做的事情越来越多,会逐渐变得很庞 大,我们可以将其中的 babel-loader 相关的配置抽离出来进行管理。在根目录新建
babel.config.js
 module.exports = {
        // 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法
        presets: [
            [
                "@babel/preset-env",
                {
                    // 设置兼容目标浏览器版本,这里可以不写,babel-loader会自动寻找上面配置好的文件.browserslistrc
                    // "targets": {
                    // "chrome": 35,
                    // "ie": 9
                    // },
                    targets: { browsers: ["> 1%", "last 2 versions", "not ie <= 8"] },
                    useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加
                    corejs: 3, // 配置使用core-js使用的版本
                    loose: true,
                },
            ],
            // 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime": "automatic" 添加到配置中。
            // 否则可能会出现错误:Uncaught ReferenceError: React is not defined
            ["@babel/preset-react", { runtime: "automatic" }],
            "@babel/preset-typescript",
        ],
    };
然后在 webpack.base.ts 文件中,就可以将 babel-loader 配置简化成:
 // ...
    module: {
        rules: [
            {
                test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
                use: "babel-loader"
            },
            // ...
        ],
    },
    // ...
webpack.dev.ts
我们需要通过 webpack-dev-server 来启动我们的项目,所以需要安装相关的依赖:
pnpm i webpack-dev-server webpack-merge -D
配置开发环境配置: webpack.dev.ts

    import path from "path";
    import { merge } from "webpack-merge";
    import { Configuration as WebpackConfiguration } from "webpack";
    import { Configuration as WebpackDevServerConfiguration } from "webpack-devserver";
    import baseConfig from "./webpack.base";
    interface Configuration extends WebpackConfiguration {
        devServer?: WebpackDevServerConfiguration;
    }
    const host = "127.0.0.1";
    const port = "8082";
    // 合并公共配置,并添加开发环境配置
    const devConfig: Configuration = merge(baseConfig, {
        mode: "development", // 开发模式,打包更加快速,省了代码优化步骤
        devtool: "eval-cheap-module-source-map",
        devServer: {
            host,
            port,
            open: true, // 是否自动打开
            compress: false, // gzip压缩,开发环境不开启,提升热更新速度
            hot: true, // 开启热更新
            historyApiFallback: true, // 解决history路由404问题
            setupExitSignals: true, // 允许在 SIGINT 和 SIGTERM 信号时关闭开发服务器和退出进程。
            static: {
                directory: path.join(__dirname, "../public"), // 托管静态资源public文件夹
            },
            headers: { "Access-Control-Allow-Origin": "*" }, // HTTP响应头设置,允许任何
            来源进行跨域请求
        },
    });


    export default devConfig;
然后再 package.json 中添加启动脚本:
  "scripts": {
        "dev": "webpack serve -c build/webpack.dev.ts"
    },
正当我们准备启动项目的时候,发现还有错误报红
这时候只需要在 tsconfig.json 中加入一行 "jsx": "react-jsx" 即可:

    {
        "compilerOptions": {
        "target": "es2016",
        "esModuleInterop": true,
        "module": "commonjs",
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "skipLibCheck": true,
        "jsx": "react-jsx" // 这里改成react-jsx,就不需要在tsx文件中手动引入React了
    },
     "include": ["./src"]
}

完事之后运行 pnpm run dev 脚本启动项目,就可以看到页面跑出来了!

webpack.prod.ts

配置 webpack.prod.ts

import { Configuration } from "webpack";
import { merge } from "webpack-merge";
import baseConfig from "./webpack.base";

const prodConfig: Configuration = merge(baseConfig, {
    mode: "production", // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
});
export default prodConfig;
package.json 中添加:
"scripts": {
// ...
    "build": "webpack -c build/webpack.prod.ts"
},
运行 pnpm run build ,如果想看打包结果,可以通过一个小工具来查看:
# 如果之前使用npm,最简单的方法就是使用如下命令
npm i serve -g
# 如果是首次使用pnpm安装全局依赖,通过如下命令
pnpm setup
source ~/.zshrc
pnpm i serve -g
copy 静态资源
一般 public 文件夹都会放一些静态资源 , 可以直接根据绝对路径引入,比如图片、 css js 文件等,不 需要 webpack 进行解析,只需要打包的时候把 public 下内容复制到构建出口文件夹中,可以借助 copy-webpack-plugin 插件,安装依赖:
pnpm i copy-webpack-plugin -D
修改 webpack.base.ts

    // ...
    const baseConfig: Configuration = {
        // ...
        plugins: [
            new HtmlWebpackPlugin({
                title: "webpack5-react-ts",
                filename: "index.html",
                // 复制 'index.html' 文件,并自动引入打包输出的所有资源(js/css)
                template: path.join(__dirname, "../public/index.html"),
                inject: true, // 自动注入静态资源
                hash: true,
                cache: false,
                // 压缩html资源
                    minify: {
                    removeAttributeQuotes: true,
                    collapseWhitespace: true, //去空格
                    removeComments: true, // 去注释
                    minifyJS: true, // 在脚本元素和事件属性中缩小JavaScript(使用UglifyJS)
                    minifyCSS: true, // 缩小CSS样式元素和样式属性
                }
            })
        ],
    };
    export default baseConfig;
开发环境已经在 devServer 中配置了 static 托管了 public 文件夹,在开发环境使用绝对路径可以访问 到 public 下的文件,但打包构建时不做处理会访问不到,所以现在需要在打包配置文件
webpack.prod.ts 中新增 copy 插件配置。
    import path from "path";
    import { Configuration } from "webpack";
    import { merge } from "webpack-merge";
    import CopyPlugin from "copy-webpack-plugin";
    import baseConfig from "./webpack.base";
    const prodConfig: Configuration = merge(baseConfig, {
        mode: "production", // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
        plugins: [
            new CopyPlugin({
                patterns: [
                    {
                        from: path.resolve(__dirname, "../public"), // 复制public下文件
                        to: path.resolve(__dirname, "../dist"), // 复制到dist目录中
                        filter: (source) => !source.includes("index.html"), // 忽略index.html
                    },
                ],
            }),
        ],
    });
    export default prodConfig;
测试一下,在 public 中新增一个 favicon.ico 图标文件,在 index.html 中引入:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <!-- 绝对路径引入图标文件 -->
    <link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>webpack5-react-ts</title>
</head>

<body>
    <!-- 容器节点 -->
    <div id="root"></div>
</body>

</html>
再执行 pnpm run build 打包,就可以看到 public 下的 favicon.ico 图标文件被复制到 dist 文件中
了。
有的人可能会遇到 favicon 不显示的问题,提供以下思路进行处理:
1. head 添加了 favicon 没生效,但是新开标签页直接访问图片可以访问到。
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"> 1
2. 改图片尺寸格式依然没显示,直接复制其他网站正常 favaicon.ico ,排除格式的原因。
3. 语法问题,尝试各种写法。
<link rel="icon" href="/favicon.ico"">
<link rel="shortcut" href="/favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
4. 添加版本号, favicon 正常显示,排除语法原因。
<link rel="shortcut icon" href="/favicon.ico?v=1.0" type="image/x-icon"> 1
5. 继续深入查找问题, F12 查看 html network ,发现 favicon.ico 的请求没有。
6. 没有 favicon 的请求,不会显示图片,怀疑是浏览器缓存问题,清除缓存后依然没有请求。
7. 打开其它浏览器,发现 favicon 正常显示,进一步确定是浏览器缓存。
8. 关闭标签页,重新打开浏览器,最后 favicon.ico 正常显示。
9. chrome 浏览器,前端资源经常会产生缓存问题,清空或禁用缓存也不一定有效。可以尝试重启浏览器,或者重启电脑。
6.配置环境变量
corss-env + DefinePlugin
环境变量按作用分为两种:
1. 区分是开发模式还是打包构建模式
2. 区分项目业务环境,开发 / 测试 / 预测 / 正式环境
区分开发模式还是打包构建模式可以用 process.env.NODE_ENV ,因为很多第三方包里面判断都
是采用的这个环境变量。
区分项目接口环境可以自定义一个环境变量 process.env.BASE_ENV ,设置环境变量可以借助
cross-env webpack.DefinePlugin 来设置。
cross-env :运行跨平台设置和使用环境变量的脚本,兼容各系统的设置环境变量的包
webpack.DefinePlugin webpack 内置的插件 , 可以为业务代码注入环境变量
安装 cross-env
pnpm i cross-env -D
修改 package.json scripts
 "scripts": {
        "dev:dev": "cross-env NODE_ENV=development BASE_ENV=development webpack
        serve - c build / webpack.dev.ts",
        "dev:test": "cross-env NODE_ENV=development BASE_ENV=test webpack serve
            - c build / webpack.dev.ts",
        "dev:pre": "cross-env NODE_ENV=development BASE_ENV=pre webpack serve -c
        build / webpack.dev.ts",
        "dev:prod": "cross-env NODE_ENV=development BASE_ENV=production webpack
        serve - c build / webpack.dev.ts",
        "build:dev": "cross-env NODE_ENV=production BASE_ENV=development webpack
            - c build / webpack.prod.ts",
        "build:test": "cross-env NODE_ENV=production BASE_ENV=test webpack -c
        build / webpack.prod.ts",
        "build:pre": "cross-env NODE_ENV=production BASE_ENV=pre webpack -c
        build / webpack.prod.ts",
        "build:prod": "cross-env NODE_ENV=production BASE_ENV=production webpack
            - c build / webpack.prod.ts"
    },
process.env.NODE_ENV 环境变量 webpack 会自动根据设置的 mode 字段来给业务代码注入对应的 development 和 prodction ,这里在命令中再次设置环境变量 NODE_ENV 是为了在 webpack 和 babel 的配置文件中访问到。
webpack.base.ts 中打印一下设置的环境变量:
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV)
执行 pnpm run build:dev ,就可以在控制台打印出:
// NODE_ENV production
// BASE_ENV development
当前是打包模式,业务环境是开发环境,这里需要把 process.env.BASE_ENV 注入到业务代码里面,就可以通过该环境变量设置对应环境的接口地址和其他数据,要借助 webpack.DefinePlugin 插件
修改 webpack.base.ts
  import { DefinePlugin } from 'webpack'
    module.export = {
        // ...
        plugins: [
            // ...
            new DefinePlugin({
                'process.env': JSON.stringify(process.env)
            })
        ]
    }
在根目录下新建 typings/global.d.ts 文件:
   declare module 'process' {
        global {
            namespace NodeJS {
                export interface ProcessEnv {
                    BASE_ENV: 'development' | 'test' | 'pre' | 'production'
                    NODE_ENV: 'development' | 'production'
                }
            }
        }
    }
并在 tsconfig.json 中配置:

{
"compilerOptions": {
"target": "es2016", // 编译输出的JavaScript版本为ES2016
"esModuleInterop": true, // 允许更好的兼容性与ECMAScript模块导入
"module": "commonjs", // 指定生成哪个模块系统代码,这里是CommonJS
"forceConsistentCasingInFileNames": true, // 确保文件名大小写一致,有助于跨平台开发
"strict": true, // 启用所有严格的类型检查选项
"skipLibCheck": true, // 跳过声明文件的类型检查,可以提高编译速度
"typeRoots": ["./typings/*.d.ts", "node_modules/@types"], // 指定类型声明文件的路径
"jsx": "react-jsx" // react18这里改成react-jsx,就不需要在tsx文件中手动引入React了
},
"include": ["./src", "./typings/*.d.ts"] // 指定哪些文件或目录应该被包含在编译范围内
}
配置后会把值注入到业务代码里面去, webpack 解析代码匹配到 process.env.BASE_ENV ,就会设置到 对应的值。测试一下,在 src/index.tsx 打印一下两个环境变量:
需要注意的是,业务环境要能访问 process ,需要安装: pnpm add @types/node -D
// src/index.tsx
// ...
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV)
执行 pnpm run dev:test ,可以在浏览器控制台看到打印的信息
// NODE_ENV development
// BASE_ENV test
7.配置多环境运行配置
安装依赖:
pnpm i dotenv
在根目录下新建一个多文件配置文件夹 env
├── env
├── .env.development # 开发环境
├── .env.test # 测试环境
├── .env.pre # 预发布环境
└── .env.production # 生产环境
文件中可以配置任意我们需要的变量:
// env/.env.development
REACT_APP_API_URL=https://api-dev.com
// env/.env.test
REACT_APP_API_URL=https://api-test.com
// env/.env.pre
REACT_APP_API_URL=https://api-pre.com
// env/.env.production
REACT_APP_API_URL=https://api-prod.com
然后再 webpack.base.ts 中引入,然后解析对应环境配置,最后通过 DefinePlugin 进行注入:

 

    import path from "path";
    import { Configuration, DefinePlugin } from "webpack";
    import HtmlWebpackPlugin from "html-webpack-plugin";
    import * as dotenv from "dotenv";
    // 加载配置文件
    const envConfig = dotenv.config({
        path: path.resolve(__dirname, "../env/.env." + process.env.BASE_ENV),
    });
    // console.log("process.env", process.env);
    // console.log("NODE_ENV", process.env.BASE_ENV);
    // console.log("REACT_APP_API_URL", process.env.REACT_APP_API_URL);
    const baseConfig: Configuration = {
        // ...
        plugins: [
            // 注入到业务
            new DefinePlugin({
                "process.env": JSON.stringify(envConfig.parsed),
                "process.env.BASE_ENV": JSON.stringify(process.env.BASE_ENV),
                "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
            }),
        ].filter(Boolean), // // 去除数组中的假值(例如,如果某些插件的条件不满足而导致未定义)
    };
    export default baseConfig;
业务代码中使用:
import { createRoot } from 'react-dom/client';
import App from './App';
// const root = document.getElementById('root');
const root = document.querySelector('#root')
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('BASE_ENV', process.env.BASE_ENV)
console.log("process.env", process.env);
if(root) {
createRoot(root).render(<App />)
}å
然后重启项目: pnpm run dev:dev
8.文件别名
先在 webpack.base.ts 中配置:
 resolve: {
        extensions: [".ts", ".tsx", ".js", ".jsx", ".less", ".css"],
            // 别名需要配置两个地方,这里和 tsconfig.json
            alias: {
            "@": path.join(__dirname, "../src")
        },
    },
然后还需要在 tsconfig.json 中配置:

    {
        "compilerOptions": {
            // ...
            "baseUrl": ".",
                "paths": {
                "@/*": ["src/*"]
            },
        },
    }
然后就可以在项目中使用了 ~
    import '@/App.css'
    function App() {
        return <h2>webpack5-react-ts</h2>
    }
    export default App
  
9.引入 lesssassscss)、stylus

less sass scss stylus 是三个比较流行的 CSS Modules 预处理库。在 React 中,使用 CSS
Modules 的好处在于:
1. 避免全局样式冲突:使用 CSS Modules 可以确保样式只应用于特定组件,避免全局样式冲突。
2. 更好的可维护性: CSS Modules 使得样式与组件代码紧密关联,方便代码维护。
3. 提高代码可重用性: CSS Modules 可以轻松地将样式从一个组件复制到另一个组件,提高代码可重用性。
4. 支持动态样式:使用 CSS Modules 可以轻松地生成动态样式,例如根据组件状态或属性更改样
式。
5. 更好的性能: CSS Modules 使用模块化的方式加载样式,提高了页面加载速度和性能。
基本用法
先安装相关的依赖:
pnpm i less less-loader sass-loader sass stylus stylus-loader -D
webpack.base.ts 添加相关的 loader
// ...
    const cssRegex = /\.css$/;
    const sassRegex = /\.(scss|sass)$/;
    const lessRegex = /\.less$/;
    const stylRegex = /\.styl$/;
    const styleLoadersArray = [
        "style-loader",
        {
            loader: "css-loader",
            options: {
                modules: {
                    localIdentName: "[path][name]__[local]--[hash:5]",
                },
            },
        },
    ];
    const baseConfig: Configuration = {
        // ...
        module: {
            rules: [
                // ...
                {
                    test: cssRegex, // 匹配css文件
                    use: styleLoadersArray,
                },
                {
                    test: lessRegex,
                    use: [
                        ...styleLoadersArray,
                        {
                            loader: 'less-loader',
                            options: {
                                lessOptions: {
                                    // 如果要在less中写js的语法,需要加这一配置
                                    javascriptEnabled: true
                                }
                            }
                        }
                    ]
                },
                {
                    test: sassRegex,
                    use: [
                        ...styleLoadersArray,
                        {
                            loader: 'sass-loader',
                            options: {
                                implementation: require('sass') // 使用dart-sass代替node-sass
                            }
                        }
                    ]
                },
                {
                    test: stylRegex,
                    use: [
                        ...styleLoadersArray,
                        'stylus-loader'
                    ]
                }
            ],
        },
        // ...
    };
    export default baseConfig;
webpack 配置说明
1. localIdentName :配置生成的 css 类名组成( path 路径, name 文件名, local 原来的
css 类名 , hash: base64:5 拼接生成 hash 5 位,具体位数可根据需要设置。
2. 如下的配置( localIdentName: '[local]__[hash:base64:5]' ):生成的 css 类名类似
class="edit__275ih" 这种,既能达到 scoped 的效果,又保留原来的 css 类名 ( edit )
然后就可以在业务中使用了:
/* src/app.less */
    @color: red;
    .lessBox {
    .box {
            color: @color;
            background - color: lightblue;
            transform: translateX(100);
    &:before{
                @arr: 'hello', 'world';
                content: `@{arr}.join(' ').toUpperCase()`;
            }
        }
    }
    /* src/app.scss */
    $blue: #1875e7;
    $side: left;
    .scssBox {
            margin: 20px 0;
    .box {
            color: $blue;
            background - color: rgb(226, 223, 223);
            border: 1px solid grey;
            margin -#{ $side }: 20px;
            padding: (20px / 2);
        }
    }
    /* src/app.styl */
    .stylBox
    .box
    color: red;
    background - color: yellow;
App.tsx 中引入:
 import '@/App.css'
    import lessStyles from './app.less'
    import scssStyles from './app.scss'
    import stylStyles from './app.styl'
    function App() {
        return <div>
            <h2>webpack5-react-ts</h2>
            <div className={lessStyles['lessBox']}>
                <div className={lessStyles['box']}>lessBox</div>
            </div>
            <div className={scssStyles['scssBox']}>
                <div className={scssStyles['box']}>scssBox</div>
            </div>
            <div className={stylStyles['stylBox']}>
                <div className={stylStyles['box']}>stylBox</div>
            </div>
        </div>
    }
    export default App
重启项目,就会发现生成了带有 hash 值的 class 类名,且里面包含了我们自定义的类名,方便日后调试用
同时在验证打包是否成功,运行 pnpm run build:dev ,然后通过 serve -S dist 查看。 当然,如果
你不希望每次写 less 的时候,都在文件名上加一个 .module ,可以在 less-loader 中添加如下配置:
 // ...
    const baseConfig: Configuration = {
        // ...
        module: {
            rules: [
                // ...
                {
                    test: lessRegex,
                    use: [
                        ...styleLoadersArray,
                        {
                            loader: "less-loader",
                            options: {
                                lessOptions: {
                                    importLoaders: 2,
                                    // 可以加入modules: true,这样就不需要在less文件名加module了
                                    modules: true,
                                    // 如果要在less中写类型js的语法,需要加这一个配置
                                    javascriptEnabled: true
                                },
                            },
                        },
                    ],
                },
                // ...
            ],
        },
        // ...
    };
    export default baseConfig;
至此,我们就完成了 less sass scss stylus 的引入。
Tips :虽然我们在样式文件名上加一个 .module 的后缀,可以明确这是 css modules ,但也带
来了额外的码字开销。可以在 global.d.ts 加入样式文件的声明,就可以避免写 .module
缀。
 // src/typings/global.d.ts
    /* CSS MODULES */
    declare module '*.css' {
        const classes: { [key: string]: string };
        export default classes;
    }
    declare module '*.scss' {
        const classes: { [key: string]: string };
        export default classes;
    }
    declare module '*.sass' {
        const classes: { [key: string]: string };
        export default classes;
    }
    declare module '*.less' {
        const classes: { [key: string]: string };
        export default classes;
    }
    declare module '*.styl' {
        const classes: { [key: string]: string };
        export default classes;
    }
然后就可以删掉样式文件中的 .module 后缀了。
处理CSS3前缀在浏览器中的兼容

虽然 css3 现在浏览器支持率已经很高了 , 但有时候需要兼容一些低版本浏览器,需要给 css3 加前缀 , 可 以借助插件来自动加前缀, postcss-loader 就是来给 css3 加浏览器前缀的,安装依赖:
pnpm i postcss-loader autoprefixer -D
为了避免 webpack.base.ts 文件过于庞大,我们将一些 loader 配置提取成单独的文件来进行管理,根目录新建 postcss.config.js ,作为 postcss-loader 的配置文件,会自动读取配置:
 module.exports = {
        ident: "postcss",
        plugins: [require("autoprefixer")],
    };
修改 webpack.base.ts ,在解析 css less 的规则中添加配置:

    // ...
    const styleLoadersArray = [
        "style-loader",
        {
            loader: "css-loader",
            options: {
                modules: {
                    localIdentName: "[path][name]__[local]--[hash:5]",
                },
            },
        },
        // 添加 postcss-loader
        'postcss-loader'
    ];
配置完成后,需要有一份要兼容浏览器的清单,让 postcss-loader 知道要加哪些浏览器的前缀,在根目录创建 .browserslistrc 文件:
IE 9 # 兼容IE 9
chrome 35 # 兼容chrome 35
以兼容到 ie9 chrome35 版本为例,配置好后,在 app.module.less 中加入一些 CSS3 的语法,重新启 动项目,就可以在浏览器的控制台-Elements 中看到配置成功了。
执行 pnpm run build:dev 打包,也可以看到打包后的 css 文件已经加上了 ie 和谷歌内核的前缀。

 

10.处理其他常用资源
处理图片
对于图片文件, webpack4 使用 file-loader url-loader 来处理的,但 webpack5 不使用这两个
loader , 而是采用自带的 asset-module 来处理,修改 webpack.base.ts ,添加图片解析配置

    {
        output: {
            // ... 这里自定义输出文件名的方式是,将某些资源发送到指定目录
            assetModuleFilename: 'images/[hash][ext][query]'
        },
        module: {
            rules: [
                // ...
                {
                    test: /\.(png|jpe?g|gif|svg)$/i, // 匹配图片文件
                    type: "asset", // type选择asset
                    parser: {
                        dataUrlCondition: {
                            maxSize: 10 * 1024, // 小于10kb转base64
                        }
                    },
                    generator: {
                        filename: 'static/images/[hash][ext][query]', // 文件输出目录和命名
                    },
                },
            ]
        }
    }

将文件编译为 Data URI 使用,可以节省 HTTP 请求,是一个性能优化的点。但是将图片文件经
base64 编码转为 Data URI ,体积会增加大约 33% 。所以,我们一般只针对小图片做 Base64
的处理,对于一些比较大的文件来说,转为 Data URI 会明显增加打包后文件的体积,反而会加
大对带宽资源和流量的需求。
由于我们希望通过 ES6 的新语法 ESModule 的方式导入资源,为了使 TypeScript 可以识别图片模
块,需要在 src/typings/global.d.ts 中加入声明:
// ...
    /* IMAGES */
    declare module '*.svg' {
        const ref: string;
        export default ref;
    }
    declare module '*.bmp' {
        const ref: string;
        export default ref;
    }
    declare module '*.gif' {
        const ref: string;
        export default ref;
    }
    declare module '*.jpg' {
        const ref: string;
        export default ref;
    }
    declare module '*.jpeg' {
        const ref: string;
        export default ref;
    }
    declare module '*.png' {
        const ref: string;
        export default ref;
    }

 然后在 App.tsx 中引入图片资源:

 import '@/App.css'
    import lessStyles from '@/app.less'
    import scssStyles from '@/app.scss'
    import stylStyles from '@/app.styl'
    import smallImg from '@/assets/images/5kb_img.jpeg'
    import bigImg from '@/assets/images/10kb_img.png'
    function App() {
        return <div>
            <h2>webpack5-react-ts</h2>
            <div className={lessStyles['lessBox']}>
                <div className={lessStyles['box']}>lessBox
                    <img src={smallImg} alt="小于10kb的图片" />
                    <img src={bigImg} alt="大于于10kb的图片" />
                    <div className={lessStyles['smallImg']}>小图片背景</div>
                    <div className={lessStyles['bigImg']}>大图片背景</div>
                </div>
            </div>
            <div className={scssStyles['scssBox']}>
                <div className={scssStyles['box']}>scssBox</div>
            </div>
            <div className={stylStyles['stylBox']}>
                <div className={stylStyles['box']}>stylBox</div>
            </div>
        </div>
    }
    export default App
app.less 文件中加入背景图片:
 @color: red;
    .lessBox {
    .box {
            color: @color;
            background - color: lightblue;
            transform: translateX(100);
    &:before{
                    @arr: 'hello', 'world';
                    content: `@{arr}.join(' ').toUpperCase()`;
                }
            }
    .smallImg {
                width: 69px;
                height: 75px;
                background: url('@/assets/imgs/5kb_img.jpeg') no - repeat;
            }
    .bigImg {
                width: 232px;
                height: 154px;
                background: url('@/assets/imgs/10kb_img.png') no - repeat;
            }
    }
重启项目之后,就可以看到图片被展示出来了
然后测试打包
图片被打包进了我们指定的 static/images 文件里面。
处理字体和媒体
字体文件和媒体文件这两种资源处理方式和处理图片是一样的,只需要把匹配的路径和打包后放置的路径修改一下就可以了。修改 webpack.base.ts 文件:
 module: {
        rules: [
            // ...
            {
                test: /.(woff2?|eot|ttf|otf)$/, // 匹配字体图标文件
                type: "asset", // type选择asset
                parser: {
                    dataUrlCondition: {
                        maxSize: 10 * 1024, // 小于10kb转base64
                    }
                },
                generator: {
                    filename: 'static/fonts/[hash][ext][query]', // 文件输出目录和命名
                },
            },
            {
                test: /.(mp4|webm|ogg|mp3|wav|flac|aac)$/, // 匹配媒体文件
                type: "asset", // type选择asset
                parser: {
                    dataUrlCondition: {
                        maxSize: 10 * 1024, // 小于10kb转base64
                    }
                },
                generator: {
                    filename: 'static/media/[hash][ext][query]', // 文件输出目录和命名
                },
            },
        ]
    }
对于 fant-family ,可以去 google font 找一个字体,放入 assets/fonts ,然后通过下面的方式引
入:
@color: red;
    @font-face {
        font - family: "GoldmanBold";
        src: local("GoldmanBold"),
            url("@/assets/fonts/Phudu-Bold.ttf") format("truetype");
        font - weight: bold;
    }
    .lessBox {
    .box {
                color: @color;
                background - color: lightblue;
                transform: translateX(100);
                font - family: "GoldmanBold";
    &:before{
                    @arr: 'hello', 'world';
                    content: `@{arr}.join(' ').toUpperCase()`;
                }
        }
    }
处理json资源
 // ...
    {
        // 匹配json文件
        test: /\.json$/,
            type: "json", // 模块资源类型为json模块
                generator: {
            // 这里专门针对json文件的处理
            filename: "static/json/[name].[hash][ext][query]",
     },
    },
    //
src 下增加一个 json 文件 test.json:
[
        {
            "name": "ian1",
            "age": 18
        },
        {
            "name": "ian2",
            "age": 22
        },
        {
            "name": "ian3",
            "age": 23
        }
    ]
然后在 App.tsx 中引入:
import memberList from './test.json'
这里可能会出现一个报错:
Cannot find module './test.json'. Consider using '--resolveJsonModule' to import
module with '.json' extension.
需要在 tsconfig.json 中加入一个配置:

    {
        "compilerOptions": {
            // ...
            "resolveJsonModule": true,
    // ...
    },
            // ...
    }
就可以在控制台打印出我们通过模块导入的 json 文件了:
11.babel处理js非标准语法
现在 react 主流开发都是函数组件和 react-hooks ,但有时也会用类组件,可以用装饰器简化代码。
新增 src/components/Cls.tsx 组件,在 App.tsx 中引入该组件使用
 import React, { PureComponent } from "react";
    // 装饰器为,组件添加age属性
    function addAge(Target: Function) {
        Target.prototype.age = 111
    }
    // 使用装饰器
    @addAge
    class Cls extends PureComponent {
        age?: number
        render() {
            return (
                <h2>我是类组件---{this.age}</h2>
            )
        }
    }
    export default Cls
需要开启一下 ts 装饰器支持,修改 tsconfig.json 文件
 // tsconfig.json
    {
        "compilerOptions": {
            // ...
            // 开启装饰器使用
            "experimentalDecorators": true
        }
    }
上面 Cls 组件代码中使用了装饰器目前 js 标准语法是不支持的,现在运行或者打包会报错,不识别装饰
器语法,需要借助 babel-loader 插件,安装依赖:
pnpm i @babel/plugin-proposal-decorators -D
babel.config.js 中添加插件:
 const isDEV = process.env.NODE_ENV === "development"; // 是否是开发模式
    module.exports = {
        // 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法
        presets: [
            [
                "@babel/preset-env",
                {
                    // 设置兼容目标浏览器版本,也可以在根目录配置.browserslistrc文件,babel-loader会自
                    动寻找上面配置好的文件.browserlistrc
            // "targets": {
            // "chrome": 35,
            // "ie": 9
            // },
            targets: { browsers: ["> 1%", "last 2 versions", "not ie <= 8"] },
                                useBuiltIns: "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入
                                polyfill按需添加
                                corejs: 3, // 配置使用core-js使用的版本
                                loose: true,
                },
            ],
            // 如果您使用的是 Babel 和 React 17,您可能需要将 "runtime": "automatic" 添加到配置中。
            // 否则可能会出现错误:Uncaught ReferenceError: React is not defined
            ["@babel/preset-react", { runtime: "automatic" }],
            "@babel/preset-typescript",
        ],
        plugins: [
            ["@babel/plugin-proposal-decorators", { legacy: true }],
        ].filter(Boolean), // 过滤空值
    };
现在项目就支持装饰器了。重启项目
12.webpack构建速度优化
webpack 进度条

webpackbar 这是一款个人感觉是个十分美观优雅的进度条,很多成名框架都用过他。而且使用起来也极其方便,也可以支持多个并发构建是个十分强大的进度插件。
pnpm i webpackbar -D
最常用的属性配置其实就是这些,注释里也写的很清楚了,我们在 webpack.base.ts 中引入:

    // ...
    import WebpackBar from 'webpackbar';
    // ...
    const baseConfig: Configuration = {
        // ...
        // plugins 的配置
        plugins: [
            // ...
            new WebpackBar({
                color: "#85d", // 默认green,进度条颜色支持HEX
                basic: false, // 默认true,启用一个简单的日志报告器
                profile: false, // 默认false,启用探查器。
            })
        ],
    };
    export default baseConfig;
当然里面还有一个属性就是 reporters 还没有写上,可以在里面注册事件,也可以理解为各种钩子函 数。如下:
{ // 注册一个自定义数组
        start(context) {
            // 在(重新)编译开始时调用
            const { start, progress, message, details, request, hasErrors } = context
        },
        change(context) {
            // 在 watch 模式下文件更改时调用
        },
        update(context) {
            // 在每次进度更新后调用
        },
        done(context) {
            // 编译完成时调用
        },
        progress(context) {
            // 构建进度更新时调用
        },
        allDone(context) {
            // 当编译完成时调用
        },
        beforeAllDone(context) {
            // 当编译完成前调用
        },
        afterAllDone(context) {
            // 当编译完成后调用
        },
    }
构建耗时

当进行优化的时候,肯定要先知道时间都花费在哪些步骤上了,而 speed-measure-webpack-plugin 插件可以帮我们做到,安装依赖:
pnpm i speed-measure-webpack-plugin -D
使用的时候为了不影响到正常的开发 / 打包模式,我们选择新建一个配置文件,新增 webpack 构建分析配置文件 build/webpack.analy.ts

    import { Configuration } from 'webpack'
    import prodConfig from './webpack.prod' // 引入打包配置
    import { merge } from 'webpack-merge' // 引入合并webpack配置方法
    const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); // 引入webpack
    打包速度分析插件
    const smp = new SpeedMeasurePlugin(); // 实例化分析插件
    // 使用smp.wrap方法,把生产环境配置传进去,由于后面可能会加分析配置,所以先留出合并空位
    const analyConfig: Configuration = smp.wrap(merge(prodConfig, {
    }))
    export default analyConfig
修改 package.json 添加启动 webpack 打包分析脚本命令,在 scripts 新增:

    {
        // ...
        "scripts": {
            // ...
            "build:analy": "cross-env NODE_ENV=production BASE_ENV=production webpack -c
            build / webpack.analy.ts"
        }
        // ...
    }
执行 pnpm build:analy 命令就能看见构建所耗时间
开启持久化存储缓存
webpack5 之前做缓存是使用 babel-loader 缓存解决 js 的解析结果, cache-loader 缓存 css 等资 源的解析结果,还有模块缓存插件 hard-source-webpack-plugin ,配置好缓存后第二次打包,通过对 文件做哈希对比来验证文件前后是否一致,如果一致则采用上一次的缓存,可以极大地节省时间。
webpack5 较于 webpack4 ,新增了持久化缓存、改进缓存算法等优化,通过配置 webpack 持久化缓 存 ,来缓存生成的 webpack 模块和 chunk ,改善下一次打包的构建速度 , 可提速 90% 左右 , 配置也简 单,修改 webpack.base.ts
// webpack.base.ts
    // ...
    module.exports = {
        // ...
        cache: {
            type: 'filesystem', // 使用文件缓存
        },
    }
通过开启 webpack5 持久化存储缓存,极大缩短了启动和打包的时间。缓存的存储位置在
node_modules/.cache/webpack ,里面又区分了 development production 缓存。
运行在 Node.js 之上的 webpack 是单线程模式的,也就是说, webpack 打包只能逐个文件处理,当 webpack 需要打包大量文件时,打包时间就会比较漫长。
多进程 / 多实例构建的方案比较知名的有以下三种:
thread-loader
parallel-webpack
HappyPack
webpack loader 默认在单线程执行,现代电脑一般都有多核 cpu ,可以借助多核 cpu 开启多线程 loader 解析,可以极大地提升 loader 解析的速度, thread-loader 就是用来开启多进程解析 loader
的,安装依赖
pnpm i thread-loader -D

使用时 , 需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的
worker 池中运行。
修改 webpack.base.ts

    module: {
        rules: [
            {
                test: /\.(ts|tsx)$/, // 匹配ts和tsx文件
                use: [
                    // 开启多进程打包。
                    // 进程启动大概为600ms,进程通信也有开销。
                    // 只有工作消耗时间比较长,才需要多进程打包
                    {
                        loader: 'thread-loader',
                        options: {
                            wokers: 4 // 进程数
                        }
                    },
                    'babel-loader']
            },
        ]
    }
由于 thread-loader 不支持抽离 css 插件 MiniCssExtractPlugin.loader ( 下面会讲 ) ,所以这里只配
置了多进程解析 ts
缩小构建目标
一般第三库都是已经处理好的 , 不需要再次使用 loader 去解析,可以按照实际情况合理配置 loader 的作 用范围,来减少不必要的 loader 解析,节省时间,通过使用 include exclude 两个配置项,可以实现这个功能,常见的例如
include :只解析该选项配置的模块
exclude :不解该选项配置的模块 , 优先级更高
修改 webpack.base.ts

    module: {
        rules: [
            {
                test: /\.(ts|tsx)$/, // 匹配ts和tsx文件
                exclude: /node_modules/,
                use: [
                    // 开启多进程打包。
                    // 进程启动大概为600ms,进程通信也有开销。
                    // 只有工作消耗时间比较长,才需要多进程打包
                    {
                        loader: 'thread-loader',
                        options: {
                            wokers: 4 // 进程数
                        }
                    },
                    'babel-loader']
            },
        ]
    }
其他 loader 也是相同的配置方式,如果除 src 文件外也还有需要解析的,就把对应的目录地址加上就可 以了,比如需要引入 antd css ,可以把 antd 的文件目录路径添加解析 css 规则到 include 里面。
devtools 配置
开发过程中或者打包后的代码都是 webpack 处理后的代码,如果进行调试肯定希望看到源代码,而不是 编译后的代码, source map 就是用来做源码映射的,不同的映射模式会明显影响到构建和重新构建的速度, devtool 选项就是 webpack 提供的选择源码映射方式的配置。
devtool 的命名规则为:
^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$

开发环境推荐: eval-cheap-module-source-map:
、本地开发首次打包慢点没关系,因为 eval 缓存的原因,热更新会很快
、开发中,我们每行代码不会写的太长,只需要定位到行就行,所以加上 cheap
、我们希望能够找到源代码的错误,而不是打包后的,所以需要加上 module

 修改 webpack.dev.ts

// webpack.dev.ts
module.exports = {
// ...
devtool: 'eval-cheap-module-source-map'
}
打包环境推荐: none( 就是不配置 devtool 选项了,不是配置 devtool: 'none')
// webpack.prod.ts
module.exports = {
// ...
// devtool: '', // 不用配置devtool此项
}
none 配置在调试的时候,只能看到编译后的代码,也不会泄露源代码,打包速度也会比较快。只
是不方便线上排查问题,但一般都可以根据报错信息在本地环境很快找出问题所在。
13.webpack构建产物优化
bundle 体积分析工具
webpack-bundle-analyzer 是分析 webpack 打包后文件的插件,使用交互式可缩放树形图可视化
webpack 输出文件的大小。通过该插件可以对打包后的文件进行观察和分析,可以方便我们对不完美的地方针对性的优化,安装依赖:
pnpm i webpack-bundle-analyzer -D
修改 webpack.analy.ts

    import { Configuration } from "webpack";
    import { merge } from "webpack-merge";
    import prodConfig from "./webpack.prod";
    const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
    const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
    // 引入webpack打包速度分析插件
    const smp = new SpeedMeasurePlugin();
    // 使用smp.wrap方法,把生产环境配置传进去,由于后面可能会加分析配置,所以先留出合并空位
    const analyConfig: Configuration = smp.wrap(merge(prodConfig, {
        plugins: [
            new BundleAnalyzerPlugin() // 配置分析打包结果插件
        ]
    }))
    export default analyConfig;
配置好后,执行 pnpm run build:analy 命令,打包完成后浏览器会自动打开窗口,可以看到打包文件的分析结果页面,可以看到各个文件所占的资源大小:

 然后,我们就可以根据这个图上给出的信息,来针对性优化产物体积。

样式提取

在开发环境我们希望 css 嵌入在 style 标签里面,方便样式热替换,但打包时我们希望把 css 单独抽离出来, 方便配置缓存策略。而插件 mini-css-extract-plugin 就是来帮我们做这件事的,安装依赖:
pnpm i mini-css-extract-plugin -D
修改 webpack.base.ts ,根据环境变量设置开发环境使用 style-looader ,打包模式抽离 css

 


    // ...
    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    const isDev = process.env.NODE_ENV === 'development' // 是否是开发模式
    const styleLoadersArray = [
        isDev ? "style-loader" : MiniCssExtractPlugin.loader, // 开发环境使用stylelooader,打包模式抽离css
        {
            loader: "css-loader",
            options: {
                modules: {
                    localIdentName: "[path][name]__[local]--[hash:5]",
                },
            },
        },
        'postcss-loader'
    ];

再修改 webpack.prod.ts ,打包时添加抽离 css 插件:
 // ...
    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    const prodConfig: Configuration = merge(baseConfig, {
        // ...
        plugins: [
            // ...
            new MiniCssExtractPlugin({
                filename: 'static/css/[name].css' // 抽离css的输出目录和名称
            }),
        ],
    });
    export default prodConfig;
配置完成后,在开发模式 css 会嵌入到 style 标签里面,方便样式热替换,打包时会把 css 抽离成单独的 css 文件。
样式压缩

可以看到,上面配置了打包时把 css 抽离为单独 css 文件的配置,打开打包后的文件查看,可以看到默认 css 是没有压缩的,需要手动配置一下压缩 css 的插件。
可以借助 css-minimizer-webpack-plugin 来压缩 css ,安装依赖:
pnpm i css-minimizer-webpack-plugin -D
修改 webpack.prod.ts 文件, 需要在优化项 optimization 下的 minimizer 属性中配置:
 // ...
    import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
    module.exports = {
        // ...
        optimization: {
            minimizer: [
                new CssMinimizerPlugin(), // 压缩css
            ],
        },
    }
再次执行打包就可以看到 css 已经被压缩了
js 压缩

 

设置 mode production ,webpack 会使用内置插件 terser-webpack-plugin 压缩 js 文件 , 该插件默认支持多线程压缩, 但是上面配置 optimization.minimizer 压缩 css ,js 压缩就失效了 , 需要手动再添加一
,webpack 内部安装了该插件 , 由于 pnpm 解决了幽灵依赖问题 , 如果用的 pnpm 的话 , 需要手动再安装一下依赖。:
pnpm i terser-webpack-plugin compression-webpack-plugin -D
修改 webpack.prod.ts 文件:

    import path from "path";
    import { Configuration } from "webpack";
    import { merge } from "webpack-merge";
    import CopyPlugin from "copy-webpack-plugin";
    import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
    import TerserPlugin from 'terser-webpack-plugin'
    import CompressionPlugin from 'compression-webpack-plugin'
    import baseConfig from "./webpack.base";
    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    const prodConfig: Configuration = merge(baseConfig, {
        mode: "production", // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
        /**
        * 打包环境推荐:none(就是不配置devtool选项了,不是配置devtool: 'none')
        * ● none话调试只能看到编译后的代码,也不会泄露源代码,打包速度也会比较快。
        * ● 只是不方便线上排查问题, 但一般都可以根据报错信息在本地环境很快找出问题所在。
        */
        plugins: [
            new CopyPlugin({
                patterns: [
                    {
                        from: path.resolve(__dirname, "../public"), // 复制public下文件
                        to: path.resolve(__dirname, "../dist"), // 复制到dist目录中
                        filter: (source) => !source.includes("index.html"), // 忽略index.html
                    },
                ],
            }),
            new MiniCssExtractPlugin({
                filename: 'static/css/[name].css' // 抽离css的输出目录和名称
            }),
            // 打包时生成gzip文件
            new CompressionPlugin({
                test: /\.(js|css)$/, // 只生成css,js压缩文件
                filename: '[path][base].gz', // 文件命名
                algorithm: 'gzip', // 压缩格式,默认是gzip
                threshold: 10240, // 只有大小大于该值的资源会被处理。默认值是 10k
                minRatio: 0.8 // 压缩率,默认值是 0.8
            })
        ],
        optimization: {
            // splitChunks: {
            // chunks: "all",
            // },
            runtimeChunk: {

                name: 'mainifels'
            },
            minimize: true,
            minimizer: [
                new CssMinimizerPlugin(), // 压缩css
                new TerserPlugin({
                    parallel: true, // 开启多线程压缩
                    terserOptions: {
                        compress: {
                            pure_funcs: ['console.log'] // 删除console.log
                        }
                    }
                })
            ],
        },
        performance: { // 配置与性能相关的选项的对象
            hints: false, // 设置为false将关闭性能提示。默认情况下,Webpack会显示有关入口点和资产大小的警告和错误消息。将hints设置为false可以禁用这些消息。
            maxAssetSize: 4000000, // 设置一个整数,表示以字节为单位的单个资源文件的最大允许大小。如果任何资源的大小超过这个限制,Webpack将发出性能警告。在你提供的配置中,这个值被设置为4000000字节(约4MB)。
            maxEntrypointSize: 5000000 // 设置一个整数,表示以字节为单位的入口点文件的最大允许大小。入口点是Webpack构建产生的主要JS文件,通常是应用程序的主要代码。如果入口点的大小超过这个限制,Webpack将发出性能警告。在你提供的配置中,这个值被设置为5000000字节(约5MB)。
        }
    });
    export default prodConfig;
配置完成后再打包, css js 就都可以被压缩了
tree-shaking清理未引用js
Tree Shaking 的字面意思是摇树,伴随着摇树这个动作,树上的枯枝败叶都会被摇下来,这里的 tree-shaking 在代码中摇掉的是未使用到的代码,也就是未引用的代码,最早是在 rollup 库中出现的,
webpack v2 版本之后也开始支持。模式 mode production 时就会默认开启 tree-shaking
能以此来标记未引入代码然后移除掉,测试一下。
src/components 目录下新增 Demo1.tsx Demo2.tsx 两个组件
// src/components/Demo1.tsx
    import React from "react";
    function Demo1() {
        return <h3>我是Demo1组件</h3>
    }
    export default Demo1
    // src/components/Demo2.tsx
    import React from "react";
    function Demo2() {
        return <h3>我是Demo2组件</h3>
    }
    export default Demo2
再在 src/components 目录下新增 index.ts ,把 Demo1 Demo2 组件引入进来再暴露出去:

 

// src/components/index.ts
export { default as Demo1 } from './Demo1'
export { default as Demo2 } from './Demo2'
App.tsx 中引入两个组件,但只使用 Demo1 组件:
 // ...
    import { Demo1, Demo2 } from '@/components'
    function App() {
        return <>
// ...
            <Demo1 />
        </>
    }
    export default App
执行打包 , 可以看到在 main.js 中搜索 Demo ,只搜索到了 Demo1 ,代表 Demo2 组件被 tree
shaking 移除掉了
tree-shaking清理未使用css

js 中会有未使用到的代码, css 中也会有未被页面使用到的样式,可以通过 purgecss-webpack-plugin 插件打包的时候移除未使用到的css 样式,这个插件是和 mini-css-extract-plugin 插件配合使用的 , 在上面已经安装过,还需要 glob-all 来选择要检测哪些文件里面的类名和 id 还有标签名称,安装依赖 :
pnpm i purgecss-webpack-plugin glob-all -D
修改 webpack.prod.ts
// ...
    import MiniCssExtractPlugin from 'mini-css-extract-plugin'
    const globAll = require('glob-all')
    const { PurgeCSSPlugin } = require('purgecss-webpack-plugin')
    const prodConfig: Configuration = merge(baseConfig, {
        // ...
        plugins: [
            // 抽离css插件
            new MiniCssExtractPlugin({
                filename: 'static/css/[name].[contenthash:8].css'
            }),
            // 清理无用css,检测src下所有tsx文件和public下index.html中使用的类名和id和标签名称
            // 只打包这些文件中用到的样式
            new PurgeCSSPlugin({
                paths: globAll.sync(
                    [`${path.join(__dirname, '../src')}/**/*`, path.join(__dirname,
                        '../public/index.html')],
                    {
                        nodir: true
                    }
                ),
                // 用 only 来指定 purgecss-webpack-plugin 的入口
                // https://github.com/FullHuman/purgecss/tree/main/packages/purgecsswebpack-plugin
                only: ["dist"],
                safelist: {
                    standard: [/^ant-/] // 过滤以ant-开头的类名,哪怕没用到也不删除
                }
            }),
        ]
    })
gzip 压缩
前端代码在浏览器运行,需要从服务器把 html css js 资源下载执行,下载的资源体积越小,页面加载速度就会越快。一般会采用 gzip 压缩,现在大部分浏览器和服务器都支持 gzip ,可以有效减少静态资源文件大小,压缩率在 70% 左右。
nginx 可以配置 gzip: on 来开启压缩,但是只在 nginx 层面开启,会在每次请求资源时都对资源进
行压缩,压缩文件会需要时间和占用服务器 cpu 资源,更好的方式是前端在打包的时候直接生成 gzip资源,服务器接收到请求,可以直接把对应压缩好的 gzip 文件返回给浏览器,节省时间和 cpu。
webpack 可以借助 compression-webpack-plugin 插件在打包时生成 gzip 文章,安装依赖:
pnpm i compression-webpack-plugin -D
添加配置,修改 webpack.prod.ts

    const glob = require('glob')
    const CompressionPlugin = require('compression-webpack-plugin')
    module.exports = {
        // ...
        plugins: [
            // ...
            new CompressionPlugin({
                test: /.(js|css)$/, // 只生成css,js压缩文件
                filename: '[path][base].gz', // 文件命名
                algorithm: 'gzip', // 压缩格式,默认是gzip
                threshold: 10240, // 只有大小大于该值的资源会被处理。默认值是 10k
                minRatio: 0.8 // 压缩率,默认值是 0.8
            })
        ]
    }
配置完成后再打包,可以看到打包后 js 的目录下多了一个 .gz 结尾的文件
ok以上介绍了webpack的构建和主要的功能,如果你从头看到了这里,那么就请先休息一下吧~
  • 15
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值