Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit719221f

Browse files
committed
chore(example): add react-in-vue examples
1 parent6d826bb commit719221f

File tree

18 files changed

+7369
-0
lines changed

18 files changed

+7369
-0
lines changed

‎examples/react-in-vue/README.md‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#React in Vue - Module Federation Demo
2+
3+
This demo is a simple proof of concept approach for fetching a React component from a*"remote"* React app and mounting it within a Vue*"host"* app using module federation.
4+
5+
The Vue app fetches and renders a**React button which can be used to increment a counter controlled by Vue**. The button text can be manipulated via a text input and it can also be unmounted/mounted using a checkbox.
6+
7+
This illusatrates that the approach used allows communication to-and-fro Vue and React, as well as correctly handling React lifecycle hooks when the host app unmounts the parent component.
8+
9+
---
10+
11+
##Running
12+
13+
Install dependencies
14+
15+
`yarn`
16+
17+
Then run development servers with
18+
19+
`yarn start`
20+
21+
This will build and serve both`app1` and`app2` on ports`3001` and`3002` respectively.
22+
23+
- Host (App1, Vue app):[localhost:3001](http://localhost:3001/)
24+
- Remote (App2, React app):[localhost:3002](http://localhost:3002/)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<divid="app"></div>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name":"app1",
3+
"private":true,
4+
"version":"1.0.0",
5+
"scripts": {
6+
"start":"webpack-cli serve",
7+
"serve":"serve dist -p 3001",
8+
"build":"webpack --mode production",
9+
"clean":"rm -rf dist"
10+
},
11+
"dependencies": {
12+
"@babel/core":"^7.11.0",
13+
"@vue/compiler-sfc":"^3.0.11",
14+
"babel-loader":"^8.1.0",
15+
"serve":"^12.0.0",
16+
"vue":"^3.0.11",
17+
"react":"^16.13.0",
18+
"react-dom":"^16.13.0"
19+
},
20+
"devDependencies": {
21+
"css-loader":"6.3.0",
22+
"file-loader":"6.2.0",
23+
"html-webpack-plugin":"5.3.2",
24+
"mini-css-extract-plugin":"2.3.0",
25+
"url-loader":"4.1.1",
26+
"vue-loader":"16.5.0",
27+
"vue-template-compiler":"2.6.14",
28+
"webpack":"5.58.0",
29+
"webpack-cli":"4.8.0",
30+
"webpack-dev-server":"4.2.1"
31+
}
32+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import{ref}from"vue";
2+
importReactButtonfrom"./ReactButton";
3+
4+
exportdefault{
5+
name:"Layout",
6+
components:{ ReactButton},
7+
setup(){
8+
constshowButton=ref(true);
9+
constbuttonText=ref("React button");
10+
constclickedCount=ref(0);
11+
constincrementCount=()=>(clickedCount.value+=1);
12+
13+
return{ showButton, buttonText, clickedCount, incrementCount};
14+
},
15+
template:`
16+
<div class="layout-app">
17+
<div>
18+
<h2>Vue State/Input</h2>
19+
<div>
20+
<label>
21+
<span>Show button:</span>
22+
<input v-model="showButton" type="checkbox" />
23+
</label>
24+
</div>
25+
<div>
26+
<label>
27+
<span>Button text:</span>
28+
<input v-model="buttonText" type="text" />
29+
</label>
30+
</div>
31+
<div>
32+
<label>
33+
<span>Times button clicked: </span>
34+
<input disabled type="number" :value="clickedCount" />
35+
</label>
36+
</div>
37+
</div>
38+
<div>
39+
<h2>React Button - loaded via Module Federation</h2>
40+
<div class="remote-component">
41+
<react-button v-if="showButton" :text="buttonText" :onClick="incrementCount" />
42+
</div>
43+
</div>
44+
</div>
45+
`,
46+
};
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import{ref,onMounted,onBeforeUnmount,onUpdated}from"vue";
2+
importReactDOMfrom"react-dom";
3+
importReactfrom"react";
4+
5+
constfirstLoad=newPromise((resolve)=>setTimeout(resolve,1000));
6+
7+
asyncfunctionfetchButton(){
8+
// simulate long network delay
9+
awaitfirstLoad;
10+
11+
// uncomment to simulate failed load
12+
// throw new Error("Failed to load button from remote.");
13+
14+
return(awaitimport("app2/Button")).default;
15+
}
16+
17+
exportdefault{
18+
name:"ReactButton",
19+
props:{
20+
text:String,
21+
onClick:Function,
22+
},
23+
setup(props){
24+
constroot=ref(null);
25+
consterror=ref(null);
26+
constButtonComponent=ref(null);
27+
28+
functionupdateReactComponent(){
29+
if(!ButtonComponent.value||!!error.value)return;
30+
31+
ReactDOM.render(
32+
React.createElement(ButtonComponent.value,props),
33+
root.value
34+
);
35+
}
36+
37+
functionunmountReactComponent(){
38+
root.value&&ReactDOM.unmountComponentAtNode(root.value);
39+
}
40+
41+
onMounted(updateReactComponent);
42+
onUpdated(updateReactComponent);
43+
onBeforeUnmount(unmountReactComponent);
44+
45+
fetchButton()
46+
.then((b)=>{
47+
ButtonComponent.value=b;
48+
updateReactComponent();
49+
})
50+
.catch((e)=>{
51+
error.value=e;
52+
});
53+
54+
return{ root, error};
55+
},
56+
template:`
57+
<!-- this element is just served to mount the React element -->
58+
<div v-if="error">error loading button</div>
59+
<div v-else ref="root">loading button...</div>
60+
`,
61+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import("./main.js");
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import{createApp,ref}from"vue";
2+
importLayoutfrom"./Layout";
3+
4+
constApp={
5+
components:{
6+
layout:Layout,
7+
},
8+
template:`
9+
<h1>React in Vue</h1>
10+
<div class="app">
11+
<layout />
12+
</div>
13+
`,
14+
};
15+
16+
createApp(App).mount("#app");
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
constpath=require("path");
2+
const{ VueLoaderPlugin}=require("vue-loader");
3+
constMiniCssExtractPlugin=require("mini-css-extract-plugin");
4+
constHtmlWebpackPlugin=require("html-webpack-plugin");
5+
const{ ModuleFederationPlugin}=require("webpack").container;
6+
constdeps=require("./package.json").dependencies;
7+
8+
module.exports=(env={})=>({
9+
mode:"development",
10+
cache:false,
11+
devtool:"source-map",
12+
optimization:{
13+
minimize:false,
14+
},
15+
target:"web",
16+
entry:path.resolve(__dirname,"./src/bootstrap.js"),
17+
output:{
18+
publicPath:"auto",
19+
},
20+
resolve:{
21+
extensions:[".vue",".jsx",".js",".json"],
22+
alias:{
23+
vue:"vue/dist/vue.esm-bundler.js",
24+
},
25+
},
26+
experiments:{
27+
topLevelAwait:true,
28+
},
29+
module:{
30+
rules:[
31+
{
32+
test:/\.jsx?$/,
33+
loader:"babel-loader",
34+
exclude:/node_modules/,
35+
options:{
36+
presets:["@babel/preset-react"],
37+
},
38+
},
39+
{
40+
test:/\.vue$/,
41+
use:"vue-loader",
42+
},
43+
{
44+
test:/\.png$/,
45+
use:{
46+
loader:"url-loader",
47+
options:{limit:8192},
48+
},
49+
},
50+
{
51+
test:/\.css$/,
52+
use:[
53+
{
54+
loader:MiniCssExtractPlugin.loader,
55+
options:{},
56+
},
57+
"css-loader",
58+
],
59+
},
60+
],
61+
},
62+
plugins:[
63+
newMiniCssExtractPlugin({
64+
filename:"[name].css",
65+
}),
66+
newModuleFederationPlugin({
67+
name:"app1",
68+
remotes:{
69+
app2:"app2@http://localhost:3002/remoteEntry.js",
70+
},
71+
shared:{
72+
...deps,
73+
react:{
74+
singleton:true,
75+
requiredVersion:deps.react,
76+
},
77+
"react-dom":{
78+
singleton:true,
79+
requiredVersion:deps["react-dom"],
80+
},
81+
},
82+
}),
83+
newHtmlWebpackPlugin({
84+
template:path.resolve(__dirname,"./index.html"),
85+
chunks:["main"],
86+
}),
87+
newVueLoaderPlugin(),
88+
],
89+
devServer:{
90+
static:{
91+
directory:path.join(__dirname),
92+
},
93+
compress:true,
94+
port:3001,
95+
hot:true,
96+
headers:{
97+
"Access-Control-Allow-Origin":"*",
98+
"Access-Control-Allow-Methods":"GET, POST, PUT, DELETE, PATCH, OPTIONS",
99+
"Access-Control-Allow-Headers":
100+
"X-Requested-With, content-type, Authorization",
101+
},
102+
},
103+
});

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp