前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Create React App v3 + Webpack v4 多页应用配置

Create React App v3 + Webpack v4 多页应用配置

作者头像
前端老王
发布2020-11-11 10:03:22
1.4K0
发布2020-11-11 10:03:22
举报
文章被收录于专栏:前端时空

环境

截止写文时(2020年09月22日),使用的环境如下

  • create-react-app / react-scripts 3.4.3
  • Webpack 4.42
  • TypeScript

仓库地址:https://github.com/xunge0613/react-multipage-app

背景

移动端 H5 想做一个多页应用项目,react + webpack,参考了这两篇写的很不错的文章 React-CRA 多页面配置(npm run eject)[1]「Webpack」配置React多个页面同时打包和调试[2]后发现有问题,一直卡在编译中,也不报错,于是记录一下解决过程。

思路

  1. 最初认为是 Webpack 本身的问题,就先参考了 Webpack 4 官方文档[3],发现没用。
  2. 然后想到是不是和 create-react-app 有关,于是使用了关键词 createreactapp multiple entry webpack4 doesn't work 进行搜索后,根据 Create React App V2 - Multiple entry points[4] 中给出的解决方案解决了。

先前两篇文章中的前几个步骤不用调整,当然由于 webpack 版本不同,需要做一些相应调整(例如:只有 webpack.config.js 没有 dev 和 prod.js ),后续会标注

  1. paths.js 不变
  2. entry 不变
  3. output 不变
  4. plugins 不变

只需调整第五步:ManifestPlugin 调整

解决方案

把原先遍历 entrypoints.main 数组

代码语言:javascript
复制
const entrypointFiles = entrypoints.main.filter(
  (fileName) => !fileName.endsWith(".map")
);

改为遍历 entrypoints 对象,即可

代码语言:javascript
复制
let entrypointFiles = [];
for (let [entryFile, fileName] of Object.entries(entrypoints)) {
  let notMapFiles = fileName.filter(fileName => !fileName.endsWith('.map'));
  entrypointFiles = entrypointFiles.concat(notMapFiles);
  };

原理目测是原先的 entry 是数组 entry: ['xxx'],调整后成了对象, entry: { index: 'xxx', test: 'xxx'}

完整步骤

ps:只新增了入口,暂不新增 html 模板

-1. 安装、运行 create-react-app

代码语言:javascript
复制
# 卸载旧版 create-react-app
npm uninstall -g create-react-app

# 使用 npx 安装最新版 
npx create-react-app react-multipage-app --template typescript

0. yarn eject

代码语言:javascript
复制
yarn eject

1. 调整 paths.js

添加新的入口 appTestJs

代码语言:javascript
复制
module.exports = {
  ...,
  appTestJs: resolveModule(resolveApp, "src/test"),
}

添加对应的入口文件 src/Test.tsx

2. 修改 webpack.config.js 的 entry

搜索:entry:

将原数组形式单入口:

改为对象形式多入口:

代码语言:javascript
复制
entry: {
  index: [
    isEnvDevelopment &&
      require.resolve("react-dev-utils/webpackHotDevClient"),
    paths.appIndexJs,
  ].filter(Boolean),
  test: [
    isEnvDevelopment &&
      require.resolve("react-dev-utils/webpackHotDevClient"),
    paths.appTestJs, // 上一步配置的新入口
  ].filter(Boolean),
},

3. 修改 webpack.config.js 的 output

搜索 output:

output 中如图所示,修改 filename,增加图中的 [name] 用于为不同入口,分别生成不同的 bundle

最终项目跑通后,打包效果如图

访问 http://localhost:3000/test.html

访问 http://localhost:3000/index.html

4. 修改 webpack.config.js 的 plugins

搜索 plugins:

复制一份已有的配置,添加 chunksfilename 字段,因目前项目只使用 paths.appHtml 作为模板,所以 template 字段不需要修改。

原:

改:

完整配置

代码语言:javascript
复制
// Generates an `index.html` file with the <script> injected.
plugins: [
  new HtmlWebpackPlugin(
    Object.assign(
      {},
      {
        chunks: ["index"],
        inject: true,
        template: paths.appHtml,
        filename: "index.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(
      {},
      {
        chunks: ["test"],
        inject: true,
        template: paths.appHtml,
        filename: "test.html",
      },
      isEnvProduction
        ? {
            minify: {
              removeComments: true,
              collapseWhitespace: true,
              removeRedundantAttributes: true,
              useShortDoctype: true,
              removeEmptyAttributes: true,
              removeStyleLinkTypeAttributes: true,
              keepClosingSlash: true,
              minifyJS: true,
              minifyCSS: true,
              minifyURLs: true,
            },
          }
        : undefined
    )
  ),
]

5. 修改 webpack.config.js 的 plugins

搜索 new ManifestPlugin

把原先遍历 entrypoints.main 数组

代码语言:javascript
复制
const entrypointFiles = entrypoints.main.filter(
  (fileName) => !fileName.endsWith(".map")
);

改为遍历 entrypoints 对象,即可

代码语言:javascript
复制
let entrypointFiles = [];
for (let [entryFile, fileName] of Object.entries(entrypoints)) {
  let notMapFiles = fileName.filter(fileName => !fileName.endsWith('.map'));
  entrypointFiles = entrypointFiles.concat(notMapFiles);
  };

6. rewrite path 配置

由于上文多次提及,目前项目没有配置多个模板,所以此处没有做任何修改。

对于配置多个模板的同学,可以参考此文文末的解决方案 Multiple html pages with create-react-app app[5]

大致如下

代码语言:javascript
复制
historyApiFallback: {
  disableDotRule: true,
  verbose: true,
  rewrites: [
    { from: /^\/test/, to: '/test.html' },
  ]
},

验证

访问 http://localhost:3000/index.html

访问 http://localhost:3000/test.html

复盘

版本、时效性

参考网上文章时,需要注意一下文章的时间和依赖库的版本,尤其当有大版本变化时,要慎重,避免花费过多时间在可能错误的方向上;尽可能多花一些时间在时效性较高的资料,从而提升解决问题的概率。

ps:本文之前参考的文章多数是基于 create-react-app v2 的,而实际自己使用的是 CRA v3 版本。

错误日志

另外一个影响解决速度的原因是:没有报错信息

webpack.config.js 中的 ManifestPlugin 插件,generate 方法其实是报错了,但没有抛出。下图简单复现了一下,但加上了 try catch,并打印了一下,所以会有提示信息。

立 flag 后续研究研究有没有好的解决方案,

HtmlWebpackPlugin 和 ManifestPlugin

简单 mark 一下这两个插件。

HtmlWebpackPlugin 该插件用来生成 HTML 文件。参考 HtmlWebpackPlugin[6]

ManifestPlugin 该插件用来生成 asset manifest 资产清单。参考Webpack Manifest Plugin[7]

不足:配置很麻烦

显然每一次添加新页面都手动维护一堆配置信息不优雅,如果网页多了就需要重复 1、2、3、4 步骤,很不方便,期望优化成无需修改配置的模式。

优化

参考了前文提到的「Webpack」配置React多个页面同时打包和调试,主要思路就是利用 nodejs 操作文件的能力,fs.readdirSync 来扫描入口文件夹,自动生成相应的配置文件。

1. 改造入口文件目录结构

src 目录下分别建立 src/indexsrc/test 文件夹,确保文件夹下都有入口文件 index.tsx,后续会扫这个文件。

2. 在 paths.js 中添加扫描函数,并导出

调整 paths.js,在 module.exports 前添加下列扫描函数:

代码语言:javascript
复制
/**
 * 扫描函数
 */
function Scan() {
  const dirs = fs.readdirSync(resolveApp('src/'));
  const map = {};
  dirs.forEach((file) => {
    const state = fs.statSync(resolveApp('src/' + file))
    if (state.isDirectory()) {
      map[file] = resolveApp('src/' + file) + '/index.js'
    }
  })
  return map
}

const dirsMap = Scan();

调整导出 module.exports,添加 dirsMap,注释或删除无用的 appIndexJsappTestJs

3. 在 webpack.config.js 中添加生成配置函数

在 module.exports 前添加

代码语言:javascript
复制
// 生成 entry、plugins 配置
function setupMultiEntryConfig(webpackEnv) {
  const isEnvDevelopment = webpackEnv === "development";
  const isEnvProduction = webpackEnv === "production";

  const entry = {};
  const plugins = [];
  // key: 'index', 'test', ...
  Object.keys(paths.dirsMap).forEach((key) => {
    // entry 配置
    entry[key] = [
      // Include an alternative client for WebpackDevServer. A client's job is to
      // connect to WebpackDevServer by a socket and get notified about changes.
      // When you save a file, the client will either apply hot updates (in case
      // of CSS changes), or refresh the page (in case of JS changes). When you
      // make a syntax error, this client will display a syntax error overlay.
      // Note: instead of the default WebpackDevServer client, we use a custom one
      // to bring better experience for Create React App users. You can replace
      // the line below with these two lines if you prefer the stock client:
      // require.resolve('webpack-dev-server/client') + '?/',
      // require.resolve('webpack/hot/dev-server'),
      isEnvDevelopment &&
        require.resolve("react-dev-utils/webpackHotDevClient"),
      // Finally, this is your app's code:
      paths.dirsMap[key],
      // We include the app code last so that if there is a runtime error during
      // initialization, it doesn't blow up the WebpackDevServer client, and
      // changing JS code would still trigger a refresh.
    ].filter(Boolean);

    // plugins 配置
    // Generates an `index.html` file with the <script> injected.
    const htmlPlugin = new HtmlWebpackPlugin(
      Object.assign(
        {},
        {
          chunks: [key],
          inject: true,
          template: paths.appHtml,
          filename: `${key}.html`,
        },
        isEnvProduction
          ? {
              minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeRedundantAttributes: true,
                useShortDoctype: true,
                removeEmptyAttributes: true,
                removeStyleLinkTypeAttributes: true,
                keepClosingSlash: true,
                minifyJS: true,
                minifyCSS: true,
                minifyURLs: true,
              },
            }
          : undefined
      )
    );
    plugins.push(htmlPlugin);
  });
  return { entry, plugins };
}

在 module.exports 中调用上述函数:

代码语言:javascript
复制
// 生成 entry、plugins 配置
const multiEntryConfig = setupMultiEntryConfig(webpackEnv);

4. 调整 entry 和 plugins 配置

entry: multiEntryConfig.entry

...multiEntryConfig.plugins,

5. 在 start.js & build.js 中调整 checkRequiredFiles 检查函数

此时如果直接运行 yarn start 会报错,全局搜一下 appIndexJs 会发现在 start.jsbuild.js 中的 checkRequiredFiles 函数里有相关的校验逻辑,需要调整一下:

原:

改为:

验证

yarn start 一下,ok 的。

然后加一个新入口,

再重新运行一下 yarn start

Done~

感谢阅读到这里~ 也感谢分享相关资料的大佬们~

参考资料

[1]

React-CRA 多页面配置(npm run eject): https://segmentfault.com/a/1190000016960824

[2]

「Webpack」配置React多个页面同时打包和调试: https://zhuanlan.zhihu.com/p/31908335

[3]

Webpack 4 官方文档: https://v4.webpack.js.org/concepts/#entry

[4]

Create React App V2 - Multiple entry points: https://stackoverflow.com/questions/55308657/create-react-app-v2-multiple-entry-points

[5]

Multiple html pages with create-react-app app: https://sapandiwakar.in/multiple-html-pages-with-create-react-app-app/

[6]

HtmlWebpackPlugin: https://www.webpackjs.com/plugins/html-webpack-plugin/

[7]

Webpack Manifest Plugin: https://github.com/danethurber/webpack-manifest-plugin

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端时空 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 环境
  • 背景
  • 思路
  • 解决方案
  • 完整步骤
    • -1. 安装、运行 create-react-app
      • 0. yarn eject
        • 1. 调整 paths.js
          • 2. 修改 webpack.config.js 的 entry
            • 3. 修改 webpack.config.js 的 output
              • 4. 修改 webpack.config.js 的 plugins
                • 5. 修改 webpack.config.js 的 plugins
                  • 6. rewrite path 配置
                    • 验证
                    • 复盘
                      • 版本、时效性
                        • 错误日志
                          • HtmlWebpackPlugin 和 ManifestPlugin
                            • 不足:配置很麻烦
                            • 优化
                              • 1. 改造入口文件目录结构
                                • 2. 在 paths.js 中添加扫描函数,并导出
                                  • 3. 在 webpack.config.js 中添加生成配置函数
                                    • 4. 调整 entry 和 plugins 配置
                                      • 5. 在 start.js & build.js 中调整 checkRequiredFiles 检查函数
                                        • 验证
                                          • 参考资料
                                          领券
                                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档