Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(react): add Generator for MFE Host
- Loading branch information
Nicholas Cunningham
authored and
Nicholas Cunningham
committed
Mar 19, 2022
1 parent
e54e36e
commit ecaca46
Showing
25 changed files
with
537 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import { readCachedProjectGraph } from '@nrwl/workspace/src/core/project-graph'; | ||
import { readWorkspaceJson } from '@nrwl/workspace/src/core/file-utils'; | ||
import { | ||
ProjectGraph, | ||
ProjectGraphDependency, | ||
} from '@nrwl/tao/src/shared/project-graph'; | ||
import { WorkspaceJsonConfiguration } from '@nrwl/tao/src/shared/workspace'; | ||
|
||
const { ModuleFederationPlugin } = require('webpack').container; | ||
const reactWebpackConfig = require('../webpack'); | ||
|
||
export type MFERemotes = string[] | [remoteName: string, remoteUrl: string][]; | ||
|
||
export type MFELibrary = { type: string; name: string }; | ||
|
||
export interface SharedLibraryConfig { | ||
singleton?: boolean; | ||
strictVersion?: boolean; | ||
requiredVersion?: string; | ||
eager?: boolean; | ||
} | ||
|
||
export interface MfeOptions { | ||
name: string; | ||
filename: string; | ||
remotes?: string[]; | ||
library?: MFELibrary; | ||
exposes?: Record<string, string>; | ||
shared?: ( | ||
library: string, | ||
config: SharedLibraryConfig | ||
) => undefined | false | SharedLibraryConfig; | ||
} | ||
|
||
function getSharedDependencies(graph: ProjectGraph<any>, options: MfeOptions) { | ||
const dependencies = graph.dependencies[options.name]; | ||
const sharedDependencies = dependencies.reduce((acc, dep) => { | ||
//npm libs | ||
if (graph.externalNodes[dep.target]) { | ||
const externalNode = graph.externalNodes[dep.target]; | ||
acc[externalNode.data.packageName] = { | ||
singleton: true, | ||
requiredVersion: externalNode.data.version, | ||
}; | ||
} | ||
|
||
// workspace libs | ||
if (graph.nodes[dep.target]) { | ||
const node = graph.nodes[dep.target]; | ||
if (node.data.projectType !== 'application') { | ||
acc[node.name] = { | ||
requiredVersion: false, | ||
}; | ||
} | ||
} | ||
return acc; | ||
}, {}); | ||
|
||
if (options.shared) { | ||
for (const [library, config] of Object.entries(sharedDependencies)) { | ||
const shouldKeepDependency = options.shared(library, config); | ||
if (shouldKeepDependency == false) { | ||
delete sharedDependencies[library]; | ||
continue; | ||
} | ||
sharedDependencies[library] = shouldKeepDependency ?? config; | ||
} | ||
} | ||
return sharedDependencies; | ||
} | ||
|
||
function getRemotes(options: MfeOptions, ws: WorkspaceJsonConfiguration) { | ||
return options.remotes.reduce((acc, name) => { | ||
const project = ws.projects[name]; | ||
|
||
if (!project) { | ||
throw Error( | ||
`Cannot find remote project "${options.name}". Check that the name is correct in mfe.config.js` | ||
); | ||
} | ||
const serveOptions = project?.targets?.serve.options; | ||
if (serveOptions) { | ||
acc[name] = `${name}@//${serveOptions.host ?? 'localhost'}:${ | ||
serveOptions.port ?? 4200 | ||
}/remoteEntry.js`; | ||
} | ||
return acc; | ||
}, {}); | ||
} | ||
|
||
// This is probably something linked to a Schema | ||
function withModuleFederation(options: MfeOptions) { | ||
const ws = readWorkspaceJson(); | ||
const graph = readCachedProjectGraph(); | ||
const project = ws.projects[options.name]; | ||
|
||
if (!project) { | ||
throw Error( | ||
`Cannot find project "${options.name}". Check that the name is correct in mfe.config.js` | ||
); | ||
} | ||
|
||
const mfeOptions = { | ||
name: options.name, | ||
filename: 'remoteEntry.js', | ||
exposes: {}, | ||
} as any; | ||
|
||
if (options.library) { | ||
mfeOptions.library = options.library; | ||
} | ||
|
||
mfeOptions.shared = getSharedDependencies(graph, options); | ||
|
||
if (options.remotes) { | ||
mfeOptions.remotes = getRemotes(options, ws); | ||
} | ||
|
||
if (options.exposes) { | ||
mfeOptions.exposes = options.exposes; | ||
} | ||
|
||
return (config, options) => { | ||
config = reactWebpackConfig(config); | ||
config.output.uniqueName = options.name; | ||
config.output.publicPath = 'auto'; | ||
|
||
config.optimization = { | ||
runtimeChunk: false, | ||
minimize: false, | ||
}; | ||
|
||
config.plugins.push(new ModuleFederationPlugin(mfeOptions)); | ||
|
||
return config; | ||
}; | ||
} | ||
|
||
module.exports = withModuleFederation; |
13 changes: 13 additions & 0 deletions
13
packages/react/src/generators/mfe-host/files/common/.babelrc__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"presets": [ | ||
[ | ||
"@nrwl/react/babel", { | ||
"runtime": "automatic"<% if (style === '@emotion/styled') { %>,<% } %> | ||
<% if (style === '@emotion/styled') { %>"importSource": "@emotion/react" <% } %> | ||
} | ||
] | ||
], | ||
"plugins": [ | ||
<% if (style === 'styled-components') { %>["styled-components", { "pure": true, "ssr": true }]<% } %><% if (style === 'styled-jsx') { %>"styled-jsx/babel"<% } %><% if (style === '@emotion/styled') { %>"@emotion/babel-plugin"<% } %> | ||
] | ||
} |
16 changes: 16 additions & 0 deletions
16
packages/react/src/generators/mfe-host/files/common/.browserslistrc__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# This file is used by: | ||
# 1. autoprefixer to adjust CSS to support the below specified browsers | ||
# 2. babel preset-env to adjust included polyfills | ||
# | ||
# For additional information regarding the format and rule options, please see: | ||
# https://github.com/browserslist/browserslist#queries | ||
# | ||
# If you need to support different browsers in production, you may tweak the list below. | ||
|
||
last 1 Chrome version | ||
last 1 Firefox version | ||
last 2 Edge major versions | ||
last 2 Safari major version | ||
last 2 iOS major versions | ||
Firefox ESR | ||
not IE 9-11 # For IE 9-11 support, remove 'not'. |
26 changes: 26 additions & 0 deletions
26
packages/react/src/generators/mfe-host/files/common/src/app/__fileName__.spec.tsx__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { render } from '@testing-library/react'; | ||
<% if (routing) { %> | ||
import { BrowserRouter } from 'react-router-dom'; | ||
<% } %> | ||
|
||
import App from './app'; | ||
|
||
describe('App', () => { | ||
it('should render successfully', () => { | ||
<% if (routing) { %> | ||
const { baseElement } = render(<BrowserRouter><App /></BrowserRouter>); | ||
<% } else { %> | ||
const { baseElement } = render(<App />); | ||
<% } %> | ||
expect(baseElement).toBeTruthy(); | ||
}); | ||
|
||
it('should have a greeting as the title', () => { | ||
<% if (routing) { %> | ||
const { getByText } = render(<BrowserRouter><App /></BrowserRouter>); | ||
<% } else { %> | ||
const { getByText } = render(<App />); | ||
<% } %> | ||
expect(getByText(/Welcome <%= projectName %>/gi)).toBeTruthy(); | ||
}); | ||
}); |
Empty file.
3 changes: 3 additions & 0 deletions
3
...s/react/src/generators/mfe-host/files/common/src/environments/environment.prod.ts__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const environment = { | ||
production: true | ||
}; |
6 changes: 6 additions & 0 deletions
6
packages/react/src/generators/mfe-host/files/common/src/environments/environment.ts__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// This file can be replaced during build by using the `fileReplacements` array. | ||
// When building for production, this file is replaced with `environment.prod.ts`. | ||
|
||
export const environment = { | ||
production: false | ||
}; |
Binary file not shown.
14 changes: 14 additions & 0 deletions
14
packages/react/src/generators/mfe-host/files/common/src/index.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<title><%= className %></title> | ||
<base href="/" /> | ||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
<link rel="icon" type="image/x-icon" href="packages/react/src/generators/mfe-host/files/common/src/favicon.ico" /> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
</body> | ||
</html> |
7 changes: 7 additions & 0 deletions
7
packages/react/src/generators/mfe-host/files/common/src/polyfills.ts__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/** | ||
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. | ||
* | ||
* See: https://github.com/zloirock/core-js#babel | ||
*/ | ||
import 'core-js/stable'; | ||
import 'regenerator-runtime/runtime'; |
14 changes: 14 additions & 0 deletions
14
packages/react/src/generators/mfe-host/files/common/tsconfig.app.json__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"extends": "./tsconfig.json", | ||
"compilerOptions": { | ||
"outDir": "<%= offsetFromRoot %>dist/out-tsc", | ||
"types": ["node"] | ||
}, | ||
"files": [ | ||
<% if (style === 'styled-jsx') { %>"<%= offsetFromRoot %>node_modules/@nrwl/react/typings/styled-jsx.d.ts",<% } %> | ||
"<%= offsetFromRoot %>node_modules/@nrwl/react/typings/cssmodule.d.ts", | ||
"<%= offsetFromRoot %>node_modules/@nrwl/react/typings/image.d.ts" | ||
], | ||
"exclude": ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.tsx", "**/*.test.tsx", "**/*.spec.js", "**/*.test.js", "**/*.spec.jsx", "**/*.test.jsx"], | ||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] | ||
} |
17 changes: 17 additions & 0 deletions
17
packages/react/src/generators/mfe-host/files/common/tsconfig.json__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"extends": "<%= offsetFromRoot %>tsconfig.base.json", | ||
"compilerOptions": { | ||
"jsx": "react-jsx", | ||
<% if (style === '@emotion/styled') { %>"jsxImportSource": "@emotion/react",<% } %> | ||
"allowJs": true, | ||
"esModuleInterop": true, | ||
"allowSyntheticDefaultImports": true | ||
}, | ||
"files": [], | ||
"include": [], | ||
"references": [ | ||
{ | ||
"path": "./tsconfig.app.json" | ||
} | ||
] | ||
} |
6 changes: 6 additions & 0 deletions
6
packages/react/src/generators/mfe-host/files/mfe/mfe.config.js__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = { | ||
name: '<%= projectName %>', | ||
remotes: [ | ||
<% remotes.forEach(function(remote) {%> "<%= remote %>", <% }); %> | ||
], | ||
} |
7 changes: 7 additions & 0 deletions
7
packages/react/src/generators/mfe-host/files/mfe/src/bootstrap.tsx__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<% if (strict) { %>import { StrictMode } from 'react';<% } %> | ||
import * as ReactDOM from 'react-dom'; | ||
<% if (routing) { %>import { BrowserRouter } from 'react-router-dom';<% } %> | ||
|
||
import App from './app/<%= fileName %>'; | ||
|
||
ReactDOM.render(<% if (strict) { %><StrictMode><% } %><% if (routing) { %><BrowserRouter><% } %><App /><% if (routing) { %></BrowserRouter><% } %><% if (strict) { %></StrictMode><% } %>, document.getElementById('root')); |
1 change: 1 addition & 0 deletions
1
packages/react/src/generators/mfe-host/files/mfe/src/main.tsx__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
import('./bootstrap'); |
4 changes: 4 additions & 0 deletions
4
packages/react/src/generators/mfe-host/files/mfe/src/remotes.d.ts__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// Declare your remote Modules here | ||
// Example declare module 'about/Module'; | ||
|
||
<% remotes.forEach(function(remote) { %>declare module '<%= remote %>/Module';<% }); %> |
6 changes: 6 additions & 0 deletions
6
packages/react/src/generators/mfe-host/files/mfe/webpack.config.js__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
const withModuleFederation = require('@nrwl/react/plugins/with-module-federation'); | ||
const mfeConfig = require('./mfe.config'); | ||
|
||
module.exports = withModuleFederation({ | ||
...mfeConfig, | ||
}); |
1 change: 1 addition & 0 deletions
1
packages/react/src/generators/mfe-host/files/mfe/webpack.config.prod.js__tmpl__
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('./webpack.config'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { NormalizedSchema } from '@nrwl/react/src/generators/application/schema'; | ||
import { generateFiles, names } from '@nrwl/devkit'; | ||
import { join } from 'path'; | ||
|
||
export function addMFEFiles(host, options: NormalizedSchema) { | ||
if (host.exists(`apps/${options.projectName}/src/main.tsx`)) { | ||
host.rename( | ||
`apps/${options.projectName}/src/main.tsx`, | ||
`apps/${options.projectName}/src/bootstrap.tsx` | ||
); | ||
} | ||
|
||
const templateVariables = { | ||
...names(options.name), | ||
...options, | ||
tmpl: '', | ||
}; | ||
|
||
generateFiles( | ||
host, | ||
join(__dirname, `../files/mfe`), | ||
options.appProjectRoot, | ||
templateVariables | ||
); | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { | ||
formatFiles, | ||
generateFiles, | ||
names, | ||
Tree, | ||
updateJson, | ||
} from '@nrwl/devkit'; | ||
import { Schema } from './schema'; | ||
import applicationGenerator from '../application/application'; | ||
import { normalizeOptions } from '@nrwl/react/src/generators/application/lib/normalize-options'; | ||
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; | ||
import { addMFEFiles } from '@nrwl/react/src/generators/mfe-host/lib/add-mfe'; | ||
import { NormalizedSchema } from '@nrwl/react/src/generators/application/schema'; | ||
|
||
export function updateProjectJson(host: Tree, options: NormalizedSchema) { | ||
updateJson(host, `${options.appProjectRoot}/project.json`, (json) => { | ||
json.targets.build.options[ | ||
'webpackConfig' | ||
] = `${options.appProjectRoot}/webpack.config.js`; | ||
return json; | ||
}); | ||
} | ||
|
||
export async function mfeHostGenerator(host: Tree, schema: Schema) { | ||
const options = normalizeOptions(host, schema); | ||
const initApp = await applicationGenerator(host, options); | ||
addMFEFiles(host, options); | ||
updateProjectJson(host, options); | ||
|
||
if (!options.skipFormat) { | ||
await formatFiles(host); | ||
} | ||
|
||
return runTasksInSerial(initApp); | ||
} |
Oops, something went wrong.