react-mfe-gen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # react-mfe-gen
2
+ CLI tool for react Microfront end initial setup
package/constants.js ADDED
@@ -0,0 +1,280 @@
1
+ export const QUESTION = {
2
+ ACTION: "Select an action:",
3
+ LANGUAGE: "Choose your preferred language:",
4
+ PROJECT_NAME: "Enter your project name:",
5
+ PROJECT_DESCRIPTION: "Enter your project description:",
6
+ NUMBER_OF_MFES: "How many micro-frontends do you plan to include?",
7
+ MFE_NAME:
8
+ "Enter your micro-frontend name:\n**Note:** This will be used as a prefix in window functions.\n:",
9
+ MFE_DESCRIPTION: "Enter a description for your micro-frontend:",
10
+ CONTAINER_NAME:
11
+ "Enter your container app name:\n**Note:** This should be prefix word in your microfrontends window functions.\n:",
12
+ CONTAINER_DESCRIPTION: "Enter a description for your container app:",
13
+ STYLING: "Select a styling solution:",
14
+ STATE_MANAGEMENT: "Select a state management library:",
15
+ FORM_MANAGEMENT: "Select a form management library:",
16
+ CONDITIONAL_MFE_NAME: "Enter your micro-frontend name:",
17
+ CONTAINER_PATH:
18
+ "Please enter a path to create container:\ne.g: G:\\workspace\\sample-container\n:",
19
+ MFE_PATH:
20
+ "Please enter a path to create microfront end:\ne.g: G:\\workspace\\sample-mfe\n:",
21
+ PATH: "Please enter a path to create ",
22
+ };
23
+ export const CHOICE_CONSTANTS = {
24
+ ACTION: {
25
+ NEW_PROJECT: "Create a new project 🚀",
26
+ CONTAINER: "Create only a container 📦",
27
+ SINGLE_MFE: "Create a single micro-frontend 🌐",
28
+ },
29
+ STYLING: {
30
+ SASS: "Sass 🎨",
31
+ TAILWIND: "Tailwind 🌊",
32
+ MATERIAL_UI: "Material UI 🧩",
33
+ BOOTSTRAP: "Bootstrap 🥾",
34
+ STYLED_COMPONENTS: "Styled Components ✍️",
35
+ },
36
+ LANGUAGE: {
37
+ JAVA_SCRIPT: "JavaScript 🟨",
38
+ TYPE_SCRIPT: "TypeScript 🔷",
39
+ },
40
+ STATE_MANAGEMENT: {
41
+ REDUX: "Redux 🔄",
42
+ ZUSTAND: "Zustand 🐻",
43
+ },
44
+ FORM_MANAGEMENT: {
45
+ REACT_HOOK_FORM: "React-hook-form 🪝",
46
+ FORMIK: "Formik 📝",
47
+ },
48
+ NONE: "None ❌",
49
+ };
50
+
51
+ export const CHOICES = {
52
+ ACTION: [
53
+ CHOICE_CONSTANTS.ACTION.NEW_PROJECT,
54
+ CHOICE_CONSTANTS.ACTION.CONTAINER,
55
+ CHOICE_CONSTANTS.ACTION.SINGLE_MFE,
56
+ ],
57
+ STYLING: [
58
+ CHOICE_CONSTANTS.NONE,
59
+ CHOICE_CONSTANTS.STYLING.SASS,
60
+ CHOICE_CONSTANTS.STYLING.BOOTSTRAP,
61
+ CHOICE_CONSTANTS.STYLING.MATERIAL_UI,
62
+ CHOICE_CONSTANTS.STYLING.TAILWIND,
63
+ CHOICE_CONSTANTS.STYLING.STYLED_COMPONENTS,
64
+ ],
65
+ LANGUAGE: [
66
+ CHOICE_CONSTANTS.LANGUAGE.JAVA_SCRIPT,
67
+ CHOICE_CONSTANTS.LANGUAGE.TYPE_SCRIPT,
68
+ ],
69
+ STATE_MANAGEMENT: [
70
+ CHOICE_CONSTANTS.NONE,
71
+ CHOICE_CONSTANTS.STATE_MANAGEMENT.REDUX,
72
+ CHOICE_CONSTANTS.STATE_MANAGEMENT.ZUSTAND,
73
+ ],
74
+ FORM_MANAGEMENT: [
75
+ CHOICE_CONSTANTS.NONE,
76
+ CHOICE_CONSTANTS.FORM_MANAGEMENT.REACT_HOOK_FORM,
77
+ CHOICE_CONSTANTS.FORM_MANAGEMENT.FORMIK,
78
+ ],
79
+ };
80
+
81
+ export const PROMPT = {
82
+ USER_NEED: [
83
+ {
84
+ message: QUESTION.ACTION,
85
+ type: "list",
86
+ name: "typeOfAction",
87
+ choices: CHOICES.ACTION,
88
+ },
89
+ {
90
+ message: QUESTION.LANGUAGE,
91
+ type: "list",
92
+ name: "language",
93
+ choices: CHOICES.LANGUAGE,
94
+ },
95
+ ],
96
+ ENTIRE_PROJECT: [
97
+ {
98
+ message: QUESTION.PROJECT_NAME,
99
+ type: "input",
100
+ name: "projectName",
101
+ validate(value) {
102
+ if (!/^[a-z-]+$/.test(value)) {
103
+ return "Please use only lowercase letters and '-' (numbers, capital letters, and other symbols are not allowed).";
104
+ }
105
+ return true;
106
+ },
107
+ },
108
+ {
109
+ message: QUESTION.PROJECT_DESCRIPTION,
110
+ type: "input",
111
+ name: "projectDescription",
112
+ },
113
+ {
114
+ type: "input",
115
+ name: "numberOfMfes",
116
+ message: QUESTION.NUMBER_OF_MFES,
117
+ validate(value) {
118
+ const isNumber = !Number.isNaN(Number.parseFloat(value));
119
+ return isNumber || "Please enter a valid number";
120
+ },
121
+ filter: Number,
122
+ },
123
+ ],
124
+ ONE_MFE: [
125
+ {
126
+ message: QUESTION.MFE_NAME,
127
+ type: "input",
128
+ name: "mfeName",
129
+ validate(value) {
130
+ if (!/^[a-z-]+$/.test(value)) {
131
+ return "Please use only lowercase letters and '-' (numbers, capital letters, and other symbols are not allowed).";
132
+ }
133
+ return true;
134
+ },
135
+ },
136
+ {
137
+ message: QUESTION.MFE_DESCRIPTION,
138
+ type: "input",
139
+ name: "mfeDescription",
140
+ },
141
+ ],
142
+ CONTAINER: [
143
+ {
144
+ message: QUESTION.CONTAINER_NAME,
145
+ type: "input",
146
+ name: "containerName",
147
+ validate(value) {
148
+ if (!/^[a-z-]+$/.test(value)) {
149
+ return "Please use only lowercase letters and '-' (numbers, capital letters, and other symbols are not allowed).";
150
+ }
151
+ return true;
152
+ },
153
+ },
154
+ {
155
+ message: QUESTION.CONTAINER_DESCRIPTION,
156
+ type: "input",
157
+ name: "containerDescription",
158
+ },
159
+ {
160
+ type: "input",
161
+ name: "numberOfMfes",
162
+ message: QUESTION.NUMBER_OF_MFES,
163
+ validate(value) {
164
+ const isNumber = !Number.isNaN(Number.parseFloat(value));
165
+ return isNumber || "Please enter a valid number";
166
+ },
167
+ filter: Number,
168
+ },
169
+ ],
170
+ COMMON: [
171
+ {
172
+ message: QUESTION.STYLING,
173
+ type: "list",
174
+ name: "styling",
175
+ choices: CHOICES.STYLING,
176
+ },
177
+ {
178
+ message: QUESTION.STATE_MANAGEMENT,
179
+ type: "list",
180
+ name: "stateManagement",
181
+ choices: CHOICES.STATE_MANAGEMENT,
182
+ },
183
+ ],
184
+ CONDITIONAL: {
185
+ MICROFRONT_END_NAME: {
186
+ message: QUESTION.CONDITIONAL_MFE_NAME,
187
+ type: "input",
188
+ name: "mfeName",
189
+ validate(value) {
190
+ if (!/^[a-z-]+$/.test(value)) {
191
+ return "Please use only lowercase letters and '-' (numbers, capital letters, and other symbols are not allowed).";
192
+ }
193
+ return true;
194
+ },
195
+ },
196
+ CONTAINER_PATH: {
197
+ message: QUESTION.CONTAINER_PATH,
198
+ type: "input",
199
+ name: "containerPath",
200
+ },
201
+ MFE_PATH: {
202
+ message: QUESTION.MFE_PATH,
203
+ type: "input",
204
+ name: "mfePath",
205
+ },
206
+ PATH: {
207
+ message: QUESTION.PATH,
208
+ type: "input",
209
+ name: "path",
210
+ },
211
+ MICROFRONT_END_NAME: {
212
+ message: QUESTION.CONDITIONAL_MFE_NAME,
213
+ type: "input",
214
+ name: "mfeName",
215
+ },
216
+ FORM_MANAGEMENT: {
217
+ message: QUESTION.FORM_MANAGEMENT,
218
+ type: "list",
219
+ name: "formManagement",
220
+ choices: CHOICES.FORM_MANAGEMENT,
221
+ },
222
+ },
223
+ };
224
+
225
+ export const INFO_MESSAGE = {
226
+ DISCLAIMER:
227
+ "Hello there! 👋\n\nThis tool currently supports micro-frontend creation using runtime integration via custom script injection ⚙️.\nPlease keep this limitation in mind when developing your MFE applications 📌.\n",
228
+ CREATE_APP: "Let's create ",
229
+ CONFIGURE_CONTAINER: "Configuring the container... 🛠️",
230
+ APP_CREATION: "Creating your React app... ⚛️ ",
231
+ i_DEPENDENCIES: "Installing dependencies... 📦 ",
232
+ i_DEV_DEPENDENCIES: "Installing dev dependencies... 🧩 ",
233
+ SUCCESS: {
234
+ NEW_PRO: "New project created successfully! 🎉",
235
+ CONTAINER: "Container created successfully! 📦",
236
+ ONE_MFE: "Microfront end created successfully! 🌐",
237
+ },
238
+ HAPPY_CODING: "Happy coding! 💻✨",
239
+ };
240
+
241
+ export const LIBRARY_PAIR = {
242
+ STYLING: {
243
+ [CHOICE_CONSTANTS.STYLING.TAILWIND]: [
244
+ "tailwindcss",
245
+ "postcss",
246
+ "autoprefixer",
247
+ ],
248
+ [CHOICE_CONSTANTS.STYLING.SASS]: ["sass"],
249
+ [CHOICE_CONSTANTS.STYLING.MATERIAL_UI]: [
250
+ "@mui/material",
251
+ "@emotion/react",
252
+ "@emotion/styled",
253
+ "@mui/icons-material",
254
+ ],
255
+ [CHOICE_CONSTANTS.STYLING.BOOTSTRAP]: ["bootstrap"],
256
+ [CHOICE_CONSTANTS.STYLING.STYLED_COMPONENTS]: ["styled-components"],
257
+ [CHOICE_CONSTANTS.NONE]: [],
258
+ },
259
+ STATE_MANAGEMENT: {
260
+ [CHOICE_CONSTANTS.NONE]: [],
261
+ [CHOICE_CONSTANTS.STATE_MANAGEMENT.REDUX]: [
262
+ "@reduxjs/toolkit",
263
+ "react-redux",
264
+ ],
265
+ [CHOICE_CONSTANTS.STATE_MANAGEMENT.ZUSTAND]: ["zustand"],
266
+ },
267
+ FORM_MANAGEMENT: {
268
+ [CHOICE_CONSTANTS.NONE]: [],
269
+ [CHOICE_CONSTANTS.FORM_MANAGEMENT.REACT_HOOK_FORM]: [
270
+ "react-hook-form",
271
+ "@hookform/resolvers",
272
+ "yup",
273
+ ],
274
+ [CHOICE_CONSTANTS.FORM_MANAGEMENT.FORMIK]: ["formik", "yup"],
275
+ },
276
+ };
277
+
278
+ export const DEFAULT_DEPENDENCIES = ["axios", "react-router-dom"];
279
+
280
+ export const DEFAULT_DEV_DEPENDENCIES = ["cross-env", "react-app-rewired"];
@@ -0,0 +1,75 @@
1
+ import inquirer from "inquirer";
2
+ import { PROMPT, INFO_MESSAGE, CHOICE_CONSTANTS } from "../constants.js";
3
+ import utils from "../utility.js";
4
+
5
+ const containerCreation = async (language) => {
6
+ // To store different working dir
7
+ const wrokingDirectories = [];
8
+ try {
9
+ // Get typescript flag
10
+ const isTypeScript = language === CHOICE_CONSTANTS.LANGUAGE.TYPE_SCRIPT;
11
+ // Declare array to store list of mfe names
12
+ const mfeNames = [];
13
+
14
+ const { containerName, containerDescription, numberOfMfes } =
15
+ await inquirer.prompt(PROMPT.CONTAINER);
16
+
17
+ // Interate to get each mfe name
18
+ for (let i = 0; i < numberOfMfes; i++) {
19
+ const { mfeName } = await inquirer.prompt([
20
+ {
21
+ message: `Enter your name of Microfront end ${i + 1}:`,
22
+ type: "input",
23
+ name: "mfeName",
24
+ validate(value) {
25
+ if (!/^[a-z-]+$/.test(value)) {
26
+ return "Please use only lowercase letters and '-' (numbers, capital letters, and other symbols are not allowed).";
27
+ }
28
+ return true;
29
+ },
30
+ },
31
+ ]);
32
+ mfeNames.push(mfeName);
33
+ }
34
+
35
+ console.log(`${INFO_MESSAGE.CREATE_APP}${containerName} as container`);
36
+
37
+ // Get container path and app requirements
38
+ const commonInfo = await inquirer.prompt([
39
+ PROMPT.CONDITIONAL.CONTAINER_PATH,
40
+ ...PROMPT.COMMON,
41
+ ]);
42
+
43
+ // store working dir
44
+ wrokingDirectories.push(commonInfo.containerPath);
45
+
46
+ // Go inside user specified dir
47
+ process.chdir(commonInfo.containerPath);
48
+
49
+ // Array to store CRA command
50
+ const appCommand = utils.getLanguageTemplate(containerName, isTypeScript);
51
+
52
+ // Create container react app
53
+ await utils.createReactApp(appCommand);
54
+
55
+ // Make normal react app into MFE container
56
+ await utils.configureContainer(
57
+ {
58
+ projectName: containerName,
59
+ projectDescription: containerDescription,
60
+ ...commonInfo,
61
+ isTypeScript,
62
+ },
63
+ mfeNames
64
+ );
65
+
66
+ // Let user know container created status
67
+ console.log(
68
+ `${INFO_MESSAGE.SUCCESS.CONTAINER}\n${INFO_MESSAGE.HAPPY_CODING}`
69
+ );
70
+ } catch {
71
+ utils.cleanupProject(wrokingDirectories);
72
+ }
73
+ };
74
+
75
+ export default containerCreation;
@@ -0,0 +1,5 @@
1
+ import newProjectCreation from "./new-pro-creation.js";
2
+ import containerCreation from "./container-creation.js";
3
+ import singleMfeCreation from "./single-mfe-creation.js";
4
+
5
+ export { newProjectCreation, containerCreation, singleMfeCreation };
@@ -0,0 +1,109 @@
1
+ import inquirer from "inquirer";
2
+ import {
3
+ PROMPT,
4
+ INFO_MESSAGE,
5
+ QUESTION,
6
+ CHOICE_CONSTANTS,
7
+ } from "../constants.js";
8
+ import utils from "../utility.js";
9
+
10
+ const newProjectCreation = async (language) => {
11
+ // To store different working dir
12
+ const wrokingDirectories = [];
13
+ try {
14
+ // Get typescript flag
15
+ const isTypeScript = language === CHOICE_CONSTANTS.LANGUAGE.TYPE_SCRIPT;
16
+ // Declare array to store list of mfe names
17
+ const mfeNames = [];
18
+ // Get basic project info
19
+ const projectInfo = await inquirer.prompt(PROMPT.ENTIRE_PROJECT);
20
+
21
+ // Interate to get each mfe name
22
+ for (let i = 0; i < projectInfo.numberOfMfes; i++) {
23
+ const { mfeName } = await inquirer.prompt([
24
+ {
25
+ message: `Enter your name of Microfront end ${i + 1}:`,
26
+ type: "input",
27
+ name: "mfeName",
28
+ validate(value) {
29
+ if (!/^[a-z-]+$/.test(value)) {
30
+ return "Please use only lowercase letters and '-' (numbers, capital letters, and other symbols are not allowed).";
31
+ }
32
+ return true;
33
+ },
34
+ },
35
+ ]);
36
+ mfeNames.push(mfeName);
37
+ }
38
+
39
+ console.log(
40
+ `${INFO_MESSAGE.CREATE_APP}${projectInfo.projectName} as container`
41
+ );
42
+
43
+ // Get container path and app requirements
44
+ const commonInfo = await inquirer.prompt([
45
+ PROMPT.CONDITIONAL.CONTAINER_PATH,
46
+ ...PROMPT.COMMON,
47
+ ]);
48
+
49
+ // store working dir
50
+ wrokingDirectories.push(commonInfo.containerPath);
51
+ // Go inside user specified dir
52
+ process.chdir(commonInfo.containerPath);
53
+
54
+ // Array to store CRA command
55
+ const appCommand = utils.getLanguageTemplate(
56
+ projectInfo.projectName,
57
+ isTypeScript
58
+ );
59
+
60
+ // Create container react app
61
+ await utils.createReactApp(appCommand);
62
+
63
+ // Make normal react app into MFE container
64
+ await utils.configureContainer(
65
+ { ...projectInfo, ...commonInfo, isTypeScript },
66
+ mfeNames
67
+ );
68
+
69
+ // Get mfe requirements for each mfe
70
+ for (let i = 0; i < mfeNames.length; i++) {
71
+ const mfeName = mfeNames[i];
72
+
73
+ console.log(`${INFO_MESSAGE.CREATE_APP}${mfeName}\n`);
74
+
75
+ const mfeInfo = await inquirer.prompt([
76
+ {
77
+ message: `${QUESTION.PATH}${mfeName} as microfront end\ne.g: G:\\workspace\\sample-mfe\n:`,
78
+ type: "input",
79
+ name: "path",
80
+ },
81
+ PROMPT.CONDITIONAL.FORM_MANAGEMENT,
82
+ ...PROMPT.COMMON,
83
+ ]);
84
+ // store working dir
85
+ wrokingDirectories.push(mfeInfo.path);
86
+ // Go inside user specified mfe dir
87
+ process.chdir(mfeInfo.path);
88
+ const mfeAppCommand = utils.getLanguageTemplate(mfeName, isTypeScript);
89
+
90
+ // Create mfe react app
91
+ await utils.createReactApp(mfeAppCommand);
92
+
93
+ // Make normal react app into MFE container
94
+ await utils.configureMfe(
95
+ { ...mfeInfo, projectName: projectInfo.projectName, isTypeScript },
96
+ mfeName,
97
+ i
98
+ );
99
+ }
100
+
101
+ console.log(
102
+ `${INFO_MESSAGE.SUCCESS.NEW_PRO}\n${INFO_MESSAGE.HAPPY_CODING}`
103
+ );
104
+ } catch {
105
+ utils.cleanupProject(wrokingDirectories);
106
+ }
107
+ };
108
+
109
+ export default newProjectCreation;
@@ -0,0 +1,49 @@
1
+ import inquirer from "inquirer";
2
+ import { PROMPT, INFO_MESSAGE, CHOICE_CONSTANTS } from "../constants.js";
3
+ import utils from "../utility.js";
4
+
5
+ const singleMfeCreation = async (language) => {
6
+ // To store different working dir
7
+ const wrokingDirectories = [];
8
+ try {
9
+ // Get typescript flag
10
+ const isTypeScript = language === CHOICE_CONSTANTS.LANGUAGE.TYPE_SCRIPT;
11
+
12
+ const { mfeName, mfeDescription } = await inquirer.prompt(PROMPT.ONE_MFE);
13
+
14
+ console.log(`${INFO_MESSAGE.CREATE_APP}${mfeName} as microfront end`);
15
+
16
+ const mfeInfo = await inquirer.prompt([
17
+ PROMPT.CONDITIONAL.MFE_PATH,
18
+ ...PROMPT.COMMON,
19
+ PROMPT.CONDITIONAL.FORM_MANAGEMENT,
20
+ ]);
21
+
22
+ // store working dir
23
+ wrokingDirectories.push(mfeInfo.mfePath);
24
+
25
+ // Go inside user specified dir
26
+ process.chdir(mfeInfo.mfePath);
27
+
28
+ // Array to store CRA command
29
+ const appCommand = utils.getLanguageTemplate(mfeName, isTypeScript);
30
+
31
+ // Create container react app
32
+ await utils.createReactApp(appCommand);
33
+
34
+ // Make normal react app into MFE container
35
+ await utils.configureMfe(
36
+ { ...mfeInfo, projectName: mfeName, isTypeScript },
37
+ mfeName,
38
+ 0
39
+ );
40
+
41
+ console.log(
42
+ `${INFO_MESSAGE.SUCCESS.ONE_MFE}\n${INFO_MESSAGE.HAPPY_CODING}`
43
+ );
44
+ } catch {
45
+ utils.cleanupProject(wrokingDirectories)
46
+ }
47
+ };
48
+
49
+ export default singleMfeCreation;
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import mfeGen from './mfe-gen.js';
3
+
4
+ mfeGen();
package/mfe-gen.js ADDED
@@ -0,0 +1,22 @@
1
+ import inquirer from "inquirer";
2
+ import { PROMPT, INFO_MESSAGE, CHOICE_CONSTANTS } from "./constants.js";
3
+ import {newProjectCreation,containerCreation,singleMfeCreation} from "./creation/index.js";
4
+
5
+ const mfeGen = async () => {
6
+ console.log(INFO_MESSAGE.DISCLAIMER);
7
+
8
+ const { typeOfAction, language } = await inquirer.prompt(PROMPT.USER_NEED);
9
+ switch (typeOfAction) {
10
+ case CHOICE_CONSTANTS.ACTION.NEW_PROJECT:
11
+ newProjectCreation(language);
12
+ break;
13
+ case CHOICE_CONSTANTS.ACTION.CONTAINER:
14
+ containerCreation(language);
15
+ break;
16
+ case CHOICE_CONSTANTS.ACTION.SINGLE_MFE:
17
+ singleMfeCreation(language);
18
+ break;
19
+ }
20
+ };
21
+
22
+ export default mfeGen;
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "react-mfe-gen",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool for react Microfront end initial setup",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/VengadeshRaj/react-mfe-gen.git"
12
+ },
13
+ "author": "Vengadesh Raj",
14
+ "license": "ISC",
15
+ "bugs": {
16
+ "url": "https://github.com/VengadeshRaj/react-mfe-gen/issues"
17
+ },
18
+ "homepage": "https://github.com/VengadeshRaj/react-mfe-gen#readme",
19
+ "dependencies": {
20
+ "cli-spinner": "^0.2.10",
21
+ "execa": "^9.6.0",
22
+ "inquirer": "^12.9.4"
23
+ },
24
+ "type": "module",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "bin": {
29
+ "react-mfe-gen": "./index.js"
30
+ }
31
+ }
@@ -0,0 +1,10 @@
1
+ export const getAppContent=(containerName='container')=>{
2
+ return`import ${containerName} from "./${containerName}";
3
+
4
+ function App() {
5
+ return <${containerName} />;
6
+ }
7
+
8
+ export default App;
9
+ `
10
+ }
@@ -0,0 +1,28 @@
1
+ import utils from "../../utility.js";
2
+
3
+ export const getContainerCompContent = (containerCompName, mfes) => {
4
+ let containerComp = `import React from "react";\n`;
5
+
6
+ for (let i = 0; i < mfes.length; i++) {
7
+ containerComp += `import {${utils.toCompName(mfes[i])}} from "./microfrontends/${utils.toCompName(mfes[i])}";\n`;
8
+ }
9
+
10
+ containerComp += `\n\nfunction ${containerCompName}() {
11
+ const getMfeOrigin =(port:string|undefined='')=> \`\${window.location.protocol}//\${window.location.hostname}:\${port}\`;
12
+
13
+ return (
14
+ <div>
15
+ <h1>Hello from ${containerCompName}</h1>\n`;
16
+
17
+ for (let i = 0; i < mfes.length; i++) {
18
+ containerComp += ` <${utils.toCompName(mfes[i]) } name={"${utils.toCompName(mfes[i])}"} host={getMfeOrigin(process.env.REACT_APP_${mfes[i].toUpperCase()})} />\n`;
19
+ }
20
+
21
+ containerComp += `</div>
22
+ );
23
+ };
24
+
25
+ export default ${containerCompName};`;
26
+
27
+ return containerComp;
28
+ };
@@ -0,0 +1,8 @@
1
+ export const getEnvContent = (mfes) => {
2
+ let envTemplate = "";
3
+
4
+ for (let i = 0; i < mfes.length; i++) {
5
+ envTemplate += `REACT_APP_${mfes[i].toUpperCase()} = 900${i}\n`
6
+ }
7
+ return envTemplate;
8
+ };
@@ -0,0 +1,60 @@
1
+ export const getHeartContent=(containerName='container')=>{
2
+ return `
3
+ import React from "react";
4
+
5
+ class MicroFrontend extends React.Component {
6
+ componentDidMount() {
7
+ const { name, host, document } = this.props;
8
+ const scriptId = \`micro-frontend-script-\${name}\`;
9
+
10
+ if (document.getElementById(scriptId)) {
11
+ this.renderMicroFrontend();
12
+ return;
13
+ }
14
+
15
+ fetch(\`\${host}/asset-manifest.json\`)
16
+ .then((res) => res.json())
17
+ .then((manifest) => {
18
+ const script = document.createElement("script");
19
+ script.id = scriptId;
20
+ script.crossOrigin = "";
21
+ script.src = \`\${host}\${manifest["files"]["main.js"]}\`;
22
+ script.onload = this.renderMicroFrontend;
23
+ document.head.appendChild(script);
24
+ const link = document.createElement("link");
25
+ link.id = scriptId;
26
+ link.href = \`\${host}\${manifest["files"]["main.css"]}\`;
27
+ link.onload = this.renderMicroFrontend;
28
+ link.rel = "stylesheet";
29
+ document.head.appendChild(link);
30
+ });
31
+ }
32
+
33
+ componentWillUnmount() {
34
+ const { name, window } = this.props;
35
+
36
+ window[\`unmount\${name}\`] && window[\`unmount\${name}\`](
37
+ \`\${name}-${containerName}\`
38
+ );
39
+ }
40
+
41
+ renderMicroFrontend = () => {
42
+ const { name, window, history } = this.props;
43
+
44
+ window[\`render\${name}\`] &&
45
+ window[\`render\${name}\`](\`\${name}-${containerName}\`, history);
46
+ };
47
+
48
+ render() {
49
+ return <main id={\`\${this.props.name}-${containerName}\`} />;
50
+ }
51
+ }
52
+
53
+ MicroFrontend.defaultProps = {
54
+ document,
55
+ window,
56
+ };
57
+
58
+ export default MicroFrontend;
59
+ `
60
+ }
@@ -0,0 +1,17 @@
1
+ import { getAppContent } from "./app-comp-template.js";
2
+
3
+ import { getContainerCompContent } from "./container-comp-template.js";
4
+
5
+ import { getEnvContent } from "./env-template.js";
6
+
7
+ import { getHeartContent } from "./heart-template.js";
8
+
9
+ import { getMfeCompContent } from "./mfe-comp-template.js";
10
+
11
+ export {
12
+ getAppContent,
13
+ getContainerCompContent,
14
+ getEnvContent,
15
+ getHeartContent,
16
+ getMfeCompContent,
17
+ };
@@ -0,0 +1,8 @@
1
+ export const getMfeCompContent = (mfeName, isTypeScript) => {
2
+ return `import MicroFrontend from "../MicroFrontend";
3
+
4
+ export const ${mfeName} = (props${isTypeScript ? ": any" : ""}) => {
5
+ return <MicroFrontend name={props.name} host={props.host} />;
6
+ };
7
+ `;
8
+ };
@@ -0,0 +1,14 @@
1
+ export const getMfeAppContent=(mfeName='mfe',isTs)=>{
2
+ return`import React from 'react';
3
+
4
+ function App(prop${isTs ? ": any" : ""}) {
5
+ return (
6
+ <div>
7
+ <h1>Hello from ${mfeName}</h1>
8
+ </div>
9
+ );
10
+ }
11
+
12
+ export default App;
13
+ `
14
+ }
@@ -0,0 +1,13 @@
1
+ export const getConfigOverridesContent =()=>{
2
+ return`module.exports = {
3
+ webpack: (config, env) => {
4
+ config.optimization.runtimeChunk = false;
5
+ config.optimization.splitChunks = {
6
+ cacheGroups: {
7
+ default: false,
8
+ },
9
+ };
10
+ return config;
11
+ },
12
+ };`
13
+ }
@@ -0,0 +1,9 @@
1
+ import { getMfeAppContent } from "./app-comp-template.js";
2
+ import { getConfigOverridesContent } from "./config-overrides-template.js";
3
+ import { getmfeIndexContent } from "./mfe-index-template.js";
4
+
5
+ export {
6
+ getMfeAppContent,
7
+ getConfigOverridesContent,
8
+ getmfeIndexContent
9
+ };
@@ -0,0 +1,36 @@
1
+ export const getmfeIndexContent=(mfeName='mfe',container='',isTs)=>{
2
+ return`import React from "react";
3
+ import { createRoot } from "react-dom/client";
4
+ import App from "./App";
5
+
6
+ declare global {
7
+ interface Window {
8
+ render${mfeName}: (containerId${isTs?": string":""}, history${isTs?"?: any":""}) => void;
9
+ unmount${mfeName}: (containerId${isTs?": string":""}) => void;
10
+ }
11
+ }
12
+
13
+ window.render${mfeName} = (containerId${isTs?": string":""}, history${isTs?"?: any":""}) => {
14
+ const container${isTs?": any":""} = document.getElementById(containerId);
15
+ if (container) {
16
+ const root = createRoot(container);
17
+ root.render(<App history={history} />);
18
+ }
19
+ };
20
+
21
+ window.unmount${mfeName} = (containerId${isTs?": string":""}) => {
22
+ const container = document.getElementById(containerId);
23
+ if (container) {
24
+ const root = createRoot(container);
25
+ root.unmount();
26
+ }
27
+ };
28
+
29
+ const rootContainer = document.getElementById("root");
30
+ if (rootContainer && !document.getElementById("${mfeName}-${container}")) {
31
+ createRoot(rootContainer).render(<App />);
32
+ }
33
+
34
+
35
+ `
36
+ }
package/utility.js ADDED
@@ -0,0 +1,231 @@
1
+ import { Spinner } from "cli-spinner";
2
+ import { execa } from "execa";
3
+ import {
4
+ INFO_MESSAGE,
5
+ LIBRARY_PAIR,
6
+ DEFAULT_DEPENDENCIES,
7
+ DEFAULT_DEV_DEPENDENCIES,
8
+ } from "./constants.js";
9
+ import {
10
+ getHeartContent,
11
+ getAppContent,
12
+ getMfeCompContent,
13
+ getContainerCompContent,
14
+ getEnvContent,
15
+ } from "./templates/container/index.js";
16
+ import { writeFile, unlink, mkdir, readFile, rm } from "fs/promises";
17
+ import {
18
+ getConfigOverridesContent,
19
+ getMfeAppContent,
20
+ getmfeIndexContent,
21
+ } from "./templates/mfe/index.js";
22
+ class utils {
23
+ static async runTask(logMessage, task) {
24
+ const spinner = new Spinner(logMessage + "%s");
25
+ spinner.setSpinnerString("⠋⠙⠹⠸⠼⠴⠦⠧⠏");
26
+ spinner.setSpinnerDelay(40);
27
+ spinner.start();
28
+ try {
29
+ await task();
30
+ spinner.stop(true);
31
+ } catch (err) {
32
+ spinner.stop(true);
33
+ throw err;
34
+ }
35
+ }
36
+ static async cleanupProject(dirs) {
37
+ console.log(`Cleaning up the project directory: ${dirs}`);
38
+ try {
39
+ for (let i = 0; i < dirs.length; i++) {
40
+ await rm(dirs[i], { recursive: true });
41
+ }
42
+ } catch (err) {
43
+ console.log(`Failed to clean project directory\n Error:${err}`);
44
+ }
45
+ }
46
+ static async createReactApp(appCommand) {
47
+ try {
48
+ await utils.runTask(INFO_MESSAGE.APP_CREATION, () =>
49
+ execa("npx", ["create-react-app", ...appCommand])
50
+ );
51
+ } catch (err) {
52
+ throw err;
53
+ }
54
+ }
55
+ static async installPackages(packages, isDev = false) {
56
+ try {
57
+ let message = INFO_MESSAGE.i_DEPENDENCIES;
58
+ if (isDev) {
59
+ message = INFO_MESSAGE.i_DEV_DEPENDENCIES;
60
+ packages = [...packages, "--save-dev"];
61
+ }
62
+ await utils.runTask(message, () =>
63
+ execa("npm", ["install", ...packages])
64
+ );
65
+ } catch (err) {
66
+ throw err;
67
+ }
68
+ }
69
+ static getLanguageTemplate(appName, isTs) {
70
+ return isTs ? [appName, "--template", "typescript"] : [appName];
71
+ }
72
+
73
+ static withExt(name, isTs) {
74
+ return isTs ? `${name}.tsx` : `${name}.jsx`;
75
+ }
76
+ static withScript(name, isTs) {
77
+ return isTs ? `${name}.ts` : `${name}.js`;
78
+ }
79
+ static toCompName(AppName) {
80
+ return AppName.replace(/[-\s]+/g, " ")
81
+ .split(" ")
82
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
83
+ .join("");
84
+ }
85
+ static async updateScripts(dir, newScripts) {
86
+ const rawPackageJson = await readFile(dir);
87
+ const packageJSON = JSON.parse(rawPackageJson, "utf8");
88
+
89
+ packageJSON.scripts = newScripts;
90
+
91
+ await writeFile(dir, JSON.stringify(packageJSON, null, 2), "utf8");
92
+ }
93
+ static async configureContainer(info, mfeNames) {
94
+ // Destructure inputs
95
+ const {
96
+ projectName,
97
+ projectDescription,
98
+ isTypeScript,
99
+ styling,
100
+ stateManagement,
101
+ } = info;
102
+
103
+ // Go inside src
104
+ process.chdir(`${process.cwd()}\\${projectName}\\src`);
105
+
106
+ // Create heart of container
107
+ await utils.runTask(INFO_MESSAGE.CONFIGURE_CONTAINER, () =>
108
+ writeFile("MicroFrontend.js", getHeartContent(projectName))
109
+ );
110
+
111
+ // Convert project name into component name format
112
+ const containerCompName = utils.toCompName(projectName);
113
+
114
+ // Create container component
115
+ await utils.runTask(INFO_MESSAGE.CONFIGURE_CONTAINER, () =>
116
+ writeFile(
117
+ utils.withExt(containerCompName, isTypeScript),
118
+ getContainerCompContent(containerCompName, mfeNames)
119
+ )
120
+ );
121
+
122
+ // Modify App.jsx or .tsx file
123
+ await utils.runTask(INFO_MESSAGE.CONFIGURE_CONTAINER, () =>
124
+ writeFile(
125
+ utils.withExt("App", isTypeScript),
126
+ getAppContent(containerCompName)
127
+ )
128
+ );
129
+
130
+ // Drop unnecessary files
131
+ await unlink("App.css");
132
+ await unlink("logo.svg");
133
+
134
+ // Create MFE folders
135
+ await mkdir("microfrontends");
136
+
137
+ // Go inside MFE folder
138
+ process.chdir(`${process.cwd()}\\microfrontends`);
139
+
140
+ // Run loop and create number of mfe components
141
+ for (let i = 0; i < mfeNames.length; i++) {
142
+ const mfeCompName = utils.toCompName(mfeNames[i]);
143
+ await writeFile(
144
+ utils.withExt(mfeCompName, isTypeScript),
145
+ getMfeCompContent(mfeCompName, isTypeScript)
146
+ );
147
+ }
148
+
149
+ // Go to root
150
+ process.chdir("../../");
151
+ // Create env file with specified MFE default ports
152
+ await writeFile(".env", getEnvContent(mfeNames));
153
+
154
+ // Install common and user defined packages
155
+ const packagesList = [
156
+ ...DEFAULT_DEPENDENCIES,
157
+ ...LIBRARY_PAIR.STYLING[styling],
158
+ ...LIBRARY_PAIR.STATE_MANAGEMENT[stateManagement],
159
+ ];
160
+ await utils.installPackages(packagesList);
161
+
162
+ // Create readme
163
+ await writeFile("README.md", `# ${projectName}\n${projectDescription}`);
164
+
165
+ console.log(`${projectName} created ✅`);
166
+ }
167
+ static async configureMfe(info, mfeName, index) {
168
+ // Destructure inputs
169
+ const {
170
+ projectName,
171
+ formManagement,
172
+ styling,
173
+ stateManagement,
174
+ isTypeScript,
175
+ } = info;
176
+ // Go inside src
177
+ process.chdir(`${process.cwd()}\\${mfeName}\\src`);
178
+
179
+ // Modify App.jsx or .tsx file
180
+ await writeFile(
181
+ utils.withExt("App", isTypeScript),
182
+ getMfeAppContent(utils.toCompName(mfeName), isTypeScript)
183
+ );
184
+
185
+ // Modify index.jsx or .tsx file
186
+ await writeFile(
187
+ utils.withExt("index", isTypeScript),
188
+ getmfeIndexContent(utils.toCompName(mfeName), projectName, isTypeScript)
189
+ );
190
+
191
+ // Drop unnecessary files
192
+ await unlink("App.css");
193
+ await unlink("logo.svg");
194
+ await unlink("index.css");
195
+
196
+ // Go to root
197
+ process.chdir("../");
198
+
199
+ // add config override file
200
+ await writeFile("config-overrides.js", getConfigOverridesContent());
201
+
202
+ const updatedScript = {
203
+ start: `cross-env PORT=900${index} react-app-rewired start`,
204
+ build: "react-app-rewired build",
205
+ test: "react-app-rewired test",
206
+ eject: "react-app-rewired eject",
207
+ };
208
+
209
+ // modify package.json scripts
210
+ utils.updateScripts(`${process.cwd()}\\package.json`, updatedScript);
211
+
212
+ // Install common and user defined packages
213
+ const packagesList = [
214
+ ...DEFAULT_DEPENDENCIES,
215
+ ...LIBRARY_PAIR.STYLING[styling],
216
+ ...LIBRARY_PAIR.STATE_MANAGEMENT[stateManagement],
217
+ ...LIBRARY_PAIR.FORM_MANAGEMENT[formManagement],
218
+ ];
219
+
220
+ await utils.installPackages(packagesList);
221
+
222
+ await utils.installPackages(DEFAULT_DEV_DEPENDENCIES, true);
223
+
224
+ // Create readme
225
+ await writeFile("README.md", `# ${mfeName}`);
226
+
227
+ console.log(`${mfeName} created ✅`);
228
+ }
229
+ }
230
+
231
+ export default utils;