refactor(localize): use the new workspaces API for ng-add schematic (#36897)
Updates the @angular/localize ng-add schematic to use the new workspaces API and removes dependency on private APIs. PR Close #36897
This commit is contained in:
parent
2de34770c4
commit
1c26f40cd4
|
@ -1,6 +1,6 @@
|
||||||
# @angular/localize schematic for `ng add`
|
# @angular/localize schematic for `ng add`
|
||||||
|
|
||||||
This schematic will be executed when a Angular CLI user runs `ng add @angular/localize`.
|
This schematic will be executed when an Angular CLI user runs `ng add @angular/localize`.
|
||||||
|
|
||||||
It will search their `angular.json` file, and find polyfills and main files for server builders.
|
It will search their `angular.json` file, and find polyfills and main files for server builders.
|
||||||
Then it will add the `@angular/localize/init` polyfill that `@angular/localize` needs to work.
|
Then it will add the `@angular/localize/init` polyfill that `@angular/localize` needs to work.
|
|
@ -8,55 +8,72 @@
|
||||||
* @fileoverview Schematics for ng-new project that builds with Bazel.
|
* @fileoverview Schematics for ng-new project that builds with Bazel.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {virtualFs} from '@angular-devkit/core';
|
import {virtualFs, workspaces} from '@angular-devkit/core';
|
||||||
import {chain, Rule, Tree} from '@angular-devkit/schematics';
|
import {chain, Rule, SchematicsException, Tree} from '@angular-devkit/schematics';
|
||||||
import {getWorkspace} from '@schematics/angular/utility/config';
|
import {getWorkspace} from '@schematics/angular/utility/workspace';
|
||||||
import {getProjectTargets} from '@schematics/angular/utility/project-targets';
|
import {Builders} from '@schematics/angular/utility/workspace-models';
|
||||||
import {validateProjectName} from '@schematics/angular/utility/validation';
|
|
||||||
import {BrowserBuilderTarget, Builders, ServeBuilderTarget} from '@schematics/angular/utility/workspace-models';
|
|
||||||
|
|
||||||
import {Schema} from './schema';
|
import {Schema} from './schema';
|
||||||
|
|
||||||
|
|
||||||
export const localizePolyfill = `import '@angular/localize/init';`;
|
export const localizePolyfill = `import '@angular/localize/init';`;
|
||||||
|
|
||||||
function getAllOptionValues<T>(
|
function getRelevantTargetDefinitions(
|
||||||
host: Tree, projectName: string, builderName: string, optionName: string) {
|
project: workspaces.ProjectDefinition, builderName: Builders): workspaces.TargetDefinition[] {
|
||||||
const targets = getProjectTargets(host, projectName);
|
const definitions: workspaces.TargetDefinition[] = [];
|
||||||
|
project.targets.forEach((target: workspaces.TargetDefinition): void => {
|
||||||
// Find all targets of a specific build in a project.
|
if (target.builder === builderName) {
|
||||||
const builderTargets: (BrowserBuilderTarget|ServeBuilderTarget)[] = Object.values(targets).filter(
|
definitions.push(target);
|
||||||
(target: BrowserBuilderTarget|ServeBuilderTarget) => target.builder === builderName);
|
}
|
||||||
|
});
|
||||||
// Get all options contained in target configuration partials.
|
return definitions;
|
||||||
const configurationOptions = builderTargets.filter(t => t.configurations)
|
}
|
||||||
.map(t => Object.values(t.configurations!))
|
|
||||||
.reduce((acc, cur) => acc.concat(...cur), []);
|
|
||||||
|
|
||||||
// Now we have all option sets. We can use it to find all references to a given property.
|
|
||||||
const allOptions = [
|
|
||||||
...builderTargets.map(t => t.options),
|
|
||||||
...configurationOptions,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Get all values for the option name and dedupe them.
|
|
||||||
// Deduping will only work for primitives, but the keys we want here are strings so it's ok.
|
|
||||||
const optionValues: T[] =
|
|
||||||
allOptions.filter(o => o[optionName])
|
|
||||||
.map(o => o[optionName])
|
|
||||||
.reduce((acc, cur) => !acc.includes(cur) ? acc.concat(cur) : acc, []);
|
|
||||||
|
|
||||||
|
function getOptionValuesForTargetDefinition(
|
||||||
|
definition: workspaces.TargetDefinition, optionName: string): string[] {
|
||||||
|
const optionValues: string[] = [];
|
||||||
|
if (definition.options && optionName in definition.options) {
|
||||||
|
let optionValue: unknown = definition.options[optionName];
|
||||||
|
if (typeof optionValue === 'string') {
|
||||||
|
optionValues.push(optionValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!definition.configurations) {
|
||||||
|
return optionValues;
|
||||||
|
}
|
||||||
|
Object.values(definition.configurations)
|
||||||
|
.forEach((configuration: Record<string, unknown>|undefined): void => {
|
||||||
|
if (configuration && optionName in configuration) {
|
||||||
|
const optionValue: unknown = configuration[optionName];
|
||||||
|
if (typeof optionValue === 'string') {
|
||||||
|
optionValues.push(optionValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return optionValues;
|
return optionValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFileListForRelevantTargetDefinitions(
|
||||||
|
project: workspaces.ProjectDefinition, builderName: Builders, optionName: string): string[] {
|
||||||
|
const fileList: string[] = [];
|
||||||
|
const definitions = getRelevantTargetDefinitions(project, builderName);
|
||||||
|
definitions.forEach((definition: workspaces.TargetDefinition): void => {
|
||||||
|
const optionValues = getOptionValuesForTargetDefinition(definition, optionName);
|
||||||
|
optionValues.forEach((filePath: string): void => {
|
||||||
|
if (fileList.indexOf(filePath) === -1) {
|
||||||
|
fileList.push(filePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return fileList;
|
||||||
|
}
|
||||||
|
|
||||||
function prendendToTargetOptionFile(
|
function prependToTargetFiles(
|
||||||
projectName: string, builderName: string, optionName: string, str: string) {
|
project: workspaces.ProjectDefinition, builderName: Builders, optionName: string, str: string) {
|
||||||
return (host: Tree) => {
|
return (host: Tree) => {
|
||||||
// Get all known polyfills for browser builders on this project.
|
const fileList = getFileListForRelevantTargetDefinitions(project, builderName, optionName);
|
||||||
const optionValues = getAllOptionValues<string>(host, projectName, builderName, optionName);
|
|
||||||
|
|
||||||
optionValues.forEach(path => {
|
fileList.forEach((path: string): void => {
|
||||||
const data = host.read(path);
|
const data = host.read(path);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
// If the file doesn't exist, just ignore it.
|
// If the file doesn't exist, just ignore it.
|
||||||
|
@ -79,12 +96,16 @@ function prendendToTargetOptionFile(
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function(options: Schema): Rule {
|
export default function(options: Schema): Rule {
|
||||||
return (host: Tree) => {
|
return async (host: Tree) => {
|
||||||
options.name = options.name || getWorkspace(host).defaultProject;
|
|
||||||
if (!options.name) {
|
if (!options.name) {
|
||||||
throw new Error('Please specify a project using "--name project-name"');
|
throw new SchematicsException('Option "name" is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspace = await getWorkspace(host);
|
||||||
|
const project: workspaces.ProjectDefinition|undefined = workspace.projects.get(options.name);
|
||||||
|
if (!project) {
|
||||||
|
throw new SchematicsException(`Invalid project name (${options.name})`);
|
||||||
}
|
}
|
||||||
validateProjectName(options.name);
|
|
||||||
|
|
||||||
const localizeStr =
|
const localizeStr =
|
||||||
`/***************************************************************************************************
|
`/***************************************************************************************************
|
||||||
|
@ -94,8 +115,8 @@ ${localizePolyfill}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return chain([
|
return chain([
|
||||||
prendendToTargetOptionFile(options.name, Builders.Browser, 'polyfills', localizeStr),
|
prependToTargetFiles(project, Builders.Browser, 'polyfills', localizeStr),
|
||||||
prendendToTargetOptionFile(options.name, Builders.Server, 'main', localizeStr),
|
prependToTargetFiles(project, Builders.Server, 'main', localizeStr),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ export { renderModule, renderModuleFactory } from '@angular/platform-server';`;
|
||||||
host.create('src/unrelated-main.server.ts', mainServerContent);
|
host.create('src/unrelated-main.server.ts', mainServerContent);
|
||||||
host.create('src/another-unrelated-main.server.ts', mainServerContent);
|
host.create('src/another-unrelated-main.server.ts', mainServerContent);
|
||||||
host.create('angular.json', JSON.stringify({
|
host.create('angular.json', JSON.stringify({
|
||||||
|
version: 1,
|
||||||
projects: {
|
projects: {
|
||||||
'demo': {
|
'demo': {
|
||||||
architect: {
|
architect: {
|
||||||
|
@ -155,6 +156,7 @@ export { renderModule, renderModuleFactory } from '@angular/platform-server';`;
|
||||||
|
|
||||||
it('should not break when there are no polyfills', async () => {
|
it('should not break when there are no polyfills', async () => {
|
||||||
host.overwrite('angular.json', JSON.stringify({
|
host.overwrite('angular.json', JSON.stringify({
|
||||||
|
version: 1,
|
||||||
projects: {
|
projects: {
|
||||||
'demo': {
|
'demo': {
|
||||||
architect: {},
|
architect: {},
|
||||||
|
|
Loading…
Reference in New Issue