Skip to content

Commit ac0db66

Browse files
committed
feat(@schematics/angular): enable standalone by default in new applications
This commit update the schematics to generate a standalone application by default.
1 parent 61f409c commit ac0db66

File tree

19 files changed

+868
-807
lines changed

19 files changed

+868
-807
lines changed

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

+126-137
Original file line numberDiff line numberDiff line change
@@ -40,63 +40,71 @@ describe('App Shell Schematic', () => {
4040

4141
beforeEach(async () => {
4242
appTree = await schematicRunner.runSchematic('workspace', workspaceOptions);
43-
appTree = await schematicRunner.runSchematic('application', appOptions, appTree);
4443
});
4544

46-
it('should add app shell configuration', async () => {
47-
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
48-
const filePath = '/angular.json';
49-
const content = tree.readContent(filePath);
50-
const workspace = JSON.parse(content);
51-
const target = workspace.projects.bar.architect['build'];
52-
expect(target.configurations.production.appShell).toBeTrue();
53-
});
45+
describe('non standalone application', () => {
46+
beforeEach(async () => {
47+
appTree = await schematicRunner.runSchematic(
48+
'application',
49+
{ ...appOptions, standalone: false },
50+
appTree,
51+
);
52+
});
5453

55-
it('should ensure the client app has a router-outlet', async () => {
56-
appTree = await schematicRunner.runSchematic('workspace', workspaceOptions);
57-
appTree = await schematicRunner.runSchematic(
58-
'application',
59-
{ ...appOptions, routing: false },
60-
appTree,
61-
);
62-
await expectAsync(
63-
schematicRunner.runSchematic('app-shell', defaultOptions, appTree),
64-
).toBeRejected();
65-
});
54+
it('should add app shell configuration', async () => {
55+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
56+
const filePath = '/angular.json';
57+
const content = tree.readContent(filePath);
58+
const workspace = JSON.parse(content);
59+
const target = workspace.projects.bar.architect['build'];
60+
expect(target.configurations.production.appShell).toBeTrue();
61+
});
6662

67-
it('should add a server app', async () => {
68-
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
69-
const filePath = '/projects/bar/src/app/app.module.server.ts';
70-
expect(tree.exists(filePath)).toEqual(true);
71-
});
63+
it('should ensure the client app has a router-outlet', async () => {
64+
appTree = await schematicRunner.runSchematic('workspace', workspaceOptions);
65+
appTree = await schematicRunner.runSchematic(
66+
'application',
67+
{ ...appOptions, routing: false },
68+
appTree,
69+
);
70+
await expectAsync(
71+
schematicRunner.runSchematic('app-shell', defaultOptions, appTree),
72+
).toBeRejected();
73+
});
7274

73-
it('should add router module to client app module', async () => {
74-
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
75-
const filePath = '/projects/bar/src/app/app.module.ts';
76-
const content = tree.readContent(filePath);
77-
expect(content).toMatch(/import { RouterModule } from '@angular\/router';/);
78-
});
75+
it('should add a server app', async () => {
76+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
77+
const filePath = '/projects/bar/src/app/app.module.server.ts';
78+
expect(tree.exists(filePath)).toEqual(true);
79+
});
7980

80-
it('should not fail when AppModule have imported RouterModule already', async () => {
81-
const updateRecorder = appTree.beginUpdate('/projects/bar/src/app/app.module.ts');
82-
updateRecorder.insertLeft(0, "import { RouterModule } from '@angular/router';");
83-
appTree.commitUpdate(updateRecorder);
81+
it('should add router module to client app module', async () => {
82+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
83+
const filePath = '/projects/bar/src/app/app.module.ts';
84+
const content = tree.readContent(filePath);
85+
expect(content).toMatch(/import { RouterModule } from '@angular\/router';/);
86+
});
8487

85-
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
86-
const filePath = '/projects/bar/src/app/app.module.ts';
87-
const content = tree.readContent(filePath);
88-
expect(content).toMatch(/import { RouterModule } from '@angular\/router';/);
89-
});
88+
it('should not fail when AppModule have imported RouterModule already', async () => {
89+
const updateRecorder = appTree.beginUpdate('/projects/bar/src/app/app.module.ts');
90+
updateRecorder.insertLeft(0, "import { RouterModule } from '@angular/router';");
91+
appTree.commitUpdate(updateRecorder);
9092

91-
describe('Add router-outlet', () => {
92-
function makeInlineTemplate(tree: UnitTestTree, template?: string): void {
93-
template =
94-
template ||
95-
`
93+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
94+
const filePath = '/projects/bar/src/app/app.module.ts';
95+
const content = tree.readContent(filePath);
96+
expect(content).toMatch(/import { RouterModule } from '@angular\/router';/);
97+
});
98+
99+
describe('Add router-outlet', () => {
100+
function makeInlineTemplate(tree: UnitTestTree, template?: string): void {
101+
template =
102+
template ||
103+
`
96104
<p>
97105
App works!
98106
</p>`;
99-
const newText = `
107+
const newText = `
100108
import { Component } from '@angular/core';
101109
102110
@Component({
@@ -109,112 +117,100 @@ describe('App Shell Schematic', () => {
109117
export class AppComponent { }
110118
111119
`;
112-
tree.overwrite('/projects/bar/src/app/app.component.ts', newText);
113-
tree.delete('/projects/bar/src/app/app.component.html');
114-
}
115-
116-
it('should not re-add the router outlet (external template)', async () => {
117-
const htmlPath = '/projects/bar/src/app/app.component.html';
118-
appTree.overwrite(htmlPath, '<router-outlet></router-outlet>');
119-
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
120-
const content = tree.readContent(htmlPath);
121-
const matches = content.match(/<router-outlet><\/router-outlet>/g);
122-
const numMatches = matches ? matches.length : 0;
123-
expect(numMatches).toEqual(1);
120+
tree.overwrite('/projects/bar/src/app/app.component.ts', newText);
121+
tree.delete('/projects/bar/src/app/app.component.html');
122+
}
123+
124+
it('should not re-add the router outlet (external template)', async () => {
125+
const htmlPath = '/projects/bar/src/app/app.component.html';
126+
appTree.overwrite(htmlPath, '<router-outlet></router-outlet>');
127+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
128+
const content = tree.readContent(htmlPath);
129+
const matches = content.match(/<router-outlet><\/router-outlet>/g);
130+
const numMatches = matches ? matches.length : 0;
131+
expect(numMatches).toEqual(1);
132+
});
133+
134+
it('should not re-add the router outlet (inline template)', async () => {
135+
makeInlineTemplate(appTree, '<router-outlet></router-outlet>');
136+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
137+
const content = tree.readContent('/projects/bar/src/app/app.component.ts');
138+
const matches = content.match(/<router-outlet><\/router-outlet>/g);
139+
const numMatches = matches ? matches.length : 0;
140+
expect(numMatches).toEqual(1);
141+
});
124142
});
125143

126-
it('should not re-add the router outlet (inline template)', async () => {
127-
makeInlineTemplate(appTree, '<router-outlet></router-outlet>');
144+
it('should add router imports to server module', async () => {
128145
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
129-
const content = tree.readContent('/projects/bar/src/app/app.component.ts');
130-
const matches = content.match(/<router-outlet><\/router-outlet>/g);
131-
const numMatches = matches ? matches.length : 0;
132-
expect(numMatches).toEqual(1);
146+
const filePath = '/projects/bar/src/app/app.module.server.ts';
147+
const content = tree.readContent(filePath);
148+
expect(content).toMatch(/import { Routes, RouterModule } from '@angular\/router';/);
133149
});
134-
});
135-
136-
it('should add router imports to server module', async () => {
137-
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
138-
const filePath = '/projects/bar/src/app/app.module.server.ts';
139-
const content = tree.readContent(filePath);
140-
expect(content).toMatch(/import { Routes, RouterModule } from '@angular\/router';/);
141-
});
142150

143-
it('should work if server config was added prior to running the app-shell schematic', async () => {
144-
let tree = await schematicRunner.runSchematic('server', defaultOptions, appTree);
145-
tree = await schematicRunner.runSchematic('app-shell', defaultOptions, tree);
146-
expect(tree.exists('/projects/bar/src/app/app-shell/app-shell.component.ts')).toBe(true);
147-
});
151+
it('should work if server config was added prior to running the app-shell schematic', async () => {
152+
let tree = await schematicRunner.runSchematic('server', defaultOptions, appTree);
153+
tree = await schematicRunner.runSchematic('app-shell', defaultOptions, tree);
154+
expect(tree.exists('/projects/bar/src/app/app-shell/app-shell.component.ts')).toBe(true);
155+
});
148156

149-
it('should define a server route', async () => {
150-
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
151-
const filePath = '/projects/bar/src/app/app.module.server.ts';
152-
const content = tree.readContent(filePath);
153-
expect(content).toMatch(/const routes: Routes = \[/);
154-
});
157+
it('should define a server route', async () => {
158+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
159+
const filePath = '/projects/bar/src/app/app.module.server.ts';
160+
const content = tree.readContent(filePath);
161+
expect(content).toMatch(/const routes: Routes = \[/);
162+
});
155163

156-
it('should import RouterModule with forRoot', async () => {
157-
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
158-
const filePath = '/projects/bar/src/app/app.module.server.ts';
159-
const content = tree.readContent(filePath);
160-
expect(content).toMatch(
161-
/const routes: Routes = \[ { path: 'shell', component: AppShellComponent }\];/,
162-
);
163-
expect(content).toMatch(/ServerModule,\r?\n\s*RouterModule\.forRoot\(routes\),/);
164-
});
164+
it('should import RouterModule with forRoot', async () => {
165+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
166+
const filePath = '/projects/bar/src/app/app.module.server.ts';
167+
const content = tree.readContent(filePath);
168+
expect(content).toMatch(
169+
/const routes: Routes = \[ { path: 'shell', component: AppShellComponent }\];/,
170+
);
171+
expect(content).toMatch(/ServerModule,\r?\n\s*RouterModule\.forRoot\(routes\),/);
172+
});
165173

166-
it('should create the shell component', async () => {
167-
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
168-
expect(tree.exists('/projects/bar/src/app/app-shell/app-shell.component.ts')).toBe(true);
169-
const content = tree.readContent('/projects/bar/src/app/app.module.server.ts');
170-
expect(content).toMatch(/app-shell\.component/);
174+
it('should create the shell component', async () => {
175+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
176+
expect(tree.exists('/projects/bar/src/app/app-shell/app-shell.component.ts')).toBe(true);
177+
const content = tree.readContent('/projects/bar/src/app/app.module.server.ts');
178+
expect(content).toMatch(/app-shell\.component/);
179+
});
171180
});
172181

173182
describe('standalone application', () => {
174-
const standaloneAppName = 'baz';
175-
const standaloneAppOptions: ApplicationOptions = {
176-
...appOptions,
177-
name: standaloneAppName,
178-
standalone: true,
179-
};
180-
const defaultStandaloneOptions: AppShellOptions = {
181-
project: standaloneAppName,
182-
};
183-
184183
beforeEach(async () => {
185-
appTree = await schematicRunner.runSchematic('application', standaloneAppOptions, appTree);
184+
appTree = await schematicRunner.runSchematic('application', appOptions, appTree);
186185
});
187186

188187
it('should ensure the client app has a router-outlet', async () => {
189-
appTree = await schematicRunner.runSchematic('workspace', workspaceOptions);
188+
const appName = 'baz';
190189
appTree = await schematicRunner.runSchematic(
191190
'application',
192-
{ ...standaloneAppOptions, routing: false },
191+
{
192+
...appOptions,
193+
name: appName,
194+
routing: false,
195+
},
193196
appTree,
194197
);
198+
195199
await expectAsync(
196-
schematicRunner.runSchematic('app-shell', defaultStandaloneOptions, appTree),
200+
schematicRunner.runSchematic('app-shell', { ...defaultOptions, project: appName }, appTree),
197201
).toBeRejected();
198202
});
199203

200204
it('should create the shell component', async () => {
201-
const tree = await schematicRunner.runSchematic(
202-
'app-shell',
203-
defaultStandaloneOptions,
204-
appTree,
205-
);
206-
expect(tree.exists('/projects/baz/src/app/app-shell/app-shell.component.ts')).toBe(true);
207-
const content = tree.readContent('/projects/baz/src/app/app.config.server.ts');
205+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
206+
expect(tree.exists('/projects/bar/src/app/app-shell/app-shell.component.ts')).toBe(true);
207+
const content = tree.readContent('/projects/bar/src/app/app.config.server.ts');
208208
expect(content).toMatch(/app-shell\.component/);
209209
});
210210

211211
it('should define a server route', async () => {
212-
const tree = await schematicRunner.runSchematic(
213-
'app-shell',
214-
defaultStandaloneOptions,
215-
appTree,
216-
);
217-
const filePath = '/projects/baz/src/app/app.config.server.ts';
212+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
213+
const filePath = '/projects/bar/src/app/app.config.server.ts';
218214
const content = tree.readContent(filePath);
219215
expect(tags.oneLine`${content}`).toContain(tags.oneLine`{
220216
provide: ROUTES,
@@ -229,23 +225,15 @@ describe('App Shell Schematic', () => {
229225
});
230226

231227
it(`should add import to 'ROUTES' token from '@angular/router'`, async () => {
232-
const tree = await schematicRunner.runSchematic(
233-
'app-shell',
234-
defaultStandaloneOptions,
235-
appTree,
236-
);
237-
const filePath = '/projects/baz/src/app/app.config.server.ts';
228+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
229+
const filePath = '/projects/bar/src/app/app.config.server.ts';
238230
const content = tree.readContent(filePath);
239231
expect(content).toContain(`import { ROUTES } from '@angular/router';`);
240232
});
241233

242234
it(`should add import to 'AppShellComponent'`, async () => {
243-
const tree = await schematicRunner.runSchematic(
244-
'app-shell',
245-
defaultStandaloneOptions,
246-
appTree,
247-
);
248-
const filePath = '/projects/baz/src/app/app.config.server.ts';
235+
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
236+
const filePath = '/projects/bar/src/app/app.config.server.ts';
249237
const content = tree.readContent(filePath);
250238
expect(content).toContain(
251239
`import { AppShellComponent } from './app-shell/app-shell.component';`,
@@ -275,7 +263,8 @@ describe('App Shell Schematic', () => {
275263
appTree.overwrite('/angular.json', JSON.stringify(config, undefined, 2));
276264
}
277265

278-
beforeEach(() => {
266+
beforeEach(async () => {
267+
appTree = await schematicRunner.runSchematic('application', appOptions, appTree);
279268
convertBuilderToLegacyBrowser();
280269
});
281270

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

+3-16
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,6 @@ export default function (options: ApplicationOptions): Rule {
3737
const { appDir, appRootSelector, componentOptions, folderName, sourceDir } =
3838
await getAppOptions(host, options);
3939

40-
if (options.standalone) {
41-
context.logger.warn(
42-
'Standalone application structure is new and not yet supported by many existing' +
43-
` 'ng add' and 'ng update' integrations with community libraries.`,
44-
);
45-
}
46-
4740
return chain([
4841
addAppToWorkspaceFile(options, appDir, folderName),
4942
options.standalone
@@ -183,20 +176,14 @@ function addAppToWorkspaceFile(
183176
];
184177

185178
schematicsWithTests.forEach((type) => {
186-
if (!(`@schematics/angular:${type}` in schematics)) {
187-
schematics[`@schematics/angular:${type}`] = {};
188-
}
189-
(schematics[`@schematics/angular:${type}`] as JsonObject).skipTests = true;
179+
((schematics[`@schematics/angular:${type}`] ??= {}) as JsonObject).skipTests = true;
190180
});
191181
}
192182

193-
if (options.standalone) {
183+
if (!options.standalone) {
194184
const schematicsWithStandalone = ['component', 'directive', 'pipe'];
195185
schematicsWithStandalone.forEach((type) => {
196-
if (!(`@schematics/angular:${type}` in schematics)) {
197-
schematics[`@schematics/angular:${type}`] = {};
198-
}
199-
(schematics[`@schematics/angular:${type}`] as JsonObject).standalone = true;
186+
((schematics[`@schematics/angular:${type}`] ??= {}) as JsonObject).standalone = false;
200187
});
201188
}
202189

0 commit comments

Comments
 (0)