@nx/react 20.2.0 → 20.2.2
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/package.json +6 -6
- 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/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/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/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/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@nx/react",
|
3
|
-
"version": "20.2.
|
3
|
+
"version": "20.2.2",
|
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": "20.2.
|
42
|
-
"@nx/js": "20.2.
|
43
|
-
"@nx/eslint": "20.2.
|
44
|
-
"@nx/web": "20.2.
|
45
|
-
"@nx/module-federation": "20.2.
|
41
|
+
"@nx/devkit": "20.2.2",
|
42
|
+
"@nx/js": "20.2.2",
|
43
|
+
"@nx/eslint": "20.2.2",
|
44
|
+
"@nx/web": "20.2.2",
|
45
|
+
"@nx/module-federation": "20.2.2",
|
46
46
|
"express": "^4.19.2",
|
47
47
|
"http-proxy-middleware": "^3.0.3"
|
48
48
|
},
|
@@ -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,
|
@@ -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",
|
@@ -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"]
|
/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
|