Skip to content

Commit 2e4f204

Browse files
jacob-ebeyForestry.io
authored andcommitted
Update from Forestry.io
Jacob Ebey updated src/posts/get-started.md
1 parent 515ed0f commit 2e4f204

File tree

1 file changed

+257
-51
lines changed

1 file changed

+257
-51
lines changed

src/posts/get-started.md

Lines changed: 257 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -37,81 +37,171 @@ Start by creating a new project folder with the following `package.json` to allo
3737

3838
**package.json**
3939

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+
}
4355

4456
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:
4557

4658
**packages/application-a/package.json**
4759

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+
}
5184

5285
**packages/application-b/package.json**
5386

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+
}
57111

58112
Install the dependancies with:
59113

60-
<SyntaxHighlighter language="bash" style={ghcolors}>
61-
{`> yarn`}
62-
</SyntaxHighlighter>
114+
> yarn
63115

64116
## Bootstrap The SPAs
65117

66118
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:
67119

68120
**packages/application-{a,b}/src/index.js**
69121

70-
<SyntaxHighlighter language="javascript" style={ghcolors}>
71-
{`import('./bootstrap');`}
72-
</SyntaxHighlighter>
122+
import('./bootstrap');
73123

74124
**packages/application-{a,b}/src/bootstrap.jsx**
75125

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'));
79132

80133
We also need a `public` directory in each of the packages with the the following html template that we will modify per SPA later:
81134

82135
**packages/application-{a,b}/public/index.html**
83136

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>
87145

88146
Now we can implement our two `app.jsx` files for each application that will house our shared components:
89147

90148
**packages/application-a/src/app.jsx**
91149

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+
}
95155

96156
**packages/application-b/src/app.jsx**
97157

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+
}
101163

102164
And now finally, we'll add our base `webpack.config.js` for each application:
103165

104166
**packages/application-{a,b}/webpack.config.js**
105167

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+
};
109201

110202
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:
111203

112-
<SyntaxHighlighter language="bash" style={ghcolors}>
113-
{`> yarn dev`}
114-
</SyntaxHighlighter>
204+
> yarn dev
115205

116206
# Start Federating
117207

@@ -121,33 +211,127 @@ We'll start by adding the `ModuleFederationPlugin` to `Application A`, this will
121211

122212
**packages/application-a/webpack.config.js**
123213

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+
};
127263

128264
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.
129265

130266
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:
131267

132268
**packages/application-b/webpack.config.js**
133269

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+
};
137319

138320
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.
139321

140322
**packages/application-a/public/index.html**
141323

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>
145328

146329
**packages/application-b/public/index.html**
147330

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>
151335

152336
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.
153337

@@ -159,17 +343,39 @@ Starting with `Application A`, we can render the `SayHelloFromB` component like
159343

160344
**packages/application-a/src/bootstrap.jsx**
161345

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+
);
165360

166361
`Application B` will look very similar, just importing from `application_a` instead:
167362

168363
**packages/application-b/src/bootstrap.jsx**
169364

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+
);
173379

174380
# A Few Notes
175381

0 commit comments

Comments
 (0)