Skip to content

Commit d27ff3b

Browse files
committed
fix(localize): add polyfill in polyfills array instead of polyfills.ts
With the recent changes in the Angular CLI (angular/angular-cli#23938) the polyfills option accepts module path that are resolved using Node module resolution. Also, the polyfills.ts file is no longer generated by default. This commit changes the way on how the polyfill is added to the projects.
1 parent bcc4a95 commit d27ff3b

File tree

3 files changed

+151
-141
lines changed

3 files changed

+151
-141
lines changed

packages/localize/schematics/ng-add/index.ts

+66-85
Original file line numberDiff line numberDiff line change
@@ -8,102 +8,96 @@
88
* @fileoverview Schematics for ng-new project that builds with Bazel.
99
*/
1010

11-
import {virtualFs, workspaces} from '@angular-devkit/core';
12-
import {chain, noop, Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics';
11+
import {tags} from '@angular-devkit/core';
12+
import {chain, noop, Rule, SchematicContext, SchematicsException, Tree,} from '@angular-devkit/schematics';
1313
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
14-
import {addPackageJsonDependency, NodeDependencyType, removePackageJsonDependency} from '@schematics/angular/utility/dependencies';
15-
import {getWorkspace} from '@schematics/angular/utility/workspace';
14+
import {addPackageJsonDependency, NodeDependencyType, removePackageJsonDependency,} from '@schematics/angular/utility/dependencies';
15+
import {allTargetOptions, getWorkspace, updateWorkspace,} from '@schematics/angular/utility/workspace';
1616
import {Builders} from '@schematics/angular/utility/workspace-models';
1717

1818
import {Schema} from './schema';
1919

20-
export const localizePolyfill = `import '@angular/localize/init';`;
20+
export const localizePolyfill = `@angular/localize/init`;
2121

22-
function getRelevantTargetDefinitions(
23-
project: workspaces.ProjectDefinition, builderName: Builders): workspaces.TargetDefinition[] {
24-
const definitions: workspaces.TargetDefinition[] = [];
25-
project.targets.forEach((target: workspaces.TargetDefinition): void => {
26-
if (target.builder === builderName) {
27-
definitions.push(target);
22+
function prependToMainFiles(projectName: string): Rule {
23+
return async (host: Tree) => {
24+
const workspace = await getWorkspace(host);
25+
const project = workspace.projects.get(projectName);
26+
if (!project) {
27+
throw new SchematicsException(`Invalid project name (${projectName})`);
2828
}
29-
});
30-
return definitions;
31-
}
3229

33-
function getOptionValuesForTargetDefinition(
34-
definition: workspaces.TargetDefinition, optionName: string): string[] {
35-
const optionValues: string[] = [];
36-
if (definition.options && optionName in definition.options) {
37-
let optionValue: unknown = definition.options[optionName];
38-
if (typeof optionValue === 'string') {
39-
optionValues.push(optionValue);
40-
}
41-
}
42-
if (!definition.configurations) {
43-
return optionValues;
44-
}
45-
Object.values(definition.configurations)
46-
.forEach((configuration: Record<string, unknown>|undefined): void => {
47-
if (configuration && optionName in configuration) {
48-
const optionValue: unknown = configuration[optionName];
49-
if (typeof optionValue === 'string') {
50-
optionValues.push(optionValue);
51-
}
52-
}
53-
});
54-
return optionValues;
55-
}
56-
57-
function getFileListForRelevantTargetDefinitions(
58-
project: workspaces.ProjectDefinition, builderName: Builders, optionName: string): string[] {
59-
const fileList: string[] = [];
60-
const definitions = getRelevantTargetDefinitions(project, builderName);
61-
definitions.forEach((definition: workspaces.TargetDefinition): void => {
62-
const optionValues = getOptionValuesForTargetDefinition(definition, optionName);
63-
optionValues.forEach((filePath: string): void => {
64-
if (fileList.indexOf(filePath) === -1) {
65-
fileList.push(filePath);
30+
const fileList = new Set<string>();
31+
for (const target of project.targets.values()) {
32+
if (target.builder !== Builders.Server) {
33+
continue;
6634
}
67-
});
68-
});
69-
return fileList;
70-
}
71-
72-
function prependToTargetFiles(
73-
project: workspaces.ProjectDefinition, builderName: Builders, optionName: string, str: string) {
74-
return (host: Tree) => {
75-
const fileList = getFileListForRelevantTargetDefinitions(project, builderName, optionName);
7635

77-
fileList.forEach((path: string): void => {
78-
const data = host.read(path);
79-
if (!data) {
80-
// If the file doesn't exist, just ignore it.
81-
return;
36+
for (const [, options] of allTargetOptions(target)) {
37+
const value = options['main'];
38+
if (typeof value === 'string') {
39+
fileList.add(value);
40+
}
8241
}
42+
}
8343

84-
const content = virtualFs.fileBufferToString(data);
85-
if (content.includes(localizePolyfill) ||
86-
content.includes(localizePolyfill.replace(/'/g, '"'))) {
44+
for (const path of fileList) {
45+
const content = host.readText(path);
46+
if (content.includes(localizePolyfill)) {
8747
// If the file already contains the polyfill (or variations), ignore it too.
88-
return;
48+
continue;
8949
}
9050

9151
// Add string at the start of the file.
9252
const recorder = host.beginUpdate(path);
93-
recorder.insertLeft(0, str);
53+
54+
const localizeStr =
55+
tags.stripIndents`/***************************************************************************************************
56+
* Load \`$localize\` onto the global scope - used if i18n tags appear in Angular templates.
57+
*/
58+
import '${localizePolyfill}';
59+
`;
60+
recorder.insertLeft(0, localizeStr);
9461
host.commitUpdate(recorder);
95-
});
62+
}
9663
};
9764
}
9865

99-
function moveToDependencies(host: Tree, context: SchematicContext) {
66+
function addToPolyfillsOption(projectName: string): Rule {
67+
return updateWorkspace((workspace) => {
68+
const project = workspace.projects.get(projectName);
69+
if (!project) {
70+
throw new SchematicsException(`Invalid project name (${projectName})`);
71+
}
72+
73+
for (const target of project.targets.values()) {
74+
if (target.builder !== Builders.Browser && target.builder !== Builders.Karma) {
75+
continue;
76+
}
77+
78+
target.options ??= {};
79+
target.options['polyfills'] ??= [localizePolyfill];
80+
81+
for (const [, options] of allTargetOptions(target)) {
82+
// Convert polyfills option to array.
83+
const polyfillsValue = typeof options['polyfills'] === 'string' ? [options['polyfills']] :
84+
options['polyfills'];
85+
if (Array.isArray(polyfillsValue) && !polyfillsValue.includes(localizePolyfill)) {
86+
options['polyfills'] = [...polyfillsValue, localizePolyfill];
87+
}
88+
}
89+
}
90+
});
91+
}
92+
93+
function moveToDependencies(host: Tree, context: SchematicContext): void {
10094
if (host.exists('package.json')) {
10195
// Remove the previous dependency and add in a new one under the desired type.
10296
removePackageJsonDependency(host, '@angular/localize');
10397
addPackageJsonDependency(host, {
10498
name: '@angular/localize',
10599
type: NodeDependencyType.Default,
106-
version: `~0.0.0-PLACEHOLDER`
100+
version: `~0.0.0-PLACEHOLDER`,
107101
});
108102

109103
// Add a task to run the package manager. This is necessary because we updated
@@ -113,7 +107,7 @@ function moveToDependencies(host: Tree, context: SchematicContext) {
113107
}
114108

115109
export default function(options: Schema): Rule {
116-
return async (host: Tree) => {
110+
return () => {
117111
// We favor the name option because the project option has a
118112
// smart default which can be populated even when unspecified by the user.
119113
const projectName = options.name ?? options.project;
@@ -122,22 +116,9 @@ export default function(options: Schema): Rule {
122116
throw new SchematicsException('Option "project" is required.');
123117
}
124118

125-
const workspace = await getWorkspace(host);
126-
const project: workspaces.ProjectDefinition|undefined = workspace.projects.get(projectName);
127-
if (!project) {
128-
throw new SchematicsException(`Invalid project name (${projectName})`);
129-
}
130-
131-
const localizeStr =
132-
`/***************************************************************************************************
133-
* Load \`$localize\` onto the global scope - used if i18n tags appear in Angular templates.
134-
*/
135-
${localizePolyfill}
136-
`;
137-
138119
return chain([
139-
prependToTargetFiles(project, Builders.Browser, 'polyfills', localizeStr),
140-
prependToTargetFiles(project, Builders.Server, 'main', localizeStr),
120+
prependToMainFiles(projectName),
121+
addToPolyfillsOption(projectName),
141122
// If `$localize` will be used at runtime then must install `@angular/localize`
142123
// into `dependencies`, rather than the default of `devDependencies`.
143124
options.useAtRuntime ? moveToDependencies : noop(),

0 commit comments

Comments
 (0)