react+webpack5+swc搭建自己的脚手架

1,507 阅读3分钟

初始化环境

mkdir react-project
cd react-project
npm init

执行命令后react-project目录下出现一个package.json文件

{
  "name": "react-project",
  "version": "1.0.0",
  "private": true,
  "license": "ISC"
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "achirsh",
}

这时文件目录结构为:

1.jpeg

创建基础目录

安装typescript

npm install typescript --save-dev

在根目录下创建tsconfig.json文件

{
    "compilerOptions": {
      "target": "esnext",  // 生成代码的语言版本
      "module": "esnext",  // 生成代码的模块化标准
      "allowJs": true,     // 允许ts编译器编译js文件
      "lib": [             // 指定要包含在编译中的library
        "dom",
        "dom.iterable",
        "esnext"
      ],
      "skipLibCheck": true,     // 跳过声明文件的类型检查
      "esModuleInterop": true,  // es模块互操作,屏蔽ESModuleCommonJS之间的差异 
      "allowSyntheticDefaultImports": true,     // 允许通过import x from 'y' 即使模块没有显式制定default导出
      "strict": true,           // 开启严格模式
      "forceConsistentCasingInFileNames": true, // 对文件名称强制区分大小写
      "noFallthroughCasesInSwitch": true,       // 对switch语句启用错误报告
      "moduleResolution": "node",               // 模块解析(查找)策略
      "resolveJsonModule": true,                // 允许导入拓展名为.json的模块
      "jsx": "react-jsx",                       // 指定将JSX编译成什么形式 
      "sourceMap": true,
      "noImplicitAny": true,
      "paths": {
        "@src/*": [
          "./src/*"
        ]
      }
    },
    "include": [
      "**/*.ts",
      "**/*.tsx",
    ],
    "exclude": [
      "node_modules",
      "build"
    ]
  }

public目录

public文件夹用于存放index.html和ico图标文件

1、在根目录下创建public文件夹,在public下创建index.html

<!DOCTYPE html>
<html lang="en-US" dir="ltr">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title></title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

2、在public文件夹下添加favicon.ico文件

scripts目录

scripts文件夹用于存放webpack的一些配置文件

在根目录下创建scripts文件夹,进入scripts,创建tsconfig.json、config.ts、build.ts、start.ts文件

src目录

src文件夹用于存放一些开发的页面、配置文件等

1、安装react、react-dom react-router-dom

npm install react react-dom react-router-dom --save
npm install @types/react @types/react-dom --save-dev

2、在根目录下创建src文件夹,进入src,创建pages、config文件夹

2.1、在src创建root.tsx

import React from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import Index from './pages'

const rootElement: any = document.getElementById('root')

const root = createRoot(rootElement);

root.render(
    <BrowserRouter>
        <Index />
    </BrowserRouter>
)

2.2、进入pages,创建index.tsx、home/index.tsx

安装@loadable/component

npm install @loadable/component --save
npm install @types/loadable__component --save-dev
index.tsx

import React from 'react'
import * as config from "../config/route"
import loadable from "@loadable/component"
import { useLocation, Routes, Route } from "react-router-dom"

const PageElement = loadable((props: { page: string }) => import(`../pages/${props.page}`), { cacheKey: (props) => props.page });

export default function PageIndex(): JSX.Element {
    const location = useLocation()

    return (
        <Routes location={location}>
            <Route path={"/"}>
                {config.routes.map((item) => {
                    return (
                        <Route
                            path={item.page}
                            key={`route-${item.page}`}
                            element={
                                <PageElement page={item.page} />
                            }
                        />
                    );
                })}
                <Route path="*" element={<div>404</div>} />
            </Route>
        </Routes>
    )
}

home/index.tsx

import React from 'react'

export default function Home(): JSX.Element {
    return <div>Hello World</div>
}

2.2、进入config,创建route.ts

interface IRoutes {
    page: string;
}

export const routes: IRoutes[] = [
    {
        page: "home"
    }
];

此时的目录结构为:

5.jpeg

配置webpack

这里使用的是webpack-merge来对开发、生产环境进行拆分与合并。

打开scripts目录下的config.ts文件

安装webpack

npm install webpack webpack-merge --save-dev

配置entry、output

import merge from "webpack-merge"
import { Configuration } from "webpack"

export default function config(config: Configuration): Configuration {
    return merge({
        mode: config.mode,
        devtool: config.devtool,
        entry: ["./src/root.tsx"],
        output: {
            clean: true,
            path: resolvePath(__dirname, "../build"),
            filename: config.mode === 'production' ? "js/[name].[contenthash:8].js" : "js/bundle.js",
            chunkFilename: config.mode === 'production' ? 'js/[name].[contenthash:8].chunk.js' : 'js/[name].chunk.js',
            publicPath: process.env.PUBLIC_PATH || "/",
        },
    })
}

这里我们将production环境的filename与chunkFilename做了hash处理

配置resolve

在上面内容中,page/index.html有一行代码是import * as config from "../config/route",这里我们可以通过配置resolve中的alias来简化引用

import { resolve as resolvePath } from "path"

return merge({
    ...
    resolve: {
        alias: {
            "@src": resolvePath(__dirname, "../src")
        },
        extensions: [".ts", ".tsx", ".js", ".jsx", ".json"]
    }
})

配置好以后就可以import * as config from "@src/config"引用了

配置module

这里使用了swc去处理javascript编译,如果对babel感兴趣的也可以去了解下

安装swc、@swc/core

npm install @swc/core swc-loader --save-dev

配置处理js、jsx

modlue: {
    strictExportPresence: true,
    rules: [
        {
            test: /\.(js|mjs|jsx|ts|tsx)$/,
            exclude: /(node_modules|bower_components)/,
            use: {
                loader: "swc-loader"
            }
        }
    ]
}

配置处理css、module.css

安装style-loader、css-loader、postcss-loader、sass-loader、postcss-preset-env、postcss-normalize、mini-css-extract-plugin

npm install style-loader css-loader postcss-loader postcss-preset-env postcss-normalize sass-loader mini-css-extract-plugin --save-dev
import MiniCssExtractPlugin from "mini-css-extract-plugin"

const styleOptions = (config: Configuration, otherOptions?: any) => {
    const options = [
        { 
            loader:  config?.mode === "development" ?  require.resolve("style-loader") :  MiniCssExtractPlugin.loader 
        },
        { 
            loader: require.resolve("css-loader"),  
            options: {
                sourceMap: true,
                modules: true
            }
        },
        {
            loader: require.resolve("postcss-loader"),
            options: {
                postcssOptions: {
                    ident: "postcss",
                    config: false,
                    plugins: [
                        [
                            "postcss-preset-env",
                            {
                                autoprefixer: { flexbox: "no-2009" },
                                stage: 3,
                            },
                        ],
                        "postcss-normalize"
                    ]
                }
            }
        }
    ]

    if (otherOptions) {
        options.push({
            loader: require.resolve(otherOptions)
        })
    }

    return options
}
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;

module: {
    ...
    rules: [
        ...
        {
            test: cssRegex,
            exclude: cssModuleRegex,
            use: styleOptions(config),
        },
        {
            test: cssModuleRegex,
            use: styleOptions(config),
        },
        {
            test: sassRegex,
            exclude: sassModuleRegex,
            use: styleOptions(config, "sass-loader")
        },
        {
            test: sassModuleRegex,
            use: styleOptions(config, "sass-loader")
        },
    ]
}

配置图片资源

module: {
    ...
    rules: [
        ...
          {
              test: [/\.gif$/, /\.jpe?g$/, /\.png$/],
              type: "asset/resource",
          },
          {
              test: /\.svg$/i,
              type: "asset/inline",
          },
    ]
}

配置plugins

提取css到单独的文件

import MiniCssExtractPlugin from "mini-css-extract-plugin"

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

配置html页面

安装html-webpack-plugin

npm install html-webpack-plugin --save-dev
import HtmlWebpackPlugin from "html-webpack-plugin"

plugins: [
    ...
    new HtmlWebpackPlugin({
        hash: true,
        template: "./public/index.html",
        favicon: "./public/favicon.ico",
    }),
]

将manifest数据提取为一个 json 文件

安装webpack-manifest-plugin

npm install webpack-manifest-plugin --save-dev
import { WebpackManifestPlugin } from "webpack-manifest-plugin"

plugins: [
    ...
     new WebpackManifestPlugin({
        fileName: "asset-manifest.json"
     }),
]

配置optimization

添加压缩bundle的插件,css-minimizer-webpack-plugin、terser-webpack-plugin

npm install css-minimizer-webpack-plugin terser-webpack-plugin --save-dev
import CssMinimizerPlugin from "css-minimizer-webpack-plugin"
import TerserPlugin from "terser-webpack-plugin"

 optimization: {
    minimize: true,
    minimizer: [
        new CssMinimizerPlugin(),
        new TerserPlugin()
    ]
},

配置开发、生产环境

配置开发环境配置

1、进入scripts/start.ts配置开发环境配置

安装open、webpack-dev-middleware @types/webpack-dev-middleware webpack-hot-middleware @types/webpack-hot-middleware connect-history-api-fallback、express

npm install open @types/open webpack-dev-middleware webpack-hot-middleware connect-history-api-fallback express @types/webpack-dev-middleware @types/webpack-hot-middleware --save-dev
import open from "open"
import webpack from "webpack"
import devMiddleware from "webpack-dev-middleware"
import hotMiddleware from "webpack-hot-middleware"
import fallback from "connect-history-api-fallback"
import express from "express"
import config from "./config"

const publicPath = process.env.PUBLIC_PATH || "/";

const compiler = webpack(
    config({
        mode: "development",
        devtool: "eval-source-map",
        output: { publicPath },
        entry: ["webpack-hot-middleware/client?reload=true"],
        plugins: [new webpack.HotModuleReplacementPlugin()],
        cache: {
            type: "filesystem",
            buildDependencies: {
                config: [__filename],
            },
        },
    })
);

express()
    .use(fallback({ index: `${publicPath}index.html` }))
    .use(devMiddleware(compiler, { publicPath }))
    .use(hotMiddleware(compiler))
    .listen(3002, () => {
        console.info("Listening on :3002");
        open(`http://localhost:3002${publicPath}`);
    });

2、在根目录package.json配置scripts

安装ts-node

npm install ts-node --save-dev

配置scripts

"scripts": {
    "start": "ts-node ./scripts/start.ts",
},

此时执行npm run start可运行项目,会自动在浏览器打开 http://localhost:3002 地址

配置生产配置文件

1、进入scripts/build.ts配置生产环境配置

import webpack from "webpack"
import config from "./config"

const compiler = webpack(
    config({
        mode: "production",
        devtool: false,
        cache: false
    })
);

compiler.run((err?: Error | null, stats?: webpack.Stats) => {
    if (err) {
        throw err;
    }
    if (stats) {
        process.stdout.write(
            stats.toString({
                colors: true,
            })
        );
        process.stdout.write("\n");
    }
});

2、在根目录package.json配置scripts

"scripts": {
    "build": "ts-node ./scripts/build.ts",
},

此时执行npm run build会将项目打包到根目录的build目录下,如下图

6.jpeg