Skip to content

Commit 9401337

Browse files
clydinalan-agius4
authored andcommitted
refactor(@schematics/angular): restructure application migration to use separate functions
To support the future addition of more application file update logic, the migration has been separated into multiple functions within the same migration file.
1 parent f329cf9 commit 9401337

File tree

1 file changed

+194
-139
lines changed

1 file changed

+194
-139
lines changed

packages/schematics/angular/migrations/update-17/use-application-builder.ts

Lines changed: 194 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,154 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import { workspaces } from '@angular-devkit/core';
10-
import { Rule, SchematicsException, chain, externalSchematic } from '@angular-devkit/schematics';
9+
import type { workspaces } from '@angular-devkit/core';
10+
import {
11+
Rule,
12+
SchematicContext,
13+
SchematicsException,
14+
Tree,
15+
chain,
16+
externalSchematic,
17+
} from '@angular-devkit/schematics';
1118
import { dirname, join } from 'node:path/posix';
1219
import { JSONFile } from '../../utility/json-file';
13-
import { TreeWorkspaceHost, allTargetOptions, getWorkspace } from '../../utility/workspace';
20+
import { allTargetOptions, updateWorkspace } from '../../utility/workspace';
1421
import { Builders, ProjectType } from '../../utility/workspace-models';
1522

16-
export default function (): Rule {
17-
return async (tree, context) => {
23+
function* updateBuildTarget(
24+
projectName: string,
25+
buildTarget: workspaces.TargetDefinition,
26+
serverTarget: workspaces.TargetDefinition | undefined,
27+
tree: Tree,
28+
context: SchematicContext,
29+
): Iterable<Rule> {
30+
// Update builder target and options
31+
buildTarget.builder = Builders.Application;
32+
33+
for (const [, options] of allTargetOptions(buildTarget, false)) {
34+
// Show warnings for using no longer supported options
35+
if (usesNoLongerSupportedOptions(options, context, projectName)) {
36+
continue;
37+
}
38+
39+
if (options['index'] === '') {
40+
options['index'] = false;
41+
}
42+
43+
// Rename and transform options
44+
options['browser'] = options['main'];
45+
if (serverTarget && typeof options['browser'] === 'string') {
46+
options['server'] = dirname(options['browser']) + '/main.server.ts';
47+
}
48+
options['serviceWorker'] = options['ngswConfigPath'] ?? options['serviceWorker'];
49+
50+
if (typeof options['polyfills'] === 'string') {
51+
options['polyfills'] = [options['polyfills']];
52+
}
53+
54+
let outputPath = options['outputPath'];
55+
if (typeof outputPath === 'string') {
56+
if (!/\/browser\/?$/.test(outputPath)) {
57+
// TODO: add prompt.
58+
context.logger.warn(
59+
`The output location of the browser build has been updated from "${outputPath}" to ` +
60+
`"${join(outputPath, 'browser')}". ` +
61+
'You might need to adjust your deployment pipeline or, as an alternative, ' +
62+
'set outputPath.browser to "" in order to maintain the previous functionality.',
63+
);
64+
} else {
65+
outputPath = outputPath.replace(/\/browser\/?$/, '');
66+
}
67+
68+
options['outputPath'] = {
69+
base: outputPath,
70+
};
71+
72+
if (typeof options['resourcesOutputPath'] === 'string') {
73+
const media = options['resourcesOutputPath'].replaceAll('/', '');
74+
if (media && media !== 'media') {
75+
options['outputPath'] = {
76+
base: outputPath,
77+
media,
78+
};
79+
}
80+
}
81+
}
82+
83+
// Delete removed options
84+
delete options['deployUrl'];
85+
delete options['vendorChunk'];
86+
delete options['commonChunk'];
87+
delete options['resourcesOutputPath'];
88+
delete options['buildOptimizer'];
89+
delete options['main'];
90+
delete options['ngswConfigPath'];
91+
}
92+
93+
// Merge browser and server tsconfig
94+
if (serverTarget) {
95+
const browserTsConfig = buildTarget.options?.tsConfig;
96+
const serverTsConfig = serverTarget.options?.tsConfig;
97+
98+
if (typeof browserTsConfig !== 'string') {
99+
throw new SchematicsException(
100+
`Cannot update project "${projectName}" to use the application builder` +
101+
` as the browser tsconfig cannot be located.`,
102+
);
103+
}
104+
105+
if (typeof serverTsConfig !== 'string') {
106+
throw new SchematicsException(
107+
`Cannot update project "${projectName}" to use the application builder` +
108+
` as the server tsconfig cannot be located.`,
109+
);
110+
}
111+
112+
const browserJson = new JSONFile(tree, browserTsConfig);
113+
const serverJson = new JSONFile(tree, serverTsConfig);
114+
115+
const filesPath = ['files'];
116+
117+
const files = new Set([
118+
...((browserJson.get(filesPath) as string[] | undefined) ?? []),
119+
...((serverJson.get(filesPath) as string[] | undefined) ?? []),
120+
]);
121+
122+
// Server file will be added later by the means of the ssr schematic.
123+
files.delete('server.ts');
124+
125+
browserJson.modify(filesPath, Array.from(files));
126+
127+
const typesPath = ['compilerOptions', 'types'];
128+
browserJson.modify(
129+
typesPath,
130+
Array.from(
131+
new Set([
132+
...((browserJson.get(typesPath) as string[] | undefined) ?? []),
133+
...((serverJson.get(typesPath) as string[] | undefined) ?? []),
134+
]),
135+
),
136+
);
137+
138+
// Delete server tsconfig
139+
yield deleteFile(serverTsConfig);
140+
}
141+
142+
// Update server file
143+
const ssrMainFile = serverTarget?.options?.['main'];
144+
if (typeof ssrMainFile === 'string') {
145+
yield deleteFile(ssrMainFile);
146+
147+
yield externalSchematic('@schematics/angular', 'ssr', {
148+
project: projectName,
149+
skipInstall: true,
150+
});
151+
}
152+
}
153+
154+
function updateProjects(tree: Tree, context: SchematicContext) {
155+
return updateWorkspace((workspace) => {
18156
const rules: Rule[] = [];
19-
const workspace = await getWorkspace(tree);
20157

21158
for (const [name, project] of workspace.projects) {
22159
if (project.extensions.projectType !== ProjectType.Application) {
@@ -41,137 +178,9 @@ export default function (): Rule {
41178
continue;
42179
}
43180

44-
// Update builder target and options
45-
buildTarget.builder = Builders.Application;
46-
const hasServerTarget = project.targets.has('server');
47-
48-
for (const [, options] of allTargetOptions(buildTarget, false)) {
49-
if (options['index'] === '') {
50-
options['index'] = false;
51-
}
52-
53-
// Rename and transform options
54-
options['browser'] = options['main'];
55-
if (hasServerTarget && typeof options['browser'] === 'string') {
56-
options['server'] = dirname(options['browser']) + '/main.server.ts';
57-
}
58-
options['serviceWorker'] = options['ngswConfigPath'] ?? options['serviceWorker'];
59-
60-
if (typeof options['polyfills'] === 'string') {
61-
options['polyfills'] = [options['polyfills']];
62-
}
63-
64-
let outputPath = options['outputPath'];
65-
if (typeof outputPath === 'string') {
66-
if (!/\/browser\/?$/.test(outputPath)) {
67-
// TODO: add prompt.
68-
context.logger.warn(
69-
`The output location of the browser build has been updated from "${outputPath}" to ` +
70-
`"${join(outputPath, 'browser')}". ` +
71-
'You might need to adjust your deployment pipeline or, as an alternative, ' +
72-
'set outputPath.browser to "" in order to maintain the previous functionality.',
73-
);
74-
} else {
75-
outputPath = outputPath.replace(/\/browser\/?$/, '');
76-
}
77-
78-
options['outputPath'] = {
79-
base: outputPath,
80-
};
81-
82-
if (typeof options['resourcesOutputPath'] === 'string') {
83-
const media = options['resourcesOutputPath'].replaceAll('/', '');
84-
if (media && media !== 'media') {
85-
options['outputPath'] = {
86-
base: outputPath,
87-
media: media,
88-
};
89-
}
90-
}
91-
}
92-
93-
// Delete removed options
94-
delete options['vendorChunk'];
95-
delete options['commonChunk'];
96-
delete options['resourcesOutputPath'];
97-
delete options['buildOptimizer'];
98-
delete options['main'];
99-
delete options['ngswConfigPath'];
100-
}
101-
102-
// Merge browser and server tsconfig
103-
if (hasServerTarget) {
104-
const browserTsConfig = buildTarget?.options?.tsConfig;
105-
const serverTsConfig = project.targets.get('server')?.options?.tsConfig;
106-
107-
if (typeof browserTsConfig !== 'string') {
108-
throw new SchematicsException(
109-
`Cannot update project "${name}" to use the application builder` +
110-
` as the browser tsconfig cannot be located.`,
111-
);
112-
}
113-
114-
if (typeof serverTsConfig !== 'string') {
115-
throw new SchematicsException(
116-
`Cannot update project "${name}" to use the application builder` +
117-
` as the server tsconfig cannot be located.`,
118-
);
119-
}
120-
121-
const browserJson = new JSONFile(tree, browserTsConfig);
122-
const serverJson = new JSONFile(tree, serverTsConfig);
123-
124-
const filesPath = ['files'];
125-
126-
const files = new Set([
127-
...((browserJson.get(filesPath) as string[] | undefined) ?? []),
128-
...((serverJson.get(filesPath) as string[] | undefined) ?? []),
129-
]);
130-
131-
// Server file will be added later by the means of the ssr schematic.
132-
files.delete('server.ts');
133-
134-
browserJson.modify(filesPath, Array.from(files));
135-
136-
const typesPath = ['compilerOptions', 'types'];
137-
browserJson.modify(
138-
typesPath,
139-
Array.from(
140-
new Set([
141-
...((browserJson.get(typesPath) as string[] | undefined) ?? []),
142-
...((serverJson.get(typesPath) as string[] | undefined) ?? []),
143-
]),
144-
),
145-
);
146-
147-
// Delete server tsconfig
148-
tree.delete(serverTsConfig);
149-
}
150-
151-
// Update main tsconfig
152-
const rootJson = new JSONFile(tree, 'tsconfig.json');
153-
rootJson.modify(['compilerOptions', 'esModuleInterop'], true);
154-
rootJson.modify(['compilerOptions', 'downlevelIteration'], undefined);
155-
rootJson.modify(['compilerOptions', 'allowSyntheticDefaultImports'], undefined);
181+
const serverTarget = project.targets.get('server');
156182

157-
// Update server file
158-
const ssrMainFile = project.targets.get('server')?.options?.['main'];
159-
if (typeof ssrMainFile === 'string') {
160-
tree.delete(ssrMainFile);
161-
162-
rules.push(
163-
externalSchematic('@schematics/angular', 'ssr', {
164-
project: name,
165-
skipInstall: true,
166-
}),
167-
);
168-
}
169-
170-
// Delete package.json helper scripts
171-
const pkgJson = new JSONFile(tree, 'package.json');
172-
['build:ssr', 'dev:ssr', 'serve:ssr', 'prerender'].forEach((s) =>
173-
pkgJson.remove(['scripts', s]),
174-
);
183+
rules.push(...updateBuildTarget(name, buildTarget, serverTarget, tree, context));
175184

176185
// Delete all redundant targets
177186
for (const [key, target] of project.targets) {
@@ -186,9 +195,55 @@ export default function (): Rule {
186195
}
187196
}
188197

189-
// Save workspace changes
190-
await workspaces.writeWorkspace(workspace, new TreeWorkspaceHost(tree));
191-
192198
return chain(rules);
199+
});
200+
}
201+
202+
function deleteFile(path: string): Rule {
203+
return (tree) => {
204+
tree.delete(path);
193205
};
194206
}
207+
208+
function updateJsonFile(path: string, updater: (json: JSONFile) => void): Rule {
209+
return (tree) => {
210+
updater(new JSONFile(tree, path));
211+
};
212+
}
213+
214+
/**
215+
* Migration main entrypoint
216+
*/
217+
export default function (): Rule {
218+
return chain([
219+
updateProjects,
220+
// Delete package.json helper scripts
221+
updateJsonFile('package.json', (pkgJson) =>
222+
['build:ssr', 'dev:ssr', 'serve:ssr', 'prerender'].forEach((s) =>
223+
pkgJson.remove(['scripts', s]),
224+
),
225+
),
226+
// Update main tsconfig
227+
updateJsonFile('tsconfig.json', (rootJson) => {
228+
rootJson.modify(['compilerOptions', 'esModuleInterop'], true);
229+
rootJson.modify(['compilerOptions', 'downlevelIteration'], undefined);
230+
rootJson.modify(['compilerOptions', 'allowSyntheticDefaultImports'], undefined);
231+
}),
232+
]);
233+
}
234+
235+
function usesNoLongerSupportedOptions(
236+
{ deployUrl }: Record<string, unknown>,
237+
context: SchematicContext,
238+
projectName: string,
239+
): boolean {
240+
let hasUsage = false;
241+
if (typeof deployUrl === 'string') {
242+
hasUsage = true;
243+
context.logger.warn(
244+
`Skipping migration for project "${projectName}". "deployUrl" option is not available in the application builder.`,
245+
);
246+
}
247+
248+
return hasUsage;
249+
}

0 commit comments

Comments
 (0)