-
Notifications
You must be signed in to change notification settings - Fork 12k
/
Copy pathapplication-extraction.ts
173 lines (154 loc) · 5.41 KB
/
application-extraction.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {
type ApplicationBuilderInternalOptions,
buildApplicationInternal,
} from '@angular/build/private';
import type { ɵParsedMessage as LocalizeMessage } from '@angular/localize';
import type { MessageExtractor } from '@angular/localize/tools';
import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import assert from 'node:assert';
import nodePath from 'node:path';
import { buildEsbuildBrowser } from '../browser-esbuild';
import type { NormalizedExtractI18nOptions } from './options';
export async function extractMessages(
options: NormalizedExtractI18nOptions,
builderName: string,
context: BuilderContext,
extractorConstructor: typeof MessageExtractor,
): Promise<{
builderResult: BuilderOutput;
basePath: string;
messages: LocalizeMessage[];
useLegacyIds: boolean;
}> {
const messages: LocalizeMessage[] = [];
// Setup the build options for the application based on the buildTarget option
const buildOptions = (await context.validateOptions(
await context.getTargetOptions(options.buildTarget),
builderName,
)) as unknown as ApplicationBuilderInternalOptions;
buildOptions.optimization = false;
buildOptions.sourceMap = { scripts: true, vendor: true, styles: false };
buildOptions.localize = false;
buildOptions.budgets = undefined;
buildOptions.index = false;
buildOptions.serviceWorker = false;
let build;
if (builderName === '@angular-devkit/build-angular:application') {
build = buildApplicationInternal;
buildOptions.ssr = false;
buildOptions.appShell = false;
buildOptions.prerender = false;
} else {
build = buildEsbuildBrowser;
}
// Build the application with the build options
let builderResult;
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
for await (const result of build(buildOptions as any, context, { write: false })) {
builderResult = result;
break;
}
assert(builderResult !== undefined, 'Application builder did not provide a result.');
} catch (err) {
builderResult = {
success: false,
error: (err as Error).message,
};
}
// Extract messages from each output JavaScript file.
// Output files are only present on a successful build.
if (builderResult.outputFiles) {
// Store the JS and JS map files for lookup during extraction
const files = new Map<string, string>();
for (const outputFile of builderResult.outputFiles) {
if (outputFile.path.endsWith('.js')) {
files.set(outputFile.path, outputFile.text);
} else if (outputFile.path.endsWith('.js.map')) {
files.set(outputFile.path, outputFile.text);
}
}
// Setup the localize message extractor based on the in-memory files
const extractor = setupLocalizeExtractor(extractorConstructor, files, context);
// Attempt extraction of all output JS files
for (const filePath of files.keys()) {
if (!filePath.endsWith('.js')) {
continue;
}
const fileMessages = extractor.extractMessages(filePath);
messages.push(...fileMessages);
}
}
return {
builderResult,
basePath: context.workspaceRoot,
messages,
// Legacy i18n identifiers are not supported with the new application builder
useLegacyIds: false,
};
}
function setupLocalizeExtractor(
extractorConstructor: typeof MessageExtractor,
files: Map<string, string>,
context: BuilderContext,
): MessageExtractor {
// Setup a virtual file system instance for the extractor
// * MessageExtractor itself uses readFile, relative and resolve
// * Internal SourceFileLoader (sourcemap support) uses dirname, exists, readFile, and resolve
const filesystem = {
readFile(path: string): string {
// Output files are stored as relative to the workspace root
const requestedPath = nodePath.relative(context.workspaceRoot, path);
const content = files.get(requestedPath);
if (content === undefined) {
throw new Error('Unknown file requested: ' + requestedPath);
}
return content;
},
relative(from: string, to: string): string {
return nodePath.relative(from, to);
},
resolve(...paths: string[]): string {
return nodePath.resolve(...paths);
},
exists(path: string): boolean {
// Output files are stored as relative to the workspace root
const requestedPath = nodePath.relative(context.workspaceRoot, path);
return files.has(requestedPath);
},
dirname(path: string): string {
return nodePath.dirname(path);
},
};
const logger = {
// level 2 is warnings
level: 2,
debug(...args: string[]): void {
// eslint-disable-next-line no-console
console.debug(...args);
},
info(...args: string[]): void {
context.logger.info(args.join(''));
},
warn(...args: string[]): void {
context.logger.warn(args.join(''));
},
error(...args: string[]): void {
context.logger.error(args.join(''));
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const extractor = new extractorConstructor(filesystem as any, logger, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
basePath: context.workspaceRoot as any,
useSourceMaps: true,
});
return extractor;
}