-
Notifications
You must be signed in to change notification settings - Fork 12k
/
Copy pathivy-extract-loader.ts
132 lines (120 loc) · 4.53 KB
/
ivy-extract-loader.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
/**
* @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.io/license
*/
import * as nodePath from 'path';
import { loadEsmModule } from '../../utils/load-esm';
// Extract loader source map parameter type since it is not exported directly
type LoaderSourceMap = Parameters<import('webpack').LoaderDefinitionFunction>[1];
interface LocalizeExtractLoaderOptions {
messageHandler: (messages: import('@angular/localize').ɵParsedMessage[]) => void;
}
export default function localizeExtractLoader(
this: import('webpack').LoaderContext<LocalizeExtractLoaderOptions>,
content: string,
map: LoaderSourceMap,
) {
// This loader is not cacheable due to how message extraction works.
// Extracted messages are not part of webpack pipeline and hence they cannot be retrieved from cache.
// TODO: We should investigate in the future on making this deterministic and more cacheable.
this.cacheable(false);
const options = this.getOptions();
const callback = this.async();
extract(this, content, map, options).then(
() => {
// Pass through the original content now that messages have been extracted
callback(undefined, content, map);
},
(error) => {
callback(error);
},
);
}
async function extract(
loaderContext: import('webpack').LoaderContext<LocalizeExtractLoaderOptions>,
content: string,
map: string | LoaderSourceMap | undefined,
options: LocalizeExtractLoaderOptions,
) {
// Try to load the `@angular/localize` message extractor.
// All the localize usages are setup to first try the ESM entry point then fallback to the deep imports.
// This provides interim compatibility while the framework is transitioned to bundled ESM packages.
let MessageExtractor;
try {
// Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround.
// Once TypeScript provides support for keeping the dynamic import this workaround can be
// changed to a direct dynamic import.
const localizeToolsModule = await loadEsmModule<typeof import('@angular/localize/tools')>(
'@angular/localize/tools',
);
MessageExtractor = localizeToolsModule.MessageExtractor;
} catch {
throw new Error(
`Unable to load message extractor. Please ensure '@angular/localize' is installed.`,
);
}
// Setup a Webpack-based logger instance
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 {
loaderContext.emitWarning(new Error(args.join('')));
},
warn(...args: string[]): void {
loaderContext.emitWarning(new Error(args.join('')));
},
error(...args: string[]): void {
loaderContext.emitError(new Error(args.join('')));
},
};
let filename = loaderContext.resourcePath;
const mapObject =
typeof map === 'string' ? (JSON.parse(map) as Exclude<LoaderSourceMap, string>) : map;
if (mapObject?.file) {
// The extractor's internal sourcemap handling expects the filenames to match
filename = nodePath.join(loaderContext.context, mapObject.file);
}
// 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 {
if (path === filename) {
return content;
} else if (path === filename + '.map') {
return typeof map === 'string' ? map : JSON.stringify(map);
} else {
throw new Error('Unknown file requested: ' + path);
}
},
relative(from: string, to: string): string {
return nodePath.relative(from, to);
},
resolve(...paths: string[]): string {
return nodePath.resolve(...paths);
},
exists(path: string): boolean {
return path === filename || path === filename + '.map';
},
dirname(path: string): string {
return nodePath.dirname(path);
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const extractor = new MessageExtractor(filesystem as any, logger, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
basePath: loaderContext.rootContext as any,
useSourceMaps: !!map,
});
const messages = extractor.extractMessages(filename);
if (messages.length > 0) {
options?.messageHandler(messages);
}
}