Skip to content

Commit c1b61f7

Browse files
authored
feat(module:schematics): support ng-add in standalone app (#8095)
* feat(module:schematics): support ng-add in standalone app * feat(module:schematics): support ng-add in standalone app * feat(module:schematics): support ng-add in standalone app * feat(module:schematics): support ng-add in standalone app * feat(module:schematics): support ng-add in standalone app
1 parent 9505c7c commit c1b61f7

32 files changed

+920
-117
lines changed

schematics/ng-add/index.spec.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { addModuleImportToRootModule, getProjectFromWorkspace, getProjectTargetOptions } from '@angular/cdk/schematics';
1+
import { addModuleImportToRootModule, getProjectFromWorkspace, getProjectTargetOptions, getProjectMainFile } from '@angular/cdk/schematics';
22

33

44
import { normalize } from '@angular-devkit/core';
@@ -167,7 +167,6 @@ describe('ng-add schematic', () => {
167167

168168
expect(fileContent).toContain('NoopAnimationsModule');
169169
expect(fileContent).not.toContain('BrowserAnimationsModule');
170-
171170
});
172171

173172
it('should not add NoopAnimationsModule if BrowserAnimationsModule is set up', async () => {
@@ -224,10 +223,7 @@ describe('ng-add schematic', () => {
224223

225224
expect(console.log)
226225
.toHaveBeenCalledWith(
227-
jasmine.stringMatching(
228-
/Could not add the registerLocaleData to your app.module file/
229-
)
226+
jasmine.stringMatching(/Could not add the registerLocaleData to file/)
230227
);
231228
});
232-
233229
});

schematics/ng-add/setup-project/add-animations-module.ts

+31-15
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44
*/
55

66
import {
7-
addModuleImportToRootModule,
87
getProjectFromWorkspace,
98
getProjectMainFile,
10-
hasNgModuleImport
9+
getAppModulePath,
10+
hasNgModuleImport,
11+
isStandaloneApp
1112
} from '@angular/cdk/schematics';
1213

13-
import { WorkspaceDefinition } from '@angular-devkit/core/src/workspace';
14-
import { Rule, Tree } from '@angular-devkit/schematics';
15-
import { getAppModulePath } from '@schematics/angular/utility/ng-ast-utils';
14+
import { Rule, Tree, noop } from '@angular-devkit/schematics';
15+
import { importsProvidersFrom } from '@schematics/angular/private/components';
16+
import { addRootImport } from '@schematics/angular/utility/standalone/rules';
1617
import { getWorkspace } from '@schematics/angular/utility/workspace';
1718
import { blue, yellow } from 'chalk';
1819

@@ -25,23 +26,38 @@ const animationsModulePath = '@angular/platform-browser/animations';
2526
export function addAnimationsModule(options: Schema): Rule {
2627
return async (host: Tree) => {
2728
const workspace = await getWorkspace(host);
28-
const project = getProjectFromWorkspace(workspace as unknown as WorkspaceDefinition, options.project);
29-
const appModulePath = getAppModulePath(host, getProjectMainFile(project));
29+
const project = getProjectFromWorkspace(workspace, options.project);
30+
const mainFile = getProjectMainFile(project);
31+
32+
let hasImportBrowserAnimationsModule: boolean;
33+
let hasImportNoopAnimationsModule: boolean;
34+
35+
if (isStandaloneApp(host, mainFile)) {
36+
hasImportBrowserAnimationsModule = importsProvidersFrom(host, mainFile, browserAnimationsModuleName);
37+
hasImportNoopAnimationsModule = importsProvidersFrom(host, mainFile, noopAnimationsModuleName);
38+
} else {
39+
const appModulePath = getAppModulePath(host, mainFile);
40+
hasImportBrowserAnimationsModule = hasNgModuleImport(host, appModulePath, browserAnimationsModuleName);
41+
hasImportNoopAnimationsModule = hasNgModuleImport(host, appModulePath, noopAnimationsModuleName);
42+
}
3043

3144
if (options.animations) {
32-
if (hasNgModuleImport(host, appModulePath, noopAnimationsModuleName)) {
45+
if (hasImportNoopAnimationsModule) {
3346
console.log();
34-
return console.log(yellow(`Could not set up "${blue(browserAnimationsModuleName)}" ` +
47+
console.log(yellow(`Could not set up "${blue(browserAnimationsModuleName)}" ` +
3548
`because "${blue(noopAnimationsModuleName)}" is already imported. Please manually ` +
3649
`set up browser animations.`));
50+
return noop();
3751
}
38-
addModuleImportToRootModule(host, browserAnimationsModuleName,
39-
animationsModulePath, project);
40-
} else if (!hasNgModuleImport(host, appModulePath, browserAnimationsModuleName)) {
41-
addModuleImportToRootModule(host, noopAnimationsModuleName,
42-
animationsModulePath, project);
52+
return addRootImport(options.project, ({code, external}) => {
53+
return code`${external(browserAnimationsModuleName, animationsModulePath)}`;
54+
});
55+
} else if (!hasImportBrowserAnimationsModule) {
56+
return addRootImport(options.project, ({code, external}) => {
57+
return code`${external(noopAnimationsModuleName, animationsModulePath)}`;
58+
});
4359
}
4460

45-
return;
61+
return noop();
4662
};
4763
}

schematics/ng-add/setup-project/add-icon-assets.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { getProjectFromWorkspace, getProjectTargetOptions } from '@angular/cdk/schematics';
77

88
import { Rule } from '@angular-devkit/schematics';
9-
import { updateWorkspace } from '@schematics/angular/utility/workspace';
9+
import { updateWorkspace } from '@schematics/angular/utility';
1010
import { cyan, yellow } from 'chalk';
1111

1212
import { Schema } from '../schema';
@@ -26,7 +26,6 @@ export function addIconToAssets(options: Schema): Rule {
2626
if (!targetOptions.assets) {
2727
targetOptions.assets = [{ ...iconAssetObject }];
2828
} else {
29-
// eslint-disable-next-line @typescript-eslint/ban-types
3029
const assets = targetOptions.assets as Array<string | object>;
3130
const assetsString = JSON.stringify(assets);
3231
if (!assetsString.includes(iconPathSegment)) {

schematics/ng-add/setup-project/add-required-modules.ts

+8-37
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,8 @@
33
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
44
*/
55

6-
import {
7-
addModuleImportToRootModule,
8-
getProjectFromWorkspace,
9-
getProjectMainFile,
10-
hasNgModuleImport
11-
} from '@angular/cdk/schematics';
12-
13-
import { ProjectDefinition, WorkspaceDefinition } from '@angular-devkit/core/src/workspace';
14-
import { Rule, Tree } from '@angular-devkit/schematics';
15-
import { getAppModulePath } from '@schematics/angular/utility/ng-ast-utils';
16-
import { getWorkspace } from '@schematics/angular/utility/workspace';
17-
import { blue, yellow } from 'chalk';
6+
import { Rule, chain } from '@angular-devkit/schematics';
7+
import { addRootImport } from '@schematics/angular/utility';
188

199
import { Schema } from '../schema';
2010

@@ -24,30 +14,11 @@ const modulesMap = {
2414
};
2515

2616
export function addRequiredModules(options: Schema): Rule {
27-
return async (host: Tree) => {
28-
const workspace = await getWorkspace(host);
29-
const project = getProjectFromWorkspace(workspace as unknown as WorkspaceDefinition, options.project);
30-
const appModulePath = getAppModulePath(host, getProjectMainFile(project));
31-
32-
for (const module in modulesMap) {
33-
if (modulesMap.hasOwnProperty(module)) {
34-
addModuleImportToApptModule(host, module, modulesMap[module],
35-
project, appModulePath, options);
36-
}
37-
}
38-
39-
return;
40-
};
41-
}
17+
const rules = Object.entries(modulesMap).map(([symbolName, moduleName]) => {
18+
return addRootImport(options.project, ({code, external}) => {
19+
return code`${external(symbolName, moduleName)}`;
20+
});
21+
});
4222

43-
function addModuleImportToApptModule(host: Tree, moduleName: string, src: string,
44-
project: ProjectDefinition, appModulePath: string,
45-
options: Schema): void {
46-
if (hasNgModuleImport(host, appModulePath, moduleName)) {
47-
console.log(yellow(`Could not set up "${blue(moduleName)}" ` +
48-
`because "${blue(moduleName)}" is already imported. Please manually ` +
49-
`check "${blue(appModulePath)}" file.`));
50-
return;
51-
}
52-
addModuleImportToRootModule(host, moduleName, src, project);
23+
return chain(rules);
5324
}

schematics/ng-add/setup-project/register-locale.ts

+59-20
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@ import {
99
getDecoratorMetadata,
1010
getProjectFromWorkspace,
1111
getProjectMainFile,
12+
getAppModulePath,
1213
insertAfterLastOccurrence,
1314
insertImport,
15+
isStandaloneApp,
1416
parseSourceFile
1517
} from '@angular/cdk/schematics';
1618

17-
import { Rule, Tree } from '@angular-devkit/schematics';
19+
import { Rule, Tree, chain } from '@angular-devkit/schematics';
20+
import { addRootProvider } from '@schematics/angular/utility';
1821
import { Change, InsertChange, NoopChange } from '@schematics/angular/utility/change';
19-
import { getAppModulePath } from '@schematics/angular/utility/ng-ast-utils';
22+
import { findAppConfig } from '@schematics/angular/utility/standalone/app_config';
23+
import { findBootstrapApplicationCall } from '@schematics/angular/utility/standalone/util';
2024
import { getWorkspace } from '@schematics/angular/utility/workspace';
2125
import { blue, cyan, yellow } from 'chalk';
2226
import * as ts from 'typescript';
@@ -27,15 +31,24 @@ export function registerLocale(options: Schema): Rule {
2731
return async (host: Tree) => {
2832
const workspace = await getWorkspace(host);
2933
const project = getProjectFromWorkspace(workspace, options.project);
30-
const appModulePath = getAppModulePath(host, getProjectMainFile(project));
34+
const mainFile = getProjectMainFile(project);
35+
if (isStandaloneApp(host, mainFile)) {
36+
return registerLocaleInStandaloneApp(mainFile, options);
37+
} else {
38+
return registerLocaleInAppModule(mainFile, options);
39+
}
40+
};
41+
}
42+
43+
function registerLocaleInAppModule(mainFile: string, options: Schema): Rule {
44+
return async (host: Tree) => {
45+
const appModulePath = getAppModulePath(host, mainFile);
3146
const moduleSource = parseSourceFile(host, appModulePath);
3247

3348
const locale = options.locale || 'en_US';
3449
const localePrefix = locale.split('_')[0];
3550

36-
const recorder = host.beginUpdate(appModulePath);
37-
38-
const changes = [
51+
applyChangesToFile(host, appModulePath, [
3952
insertImport(moduleSource, appModulePath, 'NZ_I18N',
4053
'ng-zorro-antd/i18n'),
4154
insertImport(moduleSource, appModulePath, locale,
@@ -46,18 +59,32 @@ export function registerLocale(options: Schema): Rule {
4659
`@angular/common/locales/${localePrefix}`, true),
4760
registerLocaleData(moduleSource, appModulePath, localePrefix),
4861
...insertI18nTokenProvide(moduleSource, appModulePath, locale)
49-
];
50-
51-
changes.forEach((change) => {
52-
if (change instanceof InsertChange) {
53-
recorder.insertLeft(change.pos, change.toAdd);
54-
}
55-
});
56-
57-
host.commitUpdate(recorder);
62+
]);
63+
}
64+
}
5865

59-
return;
60-
};
66+
function registerLocaleInStandaloneApp(mainFile: string, options: Schema): Rule {
67+
const locale = options.locale || 'en_US';
68+
69+
return chain([
70+
async (host: Tree) => {
71+
const bootstrapCall = findBootstrapApplicationCall(host, mainFile);
72+
const appConfig = findAppConfig(bootstrapCall, host, mainFile);
73+
const appConfigFile = appConfig.filePath;
74+
const appConfigSource = parseSourceFile(host, appConfig.filePath);
75+
const localePrefix = locale.split('_')[0];
76+
77+
applyChangesToFile(host, appConfigFile, [
78+
insertImport(appConfigSource, appConfigFile, locale, 'ng-zorro-antd/i18n'),
79+
insertImport(appConfigSource, appConfigFile, 'registerLocaleData', '@angular/common'),
80+
insertImport(appConfigSource, appConfigFile, localePrefix, `@angular/common/locales/${localePrefix}`, true),
81+
registerLocaleData(appConfigSource, appConfigFile, localePrefix)
82+
]);
83+
},
84+
addRootProvider(options.project, ({code, external}) => {
85+
return code`${external('provideNzI18n', 'ng-zorro-antd/i18n')}(${locale})`;
86+
})
87+
]);
6188
}
6289

6390
function registerLocaleData(moduleSource: ts.SourceFile, modulePath: string, locale: string): Change {
@@ -74,9 +101,9 @@ function registerLocaleData(moduleSource: ts.SourceFile, modulePath: string, loc
74101
modulePath, 0) as InsertChange;
75102
} else {
76103
console.log();
77-
console.log(yellow(`Could not add the registerLocaleData to your app.module file (${blue(modulePath)}).` +
104+
console.log(yellow(`Could not add the registerLocaleData to file (${blue(modulePath)}).` +
78105
`because there is already a registerLocaleData function.`));
79-
console.log(yellow(`Please manually add the following code to your app.module:`));
106+
console.log(yellow(`Please manually add the following code:`));
80107
console.log(cyan(`registerLocaleData(${locale});`));
81108
return new NoopChange();
82109
}
@@ -127,7 +154,7 @@ function insertI18nTokenProvide(moduleSource: ts.SourceFile, modulePath: string,
127154
return addProvide;
128155
} else {
129156
console.log();
130-
console.log(yellow(`Could not provide the locale token to your app.module file (${blue(modulePath)}).` +
157+
console.log(yellow(`Could not provide the locale token to file (${blue(modulePath)}).` +
131158
`because there is already a locale token in provides.`));
132159
console.log(yellow(`Please manually add the following code to your provides:`));
133160
console.log(cyan(`{ provide: NZ_I18N, useValue: ${locale} }`));
@@ -139,3 +166,15 @@ function insertI18nTokenProvide(moduleSource: ts.SourceFile, modulePath: string,
139166
}
140167

141168
}
169+
170+
function applyChangesToFile(host: Tree, filePath: string, changes: Change[]): void {
171+
const recorder = host.beginUpdate(filePath);
172+
173+
changes.forEach((change) => {
174+
if (change instanceof InsertChange) {
175+
recorder.insertLeft(change.pos, change.toAdd);
176+
}
177+
});
178+
179+
host.commitUpdate(recorder);
180+
}

0 commit comments

Comments
 (0)