jtwang7 / React-Note

React 学习笔记
8 stars 2 forks source link

基于 create-react-app 的多页面配置 (MPA) #51

Open jtwang7 opened 2 years ago

jtwang7 commented 2 years ago

基于 create-react-app 的多页面配置 (MPA)

引用文章:基于create-react-app的多页面配置

✅ 创建 React 项目

✅ 多页面应用配置

🔆 弹出webpack配置

由于create-react-app隐藏了webpack配置,因此首先需要将该项目的webpack配置暴露出来。运行如下命令:

💡 运行该命令之前需要将修改的代码提交到git!

🔆 创建多页面文件

假设该多页面应用程序包括前台展示页面和后台管理页面。则在 src/pages/ 目录下下创建两个文件夹 appadmin,分别对应两个页面:

// index.tsx
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>
);

然后对应这两个页面,还需要在 public 目录下创建对应的 html 模板:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

🔆 修改打包相关配置

❇️ 首先修改文件路径映射

来到 config/paths.js 文件下,在 module.exports 中,修改 appIndexJs 的文件路径,添加 adminHtmladminIndexJs 的文件路径

module.exports = {
  dotenv: resolveApp(".env"),
  appPath: resolveApp("."),
  appBuild: resolveApp(buildPath),
  appPublic: resolveApp("public"),
  // ----------- 修改位置 ---------------
  appHtml: resolveApp("public/app.html"), // 映射 html 文件位置
  appIndexJs: resolveModule(resolveApp, "src/pages/app/index"), // 映射 jsx (tsx) 模块文件位置
  adminHtml: resolveApp("public/admin.html"),
  adminIndexJs: resolveModule(resolveApp, "src/pages/admin/index"),
  // ----------- 修改位置 ---------------
  appPackageJson: resolveApp("package.json"),
  appSrc: resolveApp("src"),
  appTsConfig: resolveApp("tsconfig.json"),
  appJsConfig: resolveApp("jsconfig.json"),
  yarnLockFile: resolveApp("yarn.lock"),
  testsSetup: resolveModule(resolveApp, "src/setupTests"),
  proxySetup: resolveApp("src/setupProxy.js"),
  appNodeModules: resolveApp("node_modules"),
  appWebpackCache: resolveApp("node_modules/.cache"),
  appTsBuildInfoFile: resolveApp("node_modules/.cache/tsconfig.tsbuildinfo"),
  swSrc: resolveModule(resolveApp, "src/service-worker"),
  publicUrlOrPath,
};

❇️ 修改 webpack 中的配置

来到 config/webpack.config.js 文件中: 首先修改entry入口配置:

entry: {
      app:
        isEnvDevelopment && !shouldUseReactRefresh
          ? [webpackDevClientEntry, paths.appIndexJs]
          : paths.appIndexJs,
      admin:
        isEnvDevelopment && !shouldUseReactRefresh
          ? [webpackDevClientEntry, paths.adminIndexJs]
          : paths.adminIndexJs,
    },

其次修改output出口配置:

output: {
  /** 其余代码不动 */
  filename: isEnvProduction
    ? 'static/js/[name].[contenthash:8].js'
    : isEnvDevelopment && 'static/js/[name].bundle.js'
  chunkFilename: isEnvProduction
    ? 'static/js/*[name].[contenthash:8].chunk.js*'
    : isEnvDevelopment && 'static/js/[name].chunk.js',
},

最后修改plugin插件配置:

plugins: [
      // Generates an `index.html` file with the <script> injected.
      new HtmlWebpackPlugin(
        Object.assign(
          {},
          {
            inject: true,
            template: paths.appHtml, // 映射 html 模版路径
            chunks: ["app"], // chunks
            filename: "app.html", // html 文件名
          },
          isEnvProduction
            ? {
                minify: {
                  removeComments: true,
                  collapseWhitespace: true,
                  removeRedundantAttributes: true,
                  useShortDoctype: true,
                  removeEmptyAttributes: true,
                  removeStyleLinkTypeAttributes: true,
                  keepClosingSlash: true,
                  minifyJS: true,
                  minifyCSS: true,
                  minifyURLs: true,
                },
              }
            : undefined
        )
      ),
      new HtmlWebpackPlugin(
        Object.assign(
          {},
          {
            inject: true,
            template: paths.adminHtml,
            chunks: ["admin"],
            filename: "admin.html",
          },
          isEnvProduction
            ? {
                minify: {
                  removeComments: true,
                  collapseWhitespace: true,
                  removeRedundantAttributes: true,
                  useShortDoctype: true,
                  removeEmptyAttributes: true,
                  removeStyleLinkTypeAttributes: true,
                  keepClosingSlash: true,
                  minifyJS: true,
                  minifyCSS: true,
                  minifyURLs: true,
                },
              }
            : undefined
        )
      ),
    /** 注释掉ManifestPlugin插件配置 */
    // new ManifestPlugin({
    //   fileName: 'asset-manifest.json',
    //   publicPath: paths.publicUrlOrPath,
    //   generate: (seed, files, entrypoints) => {
    //     const manifestFiles = files.reduce((manifest, file) => {
    //       manifest[file.name] = file.path;
    //       return manifest;
    //     }, seed);
    //     const entrypointFiles = entrypoints.main.filter(
    //       fileName => !fileName.endsWith('.map')
    //     );

    //     return {
    //       files: manifestFiles,
    //       entrypoints: entrypointFiles,
    //     };
    //   },
    // }),
]

💡 HtmlWebpackPlugin这个plugin曝光率很高,他主要有两个作用:

🔆 修改启动脚本配置

在启动脚本代码里,他会检查 html 模板文件、多页面的入口文件的路径映射是否存在。由于修改了路径映射文件,因此也需要对启动脚本代码进行修改。 分别来到

找到如下代码并进行修改。

// 修改前的代码
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
  process.exit(1);
}

// 修改后的代码
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs, paths.adminHtml, paths.adminIndexJs])) {
  process.exit(1);
}

至此,多页面的相关配置已修改完毕,运行 yarn start 启动项目,并输入对应的 url 查看。

例如输入:http://localhost:3000/app.html

💡 总结

配置多页面应用的步骤如下:

  1. 在public下创建多个html模板,每个html模板对应一个页面应用。如果页面模板没有差别的话,可以只创建一个;
  2. 在src下新建多个文件夹,每个文件夹对应一个页面应用。在每个文件夹下建立一个index.tsx作为该页面应用的入口文件,在该文件中,将react挂载到html模板中的根节点中去;
  3. 修改config/paths.js文件下的路径映射,将多页面的html模板和入口文件添加到路径映射中去;
  4. 配置webpack的entry,为每个多页面应用起个名字作为key,将入口文件作为value;
  5. 配置webpack的output,将打包后的文件名改为[name].bundle.js ,其中name为entry中的key;
  6. 配置webpack的plugin,有多少个页面应用就创建多少个HtmlWebpackPlugin,并对其配置对应的html模板和打包后的文件名(即entry中的key)
  7. 修改scripts目录下的文件。更改校验文件是否存在的代码,将paths.js中新增的路径映射添加到判断条件中
  8. 重启项目,检查结果。