@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/react",
3
- "version": "20.2.0",
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.0",
42
- "@nx/js": "20.2.0",
43
- "@nx/eslint": "20.2.0",
44
- "@nx/web": "20.2.0",
45
- "@nx/module-federation": "20.2.0",
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
- tmpl: '',
35
+ isTs: options.fileExtensionType === 'ts',
36
+ ext: options.fileExtension,
36
37
  });
37
- for (const c of host.listChanges()) {
38
- let deleteFile = false;
39
- if ((options.skipTests || options.inSourceTests) &&
40
- /.*spec.tsx/.test(c.path)) {
41
- deleteFile = true;
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.js) {
59
- (0, devkit_1.toJS)(host);
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
- ? (0, devkit_1.joinPathFragments)(options.projectSourceRoot, options.js ? 'index.js' : 'index.ts')
72
- : (0, devkit_1.joinPathFragments)(options.projectRoot, 'src', options.js ? 'index.js' : 'index.ts');
73
- const indexSource = host.read(indexFilePath, 'utf-8');
74
- if (indexSource !== null) {
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
- fileExtension: 'tsx',
13
- fileName: options.fileName,
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: 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 without the file extension. Relative to the current working directory.",
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
- "default": false
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 %>(): <%= hookTypeName %> {
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 ast_utils_1 = require("../../utils/ast-utils");
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 artifact_name_and_directory_utils_1 = require("@nx/devkit/src/generators/artifact-name-and-directory-utils");
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
- tmpl: '',
20
+ ext: options.fileExtension,
21
+ specExt,
22
+ isTs: options.fileExtensionType === 'ts',
20
23
  });
21
- for (const c of host.listChanges()) {
22
- let deleteFile = false;
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
- const indexSource = host.read(indexFilePath, 'utf-8');
44
- if (indexSource !== null) {
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
- fileExtension: 'tsx',
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
  };
@@ -3,5 +3,9 @@ export interface Schema {
3
3
  name?: string;
4
4
  skipTests?: boolean;
5
5
  export?: boolean;
6
+
7
+ /**
8
+ * @deprecated Provide the full file path including the file extension in the `path` option. This option will be removed in Nx v21.
9
+ */
6
10
  js?: boolean;
7
11
  }
@@ -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 without the file extension. Relative to the current working directory.",
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
- "default": false
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 %>.slice';
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, './files'), options.projectDirectory, {
23
+ (0, devkit_1.generateFiles)(host, (0, devkit_1.joinPathFragments)(__dirname, 'files', options.fileExtensionType), options.projectDirectory, {
24
24
  ...options,
25
- tmpl: '',
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 = path.join(options.projectSourcePath, options.js ? 'index.js' : 'index.ts');
42
- const indexSource = host.read(indexFilePath, 'utf-8');
43
- if (indexSource !== null) {
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
- fileExtension: 'tsx',
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
- js?: string;
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 without the file extension. Relative to the current working directory.",
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
- "default": false
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"]