创建 React App V2 - 多个入口点
Create React App V2 - Multiple entry points
我正在尝试构建一个具有 2 个入口点的 React 应用程序,一个用于应用程序,一个用于管理面板。
我从 Create React App V2 开始,然后关注这个 gitHub 问题线程 https://github.com/facebook/create-react-app/issues/1084 and this tutorial http://imshuai.com/create-react-app-multiple-entry-points/。
我正在尝试将添加多个入口点的说明从 CRA V1 移植到 V2 中,但我想我遗漏了一些东西。
退出 CRA 后,这些是我 changed/added 到 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开发服务器:
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
我希望我的构建文件夹包含一个应用程序文件夹和一个管理文件夹,其中包含 2 个独立的 SPA。
当我 运行 yarn start
它没有抛出任何错误并说 Compiled successfully!
但是它只部分编译了应用程序而不是管理应用程序,没有编译 js 或添加到应用程序中。
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
有谁知道我错过了什么才能使这项工作正常进行?
我发现HTMLWebpackPlugin
中的filename
设置为appPublic
或adminPublic
不正确,应该是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-app
和 yarn start-admin
同时使用不同端口上的单独应用 运行ning。
我知道这是一个延迟的答案,但为了以后的搜索,步骤是:
- 弹出 (
yarn eject
)
- 编辑 paths.js 并在 appHtml
条目下添加第二个入口点 html 文件
appAdminHtml: resolveApp('public/admin.html'),
- 更新
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)
},
- 将生成的输出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',
},
- 更新插件以使用注入的 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
)
),
- 更新
ManifestPlugin configuration to include the new entry point (also inside
webpack.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,
};
},
}),
- 更新您的服务器(开发和生产)以重写路径。
- 对于开发服务器,您需要更新
webpackDevServer.config.js
。
historyApiFallback: {
disableDotRule: true,
verbose: true,
rewrites: [
{ from: /^\/admin/, to: '/admin.html' },
]
},
由于 Prod 服务器设置可能完全不同,我会让您弄清楚。
This post 描述的更详细。
我正在尝试构建一个具有 2 个入口点的 React 应用程序,一个用于应用程序,一个用于管理面板。
我从 Create React App V2 开始,然后关注这个 gitHub 问题线程 https://github.com/facebook/create-react-app/issues/1084 and this tutorial http://imshuai.com/create-react-app-multiple-entry-points/。
我正在尝试将添加多个入口点的说明从 CRA V1 移植到 V2 中,但我想我遗漏了一些东西。
退出 CRA 后,这些是我 changed/added 到 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开发服务器:
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
我希望我的构建文件夹包含一个应用程序文件夹和一个管理文件夹,其中包含 2 个独立的 SPA。
当我 运行 yarn start
它没有抛出任何错误并说 Compiled successfully!
但是它只部分编译了应用程序而不是管理应用程序,没有编译 js 或添加到应用程序中。
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
有谁知道我错过了什么才能使这项工作正常进行?
我发现HTMLWebpackPlugin
中的filename
设置为appPublic
或adminPublic
不正确,应该是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-app
和 yarn start-admin
同时使用不同端口上的单独应用 运行ning。
我知道这是一个延迟的答案,但为了以后的搜索,步骤是:
- 弹出 (
yarn eject
) - 编辑 paths.js 并在 appHtml 条目下添加第二个入口点 html 文件
appAdminHtml: resolveApp('public/admin.html'),
- 更新
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)
},
- 将生成的输出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',
},
- 更新插件以使用注入的 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
)
),
- 更新
ManifestPlugin configuration to include the new entry point (also inside
webpack.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,
};
},
}),
- 更新您的服务器(开发和生产)以重写路径。
- 对于开发服务器,您需要更新
webpackDevServer.config.js
。
- 对于开发服务器,您需要更新
historyApiFallback: {
disableDotRule: true,
verbose: true,
rewrites: [
{ from: /^\/admin/, to: '/admin.html' },
]
},
由于 Prod 服务器设置可能完全不同,我会让您弄清楚。
This post 描述的更详细。