@nx/react 0.0.0-pr-29176-d3c6c29 → 0.0.0-pr-29314-e664f92
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/generators.json +4 -2
- package/package.json +6 -6
- package/plugins/bundle-rollup.js +2 -2
- package/plugins/nx-react-webpack-plugin/lib/apply-react-config.js +3 -1
- package/src/executors/module-federation-dev-server/schema.json +14 -10
- package/src/executors/module-federation-ssr-dev-server/schema.json +13 -9
- package/src/executors/module-federation-static-server/module-federation-static-server.impl.js +5 -5
- package/src/executors/module-federation-static-server/schema.json +1 -1
- package/src/generators/application/schema.json +3 -3
- package/src/generators/component/component.js +19 -32
- package/src/generators/component/files/{__fileName__.tsx__tmpl__ → __fileName__.__ext__} +2 -2
- package/src/generators/component/lib/normalize-options.js +7 -4
- package/src/generators/component/schema.d.ts +9 -3
- package/src/generators/component/schema.json +2 -6
- package/src/generators/component-story/component-story.js +1 -2
- package/src/generators/federate-module/schema.json +14 -8
- package/src/generators/hook/files/{__fileName__.ts__tmpl__ → __fileName__.__ext__} +3 -1
- package/src/generators/hook/hook.js +21 -22
- package/src/generators/hook/schema.d.ts +4 -0
- package/src/generators/hook/schema.json +8 -4
- package/src/generators/host/schema.json +9 -8
- package/src/generators/library/lib/add-rollup-build-target.js +30 -27
- package/src/generators/library/library.js +2 -0
- package/src/generators/redux/files/js/__fileName__.__ext__ +114 -0
- package/src/generators/redux/files/{__fileName__.slice.spec.ts__tmpl__ → js/__fileName__.spec.__ext__} +1 -1
- package/src/generators/redux/files/ts/__fileName__.spec.__ext__ +56 -0
- package/src/generators/redux/redux.js +19 -16
- package/src/generators/redux/schema.d.ts +10 -2
- package/src/generators/redux/schema.json +8 -4
- package/src/generators/remote/lib/update-host-with-remote.js +2 -2
- package/src/generators/remote/schema.json +9 -8
- package/src/generators/stories/stories.js +5 -2
- package/src/generators/storybook-configuration/configuration.js +2 -2
- /package/src/generators/component/files/{__fileName__.spec.tsx__tmpl__ → __fileName__.spec.__ext__} +0 -0
- /package/src/generators/hook/files/{__fileName__.spec.tsx__tmpl__ → __fileName__.spec.__specExt__} +0 -0
- /package/src/generators/redux/files/{__fileName__.slice.ts__tmpl__ → ts/__fileName__.__ext__} +0 -0
package/generators.json
CHANGED
|
@@ -64,13 +64,15 @@
|
|
|
64
64
|
"factory": "./src/generators/host/host",
|
|
65
65
|
"schema": "./src/generators/host/schema.json",
|
|
66
66
|
"x-type": "application",
|
|
67
|
-
"description": "Generate a host react application"
|
|
67
|
+
"description": "Generate a host react application",
|
|
68
|
+
"aliases": ["consumer"]
|
|
68
69
|
},
|
|
69
70
|
"remote": {
|
|
70
71
|
"factory": "./src/generators/remote/remote",
|
|
71
72
|
"schema": "./src/generators/remote/schema.json",
|
|
72
73
|
"x-type": "application",
|
|
73
|
-
"description": "Generate a remote react application"
|
|
74
|
+
"description": "Generate a remote react application",
|
|
75
|
+
"aliases": ["producer"]
|
|
74
76
|
},
|
|
75
77
|
"cypress-component-configuration": {
|
|
76
78
|
"factory": "./src/generators/cypress-component-configuration/cypress-component-configuration#cypressComponentConfigGenerator",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nx/react",
|
|
3
|
-
"version": "0.0.0-pr-
|
|
3
|
+
"version": "0.0.0-pr-29314-e664f92",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The React plugin for Nx contains executors and generators for managing React applications and libraries within an Nx workspace. It provides:\n\n\n- Integration with libraries such as Jest, Vitest, Playwright, Cypress, and Storybook.\n\n- Generators for applications, libraries, components, hooks, and more.\n\n- Library build support for publishing packages to npm or other registries.\n\n- Utilities for automatic workspace refactoring.",
|
|
6
6
|
"repository": {
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"minimatch": "9.0.3",
|
|
39
39
|
"picocolors": "^1.1.0",
|
|
40
40
|
"tslib": "^2.3.0",
|
|
41
|
-
"@nx/devkit": "0.0.0-pr-
|
|
42
|
-
"@nx/js": "0.0.0-pr-
|
|
43
|
-
"@nx/eslint": "0.0.0-pr-
|
|
44
|
-
"@nx/web": "0.0.0-pr-
|
|
45
|
-
"@nx/module-federation": "0.0.0-pr-
|
|
41
|
+
"@nx/devkit": "0.0.0-pr-29314-e664f92",
|
|
42
|
+
"@nx/js": "0.0.0-pr-29314-e664f92",
|
|
43
|
+
"@nx/eslint": "0.0.0-pr-29314-e664f92",
|
|
44
|
+
"@nx/web": "0.0.0-pr-29314-e664f92",
|
|
45
|
+
"@nx/module-federation": "0.0.0-pr-29314-e664f92",
|
|
46
46
|
"express": "^4.19.2",
|
|
47
47
|
"http-proxy-middleware": "^3.0.3"
|
|
48
48
|
},
|
package/plugins/bundle-rollup.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
// TODO(v22): Remove this in Nx 22 and migrate to explicit rollup.config.
|
|
3
|
+
// TODO(v22): Remove this in Nx 22 and migrate to explicit rollup.config.cjs files.
|
|
4
4
|
/**
|
|
5
|
-
* @deprecated Use `withNx` function from `@nx/rollup/with-nx` in your rollup.config.
|
|
5
|
+
* @deprecated Use `withNx` function from `@nx/rollup/with-nx` in your rollup.config.cjs file instead. Use `nx g @nx/rollup:convert-to-inferred` to generate the rollup.config.cjs file if it does not exist.
|
|
6
6
|
*/
|
|
7
7
|
function getRollupOptions(options) {
|
|
8
8
|
const extraGlobals = {
|
|
@@ -80,7 +80,9 @@ function addHotReload(config) {
|
|
|
80
80
|
// We remove potentially conflicting rules that target SVGs because we use @svgr/webpack loader
|
|
81
81
|
// See https://github.com/nrwl/nx/issues/14383
|
|
82
82
|
function removeSvgLoaderIfPresent(config) {
|
|
83
|
-
const svgLoaderIdx = config.module.rules.findIndex((rule) => typeof rule === 'object' &&
|
|
83
|
+
const svgLoaderIdx = config.module.rules.findIndex((rule) => typeof rule === 'object' &&
|
|
84
|
+
typeof rule.test !== 'undefined' &&
|
|
85
|
+
rule.test.toString().includes('svg'));
|
|
84
86
|
if (svgLoaderIdx === -1)
|
|
85
87
|
return;
|
|
86
88
|
config.module.rules.splice(svgLoaderIdx, 1);
|
|
@@ -28,16 +28,18 @@
|
|
|
28
28
|
}
|
|
29
29
|
]
|
|
30
30
|
},
|
|
31
|
-
"description": "List of remote applications to run in development mode (i.e. using serve target).",
|
|
32
|
-
"x-priority": "important"
|
|
31
|
+
"description": "List of Producer (remote) applications to run in development mode (i.e. using serve target).",
|
|
32
|
+
"x-priority": "important",
|
|
33
|
+
"alias": "devProducers"
|
|
33
34
|
},
|
|
34
35
|
"skipRemotes": {
|
|
35
36
|
"type": "array",
|
|
36
37
|
"items": {
|
|
37
38
|
"type": "string"
|
|
38
39
|
},
|
|
39
|
-
"description": "List of remote applications to not automatically serve, either statically or in development mode. This will not remove the remotes from the `module-federation.config` file, and therefore the application may still try to fetch these remotes.\nThis option is useful if you have other means for serving the
|
|
40
|
-
"x-priority": "important"
|
|
40
|
+
"description": "List of Producer (remote) applications to not automatically serve, either statically or in development mode. This will not remove the Producers (remotes) from the `module-federation.config` file, and therefore the application may still try to fetch these Producers (remotes).\nThis option is useful if you have other means for serving the Producer (remote) application(s).\n**NOTE:** Producers (remotes) that are not in the workspace will be skipped automatically.",
|
|
41
|
+
"x-priority": "important",
|
|
42
|
+
"alias": "skipProducers"
|
|
41
43
|
},
|
|
42
44
|
"buildTarget": {
|
|
43
45
|
"type": "string",
|
|
@@ -111,25 +113,27 @@
|
|
|
111
113
|
},
|
|
112
114
|
"static": {
|
|
113
115
|
"type": "boolean",
|
|
114
|
-
"description": "Whether to use a static file server instead of the webpack-dev-server. This should be used for remote applications that are also host applications."
|
|
116
|
+
"description": "Whether to use a static file server instead of the webpack-dev-server. This should be used for Producer (remote) applications that are also Consumer (host) applications."
|
|
115
117
|
},
|
|
116
118
|
"isInitialHost": {
|
|
117
119
|
"type": "boolean",
|
|
118
|
-
"description": "Whether the host that is running this executor is the first in the project tree to do so.",
|
|
120
|
+
"description": "Whether the Consumer (host) that is running this executor is the first in the project tree to do so.",
|
|
119
121
|
"default": true,
|
|
120
|
-
"x-priority": "internal"
|
|
122
|
+
"x-priority": "internal",
|
|
123
|
+
"alias": "isInitialConsumer"
|
|
121
124
|
},
|
|
122
125
|
"parallel": {
|
|
123
126
|
"type": "number",
|
|
124
|
-
"description": "Max number of parallel processes for building static remotes"
|
|
127
|
+
"description": "Max number of parallel processes for building static Producers (remotes)"
|
|
125
128
|
},
|
|
126
129
|
"staticRemotesPort": {
|
|
127
130
|
"type": "number",
|
|
128
|
-
"description": "The port at which to serve the file-server for the static remotes."
|
|
131
|
+
"description": "The port at which to serve the file-server for the static Producers (remotes).",
|
|
132
|
+
"alias": "staticProducersPort"
|
|
129
133
|
},
|
|
130
134
|
"pathToManifestFile": {
|
|
131
135
|
"type": "string",
|
|
132
|
-
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
|
|
136
|
+
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic Producer (remote) applications relative to the workspace root."
|
|
133
137
|
}
|
|
134
138
|
},
|
|
135
139
|
"examplesFile": "../../../docs/module-federation-dev-server-examples.md"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": 2,
|
|
3
3
|
"outputCapture": "direct-nodejs",
|
|
4
4
|
"title": "Module Federation SSR Dev Server",
|
|
5
|
-
"description": "Serve a SSR host application along with its known remotes.",
|
|
5
|
+
"description": "Serve a SSR Consumer (host) application along with its known Producers (remotes).",
|
|
6
6
|
"cli": "nx",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
@@ -27,16 +27,18 @@
|
|
|
27
27
|
"items": {
|
|
28
28
|
"type": "string"
|
|
29
29
|
},
|
|
30
|
-
"description": "List of remote applications to run in development mode (i.e. using serve target).",
|
|
31
|
-
"x-priority": "important"
|
|
30
|
+
"description": "List of Producer (remote) applications to run in development mode (i.e. using serve target).",
|
|
31
|
+
"x-priority": "important",
|
|
32
|
+
"alias": "devProducers"
|
|
32
33
|
},
|
|
33
34
|
"skipRemotes": {
|
|
34
35
|
"type": "array",
|
|
35
36
|
"items": {
|
|
36
37
|
"type": "string"
|
|
37
38
|
},
|
|
38
|
-
"description": "List of remote applications to not automatically serve, either statically or in development mode.",
|
|
39
|
-
"x-priority": "important"
|
|
39
|
+
"description": "List of Producer (remote) applications to not automatically serve, either statically or in development mode.",
|
|
40
|
+
"x-priority": "important",
|
|
41
|
+
"alias": "skipProducers"
|
|
40
42
|
},
|
|
41
43
|
"host": {
|
|
42
44
|
"type": "string",
|
|
@@ -45,11 +47,12 @@
|
|
|
45
47
|
},
|
|
46
48
|
"staticRemotesPort": {
|
|
47
49
|
"type": "number",
|
|
48
|
-
"description": "The port at which to serve the file-server for the static remotes."
|
|
50
|
+
"description": "The port at which to serve the file-server for the static Producers (remotes).",
|
|
51
|
+
"alias": "staticProducersPort"
|
|
49
52
|
},
|
|
50
53
|
"pathToManifestFile": {
|
|
51
54
|
"type": "string",
|
|
52
|
-
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
|
|
55
|
+
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic Producer (remote) applications relative to the workspace root."
|
|
53
56
|
},
|
|
54
57
|
"ssl": {
|
|
55
58
|
"type": "boolean",
|
|
@@ -66,9 +69,10 @@
|
|
|
66
69
|
},
|
|
67
70
|
"isInitialHost": {
|
|
68
71
|
"type": "boolean",
|
|
69
|
-
"description": "Whether the host that is running this executor is the first in the project tree to do so.",
|
|
72
|
+
"description": "Whether the Consumer (host) that is running this executor is the first in the project tree to do so.",
|
|
70
73
|
"default": true,
|
|
71
|
-
"x-priority": "internal"
|
|
74
|
+
"x-priority": "internal",
|
|
75
|
+
"alias": "isInitialConsumer"
|
|
72
76
|
}
|
|
73
77
|
},
|
|
74
78
|
"required": ["browserTarget", "serverTarget"]
|
package/src/executors/module-federation-static-server/module-federation-static-server.impl.js
CHANGED
|
@@ -58,7 +58,7 @@ async function buildHost(nxBin, buildTarget, context) {
|
|
|
58
58
|
}
|
|
59
59
|
if (stdoutString.includes('Successfully ran target build')) {
|
|
60
60
|
staticProcess.stdout.removeAllListeners('data');
|
|
61
|
-
devkit_1.logger.info(`NX Built host`);
|
|
61
|
+
devkit_1.logger.info(`NX Built Consumer (host)`);
|
|
62
62
|
res();
|
|
63
63
|
}
|
|
64
64
|
});
|
|
@@ -67,7 +67,7 @@ async function buildHost(nxBin, buildTarget, context) {
|
|
|
67
67
|
staticProcess.stdout.removeAllListeners('data');
|
|
68
68
|
staticProcess.stderr.removeAllListeners('data');
|
|
69
69
|
if (code !== 0) {
|
|
70
|
-
rej(`
|
|
70
|
+
rej(`Consumer (host) failed to build. See above for details.`);
|
|
71
71
|
}
|
|
72
72
|
else {
|
|
73
73
|
res();
|
|
@@ -132,8 +132,8 @@ function startProxies(staticRemotesConfig, hostServeOptions, mappedLocationOfHos
|
|
|
132
132
|
process.on('SIGTERM', () => proxyServer.close());
|
|
133
133
|
process.on('exit', () => proxyServer.close());
|
|
134
134
|
}
|
|
135
|
-
devkit_1.logger.info(`NX Static remotes proxies started successfully`);
|
|
136
|
-
devkit_1.logger.info(`NX Starting static host proxy...`);
|
|
135
|
+
devkit_1.logger.info(`NX Static Producers (remotes) proxies started successfully`);
|
|
136
|
+
devkit_1.logger.info(`NX Starting static Consumer (host) proxy...`);
|
|
137
137
|
const expressProxy = express();
|
|
138
138
|
expressProxy.use(createProxyMiddleware({
|
|
139
139
|
target: mappedLocationOfHost,
|
|
@@ -155,7 +155,7 @@ function startProxies(staticRemotesConfig, hostServeOptions, mappedLocationOfHos
|
|
|
155
155
|
.listen(hostServeOptions.port);
|
|
156
156
|
process.on('SIGTERM', () => proxyServer.close());
|
|
157
157
|
process.on('exit', () => proxyServer.close());
|
|
158
|
-
devkit_1.logger.info('NX Static host proxy started successfully');
|
|
158
|
+
devkit_1.logger.info('NX Static Consumer (host) proxy started successfully');
|
|
159
159
|
}
|
|
160
160
|
async function* moduleFederationStaticServer(schema, context) {
|
|
161
161
|
// Force Node to resolve to look for the nx binary that is inside node_modules
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": 2,
|
|
3
3
|
"outputCapture": "direct-nodejs",
|
|
4
4
|
"title": "Module Federation Static Dev Server",
|
|
5
|
-
"description": "Serve a host application statically along with
|
|
5
|
+
"description": "Serve a Consumer (host) application statically along with its Producers (remotes).",
|
|
6
6
|
"cli": "nx",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
@@ -51,15 +51,15 @@
|
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
"value": "scss",
|
|
54
|
-
"label": "SASS(.scss) [ https://sass-lang.com
|
|
54
|
+
"label": "SASS(.scss) [ https://sass-lang.com ]"
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
"value": "less",
|
|
58
|
-
"label": "LESS [ https://lesscss.org
|
|
58
|
+
"label": "LESS [ https://lesscss.org ]"
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
"value": "tailwind",
|
|
62
|
-
"label": "tailwind
|
|
62
|
+
"label": "tailwind [ https://tailwindcss.com/ ]"
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"value": "styled-components",
|
|
@@ -32,31 +32,17 @@ function createComponentFiles(host, options) {
|
|
|
32
32
|
...options,
|
|
33
33
|
componentTests,
|
|
34
34
|
inSourceVitestTests: (0, get_in_source_vitest_tests_template_1.getInSourceVitestTestsTemplate)(componentTests),
|
|
35
|
-
|
|
35
|
+
isTs: options.fileExtensionType === 'ts',
|
|
36
|
+
ext: options.fileExtension,
|
|
36
37
|
});
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
if ((options.styledModule || !options.hasStyles) &&
|
|
44
|
-
c.path.endsWith(`.${options.style}`)) {
|
|
45
|
-
deleteFile = true;
|
|
46
|
-
}
|
|
47
|
-
if (options.globalCss && c.path.endsWith(`.module.${options.style}`)) {
|
|
48
|
-
deleteFile = true;
|
|
49
|
-
}
|
|
50
|
-
if (!options.globalCss &&
|
|
51
|
-
c.path.endsWith(`${options.fileName}.${options.style}`)) {
|
|
52
|
-
deleteFile = true;
|
|
53
|
-
}
|
|
54
|
-
if (deleteFile) {
|
|
55
|
-
host.delete(c.path);
|
|
56
|
-
}
|
|
38
|
+
if (options.skipTests || options.inSourceTests) {
|
|
39
|
+
host.delete((0, devkit_1.joinPathFragments)(options.directory, `${options.fileName}.spec.${options.fileExtension}`));
|
|
40
|
+
}
|
|
41
|
+
if (options.styledModule || !options.hasStyles || !options.globalCss) {
|
|
42
|
+
host.delete((0, path_1.join)(options.directory, `${options.fileName}.${options.style}`));
|
|
57
43
|
}
|
|
58
|
-
if (options.
|
|
59
|
-
(0,
|
|
44
|
+
if (options.styledModule || !options.hasStyles || options.globalCss) {
|
|
45
|
+
host.delete((0, path_1.join)(options.directory, `${options.fileName}.module.${options.style}`));
|
|
60
46
|
}
|
|
61
47
|
}
|
|
62
48
|
let tsModule;
|
|
@@ -67,16 +53,17 @@ function addExportsToBarrel(host, options) {
|
|
|
67
53
|
const workspace = (0, devkit_1.getProjects)(host);
|
|
68
54
|
const isApp = workspace.get(options.projectName).projectType === 'application';
|
|
69
55
|
if (options.export && !isApp) {
|
|
70
|
-
const indexFilePath = options.projectSourceRoot
|
|
71
|
-
?
|
|
72
|
-
:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const indexSourceFile = tsModule.createSourceFile(indexFilePath, indexSource, tsModule.ScriptTarget.Latest, true);
|
|
76
|
-
const relativePathFromIndex = getRelativeImportToFile(indexFilePath, options.filePath);
|
|
77
|
-
const changes = (0, devkit_1.applyChangesToString)(indexSource, (0, ast_utils_1.addImport)(indexSourceFile, `export * from '${relativePathFromIndex}';`));
|
|
78
|
-
host.write(indexFilePath, changes);
|
|
56
|
+
const indexFilePath = (0, devkit_1.joinPathFragments)(...(options.projectSourceRoot
|
|
57
|
+
? [options.projectSourceRoot]
|
|
58
|
+
: [options.projectRoot, 'src']), options.fileExtensionType === 'js' ? 'index.js' : 'index.ts');
|
|
59
|
+
if (!host.exists(indexFilePath)) {
|
|
60
|
+
return;
|
|
79
61
|
}
|
|
62
|
+
const indexSource = host.read(indexFilePath, 'utf-8');
|
|
63
|
+
const indexSourceFile = tsModule.createSourceFile(indexFilePath, indexSource, tsModule.ScriptTarget.Latest, true);
|
|
64
|
+
const relativePathFromIndex = getRelativeImportToFile(indexFilePath, options.filePath);
|
|
65
|
+
const changes = (0, devkit_1.applyChangesToString)(indexSource, (0, ast_utils_1.addImport)(indexSourceFile, `export * from '${relativePathFromIndex}';`));
|
|
66
|
+
host.write(indexFilePath, changes);
|
|
80
67
|
}
|
|
81
68
|
}
|
|
82
69
|
function getRelativeImportToFile(indexPath, filePath) {
|
|
@@ -29,8 +29,8 @@ const Styled<%= className %> = styled.div`
|
|
|
29
29
|
<%_ } _%>
|
|
30
30
|
<%_ if(!isNextPage) { _%>
|
|
31
31
|
<%_ if (classComponent) { _%>
|
|
32
|
-
export class <%= className %> extends Component
|
|
33
|
-
override render() {
|
|
32
|
+
export class <%= className %> extends Component<% if (isTs) { %><{}><% } %> {
|
|
33
|
+
<% if (isTs) { %>override<% } %> render() {
|
|
34
34
|
return (
|
|
35
35
|
<<%= wrapper %><%- extras %>>
|
|
36
36
|
<%= styledModule === 'styled-jsx' ? `<style jsx>{\`div { color: pink; }\`}</style>` : `` %>
|
|
@@ -6,11 +6,12 @@ const artifact_name_and_directory_utils_1 = require("@nx/devkit/src/generators/a
|
|
|
6
6
|
const assertion_1 = require("../../../utils/assertion");
|
|
7
7
|
async function normalizeOptions(tree, options) {
|
|
8
8
|
(0, assertion_1.assertValidStyle)(options.style);
|
|
9
|
-
const { artifactName: name, directory, fileName, filePath, project: projectName, } = await (0, artifact_name_and_directory_utils_1.determineArtifactNameAndDirectoryOptions)(tree, {
|
|
9
|
+
const { artifactName: name, directory, fileName, filePath, fileExtension, fileExtensionType, project: projectName, } = await (0, artifact_name_and_directory_utils_1.determineArtifactNameAndDirectoryOptions)(tree, {
|
|
10
10
|
path: options.path,
|
|
11
11
|
name: options.name,
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
allowedFileExtensions: ['js', 'jsx', 'ts', 'tsx'],
|
|
13
|
+
fileExtension: options.js ? 'js' : 'tsx',
|
|
14
|
+
js: options.js,
|
|
14
15
|
});
|
|
15
16
|
const project = (0, devkit_1.readProjectConfiguration)(tree, projectName);
|
|
16
17
|
const { className } = (0, devkit_1.names)(name);
|
|
@@ -37,6 +38,8 @@ async function normalizeOptions(tree, options) {
|
|
|
37
38
|
fileName,
|
|
38
39
|
filePath,
|
|
39
40
|
projectRoot,
|
|
40
|
-
projectSourceRoot
|
|
41
|
+
projectSourceRoot,
|
|
42
|
+
fileExtension,
|
|
43
|
+
fileExtensionType,
|
|
41
44
|
};
|
|
42
45
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FileExtensionType } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
|
|
1
2
|
import { SupportedStyles } from '../../../typings/style';
|
|
2
3
|
|
|
3
4
|
export interface Schema {
|
|
@@ -8,22 +9,27 @@ export interface Schema {
|
|
|
8
9
|
export?: boolean;
|
|
9
10
|
classComponent?: boolean;
|
|
10
11
|
routing?: boolean;
|
|
11
|
-
js?: boolean;
|
|
12
12
|
globalCss?: boolean;
|
|
13
|
-
fileName?: string;
|
|
14
13
|
inSourceTests?: boolean;
|
|
15
14
|
skipFormat?: boolean;
|
|
16
15
|
// Used by Next.js to determine how React should generate the page
|
|
17
16
|
isNextPage?: boolean;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @deprecated Provide the full file path including the file extension in the `path` option. This option will be removed in Nx v21.
|
|
20
|
+
*/
|
|
21
|
+
js?: boolean;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
export interface NormalizedSchema extends Schema {
|
|
24
|
+
export interface NormalizedSchema extends Omit<Schema, 'js'> {
|
|
21
25
|
directory: string;
|
|
22
26
|
projectRoot: string;
|
|
23
27
|
projectSourceRoot: string;
|
|
24
28
|
projectName: string;
|
|
25
29
|
fileName: string;
|
|
26
30
|
filePath: string;
|
|
31
|
+
fileExtension: string;
|
|
32
|
+
fileExtensionType: FileExtensionType;
|
|
27
33
|
className: string;
|
|
28
34
|
styledModule: null | string;
|
|
29
35
|
hasStyles: boolean;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"properties": {
|
|
9
9
|
"path": {
|
|
10
10
|
"type": "string",
|
|
11
|
-
"description": "The file path to the component
|
|
11
|
+
"description": "The file path to the component. Relative to the current working directory.",
|
|
12
12
|
"$default": {
|
|
13
13
|
"$source": "argv",
|
|
14
14
|
"index": 0
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"js": {
|
|
64
64
|
"type": "boolean",
|
|
65
65
|
"description": "Generate JavaScript files rather than TypeScript files.",
|
|
66
|
-
"
|
|
66
|
+
"x-deprecated": "Provide the full file path including the file extension in the `path` option. This option will be removed in Nx v21."
|
|
67
67
|
},
|
|
68
68
|
"skipTests": {
|
|
69
69
|
"type": "boolean",
|
|
@@ -93,10 +93,6 @@
|
|
|
93
93
|
"description": "Default is `false`. When `true`, the component is generated with `*.css`/`*.scss` instead of `*.module.css`/`*.module.scss`.",
|
|
94
94
|
"default": false
|
|
95
95
|
},
|
|
96
|
-
"fileName": {
|
|
97
|
-
"type": "string",
|
|
98
|
-
"description": "Create a component with this file name."
|
|
99
|
-
},
|
|
100
96
|
"inSourceTests": {
|
|
101
97
|
"type": "boolean",
|
|
102
98
|
"default": false,
|
|
@@ -13,8 +13,7 @@ function createComponentStoriesFile(host, { project, componentPath, interactionT
|
|
|
13
13
|
tsModule = (0, ensure_typescript_1.ensureTypescript)();
|
|
14
14
|
}
|
|
15
15
|
const proj = (0, devkit_1.getProjects)(host).get(project);
|
|
16
|
-
const
|
|
17
|
-
const componentFilePath = (0, devkit_1.joinPathFragments)(sourceRoot, componentPath);
|
|
16
|
+
const componentFilePath = (0, devkit_1.joinPathFragments)(proj.sourceRoot ?? proj.root, componentPath);
|
|
18
17
|
const componentDirectory = componentFilePath.replace(componentFilePath.slice(componentFilePath.lastIndexOf('/')), '');
|
|
19
18
|
const isPlainJs = componentFilePath.endsWith('.jsx') || componentFilePath.endsWith('.js');
|
|
20
19
|
const componentFileName = componentFilePath
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
"cli": "nx",
|
|
4
4
|
"$id": "NxReactFederateModule",
|
|
5
5
|
"title": "Federate Module",
|
|
6
|
-
"description": "Create a federated module, which can be loaded by a
|
|
6
|
+
"description": "Create a federated module, which can be loaded by a Consumer (host) via a Producer (remote).",
|
|
7
7
|
"examples": [
|
|
8
8
|
{
|
|
9
|
-
"command": "nx g federate-module MyModule --path=./src/component/my-cmp.ts --remote=
|
|
10
|
-
"description": "Create a federated module from
|
|
9
|
+
"command": "nx g federate-module MyModule --path=./src/component/my-cmp.ts --remote=myRemote --remoteDirectory=apps/myRemote",
|
|
10
|
+
"description": "Create a federated module from myRemote, that exposes my-cmp from ./src/component/my-cmp.ts as MyModule."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"command": "nx g federate-module MyModule --path=./src/component/my-cmp.ts --producer=myProducer --producerDirectory=apps/myProducer",
|
|
14
|
+
"description": "Create a federated module from myProducer, that exposes my-cmp from ./src/component/my-cmp.ts as MyModule."
|
|
11
15
|
}
|
|
12
16
|
],
|
|
13
17
|
"type": "object",
|
|
@@ -30,12 +34,14 @@
|
|
|
30
34
|
},
|
|
31
35
|
"remote": {
|
|
32
36
|
"type": "string",
|
|
33
|
-
"description": "The name of the remote.",
|
|
34
|
-
"x-prompt": "What is/should the remote be named?"
|
|
37
|
+
"description": "The name of the Producer (remote).",
|
|
38
|
+
"x-prompt": "What is/should the Producer (remote) be named?",
|
|
39
|
+
"alias": "producer"
|
|
35
40
|
},
|
|
36
41
|
"remoteDirectory": {
|
|
37
|
-
"description": "The directory of the new remote application if one needs to be created.",
|
|
38
|
-
"type": "string"
|
|
42
|
+
"description": "The directory of the new Producer (remote) application if one needs to be created.",
|
|
43
|
+
"type": "string",
|
|
44
|
+
"alias": "producerDirectory"
|
|
39
45
|
},
|
|
40
46
|
"style": {
|
|
41
47
|
"description": "The file extension to be used for style files.",
|
|
@@ -70,7 +76,7 @@
|
|
|
70
76
|
},
|
|
71
77
|
"host": {
|
|
72
78
|
"type": "string",
|
|
73
|
-
"description": "The host
|
|
79
|
+
"description": "The Consumer (host) application for this Producer (remote)."
|
|
74
80
|
},
|
|
75
81
|
"bundler": {
|
|
76
82
|
"description": "The bundler to use.",
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { useState, useCallback } from 'react'
|
|
2
2
|
|
|
3
|
+
<%_ if (isTs) { _%>
|
|
3
4
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
4
5
|
export interface <%= hookTypeName %> {
|
|
5
6
|
count: number;
|
|
6
7
|
increment: () => void;
|
|
7
8
|
}
|
|
9
|
+
<%_ } _%>
|
|
8
10
|
|
|
9
|
-
export function <%= hookName %>()
|
|
11
|
+
export function <%= hookName %>()<% if (isTs) { %>: <%= hookTypeName %><% } %> {
|
|
10
12
|
const [count, setCount] = useState(0)
|
|
11
13
|
const increment = useCallback(() => setCount((x) => x + 1), [])
|
|
12
14
|
return { count, increment }
|
|
@@ -3,10 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.hookGenerator = hookGenerator;
|
|
4
4
|
// TODO(jack): Remove inline renderHook function when RTL releases with its own version
|
|
5
5
|
const devkit_1 = require("@nx/devkit");
|
|
6
|
-
const
|
|
6
|
+
const artifact_name_and_directory_utils_1 = require("@nx/devkit/src/generators/artifact-name-and-directory-utils");
|
|
7
7
|
const ensure_typescript_1 = require("@nx/js/src/utils/typescript/ensure-typescript");
|
|
8
8
|
const path_1 = require("path");
|
|
9
|
-
const
|
|
9
|
+
const ast_utils_1 = require("../../utils/ast-utils");
|
|
10
10
|
async function hookGenerator(host, schema) {
|
|
11
11
|
const options = await normalizeOptions(host, schema);
|
|
12
12
|
createFiles(host, options);
|
|
@@ -14,21 +14,15 @@ async function hookGenerator(host, schema) {
|
|
|
14
14
|
return await (0, devkit_1.formatFiles)(host);
|
|
15
15
|
}
|
|
16
16
|
function createFiles(host, options) {
|
|
17
|
+
const specExt = options.fileExtensionType === 'ts' ? 'tsx' : 'js';
|
|
17
18
|
(0, devkit_1.generateFiles)(host, (0, path_1.join)(__dirname, './files'), options.directory, {
|
|
18
19
|
...options,
|
|
19
|
-
|
|
20
|
+
ext: options.fileExtension,
|
|
21
|
+
specExt,
|
|
22
|
+
isTs: options.fileExtensionType === 'ts',
|
|
20
23
|
});
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (options.skipTests && /.*spec.ts/.test(c.path)) {
|
|
24
|
-
deleteFile = true;
|
|
25
|
-
}
|
|
26
|
-
if (deleteFile) {
|
|
27
|
-
host.delete(c.path);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if (options.js) {
|
|
31
|
-
(0, devkit_1.toJS)(host);
|
|
24
|
+
if (options.skipTests) {
|
|
25
|
+
host.delete((0, devkit_1.joinPathFragments)(options.directory, `${options.fileName}.spec.${specExt}`));
|
|
32
26
|
}
|
|
33
27
|
}
|
|
34
28
|
let tsModule;
|
|
@@ -39,20 +33,23 @@ function addExportsToBarrel(host, options) {
|
|
|
39
33
|
const workspace = (0, devkit_1.getProjects)(host);
|
|
40
34
|
const isApp = workspace.get(options.projectName).projectType === 'application';
|
|
41
35
|
if (options.export && !isApp) {
|
|
42
|
-
const indexFilePath = (0, devkit_1.joinPathFragments)(options.projectSourceRoot, options.js ? 'index.js' : 'index.ts');
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const indexSourceFile = tsModule.createSourceFile(indexFilePath, indexSource, tsModule.ScriptTarget.Latest, true);
|
|
46
|
-
const changes = (0, devkit_1.applyChangesToString)(indexSource, (0, ast_utils_1.addImport)(indexSourceFile, `export * from './${options.directory}/${options.fileName}';`));
|
|
47
|
-
host.write(indexFilePath, changes);
|
|
36
|
+
const indexFilePath = (0, devkit_1.joinPathFragments)(options.projectSourceRoot, options.fileExtensionType === 'js' ? 'index.js' : 'index.ts');
|
|
37
|
+
if (!host.exists(indexFilePath)) {
|
|
38
|
+
return;
|
|
48
39
|
}
|
|
40
|
+
const indexSource = host.read(indexFilePath, 'utf-8');
|
|
41
|
+
const indexSourceFile = tsModule.createSourceFile(indexFilePath, indexSource, tsModule.ScriptTarget.Latest, true);
|
|
42
|
+
const changes = (0, devkit_1.applyChangesToString)(indexSource, (0, ast_utils_1.addImport)(indexSourceFile, `export * from './${options.directory}/${options.fileName}';`));
|
|
43
|
+
host.write(indexFilePath, changes);
|
|
49
44
|
}
|
|
50
45
|
}
|
|
51
46
|
async function normalizeOptions(host, options) {
|
|
52
|
-
const { artifactName, directory, fileName: hookFilename, project: projectName, } = await (0, artifact_name_and_directory_utils_1.determineArtifactNameAndDirectoryOptions)(host, {
|
|
47
|
+
const { artifactName, directory, fileName: hookFilename, fileExtension, fileExtensionType, project: projectName, } = await (0, artifact_name_and_directory_utils_1.determineArtifactNameAndDirectoryOptions)(host, {
|
|
53
48
|
path: options.path,
|
|
54
49
|
name: options.name,
|
|
55
|
-
|
|
50
|
+
allowedFileExtensions: ['js', 'ts'],
|
|
51
|
+
fileExtension: options.js ? 'js' : 'ts',
|
|
52
|
+
js: options.js,
|
|
56
53
|
});
|
|
57
54
|
const { className } = (0, devkit_1.names)(hookFilename);
|
|
58
55
|
// if name is provided, use it as is for the hook name, otherwise prepend
|
|
@@ -74,6 +71,8 @@ async function normalizeOptions(host, options) {
|
|
|
74
71
|
hookName,
|
|
75
72
|
hookTypeName,
|
|
76
73
|
fileName: hookFilename,
|
|
74
|
+
fileExtension,
|
|
75
|
+
fileExtensionType,
|
|
77
76
|
projectSourceRoot,
|
|
78
77
|
projectName,
|
|
79
78
|
};
|
|
@@ -8,17 +8,21 @@
|
|
|
8
8
|
"examples": [
|
|
9
9
|
{
|
|
10
10
|
"description": "Generate a hook with the exported symbol matching the file name. It results in the hook `useFoo` at `mylib/src/lib/foo.ts`",
|
|
11
|
-
"command": "nx g @nx/react:hook mylib/src/lib/foo"
|
|
11
|
+
"command": "nx g @nx/react:hook mylib/src/lib/foo.ts"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"description": "Generate a hook with the exported symbol different from the file name. It results in the hook `useCustom` at `mylib/src/lib/foo.ts`",
|
|
15
|
-
"command": "nx g @nx/react:hook mylib/src/lib/foo --name=useCustom"
|
|
15
|
+
"command": "nx g @nx/react:hook mylib/src/lib/foo.ts --name=useCustom"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"description": "Generate a hook without providing the file extension. It results in the hook `useFoo` at `mylib/src/lib/foo.ts`",
|
|
19
|
+
"command": "nx g @nx/react:hook mylib/src/lib/foo"
|
|
16
20
|
}
|
|
17
21
|
],
|
|
18
22
|
"properties": {
|
|
19
23
|
"path": {
|
|
20
24
|
"type": "string",
|
|
21
|
-
"description": "The file path to the hook
|
|
25
|
+
"description": "The file path to the hook. Relative to the current working directory.",
|
|
22
26
|
"$default": {
|
|
23
27
|
"$source": "argv",
|
|
24
28
|
"index": 0
|
|
@@ -33,7 +37,7 @@
|
|
|
33
37
|
"js": {
|
|
34
38
|
"type": "boolean",
|
|
35
39
|
"description": "Generate JavaScript files rather than TypeScript files.",
|
|
36
|
-
"
|
|
40
|
+
"x-deprecated": "Provide the full file path including the file extension in the `path` option. This option will be removed in Nx v21."
|
|
37
41
|
},
|
|
38
42
|
"skipTests": {
|
|
39
43
|
"type": "boolean",
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/schema",
|
|
3
3
|
"$id": "GeneratorReactHost",
|
|
4
4
|
"cli": "nx",
|
|
5
|
-
"title": "Generate Module Federation Setup for React Host App",
|
|
6
|
-
"description": "Create Module Federation configuration files for given React Host Application.",
|
|
5
|
+
"title": "Generate Module Federation Setup for React Consumer (Host) App",
|
|
6
|
+
"description": "Create Module Federation configuration files for given React Consumer (Host) Application.",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
9
9
|
"directory": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"name": {
|
|
20
20
|
"type": "string",
|
|
21
|
-
"description": "The name of the host application to generate the Module Federation configuration",
|
|
21
|
+
"description": "The name of the Consumer (host) application to generate the Module Federation configuration",
|
|
22
22
|
"pattern": "^[a-zA-Z][^:]*$",
|
|
23
23
|
"x-priority": "important"
|
|
24
24
|
},
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
},
|
|
75
75
|
"dynamic": {
|
|
76
76
|
"type": "boolean",
|
|
77
|
-
"description": "Should the host application use dynamic federation?",
|
|
77
|
+
"description": "Should the Consumer (host) application use dynamic federation?",
|
|
78
78
|
"default": false
|
|
79
79
|
},
|
|
80
80
|
"skipFormat": {
|
|
@@ -141,18 +141,19 @@
|
|
|
141
141
|
},
|
|
142
142
|
"remotes": {
|
|
143
143
|
"type": "array",
|
|
144
|
-
"description": "A list of remote application names that the host application should consume.",
|
|
144
|
+
"description": "A list of Producer (remote) application names that the Consumer (host) application should consume.",
|
|
145
145
|
"default": [],
|
|
146
|
-
"x-priority": "important"
|
|
146
|
+
"x-priority": "important",
|
|
147
|
+
"alias": "producers"
|
|
147
148
|
},
|
|
148
149
|
"devServerPort": {
|
|
149
150
|
"type": "number",
|
|
150
|
-
"description": "The port for the dev server of the remote app.",
|
|
151
|
+
"description": "The port for the dev server of the Producer (remote) app.",
|
|
151
152
|
"default": 4200,
|
|
152
153
|
"x-priority": "important"
|
|
153
154
|
},
|
|
154
155
|
"ssr": {
|
|
155
|
-
"description": "Whether to configure SSR for the host application",
|
|
156
|
+
"description": "Whether to configure SSR for the Consumer (host) application",
|
|
156
157
|
"type": "boolean",
|
|
157
158
|
"default": false
|
|
158
159
|
},
|
|
@@ -32,33 +32,36 @@ async function addRollupBuildTarget(host, options) {
|
|
|
32
32
|
: p.plugin === '@nx/rollup/plugin');
|
|
33
33
|
if (hasRollupPlugin) {
|
|
34
34
|
// New behavior, using rollup config file and inferred target.
|
|
35
|
-
host.write((0, devkit_1.joinPathFragments)(options.projectRoot, 'rollup.config.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
})
|
|
61
|
-
|
|
35
|
+
host.write((0, devkit_1.joinPathFragments)(options.projectRoot, 'rollup.config.cjs'), `const { withNx } = require('@nx/rollup/with-nx');
|
|
36
|
+
const url = require('@rollup/plugin-url');
|
|
37
|
+
const svg = require('@svgr/rollup');
|
|
38
|
+
|
|
39
|
+
module.exports = withNx(
|
|
40
|
+
{
|
|
41
|
+
main: '${(0, maybe_js_1.maybeJs)(options, './src/index.ts')}',
|
|
42
|
+
outputPath: '${options.isUsingTsSolutionConfig
|
|
43
|
+
? './dist'
|
|
44
|
+
: (0, devkit_1.joinPathFragments)((0, devkit_1.offsetFromRoot)(options.projectRoot), 'dist', options.projectRoot)}',
|
|
45
|
+
tsConfig: './tsconfig.lib.json',
|
|
46
|
+
compiler: '${options.compiler ?? 'babel'}',
|
|
47
|
+
external: ${JSON.stringify(external)},
|
|
48
|
+
format: ['esm'],
|
|
49
|
+
assets:[{ input: '.', output: '.', glob: 'README.md'}],
|
|
50
|
+
}, {
|
|
51
|
+
// Provide additional rollup configuration here. See: https://rollupjs.org/configuration-options
|
|
52
|
+
plugins: [
|
|
53
|
+
svg({
|
|
54
|
+
svgo: false,
|
|
55
|
+
titleProp: true,
|
|
56
|
+
ref: true,
|
|
57
|
+
}),
|
|
58
|
+
url({
|
|
59
|
+
limit: 10000, // 10kB
|
|
60
|
+
}),
|
|
61
|
+
],
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
`);
|
|
62
65
|
}
|
|
63
66
|
else {
|
|
64
67
|
// Legacy behavior, there is a target in project.json using rollup executor.
|
|
@@ -55,6 +55,7 @@ async function libraryGeneratorInternal(host, schema) {
|
|
|
55
55
|
: undefined;
|
|
56
56
|
(0, devkit_1.writeJson)(host, `${options.projectRoot}/package.json`, {
|
|
57
57
|
name: options.importPath,
|
|
58
|
+
version: '0.0.1',
|
|
58
59
|
main: sourceEntry,
|
|
59
60
|
types: sourceEntry,
|
|
60
61
|
nx: {
|
|
@@ -63,6 +64,7 @@ async function libraryGeneratorInternal(host, schema) {
|
|
|
63
64
|
sourceRoot: `${options.projectRoot}/src`,
|
|
64
65
|
tags: options.parsedTags?.length ? options.parsedTags : undefined,
|
|
65
66
|
},
|
|
67
|
+
files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined,
|
|
66
68
|
});
|
|
67
69
|
}
|
|
68
70
|
else {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAsyncThunk,
|
|
3
|
+
createEntityAdapter,
|
|
4
|
+
createSelector,
|
|
5
|
+
createSlice,
|
|
6
|
+
} from '@reduxjs/toolkit';
|
|
7
|
+
|
|
8
|
+
export const <%= constantName %>_FEATURE_KEY = '<%= propertyName %>';
|
|
9
|
+
export const <%= propertyName %>Adapter = createEntityAdapter();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Export an effect using createAsyncThunk from
|
|
13
|
+
* the Redux Toolkit: https://redux-toolkit.js.org/api/createAsyncThunk
|
|
14
|
+
*
|
|
15
|
+
* e.g.
|
|
16
|
+
* ```
|
|
17
|
+
* import React, { useEffect } from 'react';
|
|
18
|
+
* import { useDispatch } from 'react-redux';
|
|
19
|
+
*
|
|
20
|
+
* // ...
|
|
21
|
+
*
|
|
22
|
+
* const dispatch = useDispatch();
|
|
23
|
+
* useEffect(() => {
|
|
24
|
+
* dispatch(fetch<%= className %>())
|
|
25
|
+
* }, [dispatch]);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const fetch<%= className %> = createAsyncThunk(
|
|
29
|
+
'<%= propertyName %>/fetchStatus',
|
|
30
|
+
async (_, thunkAPI) => {
|
|
31
|
+
/**
|
|
32
|
+
* Replace this with your custom fetch call.
|
|
33
|
+
* For example, `return myApi.get<%= className %>s()`;
|
|
34
|
+
* Right now we just return an empty array.
|
|
35
|
+
*/
|
|
36
|
+
return Promise.resolve([]);
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export const initial<%= className %>State = <%= propertyName %>Adapter.getInitialState({
|
|
41
|
+
loadingStatus: 'not loaded',
|
|
42
|
+
error: null
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const <%= propertyName %>Slice = createSlice({
|
|
46
|
+
name: <%= constantName %>_FEATURE_KEY,
|
|
47
|
+
initialState: initial<%= className %>State,
|
|
48
|
+
reducers: {
|
|
49
|
+
add: <%= propertyName %>Adapter.addOne,
|
|
50
|
+
remove: <%= propertyName %>Adapter.removeOne
|
|
51
|
+
// ...
|
|
52
|
+
},
|
|
53
|
+
extraReducers: builder => {
|
|
54
|
+
builder
|
|
55
|
+
.addCase(fetch<%= className %>.pending, (state) => {
|
|
56
|
+
state.loadingStatus = 'loading';
|
|
57
|
+
})
|
|
58
|
+
.addCase(fetch<%= className %>.fulfilled, (state, action) => {
|
|
59
|
+
<%= propertyName %>Adapter.setAll(state, action.payload);
|
|
60
|
+
state.loadingStatus = 'loaded';
|
|
61
|
+
})
|
|
62
|
+
.addCase(fetch<%= className %>.rejected, (state, action) => {
|
|
63
|
+
state.loadingStatus = 'error';
|
|
64
|
+
state.error = action.error.message;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* Export reducer for store configuration.
|
|
71
|
+
*/
|
|
72
|
+
export const <%= propertyName %>Reducer = <%= propertyName %>Slice.reducer;
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* Export action creators to be dispatched. For use with the `useDispatch` hook.
|
|
76
|
+
*
|
|
77
|
+
* e.g.
|
|
78
|
+
* ```
|
|
79
|
+
* import React, { useEffect } from 'react';
|
|
80
|
+
* import { useDispatch } from 'react-redux';
|
|
81
|
+
*
|
|
82
|
+
* // ...
|
|
83
|
+
*
|
|
84
|
+
* const dispatch = useDispatch();
|
|
85
|
+
* useEffect(() => {
|
|
86
|
+
* dispatch(<%= propertyName %>Actions.add({ id: 1 }))
|
|
87
|
+
* }, [dispatch]);
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* See: https://react-redux.js.org/next/api/hooks#usedispatch
|
|
91
|
+
*/
|
|
92
|
+
export const <%= propertyName %>Actions = <%= propertyName %>Slice.actions;
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
* Export selectors to query state. For use with the `useSelector` hook.
|
|
96
|
+
*
|
|
97
|
+
* e.g.
|
|
98
|
+
* ```
|
|
99
|
+
* import { useSelector } from 'react-redux';
|
|
100
|
+
*
|
|
101
|
+
* // ...
|
|
102
|
+
*
|
|
103
|
+
* const entities = useSelector(selectAll<%= className %>);
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* See: https://react-redux.js.org/next/api/hooks#useselector
|
|
107
|
+
*/
|
|
108
|
+
const { selectAll, selectEntities } = <%= propertyName %>Adapter.getSelectors();
|
|
109
|
+
|
|
110
|
+
export const get<%= className %>State = (rootState) => rootState[<%= constantName %>_FEATURE_KEY];
|
|
111
|
+
|
|
112
|
+
export const selectAll<%= className %> = createSelector(get<%= className %>State, selectAll);
|
|
113
|
+
|
|
114
|
+
export const select<%= className %>Entities = createSelector(get<%= className %>State, selectEntities);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetch<%= className %>, <%= propertyName %>Adapter, <%= propertyName %>Reducer } from './<%= fileName
|
|
1
|
+
import { fetch<%= className %>, <%= propertyName %>Adapter, <%= propertyName %>Reducer } from './<%= fileName %>';
|
|
2
2
|
|
|
3
3
|
describe('<%= propertyName %> reducer', () => {
|
|
4
4
|
it('should handle initial state', () => {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { fetch<%= className %>, <%= propertyName %>Adapter, <%= propertyName %>Reducer } from './<%= fileName %>';
|
|
2
|
+
|
|
3
|
+
describe('<%= propertyName %> reducer', () => {
|
|
4
|
+
it('should handle initial state', () => {
|
|
5
|
+
const expected = <%= propertyName %>Adapter.getInitialState({
|
|
6
|
+
loadingStatus: 'not loaded',
|
|
7
|
+
error: null
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
expect(<%= propertyName %>Reducer(undefined, { type: '' })).toEqual(expected);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should handle fetch<%= className %>', () => {
|
|
14
|
+
let state = <%= propertyName %>Reducer(
|
|
15
|
+
undefined,
|
|
16
|
+
fetch<%= className %>.pending('')
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
expect(state).toEqual(
|
|
20
|
+
expect.objectContaining({
|
|
21
|
+
loadingStatus: 'loading',
|
|
22
|
+
error: null,
|
|
23
|
+
entities: {},
|
|
24
|
+
ids: []
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
state = <%= propertyName %>Reducer(
|
|
29
|
+
state,
|
|
30
|
+
fetch<%= className %>.fulfilled([{ id: 1 }], '')
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(state).toEqual(
|
|
34
|
+
expect.objectContaining({
|
|
35
|
+
loadingStatus: 'loaded',
|
|
36
|
+
error: null,
|
|
37
|
+
entities: { 1: { id: 1 } },
|
|
38
|
+
ids: [1]
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
state = <%= propertyName %>Reducer(
|
|
43
|
+
state,
|
|
44
|
+
fetch<%= className %>.rejected(new Error('Uh oh'), '')
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(state).toEqual(
|
|
48
|
+
expect.objectContaining({
|
|
49
|
+
loadingStatus: 'error',
|
|
50
|
+
error: 'Uh oh',
|
|
51
|
+
entities: { 1: { id: 1 } },
|
|
52
|
+
ids: [1]
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -20,13 +20,10 @@ async function reduxGenerator(host, schema) {
|
|
|
20
20
|
return installTask;
|
|
21
21
|
}
|
|
22
22
|
function generateReduxFiles(host, options) {
|
|
23
|
-
(0, devkit_1.generateFiles)(host, (0, devkit_1.joinPathFragments)(__dirname, '
|
|
23
|
+
(0, devkit_1.generateFiles)(host, (0, devkit_1.joinPathFragments)(__dirname, 'files', options.fileExtensionType), options.projectDirectory, {
|
|
24
24
|
...options,
|
|
25
|
-
|
|
25
|
+
ext: options.fileExtension,
|
|
26
26
|
});
|
|
27
|
-
if (options.js) {
|
|
28
|
-
(0, devkit_1.toJS)(host);
|
|
29
|
-
}
|
|
30
27
|
}
|
|
31
28
|
function addReduxPackageDependencies(host) {
|
|
32
29
|
return (0, devkit_1.addDependenciesToPackageJson)(host, {
|
|
@@ -38,16 +35,17 @@ function addExportsToBarrel(host, options) {
|
|
|
38
35
|
if (!tsModule) {
|
|
39
36
|
tsModule = (0, ensure_typescript_1.ensureTypescript)();
|
|
40
37
|
}
|
|
41
|
-
const indexFilePath =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const indexSourceFile = tsModule.createSourceFile(indexFilePath, indexSource, tsModule.ScriptTarget.Latest, true);
|
|
45
|
-
const statePath = options.path
|
|
46
|
-
? `./lib/${options.path}/${options.fileName}`
|
|
47
|
-
: `./lib/${options.fileName}`;
|
|
48
|
-
const changes = (0, devkit_1.applyChangesToString)(indexSource, (0, ast_utils_1.addImport)(indexSourceFile, `export * from '${statePath}.slice';`));
|
|
49
|
-
host.write(indexFilePath, changes);
|
|
38
|
+
const indexFilePath = (0, devkit_1.joinPathFragments)(options.projectSourcePath, options.fileExtensionType === 'js' ? 'index.js' : 'index.ts');
|
|
39
|
+
if (!host.exists(indexFilePath)) {
|
|
40
|
+
return;
|
|
50
41
|
}
|
|
42
|
+
const indexSource = host.read(indexFilePath, 'utf-8');
|
|
43
|
+
const indexSourceFile = tsModule.createSourceFile(indexFilePath, indexSource, tsModule.ScriptTarget.Latest, true);
|
|
44
|
+
const statePath = options.path
|
|
45
|
+
? `./lib/${options.path}/${options.fileName}`
|
|
46
|
+
: `./lib/${options.fileName}`;
|
|
47
|
+
const changes = (0, devkit_1.applyChangesToString)(indexSource, (0, ast_utils_1.addImport)(indexSourceFile, `export * from '${statePath}.slice';`));
|
|
48
|
+
host.write(indexFilePath, changes);
|
|
51
49
|
}
|
|
52
50
|
function addStoreConfiguration(host, options) {
|
|
53
51
|
if (!options.appProjectSourcePath) {
|
|
@@ -77,10 +75,13 @@ function updateReducerConfiguration(host, options) {
|
|
|
77
75
|
host.write(options.appMainFilePath, changes);
|
|
78
76
|
}
|
|
79
77
|
async function normalizeOptions(host, options) {
|
|
80
|
-
const { artifactName: name, directory, fileName, project: projectName, } = await (0, artifact_name_and_directory_utils_1.determineArtifactNameAndDirectoryOptions)(host, {
|
|
78
|
+
const { artifactName: name, directory, fileName, fileExtension, fileExtensionType, project: projectName, } = await (0, artifact_name_and_directory_utils_1.determineArtifactNameAndDirectoryOptions)(host, {
|
|
81
79
|
path: options.path,
|
|
82
80
|
name: options.name,
|
|
83
|
-
|
|
81
|
+
suffix: 'slice',
|
|
82
|
+
allowedFileExtensions: ['js', 'ts'],
|
|
83
|
+
fileExtension: options.js ? 'js' : 'ts',
|
|
84
|
+
js: options.js,
|
|
84
85
|
});
|
|
85
86
|
let appProjectSourcePath;
|
|
86
87
|
let appMainFilePath;
|
|
@@ -117,6 +118,8 @@ async function normalizeOptions(host, options) {
|
|
|
117
118
|
...options,
|
|
118
119
|
...extraNames,
|
|
119
120
|
fileName,
|
|
121
|
+
fileExtension,
|
|
122
|
+
fileExtensionType,
|
|
120
123
|
constantName: (0, devkit_1.names)(name).constantName.toUpperCase(),
|
|
121
124
|
projectDirectory: directory,
|
|
122
125
|
projectType,
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import type { FileExtensionType } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
|
|
2
|
+
|
|
1
3
|
export interface Schema {
|
|
2
4
|
path: string;
|
|
3
5
|
name?: string;
|
|
4
6
|
appProject?: string;
|
|
5
|
-
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Provide the full file path including the file extension in the `path` option. This option will be removed in Nx v21.
|
|
10
|
+
*/
|
|
11
|
+
js?: boolean;
|
|
6
12
|
}
|
|
7
13
|
|
|
8
|
-
interface NormalizedSchema extends Schema {
|
|
14
|
+
interface NormalizedSchema extends Omit<Schema, 'js'> {
|
|
9
15
|
projectType: string;
|
|
10
16
|
projectDirectory: string;
|
|
11
17
|
projectSourcePath: string;
|
|
@@ -16,4 +22,6 @@ interface NormalizedSchema extends Schema {
|
|
|
16
22
|
constantName: string;
|
|
17
23
|
propertyName: string;
|
|
18
24
|
fileName: string;
|
|
25
|
+
fileExtension: string;
|
|
26
|
+
fileExtensionType: FileExtensionType;
|
|
19
27
|
}
|
|
@@ -8,17 +8,21 @@
|
|
|
8
8
|
"examples": [
|
|
9
9
|
{
|
|
10
10
|
"description": "Generate a Redux state slice with the exported symbol matching the file name. It results in the slice `fooSlice` at `mylib/src/lib/foo.slice.ts`",
|
|
11
|
-
"command": "nx g @nx/react:redux mylib/src/lib/foo"
|
|
11
|
+
"command": "nx g @nx/react:redux mylib/src/lib/foo.slice.ts"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"description": "Generate a Redux state slice with the exported symbol different from the file name. It results in the slice `customSlice` at `mylib/src/lib/foo.slice.ts`",
|
|
15
|
-
"command": "nx g @nx/react:redux mylib/src/lib/foo --name=custom"
|
|
15
|
+
"command": "nx g @nx/react:redux mylib/src/lib/foo.slice.ts --name=custom"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"description": "Generate a Redux state slice without providing the \"slice\" suffix and the file extension. It results in the slice `fooSlice` at `mylib/src/lib/foo.slice.ts`",
|
|
19
|
+
"command": "nx g @nx/react:redux mylib/src/lib/foo"
|
|
16
20
|
}
|
|
17
21
|
],
|
|
18
22
|
"properties": {
|
|
19
23
|
"path": {
|
|
20
24
|
"type": "string",
|
|
21
|
-
"description": "The file path to the Redux state slice
|
|
25
|
+
"description": "The file path to the Redux state slice. Relative to the current working directory.",
|
|
22
26
|
"$default": {
|
|
23
27
|
"$source": "argv",
|
|
24
28
|
"index": 0
|
|
@@ -38,7 +42,7 @@
|
|
|
38
42
|
"js": {
|
|
39
43
|
"type": "boolean",
|
|
40
44
|
"description": "Generate JavaScript files rather than TypeScript files.",
|
|
41
|
-
"
|
|
45
|
+
"x-deprecated": "Provide the full file path including the file extension in the `path` option. This option will be removed in Nx v21."
|
|
42
46
|
}
|
|
43
47
|
},
|
|
44
48
|
"required": ["path"]
|
|
@@ -24,7 +24,7 @@ function updateHostWithRemote(host, hostName, remoteName) {
|
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
26
26
|
// TODO(jack): Point to the nx.dev guide when ready.
|
|
27
|
-
devkit_1.logger.warn(`Could not find configuration at ${moduleFederationConfigPath}. Did you generate this project with "@nx/react:host"?`);
|
|
27
|
+
devkit_1.logger.warn(`Could not find configuration at ${moduleFederationConfigPath}. Did you generate this project with "@nx/react:host" or "@nx/react:consumer"?`);
|
|
28
28
|
}
|
|
29
29
|
if (host.exists(appComponentPath)) {
|
|
30
30
|
let sourceCode = host.read(appComponentPath).toString();
|
|
@@ -32,7 +32,7 @@ function updateHostWithRemote(host, hostName, remoteName) {
|
|
|
32
32
|
host.write(appComponentPath, (0, devkit_1.applyChangesToString)(sourceCode, (0, ast_utils_1.addRemoteRoute)(source, (0, devkit_1.names)(remoteName))));
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
|
-
devkit_1.logger.warn(`Could not find app component at ${appComponentPath}. Did you generate this project with "@nx/react:host"?`);
|
|
35
|
+
devkit_1.logger.warn(`Could not find app component at ${appComponentPath}. Did you generate this project with "@nx/react:host" or "@nx/react:consumer"?`);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
function findAppComponentPath(host, sourceRoot) {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/schema",
|
|
3
3
|
"$id": "GeneratorReactRemote",
|
|
4
4
|
"cli": "nx",
|
|
5
|
-
"title": "Generate Module Federation Setup for React Remote App",
|
|
6
|
-
"description": "Create Module Federation configuration files for given React Remote Application.",
|
|
5
|
+
"title": "Generate Module Federation Setup for React Producer (Remote) App",
|
|
6
|
+
"description": "Create Module Federation configuration files for given React Producer (Remote) Application.",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
9
9
|
"directory": {
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
},
|
|
19
19
|
"name": {
|
|
20
20
|
"type": "string",
|
|
21
|
-
"description": "The name of the remote application to generate the Module Federation configuration",
|
|
21
|
+
"description": "The name of the Producer (remote) application to generate the Module Federation configuration",
|
|
22
22
|
"pattern": "^[a-zA-Z_$][a-zA-Z_$0-9]*$",
|
|
23
23
|
"x-priority": "important"
|
|
24
24
|
},
|
|
25
25
|
"dynamic": {
|
|
26
26
|
"type": "boolean",
|
|
27
|
-
"description": "Should the host application use dynamic federation?",
|
|
27
|
+
"description": "Should the Consumer (host) application use dynamic federation?",
|
|
28
28
|
"default": false,
|
|
29
29
|
"x-priority": "internal"
|
|
30
30
|
},
|
|
@@ -147,16 +147,17 @@
|
|
|
147
147
|
},
|
|
148
148
|
"host": {
|
|
149
149
|
"type": "string",
|
|
150
|
-
"description": "The host
|
|
151
|
-
"x-priority": "important"
|
|
150
|
+
"description": "The Consumer (host) application for this Producer (remote).",
|
|
151
|
+
"x-priority": "important",
|
|
152
|
+
"alias": "consumer"
|
|
152
153
|
},
|
|
153
154
|
"devServerPort": {
|
|
154
155
|
"type": "number",
|
|
155
|
-
"description": "The port for the dev server of the remote app.",
|
|
156
|
+
"description": "The port for the dev server of the Producer (remote) app.",
|
|
156
157
|
"x-priority": "important"
|
|
157
158
|
},
|
|
158
159
|
"ssr": {
|
|
159
|
-
"description": "Whether to configure SSR for the host application",
|
|
160
|
+
"description": "Whether to configure SSR for the Consumer (host) application",
|
|
160
161
|
"type": "boolean",
|
|
161
162
|
"default": false
|
|
162
163
|
},
|
|
@@ -29,7 +29,10 @@ async function projectRootPath(tree, config) {
|
|
|
29
29
|
// libs/test-lib/src/lib
|
|
30
30
|
projectDir = 'lib';
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
else {
|
|
33
|
+
projectDir = '.';
|
|
34
|
+
}
|
|
35
|
+
return (0, devkit_1.joinPathFragments)(config.sourceRoot ?? config.root, projectDir);
|
|
33
36
|
}
|
|
34
37
|
function containsComponentDeclaration(tree, componentPath) {
|
|
35
38
|
if (!tsModule) {
|
|
@@ -70,7 +73,7 @@ async function createAllStories(tree, projectName, interactionTests, js, project
|
|
|
70
73
|
}
|
|
71
74
|
});
|
|
72
75
|
await Promise.all(componentPaths.map(async (componentPath) => {
|
|
73
|
-
const relativeCmpDir = componentPath.replace((0, path_1.join)(sourceRoot, '/'), '');
|
|
76
|
+
const relativeCmpDir = componentPath.replace((0, path_1.join)(sourceRoot ?? root, '/'), '');
|
|
74
77
|
if (!containsComponentDeclaration(tree, componentPath)) {
|
|
75
78
|
return;
|
|
76
79
|
}
|
|
@@ -32,8 +32,8 @@ async function storybookConfigurationGeneratorInternal(host, schema) {
|
|
|
32
32
|
let uiFramework = '@storybook/react-vite';
|
|
33
33
|
const projectConfig = (0, devkit_1.readProjectConfiguration)(host, schema.project);
|
|
34
34
|
if (findWebpackConfig(host, projectConfig.root) ||
|
|
35
|
-
projectConfig.targets['build']?.executor === '@nx/rollup:rollup' ||
|
|
36
|
-
projectConfig.targets['build']?.executor === '@nx/expo:build') {
|
|
35
|
+
projectConfig.targets?.['build']?.executor === '@nx/rollup:rollup' ||
|
|
36
|
+
projectConfig.targets?.['build']?.executor === '@nx/expo:build') {
|
|
37
37
|
uiFramework = '@storybook/react-webpack5';
|
|
38
38
|
}
|
|
39
39
|
if (uiFramework === '@storybook/react-vite') {
|
/package/src/generators/component/files/{__fileName__.spec.tsx__tmpl__ → __fileName__.spec.__ext__}
RENAMED
|
File without changes
|
/package/src/generators/hook/files/{__fileName__.spec.tsx__tmpl__ → __fileName__.spec.__specExt__}
RENAMED
|
File without changes
|
/package/src/generators/redux/files/{__fileName__.slice.ts__tmpl__ → ts/__fileName__.__ext__}
RENAMED
|
File without changes
|