@@ -37,81 +37,171 @@ Start by creating a new project folder with the following `package.json` to allo
37
37
38
38
** package.json**
39
39
40
- <SyntaxHighlighter language =" json " style ={ghcolors } >
41
- {` { "name": "federation-example", "private": true, "workspaces": [ "packages/*" ], "scripts": { "start": "wsrun --parallel start", "build": "yarn workspaces run build", "dev": "wsrun --parallel dev" }, "devDependencies": { "wsrun": "^5.2.0" } } ` }
42
- </SyntaxHighlighter >
40
+ {
41
+ "name": "federation-example",
42
+ "private": true,
43
+ "workspaces": [
44
+ "packages/*"
45
+ ],
46
+ "scripts": {
47
+ "start": "wsrun --parallel start",
48
+ "build": "yarn workspaces run build",
49
+ "dev": "wsrun --parallel dev"
50
+ },
51
+ "devDependencies": {
52
+ "wsrun": "^5.2.0"
53
+ }
54
+ }
43
55
44
56
We will now create two folders for our SPAs to live in under a new ` packages ` directory called ` application-a ` and ` application-b ` , these will respectively contain the following ` package.json ` files:
45
57
46
58
** packages/application-a/package.json**
47
59
48
- <SyntaxHighlighter language =" json " style ={ghcolors } >
49
- {` { "name": "application-a", "version": "1.0.0", "private": true, "scripts": { "start": "serve dist -p 3001", "build": "webpack --mode production", "dev": "concurrently \\"webpack --watch\\" \\"serve dist -p 3001\\"" }, "dependencies": { "react": "^16.13.1", "react-dom": "^16.13.1" }, "devDependencies": { "@babel/core": "^7.8.6", "@babel/preset-react": "^7.8.3", "babel-loader": "^8.0.6", "concurrently": "^5.1.0", "html-webpack-plugin": "git://github.com/ScriptedAlchemy/html-webpack-plugin#master", "serve": "^11.3.0", "webpack": "git://github.com/webpack/webpack.git#dev-1", "webpack-cli": "^3.3.11" } } ` }
50
- </SyntaxHighlighter >
60
+ {
61
+ "name": "application-a",
62
+ "version": "1.0.0",
63
+ "private": true,
64
+ "scripts": {
65
+ "start": "serve dist -p 3001",
66
+ "build": "webpack --mode production",
67
+ "dev": "concurrently \"webpack --watch\" \"serve dist -p 3001\""
68
+ },
69
+ "dependencies": {
70
+ "react": "^16.13.1",
71
+ "react-dom": "^16.13.1"
72
+ },
73
+ "devDependencies": {
74
+ "@babel/core": "^7.8.6",
75
+ "@babel/preset-react": "^7.8.3",
76
+ "babel-loader": "^8.0.6",
77
+ "concurrently": "^5.1.0",
78
+ "html-webpack-plugin": "git://github.com/ScriptedAlchemy/html-webpack-plugin#master",
79
+ "serve": "^11.3.0",
80
+ "webpack": "git://github.com/webpack/webpack.git#dev-1",
81
+ "webpack-cli": "^3.3.11"
82
+ }
83
+ }
51
84
52
85
** packages/application-b/package.json**
53
86
54
- <SyntaxHighlighter language =" json " style ={ghcolors } >
55
- {` { "name": "application-b", "version": "1.0.0", "private": true, "scripts": { "start": "serve dist -p 3002", "build": "webpack --mode production", "dev": "concurrently \\"webpack --watch\\" \\"serve dist -p 3002\\"" }, "dependencies": { "react": "^16.13.1", "react-dom": "^16.13.1" }, "devDependencies": { "@babel/core": "^7.8.6", "@babel/preset-react": "^7.8.3", "babel-loader": "^8.0.6", "concurrently": "^5.1.0", "html-webpack-plugin": "git://github.com/ScriptedAlchemy/html-webpack-plugin#master", "serve": "^11.3.0", "webpack": "git://github.com/webpack/webpack.git#dev-1", "webpack-cli": "^3.3.11" } } ` }
56
- </SyntaxHighlighter >
87
+ {
88
+ "name": "application-b",
89
+ "version": "1.0.0",
90
+ "private": true,
91
+ "scripts": {
92
+ "start": "serve dist -p 3002",
93
+ "build": "webpack --mode production",
94
+ "dev": "concurrently \"webpack --watch\" \"serve dist -p 3002\""
95
+ },
96
+ "dependencies": {
97
+ "react": "^16.13.1",
98
+ "react-dom": "^16.13.1"
99
+ },
100
+ "devDependencies": {
101
+ "@babel/core": "^7.8.6",
102
+ "@babel/preset-react": "^7.8.3",
103
+ "babel-loader": "^8.0.6",
104
+ "concurrently": "^5.1.0",
105
+ "html-webpack-plugin": "git://github.com/ScriptedAlchemy/html-webpack-plugin#master",
106
+ "serve": "^11.3.0",
107
+ "webpack": "git://github.com/webpack/webpack.git#dev-1",
108
+ "webpack-cli": "^3.3.11"
109
+ }
110
+ }
57
111
58
112
Install the dependancies with:
59
113
60
- <SyntaxHighlighter language =" bash " style ={ghcolors } >
61
- {` > yarn ` }
62
- </SyntaxHighlighter >
114
+ > yarn
63
115
64
116
## Bootstrap The SPAs
65
117
66
118
Next up is bootstraping our SPA React applications. We need to create a ` src ` directory in each of our packages that contain the following files:
67
119
68
120
** packages/application-{a,b}/src/index.js**
69
121
70
- <SyntaxHighlighter language =" javascript " style ={ghcolors } >
71
- {` import('./bootstrap'); ` }
72
- </SyntaxHighlighter >
122
+ import('./bootstrap');
73
123
74
124
** packages/application-{a,b}/src/bootstrap.jsx**
75
125
76
- <SyntaxHighlighter language =" javascript " style ={ghcolors } >
77
- {bootstrapCodeSample}
78
- </SyntaxHighlighter >
126
+ import React from 'react';
127
+ import ReactDOM from 'react-dom';
128
+
129
+ import App from './app';
130
+
131
+ ReactDOM.render(<App />, document.getElementById('root'));
79
132
80
133
We also need a ` public ` directory in each of the packages with the the following html template that we will modify per SPA later:
81
134
82
135
** packages/application-{a,b}/public/index.html**
83
136
84
- <SyntaxHighlighter language =" html " style ={ghcolors } >
85
- {` <!DOCTYPE html> <html lang="en"> <head> </head> <body> <div id="root"></div> </body> </html> ` }
86
- </SyntaxHighlighter >
137
+ <!DOCTYPE html>
138
+ <html lang="en">
139
+ <head>
140
+ </head>
141
+ <body>
142
+ <div id="root"></div>
143
+ </body>
144
+ </html>
87
145
88
146
Now we can implement our two ` app.jsx ` files for each application that will house our shared components:
89
147
90
148
** packages/application-a/src/app.jsx**
91
149
92
- <SyntaxHighlighter language =" javascript " style ={ghcolors } >
93
- {appACodeSample}
94
- </SyntaxHighlighter >
150
+ import React from 'react';
151
+
152
+ export default function SayHelloFromA() {
153
+ return <h1>Hello from Application A!</h1>;
154
+ }
95
155
96
156
** packages/application-b/src/app.jsx**
97
157
98
- <SyntaxHighlighter language =" javascript " style ={ghcolors } >
99
- {appBCodeSample}
100
- </SyntaxHighlighter >
158
+ import React from 'react';
159
+
160
+ export default function SayHelloFromB() {
161
+ return <h1>Hello from Application B!</h1>;
162
+ }
101
163
102
164
And now finally, we'll add our base ` webpack.config.js ` for each application:
103
165
104
166
** packages/application-{a,b}/webpack.config.js**
105
167
106
- <SyntaxHighlighter language =" javascript " style ={ghcolors } >
107
- {baseWebpackConfig}
108
- </SyntaxHighlighter >
168
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
169
+ const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
170
+
171
+ const mode = process.env.NODE_ENV || 'production';
172
+
173
+ module.exports = {
174
+ mode,
175
+ entry: './src/index',
176
+ devtool: 'source-map',
177
+ optimization: {
178
+ minimize: mode === 'production',
179
+ },
180
+ resolve: {
181
+ extensions: ['.jsx', '.js', '.json'],
182
+ },
183
+ module: {
184
+ rules: [
185
+ {
186
+ test: /\.jsx?$/,
187
+ loader: require.resolve('babel-loader'),
188
+ options: {
189
+ presets: [require.resolve('@babel/preset-react')],
190
+ },
191
+ },
192
+ ],
193
+ },
194
+
195
+ plugins: [
196
+ new HtmlWebpackPlugin({
197
+ template: './public/index.html',
198
+ }),
199
+ ],
200
+ };
109
201
110
202
From the root of the application, you should now be able to access your two SPAs on [ http://localhost:3001 ] ( http://localhost:3001 ) and [ http://localhost:3002 ] ( http://localhost:3002 ) when runing:
111
203
112
- <SyntaxHighlighter language =" bash " style ={ghcolors } >
113
- {` > yarn dev ` }
114
- </SyntaxHighlighter >
204
+ > yarn dev
115
205
116
206
# Start Federating
117
207
@@ -121,33 +211,127 @@ We'll start by adding the `ModuleFederationPlugin` to `Application A`, this will
121
211
122
212
** packages/application-a/webpack.config.js**
123
213
124
- <SyntaxHighlighter language =" javascript " style ={ghcolors } >
125
- {federationPluginA}
126
- </SyntaxHighlighter >
214
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
215
+ const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
216
+
217
+ const mode = process.env.NODE_ENV || 'production';
218
+
219
+ module.exports = {
220
+ mode,
221
+ entry: './src/index',
222
+ output: {
223
+ publicPath: 'http://localhost:3001/', // New
224
+ },
225
+ devtool: 'source-map',
226
+ optimization: {
227
+ minimize: mode === 'production',
228
+ },
229
+ resolve: {
230
+ extensions: ['.jsx', '.js', '.json'],
231
+ },
232
+ module: {
233
+ rules: [
234
+ {
235
+ test: /\.jsx?$/,
236
+ loader: require.resolve('babel-loader'),
237
+ options: {
238
+ presets: [require.resolve('@babel/preset-react')],
239
+ },
240
+ },
241
+ ],
242
+ },
243
+
244
+ plugins: [
245
+ // New
246
+ new ModuleFederationPlugin({
247
+ name: 'application_a',
248
+ library: { type: 'var', name: 'application_a' },
249
+ filename: 'remoteEntry.js',
250
+ exposes: {
251
+ 'SayHelloFromA': './src/app',
252
+ },
253
+ remotes: {
254
+ 'application_b': 'application_b',
255
+ },
256
+ shared: ['react', 'react-dom'],
257
+ }),
258
+ new HtmlWebpackPlugin({
259
+ template: './public/index.html',
260
+ }),
261
+ ],
262
+ };
127
263
128
264
This specifies that ` Application A ` exposese it's App component to the world as a Federated Module called ` SayHelloFromA ` , while whenever you import from ` application_b ` , those modules should come from ` Application B ` at runtime.
129
265
130
266
We will do the same thing fro ` Applicaiton B ` , specifying that it exposes it's App component as ` SayHelloFromB ` and whenever we import from ` application_a ` , those modules should come from ` Application A ` at runtime:
131
267
132
268
** packages/application-b/webpack.config.js**
133
269
134
- <SyntaxHighlighter language =" javascript " style ={ghcolors } >
135
- {federationPluginB}
136
- </SyntaxHighlighter >
270
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
271
+ const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
272
+
273
+ const mode = process.env.NODE_ENV || 'production';
274
+
275
+ module.exports = {
276
+ mode,
277
+ entry: './src/index',
278
+ output: {
279
+ publicPath: 'http://localhost:3002/', // New
280
+ },
281
+ devtool: 'source-map',
282
+ optimization: {
283
+ minimize: mode === 'production',
284
+ },
285
+ resolve: {
286
+ extensions: ['.jsx', '.js', '.json'],
287
+ },
288
+ module: {
289
+ rules: [
290
+ {
291
+ test: /\.jsx?$/,
292
+ loader: require.resolve('babel-loader'),
293
+ options: {
294
+ presets: [require.resolve('@babel/preset-react')],
295
+ },
296
+ },
297
+ ],
298
+ },
299
+
300
+ plugins: [
301
+ // New
302
+ new ModuleFederationPlugin({
303
+ name: 'application_b',
304
+ library: { type: 'var', name: 'application_b' },
305
+ filename: 'remoteEntry.js',
306
+ exposes: {
307
+ 'SayHelloFromB': './src/app',
308
+ },
309
+ remotes: {
310
+ 'application_a': 'application_a',
311
+ },
312
+ shared: ['react', 'react-dom'],
313
+ }),
314
+ new HtmlWebpackPlugin({
315
+ template: './public/index.html',
316
+ }),
317
+ ],
318
+ };
137
319
138
320
The last step before we can start to utilize the exposed components is to specify for the runtime where the Remote Entries for the Containers you wish to consume are located. We do this by simply adding a script tag to the HTML template of the remotes you wish to consume.
139
321
140
322
** packages/application-a/public/index.html**
141
323
142
- <SyntaxHighlighter language =" html " style ={ghcolors } >
143
- {` <head> <!-- The remote entry for Application B --> <script src="http://localhost:3002/remoteEntry.js"></script> </head> ` }
144
- </SyntaxHighlighter >
324
+ <head>
325
+ <!-- The remote entry for Application B -->
326
+ <script src="http://localhost:3002/remoteEntry.js"></script>
327
+ </head>
145
328
146
329
** packages/application-b/public/index.html**
147
330
148
- <SyntaxHighlighter language =" html " style ={ghcolors } >
149
- {` <head> <!-- The remote entry for Application A --> <script src="http://localhost:3001/remoteEntry.js"></script> </head> ` }
150
- </SyntaxHighlighter >
331
+ <head>
332
+ <!-- The remote entry for Application A -->
333
+ <script src="http://localhost:3001/remoteEntry.js"></script>
334
+ </head>
151
335
152
336
The remote entry files are tiny mappings for webpack to resolve the individually imported modules without trasfering unnessesary info. They are also responsible for enabling the sharing of libraries that the packages use, in this case, when ` Application A ` requests ` Application B ` 's SayHelloFromB component, we do not send the React or ReactDOM over the wire as ` Application A ` already has a copy of it.
153
337
@@ -159,17 +343,39 @@ Starting with `Application A`, we can render the `SayHelloFromB` component like
159
343
160
344
** packages/application-a/src/bootstrap.jsx**
161
345
162
- <SyntaxHighlighter language =" javascript " style ={ghcolors } >
163
- {applicationAConsumesB}
164
- </SyntaxHighlighter >
346
+ import React from 'react';
347
+ import ReactDOM from 'react-dom';
348
+
349
+ import SayHelloFromB from 'application_b/SayHelloFromB';
350
+
351
+ import App from './app';
352
+
353
+ ReactDOM.render(
354
+ <>
355
+ <App />
356
+ <SayHelloFromB />
357
+ </>,
358
+ document.getElementById('root')
359
+ );
165
360
166
361
` Application B ` will look very similar, just importing from ` application_a ` instead:
167
362
168
363
** packages/application-b/src/bootstrap.jsx**
169
364
170
- <SyntaxHighlighter language =" javascript " style ={ghcolors } >
171
- {applicationBConsumesA}
172
- </SyntaxHighlighter >
365
+ import React from 'react';
366
+ import ReactDOM from 'react-dom';
367
+
368
+ import SayHelloFromA from 'application_a/SayHelloFromA';
369
+
370
+ import App from './app';
371
+
372
+ ReactDOM.render(
373
+ <>
374
+ <App />
375
+ <SayHelloFromA />
376
+ </>,
377
+ document.getElementById('root')
378
+ );
173
379
174
380
# A Few Notes
175
381
0 commit comments