【问题标题】:Create React App V2 - Multiple entry points创建 React App V2 - 多个入口点
【发布时间】:2019-03-22 22:36:27
【问题描述】:

我正在尝试构建一个具有 2 个入口点的 React 应用程序,一个用于应用程序,一个用于管理面板。

我从 Create React App V2 开始,并关注这个 gitHub 问题线程 https://github.com/facebook/create-react-app/issues/1084 和本教程 http://imshuai.com/create-react-app-multiple-entry-points/

我正在尝试移植从 CRA V1 添加多个入口点以在 V2 中工作的说明,但我认为我遗漏了一些东西。

退出 CRA 后,这些是我更改/添加到 paths.js 的路径:

module.exports = {
    appBuild: resolveApp('build/app'),
    appPublic: resolveApp('public/app'),
    appHtml: resolveApp('public/app/index.html'),
    appIndexJs: resolveModule(resolveApp, 'src/app'),
    appSrc: resolveApp('src'),
    adminIndexJs: resolveModule(resolveApp, 'src/admin'),
    adminSrc: resolveApp('src'),
    adminPublic: resolveApp('public/admin'),
    adminHtml: resolveApp('public/admin/index.html'),
};

我已将这些入口点添加到 webpack:

    entry: {
        app: [
            isEnvDevelopment &&
                require.resolve('react-dev-utils/webpackHotDevClient'),
            paths.appIndexJs,
        ].filter(Boolean),
        admin: [
            isEnvDevelopment &&
                require.resolve('react-dev-utils/webpackHotDevClient'),
            paths.adminIndexJs,
        ].filter(Boolean)
    },
    output: {
      path: isEnvProduction ? paths.appBuild : undefined,
      pathinfo: isEnvDevelopment,
      filename: isEnvProduction
        ? 'static/js/[name].[contenthash:8].js'
        : isEnvDevelopment && 'static/js/bundle.js',
      chunkFilename: isEnvProduction
        ? 'static/js/[name].[contenthash:8].chunk.js'
        : isEnvDevelopment && 'static/js/[name].chunk.js',
      publicPath: publicPath,
      devtoolModuleFilenameTemplate: isEnvProduction
        ? info =>
            path
              .relative(paths.appSrc, info.absoluteResourcePath)
              .replace(/\\/g, '/')
        : isEnvDevelopment &&
          (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
    },

我已经像这样修改了 HtmlWebpackPlugin:

  new HtmlWebpackPlugin(
    Object.assign(
      {},
      {
        inject: true,
        template: paths.appHtml,
        filename: paths.appPublic,
      },
      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,
        filename: paths.adminPublic,
      },
      isEnvProduction
        ? {
            minify: {
              removeComments: true,
              collapseWhitespace: true,
              removeRedundantAttributes: true,
              useShortDoctype: true,
              removeEmptyAttributes: true,
              removeStyleLinkTypeAttributes: true,
              keepClosingSlash: true,
              minifyJS: true,
              minifyCSS: true,
              minifyURLs: true,
            },
          }
        : undefined
    )
  ),

并修改webpack Dev Server:

historyApiFallback: {
  disableDotRule: true,
  rewrites: [
    { from: /^\/admin.html/, to: '/build/admin/index.html' },
  ]
},

我的文件结构如下:

.
+-- _src
|   +-- app.js
|   +-- admin.js
|   +-- _app
|       +-- App.js
|   +-- _admin
|       +-- App.js
|   +-- _shared
|       +-- serviceWorker.js
+-- _public
|   +-- _app
|       +-- index.html
|       +-- manifest.json
|   +-- _admin
|       +-- index.html
|       +-- manifest.json

我希望我的 build 文件夹包含一个 app 文件夹和一个 admin 文件夹,其中包含 2 个单独的 SPA。

当我运行 yarn start 时,它不会抛出任何错误并显示 Compiled successfully! 但是它只是部分编译了应用程序而不是管理应用程序,也没有编译或添加到应用程序中。

yarn build 确实会抛出一个错误和一个半编译的应用程序,没有管理应用程序。这是错误:

yarn run v1.12.3
$ node scripts/build.js
Creating an optimized production build...
Failed to compile.

EISDIR: illegal operation on a directory, open 
'foo/bar/public/app'


error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

在它退出之前在构建文件夹中创建了很多:

.
+-- _static
|   +-- _css
|   +-- _js
|   +-- _media
|       +-- logo.5d5d9eef.svg
+-- precache-manifest.a9c066d088142837bfe429bd3779ebfa.js
+-- service-worker.js
+-- asset-manifest.json
+-- manifest.json

有谁知道我错过了什么才能使其正常工作?

【问题讨论】:

    标签: reactjs webpack create-react-app


    【解决方案1】:

    我意识到将HTMLWebpackPlugin 中的filename 设置为appPublicadminPublic 是不正确的,它应该是app/index.html admin/index.html

    但是,我希望构建文件夹中有 2 个单独的文件夹,一个用于应用程序,另一个用于管理应用程序,使用此方法需要更多复杂性,因为 webpack 中没有可用于设置目标的入口变量小路。例如,我需要能够执行[entry]/static/js/[name].[contenthash:8].chunk.js 之类的操作。我认为一种方法是使用 Webpack MultiCompiler。

    但是,我没有这样做,而是将入口点作为环境变量传递到 package.json 中,像这样添加 REACT_APP_ENTRY=

      "scripts": {
        "start-app": "REACT_APP_ENTRY=app node scripts/start.js",
        "build-app": "REACT_APP_ENTRY=app node scripts/build.js",
        "start-admin": "REACT_APP_ENTRY=admin node scripts/start.js",
        "build-admin": "REACT_APP_ENTRY=admin node scripts/build.js",
        "test": "node scripts/test.js"
      },
    

    在 start.js 中我在顶部添加了const isApp = process.env.REACT_APP_ENTRY === 'app';

    'use strict';
    
    process.env.BABEL_ENV = 'development';
    process.env.NODE_ENV = 'development';
    
    const isApp = process.env.REACT_APP_ENTRY === 'app';
    

    并且更新了设置端口的位置,这样我就可以同时运行两个开发服务器而不会发生冲突:

    const DEFAULT_PORT = parseInt(process.env.PORT, 10) || (isApp ? 3000 : 3001);
    const HOST = process.env.HOST || '0.0.0.0';
    

    然后在paths.js的顶部添加const isApp = process.env.REACT_APP_ENTRY === 'app';:

    const envPublicUrl = process.env.PUBLIC_URL;
    const isApp = process.env.REACT_APP_ENTRY === 'app';
    

    最后根据环境变量集更新路径:

    module.exports = {
      dotenv: resolveApp('.env'),
      appPath: resolveApp('.'),
      appBuild: isApp ? resolveApp('build/app') : resolveApp('build/admin'),
      appPublic: isApp ? resolveApp('public/app') : resolveApp('public/admin'),
      appHtml: isApp ? resolveApp('public/app/index.html') : resolveApp('public/admin/index.html'),
      appIndexJs: isApp ? resolveModule(resolveApp, 'src/app') : resolveModule(resolveApp, 'src/admin'),
      appPackageJson: resolveApp('package.json'),
      appSrc: resolveApp('src'),
      appTsConfig: resolveApp('tsconfig.json'),
      yarnLockFile: resolveApp('yarn.lock'),
      testsSetup: resolveModule(resolveApp, 'src/setupTests'),
      proxySetup: resolveApp('src/setupProxy.js'),
      appNodeModules: resolveApp('node_modules'),
      publicUrl: getPublicUrl(resolveApp('package.json')),
      servedPath: getServedPath(resolveApp('package.json')),
    };
    

    我认为这种方法以及更简单的方法更适合此用例,因为它允许灵活地仅编译应用程序或仅编译管理员,而不是在仅更改一个时强制您编译两者。我可以同时运行yarn start-appyarn start-admin,而不同的应用程序在不同的端口上运行。

    【讨论】:

      【解决方案2】:

      我知道这是一个延迟的答案,但只是为了将来的搜索,步骤是:

      1. 弹出 (yarn eject)
      2. 编辑paths.js并在appHtml的入口下添加第二个入口点html文件
      appAdminHtml: resolveApp('public/admin.html'),
      
      1. 更新webpack.config.js 中的条目,使每个条目点包含一个条目。
      entry: {
        index: [
          isEnvDevelopment &&
          require.resolve('react-dev-utils/webpackHotDevClient'),
          paths.appIndexJs,
        ].filter(Boolean),
        admin: [
          isEnvDevelopment &&
          require.resolve('react-dev-utils/webpackHotDevClient'),
          paths.appSrc + '/admin/index.js',
        ].filter(Boolean)
      },
      
      1. 将生成的输出JS文件改成入口名(webpack.config.js内)
      output: {
        path: isEnvProduction ? paths.appBuild : undefined,
        pathinfo: isEnvDevelopment,
        // This is the important entry
        filename: isEnvProduction
          ? 'static/js/[name].[contenthash:8].js'
          : isEnvDevelopment && 'static/js/[name].bundle.js',
        futureEmitAssets: true,
        chunkFilename: isEnvProduction
          ? 'static/js/[name].[contenthash:8].chunk.js'
          : isEnvDevelopment && 'static/js/[name].chunk.js',
        publicPath: publicPath,
        devtoolModuleFilenameTemplate: isEnvProduction
          ? info =>
              path
                .relative(paths.appSrc, info.absoluteResourcePath)
                .replace(/\\/g, '/')
          : isEnvDevelopment &&
            (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
        jsonpFunction: `webpackJsonp${appPackageJson.name}`,
        globalObject: 'this',
      },
      
      1. 更新插件以使用注入的 js 脚本生成第二个文件(也在 webpack.config.js 内)。
      // Generates an `index.html` file with the <script> injected.
      new HtmlWebpackPlugin(
        Object.assign(
          {},
          {
            inject: true,
            chunks: ['index'],
            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
        )
      ),
      // Generates an `admin.html` file with the <script> injected.
      new HtmlWebpackPlugin(
        Object.assign(
          {},
          {
            inject: true,
            chunks: ['admin'],
            template: paths.appAdminHtml,
            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
        )
      ),
      
      1. 更新ManifestPlugin configuration to include the new entry point (also insidewebpack.config.js`):
      new ManifestPlugin({
        fileName: 'asset-manifest.json',
        publicPath: publicPath,
        generate: (seed, files, entrypoints) => {
          const manifestFiles = files.reduce((manifest, file) => {
            manifest[file.name] = file.path;
            return manifest;
          }, seed);
          let entrypointFiles = [];
          for (let [entryFile, fileName] of Object.entries(entrypoints)) {
            let notMapFiles = fileName.filter(fileName => !fileName.endsWith('.map'));
            entrypointFiles = entrypointFiles.concat(notMapFiles);
          };
      
          return {
            files: manifestFiles,
            entrypoints: entrypointFiles,
          };
        },
      }),
      
      1. 更新您的服务器(开发和生产)以重写路径。
        • 对于开发服务器,您需要更新webpackDevServer.config.js
      historyApiFallback: {
        disableDotRule: true,
        verbose: true,
        rewrites: [
          { from: /^\/admin/, to: '/admin.html' },
        ]
      },
      
      

      由于 Prod 服务器设置可能完全不同,我会让您弄清楚。

      This post 更详细地描述了所有内容。

      【讨论】:

        猜你喜欢
        • 2019-04-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-16
        • 2017-07-18
        • 1970-01-01
        相关资源
        最近更新 更多