feat(angular): add the extract-i18n executor (#21802)

This commit is contained in:
Leosvel Pérez Espinosa 2024-02-14 14:37:25 +01:00 committed by GitHub
parent b625a79cca
commit 343c0f6690
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 275 additions and 64 deletions

View File

@ -6706,6 +6706,14 @@
"isExternal": false,
"disableCollapsible": false
},
{
"id": "extract-i18n",
"path": "/nx-api/angular/executors/extract-i18n",
"name": "extract-i18n",
"children": [],
"isExternal": false,
"disableCollapsible": false
},
{
"id": "webpack-browser",
"path": "/nx-api/angular/executors/webpack-browser",

View File

@ -85,6 +85,15 @@
"path": "/nx-api/angular/executors/application",
"type": "executor"
},
"/nx-api/angular/executors/extract-i18n": {
"description": "Extracts i18n messages from source code.",
"file": "generated/packages/angular/executors/extract-i18n.json",
"hidden": false,
"name": "extract-i18n",
"originalFilePath": "/packages/angular/src/executors/extract-i18n/schema.json",
"path": "/nx-api/angular/executors/extract-i18n",
"type": "executor"
},
"/nx-api/angular/executors/webpack-browser": {
"description": "The `webpack-browser` executor is very similar to the standard `browser` builder provided by the Angular Devkit. It allows you to build your Angular application to a build artifact that can be hosted online. There are some key differences: \n- Supports Custom Webpack Configurations \n- Supports Incremental Building",
"file": "generated/packages/angular/executors/webpack-browser.json",

View File

@ -80,6 +80,15 @@
"path": "angular/executors/application",
"type": "executor"
},
{
"description": "Extracts i18n messages from source code.",
"file": "generated/packages/angular/executors/extract-i18n.json",
"hidden": false,
"name": "extract-i18n",
"originalFilePath": "/packages/angular/src/executors/extract-i18n/schema.json",
"path": "angular/executors/extract-i18n",
"type": "executor"
},
{
"description": "The `webpack-browser` executor is very similar to the standard `browser` builder provided by the Angular Devkit. It allows you to build your Angular application to a build artifact that can be hosted online. There are some key differences: \n- Supports Custom Webpack Configurations \n- Supports Incremental Building",
"file": "generated/packages/angular/executors/webpack-browser.json",

View File

@ -0,0 +1,55 @@
{
"name": "extract-i18n",
"implementation": "/packages/angular/src/executors/extract-i18n/extract-i18n.impl.ts",
"schema": {
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Schema for Nx extract-i18n Executor",
"description": "Extracts i18n messages from source code.",
"outputCapture": "direct-nodejs",
"type": "object",
"properties": {
"buildTarget": {
"type": "string",
"description": "A builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
},
"format": {
"type": "string",
"description": "Output format for the generated file.",
"default": "xlf",
"enum": [
"xmb",
"xlf",
"xlif",
"xliff",
"xlf2",
"xliff2",
"json",
"arb",
"legacy-migrate"
]
},
"progress": {
"type": "boolean",
"description": "Log progress to the console.",
"default": true
},
"outputPath": {
"type": "string",
"description": "Path where output will be placed."
},
"outFile": {
"type": "string",
"description": "Name of the file to output."
}
},
"additionalProperties": false,
"required": ["buildTarget"],
"presets": []
},
"description": "Extracts i18n messages from source code.",
"aliases": [],
"hidden": false,
"path": "/packages/angular/src/executors/extract-i18n/schema.json",
"type": "executor"
}

View File

@ -325,6 +325,7 @@
- [browser-esbuild](/nx-api/angular/executors/browser-esbuild)
- [module-federation-dev-server](/nx-api/angular/executors/module-federation-dev-server)
- [application](/nx-api/angular/executors/application)
- [extract-i18n](/nx-api/angular/executors/extract-i18n)
- [webpack-browser](/nx-api/angular/executors/webpack-browser)
- [dev-server](/nx-api/angular/executors/dev-server)
- [webpack-server](/nx-api/angular/executors/webpack-server)

View File

@ -29,6 +29,11 @@
"implementation": "./src/executors/application/application.impl",
"schema": "./src/executors/application/schema.json",
"description": "Builds an application with esbuild with support for incremental builds. _Note: this is only supported in Angular versions >= 17.0.0_."
},
"extract-i18n": {
"implementation": "./src/executors/extract-i18n/extract-i18n.impl",
"schema": "./src/executors/extract-i18n/schema.json",
"description": "Extracts i18n messages from source code."
}
},
"builders": {

View File

@ -7,6 +7,7 @@ export * from './src/executors/ng-packagr-lite/ng-packagr-lite.impl';
export * from './src/executors/package/package.impl';
export * from './src/executors/browser-esbuild/browser-esbuild.impl';
export * from './src/executors/application/application.impl';
export * from './src/executors/extract-i18n/extract-i18n.impl';
import { executeDevServerBuilder } from './src/builders/dev-server/dev-server.impl';

View File

@ -1,16 +1,10 @@
import type { BuilderContext } from '@angular-devkit/architect';
import type {
ApplicationBuilderOptions,
BrowserBuilderOptions,
DevServerBuilderOptions,
} from '@angular-devkit/build-angular';
import type { Schema as BrowserEsbuildBuilderOptions } from '@angular-devkit/build-angular/src/builders/browser-esbuild/schema';
import type { DevServerBuilderOptions } from '@angular-devkit/build-angular';
import {
joinPathFragments,
normalizePath,
parseTargetString,
readCachedProjectGraph,
type Target,
} from '@nx/devkit';
import { getRootTsConfigPath } from '@nx/js';
import type { DependentBuildableProjectNode } from '@nx/js/src/utils/buildable-libs-utils';
@ -28,6 +22,7 @@ import {
loadPlugins,
type PluginSpec,
} from '../../executors/utilities/esbuild-extensions';
import { patchBuilderContext } from '../../executors/utilities/patch-builder-context';
import { createTmpTsConfigForBuildableLibs } from '../utilities/buildable-libs';
import {
mergeCustomWebpackConfig,
@ -288,60 +283,3 @@ async function loadIndexHtmlFileTransformer(
)
: await loadIndexHtmlTransformer(pathToIndexFileTransformer, tsConfig);
}
const executorToBuilderMap = new Map<string, string>([
[
'@nx/angular:browser-esbuild',
'@angular-devkit/build-angular:browser-esbuild',
],
['@nx/angular:application', '@angular-devkit/build-angular:application'],
]);
function patchBuilderContext(
context: BuilderContext,
isUsingEsbuildBuilder: boolean,
buildTarget: Target
): void {
const originalGetBuilderNameForTarget = context.getBuilderNameForTarget;
context.getBuilderNameForTarget = async (target) => {
const builderName = await originalGetBuilderNameForTarget(target);
if (executorToBuilderMap.has(builderName)) {
return executorToBuilderMap.get(builderName)!;
}
return builderName;
};
if (isUsingEsbuildBuilder) {
const originalGetTargetOptions = context.getTargetOptions;
context.getTargetOptions = async (target) => {
const options = await originalGetTargetOptions(target);
if (
target.project === buildTarget.project &&
target.target === buildTarget.target &&
target.configuration === buildTarget.configuration
) {
cleanBuildTargetOptions(options);
}
return options;
};
}
}
function cleanBuildTargetOptions(
options: any
):
| ApplicationBuilderOptions
| BrowserBuilderOptions
| BrowserEsbuildBuilderOptions {
delete options.buildLibsFromSource;
delete options.customWebpackConfig;
delete options.indexHtmlTransformer;
delete options.indexFileTransformer;
delete options.plugins;
return options;
}

View File

@ -0,0 +1,68 @@
import type { ExtractI18nBuilderOptions } from '@angular-devkit/build-angular';
import { parseTargetString, type ExecutorContext } from '@nx/devkit';
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph';
import { getInstalledAngularVersionInfo } from '../utilities/angular-version-utils';
import { patchBuilderContext } from '../utilities/patch-builder-context';
import type { ExtractI18nExecutorOptions } from './schema';
export default async function* extractI18nExecutor(
options: ExtractI18nExecutorOptions,
context: ExecutorContext
) {
const parsedBuildTarget = parseTargetString(options.buildTarget, context);
const browserTargetProjectConfiguration = readCachedProjectConfiguration(
parsedBuildTarget.project
);
const buildTarget =
browserTargetProjectConfiguration.targets[parsedBuildTarget.target];
const isUsingEsbuildBuilder = [
'@angular-devkit/build-angular:application',
'@angular-devkit/build-angular:browser-esbuild',
'@nx/angular:application',
'@nx/angular:browser-esbuild',
].includes(buildTarget.executor);
const builderContext = await createBuilderContext(
{
builderName: 'extrct-i18n',
description: 'Extracts i18n messages from source code.',
optionSchema: await import('./schema.json'),
},
context
);
/**
* The Angular CLI extract-i18n builder make some decisions based on the build
* target builder but it only considers `@angular-devkit/build-angular:*`
* builders. Since we are using a custom builder, we patch the context to
* handle `@nx/angular:*` executors.
*/
patchBuilderContext(builderContext, isUsingEsbuildBuilder, parsedBuildTarget);
const { executeExtractI18nBuilder } = await import(
'@angular-devkit/build-angular'
);
const delegateBuilderOptions = getDelegateBuilderOptions(options);
return await executeExtractI18nBuilder(
delegateBuilderOptions,
builderContext
);
}
function getDelegateBuilderOptions(
options: ExtractI18nExecutorOptions
): ExtractI18nBuilderOptions {
const delegateBuilderOptions: ExtractI18nBuilderOptions = { ...options };
const { major: angularMajorVersion } = getInstalledAngularVersionInfo();
if (angularMajorVersion <= 17) {
delegateBuilderOptions.browserTarget = delegateBuilderOptions.buildTarget;
delete delegateBuilderOptions.buildTarget;
}
return delegateBuilderOptions;
}

View File

@ -0,0 +1,8 @@
import type { ExtractI18nBuilderOptions } from '@angular-devkit/build-angular';
export type ExtractI18nExecutorOptions = Omit<
ExtractI18nBuilderOptions,
'browserTarget'
> & {
buildTarget: string;
};

View File

@ -0,0 +1,45 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Schema for Nx extract-i18n Executor",
"description": "Extracts i18n messages from source code.",
"outputCapture": "direct-nodejs",
"type": "object",
"properties": {
"buildTarget": {
"type": "string",
"description": "A builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
},
"format": {
"type": "string",
"description": "Output format for the generated file.",
"default": "xlf",
"enum": [
"xmb",
"xlf",
"xlif",
"xliff",
"xlf2",
"xliff2",
"json",
"arb",
"legacy-migrate"
]
},
"progress": {
"type": "boolean",
"description": "Log progress to the console.",
"default": true
},
"outputPath": {
"type": "string",
"description": "Path where output will be placed."
},
"outFile": {
"type": "string",
"description": "Name of the file to output."
}
},
"additionalProperties": false,
"required": ["buildTarget"]
}

View File

@ -0,0 +1,64 @@
import type { BuilderContext } from '@angular-devkit/architect';
import type {
ApplicationBuilderOptions,
BrowserBuilderOptions,
} from '@angular-devkit/build-angular';
import type { Schema as BrowserEsbuildBuilderOptions } from '@angular-devkit/build-angular/src/builders/browser-esbuild/schema';
import type { Target } from '@nx/devkit';
const executorToBuilderMap = new Map<string, string>([
[
'@nx/angular:browser-esbuild',
'@angular-devkit/build-angular:browser-esbuild',
],
['@nx/angular:application', '@angular-devkit/build-angular:application'],
]);
export function patchBuilderContext(
context: BuilderContext,
isUsingEsbuildBuilder: boolean,
buildTarget: Target
): void {
const originalGetBuilderNameForTarget = context.getBuilderNameForTarget;
context.getBuilderNameForTarget = async (target) => {
const builderName = await originalGetBuilderNameForTarget(target);
if (executorToBuilderMap.has(builderName)) {
return executorToBuilderMap.get(builderName)!;
}
return builderName;
};
if (isUsingEsbuildBuilder) {
const originalGetTargetOptions = context.getTargetOptions;
context.getTargetOptions = async (target) => {
const options = await originalGetTargetOptions(target);
if (
target.project === buildTarget.project &&
target.target === buildTarget.target &&
target.configuration === buildTarget.configuration
) {
cleanBuildTargetOptions(options);
}
return options;
};
}
}
function cleanBuildTargetOptions(
options: any
):
| ApplicationBuilderOptions
| BrowserBuilderOptions
| BrowserEsbuildBuilderOptions {
delete options.buildLibsFromSource;
delete options.customWebpackConfig;
delete options.indexHtmlTransformer;
delete options.indexFileTransformer;
delete options.plugins;
return options;
}