在多页面应用中使用 React
Using React in a multi-page app
我一直在玩 React,到目前为止我真的很喜欢它。我正在使用 NodeJS 构建一个应用程序,并希望将 React 用于应用程序中的一些交互式组件。我不想让它成为单页应用程序。
我还没有在网上找到任何可以回答以下问题的内容:
如何在多页面应用程序中分解或捆绑我的 React 组件?
目前我的所有组件都在一个文件中,尽管我可能永远不会在应用程序的某些部分加载它们。
到目前为止,我正在尝试使用条件语句通过搜索 React 将呈现的容器的 ID 来呈现组件。我不是 100% 确定 React 的最佳实践是什么。它看起来像这样。
if(document.getElementById('a-compenent-in-page-1')) {
React.render(
<AnimalBox url="/api/birds" />,
document.getElementById('a-compenent-in-page-1')
);
}
if(document.getElementById('a-compenent-in-page-2')) {
React.render(
<AnimalBox url="/api/cats" />,
document.getElementById('a-compenent-in-page-2')
);
}
if(document.getElementById('a-compenent-in-page-3')) {
React.render(
<AnimalSearchBox url="/api/search/:term" />,
document.getElementById('a-compenent-in-page-3')
);
}
我仍在阅读文档,但尚未找到我需要的多页应用程序。
提前致谢。
我正在从头开始构建应用程序并边学边做,但我认为您正在寻找的是 React-Router。 React-Router 将您的组件映射到特定的 URL。例如:
render((
<Router>
<Route path="/" component={App}>
<Route path="api/animals" component={Animals}>
<Route path="birds" component={Birds}/>
<Route path="cats" component={Cats}/>
</Route>
</Route>
<Route path="api/search:term" component={AnimalSearchBox}>
</Router>
), document.body)
在搜索案例中,'term' 可作为 AnimalSearchBox 中的 属性 访问:
componentDidMount() {
// from the path `/api/search/:term`
const term = this.props.params.term
}
试试吧。 This 教程使我对这个主题和其他相关主题的理解更上一层楼。
原回答如下:
我在这里找到了相同的答案。看看 this post 是否对您有所启发。如果您的应用程序和我的一样,它的区域变化很小,仅在主体部分有所不同。您可以创建一个小部件,其职责是根据应用程序的状态呈现不同的小部件。使用 flux 架构,您可以分派一个导航操作来更改您的 body 小部件切换的状态,从而有效地仅更新页面的主体。
这就是我现在正在尝试的方法。
您使用的是内容管理系统吗?他们往往喜欢更改可能会破坏您的应用程序的网址。
另一种方法是使用 React Habitat.
有了它,您可以注册组件,它们会自动暴露给 dom。
例子
注册组件:
container.register('AnimalBox', AnimalBox);
container.register('AnimalSearchBox', AnimalSearchBox);
然后它们可以在您的 dom 中使用,如下所示:
<div data-component="AnimalBox"></div>
<div data-component="AnimalSearchBox"></div>
以上将自动替换为您的 React 组件。
然后您也可以自动将属性(或道具)传递给您的组件:
<div data-component="AnimalBox" data-prop-size="small"></div>
这会将 size
作为组件的 prop 公开。有 additional options 用于传递其他类型,例如 json、数组、整数、浮点数等
目前我也在做类似的事情
该应用程序不是完整的 React 应用程序,我将 React 用于动态 Stuff,例如 CommentBox,它是 autark。并且可以包含在具有特殊参数的任何点..
然而,我所有的子应用程序都被加载并包含在一个文件中all.js
,因此它可以被浏览器跨页面缓存。
当我需要将应用程序包含到 SSR 模板中时,我只需要包含一个带有 class“__react-root”的 DIV 和一个特殊 ID,(名称要渲染的 React App 的数量)
逻辑很简单:
import CommentBox from './apps/CommentBox';
import OtherApp from './apps/OtherApp';
const APPS = {
CommentBox,
OtherApp
};
function renderAppInElement(el) {
var App = APPS[el.id];
if (!App) return;
// get props from elements data attribute, like the post_id
const props = Object.assign({}, el.dataset);
ReactDOM.render(<App {...props} />, el);
}
document
.querySelectorAll('.__react-root')
.forEach(renderAppInElement)
<div>Some Article</div>
<div id="CommentBox" data-post_id="10" class="__react-root"></div>
<script src="/all.js"></script>
编辑
由于 webpack 完美支持代码拆分和延迟加载,我认为包含一个示例是有意义的,其中您不需要将所有应用程序加载到一个包中,而是将它们拆分并按需加载。
import React from 'react';
import ReactDOM from 'react-dom';
const apps = {
'One': () => import('./One'),
'Two': () => import('./Two'),
}
const renderAppInElement = (el) => {
if (apps[el.id]) {
apps[el.id]().then((App) => {
ReactDOM.render(<App {...el.dataset} />, el);
});
}
}
您可以在 webpack.config.js 文件中为应用程序提供多个入口点:
var config = {
entry: {
home: path.resolve(__dirname, './src/main'),
page1: path.resolve(__dirname, './src/page1'),
page2: path.resolve(__dirname, './src/page2'),
vendors: ['react']
},
output: {
path: path.join(__dirname, 'js'),
filename: '[name].bundle.js',
chunkFilename: '[id].chunk.js'
},
}
然后您可以在 src 文件夹中包含三个不同的 html 文件及其各自的 js 文件(第 1 页的示例):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Page 1</title>
</head>
<body>
<div id="app"></div>
<script src="./vendors.js"></script>
<script src="./page1.bundle.js"></script>
</body>
</html>
JavaScript 文件:
import React from 'react'
import ReactDom from 'react-dom'
import App from './components/App'
import ComponentA from './components/ReactComponentA'
ReactDom.render(<div>
<App title='page1' />
<ReactComponentA/>
</div>, document.getElementById('app'))
然后可以为每个页面加载不同的 React 组件。
我知道这个问题已经有一段时间了,但希望这对某人有所帮助。
正如@Cocomico 提到的,您可以在 webpack.config.js 文件中为应用程序提供多个入口点。如果你正在寻找一个简单的 Webpack 设置(基于多个入口点的想法),它允许你将 React 组件添加到静态页面,你可以考虑使用这个:https://github.com/przemek-nowicki/multi-page-app-with-react
我建议你看看 InertiaJS:https://inertiajs.com/
With Inertia you build apps just like you've always done with your server-side web framework of choice. You use your framework's existing functionality for routing, controllers, middleware, authentication, authorization, data fetching, and more.
The only thing that's different is your view layer. Instead of using server-side rendering (eg. Blade or ERB templates), the views are JavaScript page components. This allows you to build your entire front-end using React, Vue or Svelte.
因为我遇到了同样的情况,没有找到满足我需求的答案,所以我又重温了这个老问题。因此,根据@webdeb 的回答,我编写了一个迷你框架,它使用 CRA(不弹出)在任何 HTML 页面中注入任意数量的组件,同时保留 CRA 的所有优势。
TL;DR
您可以查看我的 public 存储库 here that contains all the needed files and a link to a Medium article,我在其中彻底解释了所有这些内容。
总体思路
诀窍是像往常一样安装 CRA,并按如下方式更新 index.js
文件:
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import reportWebVitals from './reportWebVitals';
//list here all the components that could be inserted in a web page
const apps = {
'App': React.lazy(() => import('./App')),
'TestComponent1': React.lazy(() => import('./TestComponent1')),
'TestComponent2': React.lazy(() => import('./TestComponent2')),
}
//event manager to communicate between the components
const bridgeEvent = new EventTarget();
//common fallback for all the components
function Fallback() {
return <div>Loading...</div>;
}
const renderAppInElement = (el) => {
if(apps[el.dataset.reactComponent] && !el.dataset.rendered){
//get the component's name stored in the data-react-component attribute
const App = apps[el.dataset.reactComponent];
//render the component, inject all the HTML attributes and the Event bridge
ReactDOM.render(
<Suspense fallback={<Fallback />}>
<App {...el.dataset} bridgeEvent={bridgeEvent}/>
</Suspense>
, el);
el.dataset.rendered = true;
}
else if(el.dataset.rendered){
console.log('el', el, 'is already rendered')
}
}
//ONLY FOR THE DEV PHASE
const rootEl = document.getElementById('root');
//generate components without attributes
if(process.env.REACT_APP_RENDER_CMP){
const components = process.env.REACT_APP_RENDER_CMP.split(',');
components.forEach(item => {
const componentEl = document.createElement('div');
componentEl.setAttribute("data-react-component", item);
componentEl.className = "__react-cmp";
rootEl.append(componentEl);
});
}
//generate components with attributes
if(process.env.REACT_APP_RENDER_CMP_WITH_ATTRS){
let componentsWithAttrs;
try{
componentsWithAttrs = JSON.parse(process.env.REACT_APP_RENDER_CMP_WITH_ATTRS);
}
catch(e){
console.log('fail to parse REACT_APP_RENDER_CMP_WITH_ATTRS', e);
}
if(componentsWithAttrs){
componentsWithAttrs.forEach(cmp => {
const componentEl = document.createElement('div');
componentEl.setAttribute("data-react-component", cmp.class);
componentEl.className = "__react-cmp";
Object.keys(cmp.data).forEach(attrKey => {
componentEl.setAttribute(attrKey, cmp.data[attrKey]);
});
rootEl.append(componentEl);
});
}
}
//the default name of the global object is ReactComponents, but it could be customized via the REACT_APP_NAMESPACE environment variable
const appNamespace = process.env.REACT_APP_NAMESPACE || "ReactComponents";
window[appNamespace] = {
ready: false,
parseComponents(container){
//parse the container or the whole document and inject all the components in the containers that have a "__react-cmp" class
(container || document)
.querySelectorAll('.__react-cmp')
.forEach(renderAppInElement);
}
}
window[appNamespace].parseComponents();
window[appNamespace].ready = true;
//if dynamic parsing must be done via the window.ReactComponents.parseComponents() method
//check the availability of window.ReactComponents object via window.ReactComponents.ready property
//or define a window.ReactComponentsAsyncInit() method to be notified of the availability
if(typeof window[`${appNamespace}AsyncInit`] === 'function'){
window[`${appNamespace}AsyncInit`]();
}
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
reportWebVitals();
然后您可以添加 REACT_APP_RENDER_CMP
and/or REACT_APP_RENDER_CMP_WITH_ATTRS
环境变量来测试您的组件,同时使用 CRA 的开发服务器。您的 .env.development.local
文件可能如下所示:
#this will render the TestComponent1 and TestComponent2 without any attributes
REACT_APP_RENDER_CMP="TestComponent1,TestComponent2"
#this will render TestComponent1 with the data-test-attribute attribute set to "test attribute value"
REACT_APP_RENDER_CMP_WITH_ATTRS="[{"class":"TestComponent1","data":{"data-test-attribute":"test attribute value"}}]"
构建文件后,您的 index.html
文件应该包含所有 .js
和 .css
文件,您需要将其包含在多页应用程序的每个页面中加载你的 React 组件。不要忘记在 .env
文件中添加 INLINE_RUNTIME_CHUNK=false
以避免任何内联 javascript!
然后,将组件的容器添加到 HTML 页面中您希望它们显示的位置。例如:
<div class="__react-cmp" data-react-component="TestComponent1"></div>
在 CRA 的 index.js
文件中声明的 parseComponents()
应该被执行,用 .__react-cmp
class 抓取你的 div
,然后将其用作TestComponent1
React 组件的容器。
在专门的 repo and article 中,我解释了如何使用 CRA 的 BUILD_PATH
环境变量更改构建路径(这样您就可以在服务器或 CDN 中托管构建的文件)和我提供一个加载器,它将解析构建的 index.html
文件并在页面中动态插入所有需要的 .js
和 .css
文件(因此您只需要包含加载器,而不是所有文件).这是加载程序的样子,假设它的文件名为 cmp-loader.js
并托管在您构建的 index.html
文件旁边:
(async () => {
const head = document.getElementsByTagName('head')[0];
const scriptSrcRegexp = new RegExp('<script.*?src="(.*?)"', 'gmi');
//get the exact script's src as defined in the src attribute
const scriptSrc = scriptSrcRegexp.exec(document.currentScript.outerHTML);
//all the resources should be relative to the path of this script
const resourcesPath = (scriptSrc && scriptSrc.length > 1) ? scriptSrc[1].replace('cmp-loader.js', '') : '';
//get the index content
const indexHTML = await (await fetch(resourcesPath+'index.html', {cache:'reload'})).text();
//assume that all the .js and .css files to load are in the "static" folder
const reactCSSRegexp = new RegExp(`<link href="${resourcesPath}static\/css\/(.*?)\.css" rel="stylesheet">`, 'gm');
const reactJSRegexp = new RegExp(`<script (.*?) src="${resourcesPath}static\/js\/(.*?)\.js"><\/script>`, 'gm');
//grab all the css tags
const ReactCSS = [].concat(indexHTML.match(reactCSSRegexp)).join('');
//grab all the js tags
const ReactJS = [].concat(indexHTML.match(reactJSRegexp)).join('');
//parse and execute the scripts
const scriptsDoc = new DOMParser().parseFromString(ReactJS, 'text/html');
Array.from(scriptsDoc.getElementsByTagName('script')).forEach(item => {
const script = document.createElement('script');
[...item.attributes].forEach(attr => {
script.setAttribute(attr.name, attr.value)
})
head.appendChild(script);
});
//inject the CSS
head.insertAdjacentHTML('beforeend', ReactCSS);
})().catch(e => {
console.log('fail to load react-cmp', e)
});
我一直在玩 React,到目前为止我真的很喜欢它。我正在使用 NodeJS 构建一个应用程序,并希望将 React 用于应用程序中的一些交互式组件。我不想让它成为单页应用程序。
我还没有在网上找到任何可以回答以下问题的内容:
如何在多页面应用程序中分解或捆绑我的 React 组件?
目前我的所有组件都在一个文件中,尽管我可能永远不会在应用程序的某些部分加载它们。
到目前为止,我正在尝试使用条件语句通过搜索 React 将呈现的容器的 ID 来呈现组件。我不是 100% 确定 React 的最佳实践是什么。它看起来像这样。
if(document.getElementById('a-compenent-in-page-1')) {
React.render(
<AnimalBox url="/api/birds" />,
document.getElementById('a-compenent-in-page-1')
);
}
if(document.getElementById('a-compenent-in-page-2')) {
React.render(
<AnimalBox url="/api/cats" />,
document.getElementById('a-compenent-in-page-2')
);
}
if(document.getElementById('a-compenent-in-page-3')) {
React.render(
<AnimalSearchBox url="/api/search/:term" />,
document.getElementById('a-compenent-in-page-3')
);
}
我仍在阅读文档,但尚未找到我需要的多页应用程序。
提前致谢。
我正在从头开始构建应用程序并边学边做,但我认为您正在寻找的是 React-Router。 React-Router 将您的组件映射到特定的 URL。例如:
render((
<Router>
<Route path="/" component={App}>
<Route path="api/animals" component={Animals}>
<Route path="birds" component={Birds}/>
<Route path="cats" component={Cats}/>
</Route>
</Route>
<Route path="api/search:term" component={AnimalSearchBox}>
</Router>
), document.body)
在搜索案例中,'term' 可作为 AnimalSearchBox 中的 属性 访问:
componentDidMount() {
// from the path `/api/search/:term`
const term = this.props.params.term
}
试试吧。 This 教程使我对这个主题和其他相关主题的理解更上一层楼。
原回答如下:
我在这里找到了相同的答案。看看 this post 是否对您有所启发。如果您的应用程序和我的一样,它的区域变化很小,仅在主体部分有所不同。您可以创建一个小部件,其职责是根据应用程序的状态呈现不同的小部件。使用 flux 架构,您可以分派一个导航操作来更改您的 body 小部件切换的状态,从而有效地仅更新页面的主体。
这就是我现在正在尝试的方法。
您使用的是内容管理系统吗?他们往往喜欢更改可能会破坏您的应用程序的网址。
另一种方法是使用 React Habitat.
有了它,您可以注册组件,它们会自动暴露给 dom。
例子
注册组件:
container.register('AnimalBox', AnimalBox);
container.register('AnimalSearchBox', AnimalSearchBox);
然后它们可以在您的 dom 中使用,如下所示:
<div data-component="AnimalBox"></div>
<div data-component="AnimalSearchBox"></div>
以上将自动替换为您的 React 组件。
然后您也可以自动将属性(或道具)传递给您的组件:
<div data-component="AnimalBox" data-prop-size="small"></div>
这会将 size
作为组件的 prop 公开。有 additional options 用于传递其他类型,例如 json、数组、整数、浮点数等
目前我也在做类似的事情
该应用程序不是完整的 React 应用程序,我将 React 用于动态 Stuff,例如 CommentBox,它是 autark。并且可以包含在具有特殊参数的任何点..
然而,我所有的子应用程序都被加载并包含在一个文件中all.js
,因此它可以被浏览器跨页面缓存。
当我需要将应用程序包含到 SSR 模板中时,我只需要包含一个带有 class“__react-root”的 DIV 和一个特殊 ID,(名称要渲染的 React App 的数量)
逻辑很简单:
import CommentBox from './apps/CommentBox';
import OtherApp from './apps/OtherApp';
const APPS = {
CommentBox,
OtherApp
};
function renderAppInElement(el) {
var App = APPS[el.id];
if (!App) return;
// get props from elements data attribute, like the post_id
const props = Object.assign({}, el.dataset);
ReactDOM.render(<App {...props} />, el);
}
document
.querySelectorAll('.__react-root')
.forEach(renderAppInElement)
<div>Some Article</div>
<div id="CommentBox" data-post_id="10" class="__react-root"></div>
<script src="/all.js"></script>
编辑
由于 webpack 完美支持代码拆分和延迟加载,我认为包含一个示例是有意义的,其中您不需要将所有应用程序加载到一个包中,而是将它们拆分并按需加载。
import React from 'react';
import ReactDOM from 'react-dom';
const apps = {
'One': () => import('./One'),
'Two': () => import('./Two'),
}
const renderAppInElement = (el) => {
if (apps[el.id]) {
apps[el.id]().then((App) => {
ReactDOM.render(<App {...el.dataset} />, el);
});
}
}
您可以在 webpack.config.js 文件中为应用程序提供多个入口点:
var config = {
entry: {
home: path.resolve(__dirname, './src/main'),
page1: path.resolve(__dirname, './src/page1'),
page2: path.resolve(__dirname, './src/page2'),
vendors: ['react']
},
output: {
path: path.join(__dirname, 'js'),
filename: '[name].bundle.js',
chunkFilename: '[id].chunk.js'
},
}
然后您可以在 src 文件夹中包含三个不同的 html 文件及其各自的 js 文件(第 1 页的示例):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Page 1</title>
</head>
<body>
<div id="app"></div>
<script src="./vendors.js"></script>
<script src="./page1.bundle.js"></script>
</body>
</html>
JavaScript 文件:
import React from 'react'
import ReactDom from 'react-dom'
import App from './components/App'
import ComponentA from './components/ReactComponentA'
ReactDom.render(<div>
<App title='page1' />
<ReactComponentA/>
</div>, document.getElementById('app'))
然后可以为每个页面加载不同的 React 组件。
我知道这个问题已经有一段时间了,但希望这对某人有所帮助。
正如@Cocomico 提到的,您可以在 webpack.config.js 文件中为应用程序提供多个入口点。如果你正在寻找一个简单的 Webpack 设置(基于多个入口点的想法),它允许你将 React 组件添加到静态页面,你可以考虑使用这个:https://github.com/przemek-nowicki/multi-page-app-with-react
我建议你看看 InertiaJS:https://inertiajs.com/
With Inertia you build apps just like you've always done with your server-side web framework of choice. You use your framework's existing functionality for routing, controllers, middleware, authentication, authorization, data fetching, and more.
The only thing that's different is your view layer. Instead of using server-side rendering (eg. Blade or ERB templates), the views are JavaScript page components. This allows you to build your entire front-end using React, Vue or Svelte.
因为我遇到了同样的情况,没有找到满足我需求的答案,所以我又重温了这个老问题。因此,根据@webdeb 的回答,我编写了一个迷你框架,它使用 CRA(不弹出)在任何 HTML 页面中注入任意数量的组件,同时保留 CRA 的所有优势。
TL;DR
您可以查看我的 public 存储库 here that contains all the needed files and a link to a Medium article,我在其中彻底解释了所有这些内容。
总体思路
诀窍是像往常一样安装 CRA,并按如下方式更新 index.js
文件:
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import reportWebVitals from './reportWebVitals';
//list here all the components that could be inserted in a web page
const apps = {
'App': React.lazy(() => import('./App')),
'TestComponent1': React.lazy(() => import('./TestComponent1')),
'TestComponent2': React.lazy(() => import('./TestComponent2')),
}
//event manager to communicate between the components
const bridgeEvent = new EventTarget();
//common fallback for all the components
function Fallback() {
return <div>Loading...</div>;
}
const renderAppInElement = (el) => {
if(apps[el.dataset.reactComponent] && !el.dataset.rendered){
//get the component's name stored in the data-react-component attribute
const App = apps[el.dataset.reactComponent];
//render the component, inject all the HTML attributes and the Event bridge
ReactDOM.render(
<Suspense fallback={<Fallback />}>
<App {...el.dataset} bridgeEvent={bridgeEvent}/>
</Suspense>
, el);
el.dataset.rendered = true;
}
else if(el.dataset.rendered){
console.log('el', el, 'is already rendered')
}
}
//ONLY FOR THE DEV PHASE
const rootEl = document.getElementById('root');
//generate components without attributes
if(process.env.REACT_APP_RENDER_CMP){
const components = process.env.REACT_APP_RENDER_CMP.split(',');
components.forEach(item => {
const componentEl = document.createElement('div');
componentEl.setAttribute("data-react-component", item);
componentEl.className = "__react-cmp";
rootEl.append(componentEl);
});
}
//generate components with attributes
if(process.env.REACT_APP_RENDER_CMP_WITH_ATTRS){
let componentsWithAttrs;
try{
componentsWithAttrs = JSON.parse(process.env.REACT_APP_RENDER_CMP_WITH_ATTRS);
}
catch(e){
console.log('fail to parse REACT_APP_RENDER_CMP_WITH_ATTRS', e);
}
if(componentsWithAttrs){
componentsWithAttrs.forEach(cmp => {
const componentEl = document.createElement('div');
componentEl.setAttribute("data-react-component", cmp.class);
componentEl.className = "__react-cmp";
Object.keys(cmp.data).forEach(attrKey => {
componentEl.setAttribute(attrKey, cmp.data[attrKey]);
});
rootEl.append(componentEl);
});
}
}
//the default name of the global object is ReactComponents, but it could be customized via the REACT_APP_NAMESPACE environment variable
const appNamespace = process.env.REACT_APP_NAMESPACE || "ReactComponents";
window[appNamespace] = {
ready: false,
parseComponents(container){
//parse the container or the whole document and inject all the components in the containers that have a "__react-cmp" class
(container || document)
.querySelectorAll('.__react-cmp')
.forEach(renderAppInElement);
}
}
window[appNamespace].parseComponents();
window[appNamespace].ready = true;
//if dynamic parsing must be done via the window.ReactComponents.parseComponents() method
//check the availability of window.ReactComponents object via window.ReactComponents.ready property
//or define a window.ReactComponentsAsyncInit() method to be notified of the availability
if(typeof window[`${appNamespace}AsyncInit`] === 'function'){
window[`${appNamespace}AsyncInit`]();
}
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
reportWebVitals();
然后您可以添加 REACT_APP_RENDER_CMP
and/or REACT_APP_RENDER_CMP_WITH_ATTRS
环境变量来测试您的组件,同时使用 CRA 的开发服务器。您的 .env.development.local
文件可能如下所示:
#this will render the TestComponent1 and TestComponent2 without any attributes
REACT_APP_RENDER_CMP="TestComponent1,TestComponent2"
#this will render TestComponent1 with the data-test-attribute attribute set to "test attribute value"
REACT_APP_RENDER_CMP_WITH_ATTRS="[{"class":"TestComponent1","data":{"data-test-attribute":"test attribute value"}}]"
构建文件后,您的 index.html
文件应该包含所有 .js
和 .css
文件,您需要将其包含在多页应用程序的每个页面中加载你的 React 组件。不要忘记在 .env
文件中添加 INLINE_RUNTIME_CHUNK=false
以避免任何内联 javascript!
然后,将组件的容器添加到 HTML 页面中您希望它们显示的位置。例如:
<div class="__react-cmp" data-react-component="TestComponent1"></div>
在 CRA 的 index.js
文件中声明的 parseComponents()
应该被执行,用 .__react-cmp
class 抓取你的 div
,然后将其用作TestComponent1
React 组件的容器。
在专门的 repo and article 中,我解释了如何使用 CRA 的 BUILD_PATH
环境变量更改构建路径(这样您就可以在服务器或 CDN 中托管构建的文件)和我提供一个加载器,它将解析构建的 index.html
文件并在页面中动态插入所有需要的 .js
和 .css
文件(因此您只需要包含加载器,而不是所有文件).这是加载程序的样子,假设它的文件名为 cmp-loader.js
并托管在您构建的 index.html
文件旁边:
(async () => {
const head = document.getElementsByTagName('head')[0];
const scriptSrcRegexp = new RegExp('<script.*?src="(.*?)"', 'gmi');
//get the exact script's src as defined in the src attribute
const scriptSrc = scriptSrcRegexp.exec(document.currentScript.outerHTML);
//all the resources should be relative to the path of this script
const resourcesPath = (scriptSrc && scriptSrc.length > 1) ? scriptSrc[1].replace('cmp-loader.js', '') : '';
//get the index content
const indexHTML = await (await fetch(resourcesPath+'index.html', {cache:'reload'})).text();
//assume that all the .js and .css files to load are in the "static" folder
const reactCSSRegexp = new RegExp(`<link href="${resourcesPath}static\/css\/(.*?)\.css" rel="stylesheet">`, 'gm');
const reactJSRegexp = new RegExp(`<script (.*?) src="${resourcesPath}static\/js\/(.*?)\.js"><\/script>`, 'gm');
//grab all the css tags
const ReactCSS = [].concat(indexHTML.match(reactCSSRegexp)).join('');
//grab all the js tags
const ReactJS = [].concat(indexHTML.match(reactJSRegexp)).join('');
//parse and execute the scripts
const scriptsDoc = new DOMParser().parseFromString(ReactJS, 'text/html');
Array.from(scriptsDoc.getElementsByTagName('script')).forEach(item => {
const script = document.createElement('script');
[...item.attributes].forEach(attr => {
script.setAttribute(attr.name, attr.value)
})
head.appendChild(script);
});
//inject the CSS
head.insertAdjacentHTML('beforeend', ReactCSS);
})().catch(e => {
console.log('fail to load react-cmp', e)
});