Skip to content

Commit cf747fc

Browse files
refactor(devtools): use features for better treeshaking
1 parent 1f51567 commit cf747fc

File tree

9 files changed

+71
-105
lines changed

9 files changed

+71
-105
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
node-version: '18'
2020
cache: "pnpm"
2121
- run: pnpm install --frozen-lockfile
22+
- run: pnpm run test:all
2223
- run: pnpm run build:all
2324
- run: ./integration-tests.sh
2425

apps/demo/src/app/test.ts

Lines changed: 0 additions & 54 deletions
This file was deleted.

libs/ngrx-toolkit/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { withDevToolsStub } from './lib/devtools/with-dev-tools-stub';
22
export { withDevtools } from './lib/devtools/with-devtools';
3+
export { withDisabledNameIndices } from './lib/devtools/with-disabled-name-indicies';
34
export { patchState, updateState } from './lib/devtools/update-state';
45
export { renameDevtoolsName } from './lib/devtools/rename-devtools-name';
56

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export const DEVTOOLS_FEATURE = Symbol('DEVTOOLS_FEATURE');
2+
3+
/**
4+
* A DevtoolsFeature adds or modifies the behavior of the
5+
* devtools extension.
6+
*
7+
* We use them (function calls) instead of a config object,
8+
* because of tree-shaking.
9+
*/
10+
export interface DevtoolsFeature {
11+
[DEVTOOLS_FEATURE]: true;
12+
indexNames: boolean | undefined; // defines if names should be indexed.
13+
}
14+
15+
export function createDevtoolsFeature(indexNames = true): DevtoolsFeature {
16+
return {
17+
[DEVTOOLS_FEATURE]: true,
18+
indexNames,
19+
};
20+
}

libs/ngrx-toolkit/src/lib/devtools/internal/devtools-syncer.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Enable automatic indexing via withDevTools('${storeName}', { indexNames: true })
117117
this.#stores.update((stores) => {
118118
if (newName in stores) {
119119
throw new Error(
120-
`NgRx Toolkit/DevTools: cannot rename from ${oldName} to ${newName}. ${oldName} does not exist.`
120+
`NgRx Toolkit/DevTools: cannot rename from ${oldName} to ${newName}. ${newName} is already assigned to another SignalStore instance.`
121121
);
122122
}
123123

libs/ngrx-toolkit/src/lib/devtools/tests/naming.spec.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { patchState, signalStore, withMethods, withState } from '@ngrx/signals';
1+
import { signalStore, withState } from '@ngrx/signals';
22
import { withDevtools } from '../with-devtools';
3-
import { TestBed, waitForAsync } from '@angular/core/testing';
3+
import { TestBed } from '@angular/core/testing';
44
import { setupExtensions } from './helpers';
55
import {
66
createEnvironmentInjector,
@@ -9,6 +9,7 @@ import {
99
runInInjectionContext,
1010
} from '@angular/core';
1111
import { renameDevtoolsName } from '../rename-devtools-name';
12+
import { withDisabledNameIndices } from '../with-disabled-name-indicies';
1213

1314
describe('withDevtools / renaming', () => {
1415
it('should automatically index multiple instances', () => {
@@ -84,7 +85,7 @@ describe('withDevtools / renaming', () => {
8485
setupExtensions();
8586
const Store = signalStore(
8687
{ providedIn: 'root' },
87-
withDevtools('flights', { indexNames: false }),
88+
withDevtools('flights', withDisabledNameIndices()),
8889
withState({ airline: 'Lufthansa' })
8990
);
9091

@@ -103,7 +104,7 @@ Enable automatic indexing via withDevTools('flights', { indexNames: true }), or
103104
});
104105

105106
it('should throw if name already exists', () => {
106-
const { sendSpy } = setupExtensions();
107+
setupExtensions();
107108
signalStore(withDevtools('flights'));
108109
expect(() => signalStore(withDevtools('flights'))).toThrow(
109110
'The store "flights" has already been registered in the DevTools. Duplicate registration is not allowed.'
@@ -151,7 +152,7 @@ Enable automatic indexing via withDevTools('flights', { indexNames: true }), or
151152

152153
it('should throw on rename if name already exists', () => {
153154
setupExtensions();
154-
signalStore(
155+
const Store1 = signalStore(
155156
{ providedIn: 'root' },
156157
withState({ name: 'Product', price: 10.5 }),
157158
withDevtools('shop')
@@ -162,11 +163,12 @@ Enable automatic indexing via withDevTools('flights', { indexNames: true }), or
162163
withState({ name: 'Product', price: 10.5 }),
163164
withDevtools('mall')
164165
);
166+
TestBed.inject(Store1);
165167
const store = TestBed.inject(Store2);
166168
TestBed.flushEffects();
167169

168170
expect(() => renameDevtoolsName(store, 'shop')).toThrow(
169-
'NgRx Toolkit/DevTools: cannot rename from mall to shop. mall has already been send to DevTools.'
171+
'NgRx Toolkit/DevTools: cannot rename from mall to shop. shop is already assigned to another SignalStore instance.'
170172
);
171173
});
172174

libs/ngrx-toolkit/src/lib/devtools/with-devtools.ts

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
1-
import {
2-
SignalStoreFeature,
3-
signalStoreFeature,
4-
SignalStoreFeatureResult,
5-
withHooks,
6-
withMethods,
7-
} from '@ngrx/signals';
1+
import { signalStoreFeature, withHooks, withMethods } from '@ngrx/signals';
82
import { inject } from '@angular/core';
93
import { DevtoolsSyncer } from './internal/devtools-syncer.service';
4+
import { DevtoolsFeature } from './devtools-feature';
105

116
export type Action = { type: string };
127
export type Connection = {
@@ -23,36 +18,6 @@ declare global {
2318
}
2419

2520
export type DevtoolsOptions = {
26-
/**
27-
* If multiple instances of the same SignalStore class
28-
* exist, their devtool names are indexed.
29-
*
30-
* If this feature is disabled, this feature throws
31-
* a runtime error.
32-
*
33-
* By default, the value is `true`.
34-
*
35-
* For example:
36-
* <pre>
37-
* const Store = signalStore(
38-
* withDevtools('flights', { indexNames: true })
39-
* )
40-
*
41-
* const store1 = new Store(); // will show up as 'flights'
42-
* const store2 = new Store(); // will show up as 'flights-1'
43-
* </pre>
44-
*
45-
* With value set to `false`:
46-
* <pre>
47-
* const Store = signalStore(
48-
* withDevtools('flights', { indexNames: false })
49-
* )
50-
*
51-
* const store1 = new Store(); // will show up as 'flights'
52-
* const store2 = new Store(); //💥 throws an error
53-
* </pre>
54-
*
55-
*/
5621
indexNames: boolean;
5722
};
5823

@@ -65,26 +30,26 @@ export const uniqueDevtoolsId = '___uniqueDevtoolsId';
6530
* Adds this store as a feature state to the Redux DevTools.
6631
*
6732
* By default, the action name is 'Store Update'. You can
68-
* change that via the `patch` method, which has as second
33+
* change that via the {@link updateState} method, which has as second
6934
* parameter the action name.
7035
*
7136
* The standalone function {@link renameDevtoolsName} can rename
7237
* the store name.
7338
*
7439
* @param name name of the store as it should appear in the DevTools
75-
* @param options options for the DevTools
40+
* @param features features to extend or modify the behavior of the Devtools
7641
*/
77-
export function withDevtools(
78-
name: string,
79-
options: Partial<DevtoolsOptions> = {}
80-
) {
42+
export function withDevtools(name: string, ...features: DevtoolsFeature[]) {
8143
if (existingNames.has(name)) {
8244
throw new Error(
8345
`The store "${name}" has already been registered in the DevTools. Duplicate registration is not allowed.`
8446
);
8547
}
8648
existingNames.set(name, true);
87-
const finalOptions: DevtoolsOptions = { ...{ indexNames: true }, ...options };
49+
const finalOptions = {
50+
indexNames: !features.some((f) => f.indexNames === false),
51+
};
52+
8853
return signalStoreFeature(
8954
withMethods((store) => {
9055
const syncer = inject(DevtoolsSyncer);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { createDevtoolsFeature } from './devtools-feature';
2+
3+
/**
4+
* If multiple instances of the same SignalStore class
5+
* exist, their devtool names are indexed.
6+
*
7+
* For example:
8+
* <pre>
9+
* const Store = signalStore(
10+
* withDevtools('flights')
11+
* )
12+
*
13+
* const store1 = new Store(); // will show up as 'flights'
14+
* const store2 = new Store(); // will show up as 'flights-1'
15+
* </pre>
16+
*
17+
* With adding `withDisabledNameIndices` to the store:
18+
* <pre>
19+
* const Store = signalStore(
20+
* withDevtools('flights', withDisabledNameIndices())
21+
* )
22+
*
23+
* const store1 = new Store(); // will show up as 'flights'
24+
* const store2 = new Store(); //💥 throws an error
25+
* </pre>
26+
*
27+
*/
28+
export function withDisabledNameIndices() {
29+
return createDevtoolsFeature(false);
30+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"scripts": {
66
"start": "nx serve demo",
77
"build:all": "nx run-many --targets=build",
8+
"test:all": "nx run-many --targets=test",
89
"verify:all": "nx run-many --targets=lint,test,build"
910
},
1011
"private": true,

0 commit comments

Comments
 (0)