Skip to content

Commit 070d60f

Browse files
committed
fix(@schematics/angular): generate modules with a dash type separator
To align with the updated style guide, Angular v20 will generate modules with file extension `module` type prefixed with a `-` separator instead of a `.` by default. Projects will automatically use this naming convention. Projects can however opt-out by setting the `typeSeparator` option to `.` for the module schematic. This can be done as a default in the `angular.json` or directly on the commandline via `--type-separator=.` when executing `ng generate`. As an example, `example.module.ts` will now be named `example-module.ts`. The TypeScript declaration will continue to contain `Module` such as with `ExampleModule`.
1 parent 25e858d commit 070d60f

27 files changed

+187
-147
lines changed

Diff for: packages/schematics/angular/app-shell/index_spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,12 @@ describe('App Shell Schematic', () => {
6969
});
7070

7171
it('should not fail when AppModule have imported RouterModule already', async () => {
72-
const updateRecorder = appTree.beginUpdate('/projects/bar/src/app/app.module.ts');
72+
const updateRecorder = appTree.beginUpdate('/projects/bar/src/app/app-module.ts');
7373
updateRecorder.insertLeft(0, "import { RouterModule } from '@angular/router';");
7474
appTree.commitUpdate(updateRecorder);
7575

7676
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
77-
const filePath = '/projects/bar/src/app/app.module.ts';
77+
const filePath = '/projects/bar/src/app/app-module.ts';
7878
const content = tree.readContent(filePath);
7979
expect(content).toMatch(/import { RouterModule } from '@angular\/router';/);
8080
});

Diff for: packages/schematics/angular/application/files/module-files/src/app/app.module.ts.template renamed to packages/schematics/angular/application/files/module-files/src/app/app-module.ts.template

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NgModule<% if(experimentalZoneless) { %>, provideExperimentalZonelessChangeDetection<% } %> } from '@angular/core';
22
import { BrowserModule } from '@angular/platform-browser';
33
<% if (routing) { %>
4-
import { AppRoutingModule } from './app-routing.module';<% } %>
4+
import { AppRoutingModule } from './app-routing-module';<% } %>
55
import { App } from './app';
66

77
@NgModule({

Diff for: packages/schematics/angular/application/files/module-files/src/main.ts.template

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<% if(!!viewEncapsulation) { %>import { ViewEncapsulation } from '@angular/core';
22
<% }%>import { platformBrowser } from '@angular/platform-browser';
3-
import { AppModule } from './app/app.module';
3+
import { AppModule } from './app/app-module';
44

55
platformBrowser().bootstrapModule(AppModule, {
66
<% if(!experimentalZoneless) { %>ngZoneEventCoalescing: true,<% } %><% if(!!viewEncapsulation) { %>

Diff for: packages/schematics/angular/application/index_spec.ts

+15-15
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('Application Schematic', () => {
5454
'/projects/foo/src/index.html',
5555
'/projects/foo/src/main.ts',
5656
'/projects/foo/src/styles.css',
57-
'/projects/foo/src/app/app.module.ts',
57+
'/projects/foo/src/app/app-module.ts',
5858
'/projects/foo/src/app/app.css',
5959
'/projects/foo/src/app/app.ng.html',
6060
'/projects/foo/src/app/app.spec.ts',
@@ -542,7 +542,7 @@ describe('Application Schematic', () => {
542542
const options = { ...defaultOptions, standalone: true };
543543

544544
const tree = await schematicRunner.runSchematic('application', options, workspaceTree);
545-
const moduleFiles = tree.files.filter((file) => file.endsWith('.module.ts'));
545+
const moduleFiles = tree.files.filter((file) => file.endsWith('-module.ts'));
546546
expect(moduleFiles.length).toEqual(0);
547547
});
548548

@@ -625,11 +625,11 @@ describe('Application Schematic', () => {
625625
const tree = await schematicRunner.runSchematic('application', options, workspaceTree);
626626

627627
const files = tree.files;
628-
expect(files).toContain('/projects/foo/src/app/app.module.ts');
629-
expect(files).toContain('/projects/foo/src/app/app-routing.module.ts');
630-
const moduleContent = tree.readContent('/projects/foo/src/app/app.module.ts');
631-
expect(moduleContent).toMatch(/import { AppRoutingModule } from '.\/app-routing.module'/);
632-
const routingModuleContent = tree.readContent('/projects/foo/src/app/app-routing.module.ts');
628+
expect(files).toContain('/projects/foo/src/app/app-module.ts');
629+
expect(files).toContain('/projects/foo/src/app/app-routing-module.ts');
630+
const moduleContent = tree.readContent('/projects/foo/src/app/app-module.ts');
631+
expect(moduleContent).toMatch(/import { AppRoutingModule } from '.\/app-routing-module'/);
632+
const routingModuleContent = tree.readContent('/projects/foo/src/app/app-routing-module.ts');
633633
expect(routingModuleContent).toMatch(/RouterModule.forRoot\(routes\)/);
634634
});
635635

@@ -640,7 +640,7 @@ describe('Application Schematic', () => {
640640
workspaceTree,
641641
);
642642

643-
const path = '/projects/foo/src/app/app.module.ts';
643+
const path = '/projects/foo/src/app/app-module.ts';
644644
const content = tree.readContent(path);
645645
expect(content).toMatch(/import { BrowserModule } from '@angular\/platform-browser';/);
646646
});
@@ -652,7 +652,7 @@ describe('Application Schematic', () => {
652652
workspaceTree,
653653
);
654654

655-
const path = '/projects/foo/src/app/app.module.ts';
655+
const path = '/projects/foo/src/app/app-module.ts';
656656
const content = tree.readContent(path);
657657
expect(content).toMatch(/import { App } from '\.\/app';/);
658658
});
@@ -669,8 +669,8 @@ describe('Application Schematic', () => {
669669
'/projects/foo/tsconfig.spec.json',
670670
'/projects/foo/src/main.ts',
671671
'/projects/foo/src/styles.css',
672-
'/projects/foo/src/app/app-routing.module.ts',
673-
'/projects/foo/src/app/app.module.ts',
672+
'/projects/foo/src/app/app-routing-module.ts',
673+
'/projects/foo/src/app/app-module.ts',
674674
'/projects/foo/src/app/app.css',
675675
'/projects/foo/src/app/app.ng.html',
676676
'/projects/foo/src/app/app.spec.ts',
@@ -696,7 +696,7 @@ describe('Application Schematic', () => {
696696
);
697697
});
698698

699-
it('should add provideExperimentalZonelessChangeDetection() in app.module.ts when experimentalZoneless is true', async () => {
699+
it('should add provideExperimentalZonelessChangeDetection() in app-module.ts when experimentalZoneless is true', async () => {
700700
const tree = await schematicRunner.runSchematic(
701701
'application',
702702
{
@@ -706,12 +706,12 @@ describe('Application Schematic', () => {
706706
},
707707
workspaceTree,
708708
);
709-
const path = '/projects/foo/src/app/app.module.ts';
709+
const path = '/projects/foo/src/app/app-module.ts';
710710
const fileContent = tree.readContent(path);
711711
expect(fileContent).toContain('provideExperimentalZonelessChangeDetection()');
712712
});
713713

714-
it('should not add provideExperimentalZonelessChangeDetection() in app.module.ts when experimentalZoneless is false', async () => {
714+
it('should not add provideExperimentalZonelessChangeDetection() in app-module.ts when experimentalZoneless is false', async () => {
715715
const tree = await schematicRunner.runSchematic(
716716
'application',
717717
{
@@ -721,7 +721,7 @@ describe('Application Schematic', () => {
721721
},
722722
workspaceTree,
723723
);
724-
const path = '/projects/foo/src/app/app.module.ts';
724+
const path = '/projects/foo/src/app/app-module.ts';
725725
const fileContent = tree.readContent(path);
726726
expect(fileContent).not.toContain('provideExperimentalZonelessChangeDetection()');
727727
});

Diff for: packages/schematics/angular/component/index.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,15 @@ export default function (options: ComponentOptions): Rule {
5353
options.path = buildDefaultPath(project);
5454
}
5555

56-
options.module = findModuleFromOptions(host, options);
56+
try {
57+
options.module = findModuleFromOptions(host, options);
58+
} catch {
59+
options.module = findModuleFromOptions(host, {
60+
...options,
61+
moduleExt: '-module.ts',
62+
routingModuleExt: '-routing-module.ts',
63+
});
64+
}
5765

5866
// Schematic templates require a defined type value
5967
options.type ??= '';

Diff for: packages/schematics/angular/component/index_spec.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ describe('Component Schematic', () => {
374374
it('should create a standalone component', async () => {
375375
const options = { ...defaultOptions, standalone: true };
376376
const tree = await schematicRunner.runSchematic('component', options, appTree);
377-
const moduleContent = tree.readContent('/projects/bar/src/app/app.module.ts');
377+
const moduleContent = tree.readContent('/projects/bar/src/app/app-module.ts');
378378
const componentContent = tree.readContent('/projects/bar/src/app/foo/foo.component.ts');
379379
expect(componentContent).toContain('class FooComponent');
380380
expect(moduleContent).not.toContain('FooComponent');
@@ -416,13 +416,13 @@ describe('Component Schematic', () => {
416416
'/projects/baz/src/app/foo/foo.component.ts',
417417
]),
418418
);
419-
const moduleContent = tree.readContent('/projects/baz/src/app/app.module.ts');
419+
const moduleContent = tree.readContent('/projects/baz/src/app/app-module.ts');
420420
expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo\/foo.component'/);
421421
expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+FooComponent\r?\n/m);
422422
});
423423

424424
it('should use the module flag even if the module is a routing module', async () => {
425-
const routingFileName = 'app-routing.module.ts';
425+
const routingFileName = 'app-routing-module.ts';
426426
const routingModulePath = `/projects/baz/src/app/${routingFileName}`;
427427
const newTree = createAppModule(appTree, routingModulePath);
428428
const options = { ...defaultNonStandaloneOptions, module: routingFileName };
@@ -435,7 +435,7 @@ describe('Component Schematic', () => {
435435
const options = { ...defaultNonStandaloneOptions, name: 'dir/test-component' };
436436

437437
const tree = await schematicRunner.runSchematic('component', options, appTree);
438-
const content = tree.readContent('/projects/baz/src/app/app.module.ts');
438+
const content = tree.readContent('/projects/baz/src/app/app-module.ts');
439439
expect(content).toMatch(
440440
/import { TestComponentComponent } from '\.\/dir\/test-component\/test-component.component'/,
441441
);
@@ -455,15 +455,15 @@ describe('Component Schematic', () => {
455455
};
456456
appTree = await schematicRunner.runSchematic('component', options, appTree);
457457

458-
const content = appTree.readContent('/projects/baz/src/app/admin/module/module.module.ts');
458+
const content = appTree.readContent('/projects/baz/src/app/admin/module/module-module.ts');
459459
expect(content).toMatch(
460460
/import { TestComponentComponent } from '..\/..\/other\/test-component\/test-component.component'/,
461461
);
462462
});
463463

464464
it('should find the closest module', async () => {
465465
const options = { ...defaultNonStandaloneOptions };
466-
const fooModule = '/projects/baz/src/app/foo/foo.module.ts';
466+
const fooModule = '/projects/baz/src/app/foo/foo-module.ts';
467467
appTree.create(
468468
fooModule,
469469
`
@@ -486,15 +486,15 @@ describe('Component Schematic', () => {
486486
const options = { ...defaultNonStandaloneOptions, export: true };
487487

488488
const tree = await schematicRunner.runSchematic('component', options, appTree);
489-
const appModuleContent = tree.readContent('/projects/baz/src/app/app.module.ts');
489+
const appModuleContent = tree.readContent('/projects/baz/src/app/app-module.ts');
490490
expect(appModuleContent).toMatch(/exports: \[\n(\s*) {2}FooComponent\n\1\]/);
491491
});
492492

493493
it('should import into a specified module', async () => {
494-
const options = { ...defaultNonStandaloneOptions, module: 'app.module.ts' };
494+
const options = { ...defaultNonStandaloneOptions, module: 'app-module.ts' };
495495

496496
const tree = await schematicRunner.runSchematic('component', options, appTree);
497-
const appModule = tree.readContent('/projects/baz/src/app/app.module.ts');
497+
const appModule = tree.readContent('/projects/baz/src/app/app-module.ts');
498498

499499
expect(appModule).toMatch(/import { FooComponent } from '.\/foo\/foo.component'/);
500500
});
@@ -511,8 +511,8 @@ describe('Component Schematic', () => {
511511

512512
// move the module
513513
appTree.rename(
514-
'/projects/baz/src/app/app.module.ts',
515-
'/projects/baz/custom/app/app.module.ts',
514+
'/projects/baz/src/app/app-module.ts',
515+
'/projects/baz/custom/app/app-module.ts',
516516
);
517517
appTree = await schematicRunner.runSchematic(
518518
'component',

Diff for: packages/schematics/angular/directive/index.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,15 @@ export default function (options: DirectiveOptions): Rule {
3838
options.path = buildDefaultPath(project);
3939
}
4040

41-
options.module = findModuleFromOptions(host, options);
42-
41+
try {
42+
options.module = findModuleFromOptions(host, options);
43+
} catch {
44+
options.module = findModuleFromOptions(host, {
45+
...options,
46+
moduleExt: '-module.ts',
47+
routingModuleExt: '-routing-module.ts',
48+
});
49+
}
4350
const parsedPath = parseName(options.path, options.name);
4451
options.name = parsedPath.name;
4552
options.path = parsedPath.path;

Diff for: packages/schematics/angular/directive/index_spec.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ describe('Directive Schematic', () => {
159159
const files = tree.files;
160160
expect(files).toContain('/projects/baz/src/app/foo.spec.ts');
161161
expect(files).toContain('/projects/baz/src/app/foo.ts');
162-
const moduleContent = tree.readContent('/projects/baz/src/app/app.module.ts');
162+
const moduleContent = tree.readContent('/projects/baz/src/app/app-module.ts');
163163
expect(moduleContent).toMatch(/import.*Foo.*from '.\/foo'/);
164164
expect(moduleContent).toMatch(/declarations:\s*\[[^\]]+?,\r?\n\s+Foo\r?\n/m);
165165
});
@@ -176,8 +176,8 @@ describe('Directive Schematic', () => {
176176

177177
// move the module
178178
appTree.rename(
179-
'/projects/baz/src/app/app.module.ts',
180-
'/projects/baz/custom/app/app.module.ts',
179+
'/projects/baz/src/app/app-module.ts',
180+
'/projects/baz/custom/app/app-module.ts',
181181
);
182182
appTree = await schematicRunner.runSchematic(
183183
'directive',
@@ -190,7 +190,7 @@ describe('Directive Schematic', () => {
190190

191191
it('should find the closest module', async () => {
192192
const options = { ...defaultNonStandaloneOptions, flat: false };
193-
const fooModule = '/projects/baz/src/app/foo/foo.module.ts';
193+
const fooModule = '/projects/baz/src/app/foo/foo-module.ts';
194194
appTree.create(
195195
fooModule,
196196
`
@@ -213,15 +213,15 @@ describe('Directive Schematic', () => {
213213
const options = { ...defaultNonStandaloneOptions, export: true };
214214

215215
const tree = await schematicRunner.runSchematic('directive', options, appTree);
216-
const appModuleContent = tree.readContent('/projects/baz/src/app/app.module.ts');
216+
const appModuleContent = tree.readContent('/projects/baz/src/app/app-module.ts');
217217
expect(appModuleContent).toMatch(/exports: \[\n(\s*) {2}Foo\n\1\]/);
218218
});
219219

220220
it('should import into a specified module', async () => {
221-
const options = { ...defaultNonStandaloneOptions, module: 'app.module.ts' };
221+
const options = { ...defaultNonStandaloneOptions, module: 'app-module.ts' };
222222

223223
const tree = await schematicRunner.runSchematic('directive', options, appTree);
224-
const appModule = tree.readContent('/projects/baz/src/app/app.module.ts');
224+
const appModule = tree.readContent('/projects/baz/src/app/app-module.ts');
225225

226226
expect(appModule).toMatch(/import { Foo } from '.\/foo'/);
227227
});

Diff for: packages/schematics/angular/library/files/src/__entryFile__.ts.template

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
*/
44

55
export * from './lib/<%= dasherize(name) %>';<% if (!standalone) { %>
6-
export * from './lib/<%= dasherize(name) %>.module';<% } %>
6+
export * from './lib/<%= dasherize(name) %>-module';<% } %>

Diff for: packages/schematics/angular/library/index_spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe('Library Schematic', () => {
6363

6464
it('should not add reference to module file in entry-file', async () => {
6565
const tree = await schematicRunner.runSchematic('library', defaultOptions, workspaceTree);
66-
expect(tree.readContent('/projects/foo/src/my-index.ts')).not.toContain('foo.module');
66+
expect(tree.readContent('/projects/foo/src/my-index.ts')).not.toContain('foo-module');
6767
});
6868

6969
it('should create a standalone component', async () => {
@@ -400,7 +400,7 @@ describe('Library Schematic', () => {
400400
workspaceTree,
401401
);
402402

403-
const fileContent = getFileContent(tree, '/projects/foo/src/lib/foo.module.ts');
403+
const fileContent = getFileContent(tree, '/projects/foo/src/lib/foo-module.ts');
404404
expect(fileContent).toMatch(/exports: \[\n(\s*) {2}Foo\n\1\]/);
405405
});
406406

@@ -420,7 +420,7 @@ describe('Library Schematic', () => {
420420
'/projects/foo/tsconfig.lib.json',
421421
'/projects/foo/tsconfig.lib.prod.json',
422422
'/projects/foo/src/my-index.ts',
423-
'/projects/foo/src/lib/foo.module.ts',
423+
'/projects/foo/src/lib/foo-module.ts',
424424
'/projects/foo/src/lib/foo.spec.ts',
425425
'/projects/foo/src/lib/foo.ts',
426426
]),

Diff for: packages/schematics/angular/module/files/__name@dasherize@if-flat__/__name@dasherize__.module.ts.template renamed to packages/schematics/angular/module/files/__name@dasherize@if-flat__/__name@dasherize____typeSeparator__module.ts.template

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';<% if (commonModule) { %>
22
import { CommonModule } from '@angular/common';<% } %><% if (lazyRouteWithoutRouteModule) { %>
33
import { Routes, RouterModule } from '@angular/router';<% } %>
44
<% if ((!lazyRoute && routing) || lazyRouteWithRouteModule) { %>
5-
import { <%= classify(name) %>RoutingModule } from './<%= dasherize(name) %>-routing.module';<% } %>
5+
import { <%= classify(name) %>RoutingModule } from './<%= dasherize(name) %>-routing<%= typeSeparator %>module';<% } %>
66
<% if (lazyRouteWithoutRouteModule) { %>
77
const routes: Routes = [
88
{ path: '', component: <%= classify(name) %> }

Diff for: packages/schematics/angular/module/index.ts

+18-7
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function buildRelativeModulePath(options: ModuleOptions, modulePath: string): st
4040
const importModulePath = join(
4141
options.path ?? '',
4242
options.flat ? '' : strings.dasherize(options.name),
43-
strings.dasherize(options.name) + '.module',
43+
strings.dasherize(options.name) + options.typeSeparator + 'module',
4444
);
4545

4646
return buildRelativePath(modulePath, importModulePath);
@@ -113,9 +113,12 @@ function addRouteDeclarationToNgModule(
113113
}
114114

115115
function getRoutingModulePath(host: Tree, modulePath: string): string | undefined {
116-
const routingModulePath = modulePath.endsWith(ROUTING_MODULE_EXT)
117-
? modulePath
118-
: modulePath.replace(MODULE_EXT, ROUTING_MODULE_EXT);
116+
const routingModulePath =
117+
modulePath.endsWith(ROUTING_MODULE_EXT) || modulePath.endsWith('-routing-module.ts')
118+
? modulePath
119+
: modulePath
120+
.replace(MODULE_EXT, ROUTING_MODULE_EXT)
121+
.replace('-module.ts', '-routing-module.ts');
119122

120123
return host.exists(routingModulePath) ? routingModulePath : undefined;
121124
}
@@ -135,7 +138,15 @@ export default function (options: ModuleOptions): Rule {
135138
}
136139

137140
if (options.module) {
138-
options.module = findModuleFromOptions(host, options);
141+
try {
142+
options.module = findModuleFromOptions(host, options);
143+
} catch {
144+
options.module = findModuleFromOptions(host, {
145+
...options,
146+
moduleExt: '-module.ts',
147+
routingModuleExt: '-routing-module.ts',
148+
});
149+
}
139150
}
140151

141152
let routingModulePath;
@@ -153,7 +164,7 @@ export default function (options: ModuleOptions): Rule {
153164
const templateSource = apply(url('./files'), [
154165
options.routing || (isLazyLoadedModuleGen && routingModulePath)
155166
? noop()
156-
: filter((path) => !path.endsWith('-routing.module.ts.template')),
167+
: filter((path) => !path.includes('-routing')),
157168
applyTemplates({
158169
...strings,
159170
'if-flat': (s: string) => (options.flat ? '' : s),
@@ -167,7 +178,7 @@ export default function (options: ModuleOptions): Rule {
167178
const moduleDasherized = strings.dasherize(options.name);
168179
const modulePath = `${
169180
!options.flat ? moduleDasherized + '/' : ''
170-
}${moduleDasherized}.module.ts`;
181+
}${moduleDasherized}${options.typeSeparator}module.ts`;
171182

172183
const componentOptions: ComponentOptions = {
173184
module: modulePath,

0 commit comments

Comments
 (0)