@nx/angular-rspack 23.1.0 → 23.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/lib/config/config-utils/browser-config.d.ts +4 -1
  2. package/dist/lib/config/config-utils/browser-config.d.ts.map +1 -1
  3. package/dist/lib/config/config-utils/browser-config.js +5 -18
  4. package/dist/lib/config/config-utils/server-config.d.ts +4 -1
  5. package/dist/lib/config/config-utils/server-config.d.ts.map +1 -1
  6. package/dist/lib/config/config-utils/server-config.js +10 -18
  7. package/dist/lib/config/config-utils/swc-transpilation.d.ts +16 -0
  8. package/dist/lib/config/config-utils/swc-transpilation.d.ts.map +1 -0
  9. package/dist/lib/config/config-utils/swc-transpilation.js +50 -0
  10. package/dist/lib/config/create-config.d.ts.map +1 -1
  11. package/dist/lib/config/create-config.js +20 -2
  12. package/dist/lib/models/augmented-compilation.d.ts +6 -0
  13. package/dist/lib/models/augmented-compilation.d.ts.map +1 -1
  14. package/dist/lib/plugins/angular-rspack-plugin.d.ts +9 -0
  15. package/dist/lib/plugins/angular-rspack-plugin.d.ts.map +1 -1
  16. package/dist/lib/plugins/angular-rspack-plugin.js +245 -83
  17. package/dist/lib/plugins/extract-licenses-plugin.d.ts +78 -0
  18. package/dist/lib/plugins/extract-licenses-plugin.d.ts.map +1 -0
  19. package/dist/lib/plugins/extract-licenses-plugin.js +217 -0
  20. package/dist/lib/plugins/loaders/angular-partial-transform.loader.d.ts +2 -2
  21. package/dist/lib/plugins/loaders/angular-partial-transform.loader.d.ts.map +1 -1
  22. package/dist/lib/plugins/loaders/angular-partial-transform.loader.js +78 -16
  23. package/dist/lib/plugins/loaders/angular-transform.loader.d.ts.map +1 -1
  24. package/dist/lib/plugins/loaders/angular-transform.loader.js +49 -20
  25. package/dist/lib/plugins/loaders/inline-source-map.d.ts +34 -0
  26. package/dist/lib/plugins/loaders/inline-source-map.d.ts.map +1 -0
  27. package/dist/lib/plugins/loaders/inline-source-map.js +76 -0
  28. package/dist/lib/plugins/ng-rspack.d.ts +18 -0
  29. package/dist/lib/plugins/ng-rspack.d.ts.map +1 -1
  30. package/dist/lib/plugins/ng-rspack.js +43 -9
  31. package/dist/lib/plugins/prerender-plugin.d.ts.map +1 -1
  32. package/dist/lib/plugins/prerender-plugin.js +26 -3
  33. package/dist/lib/utils/postcss-cli-resources.d.ts.map +1 -1
  34. package/dist/lib/utils/postcss-cli-resources.js +8 -4
  35. package/package.json +5 -11
  36. package/LICENSE +0 -22
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.ExtractLicensesPlugin = void 0;
11
+ exports.extractLicenses = extractLicenses;
12
+ const tslib_1 = require("tslib");
13
+ const promises_1 = require("node:fs/promises");
14
+ const node_path_1 = tslib_1.__importDefault(require("node:path"));
15
+ const models_1 = require("../models");
16
+ /**
17
+ * The path segment used to signify that a file is part of a package.
18
+ */
19
+ const NODE_MODULE_SEGMENT = 'node_modules';
20
+ /**
21
+ * String constant for the NPM recommended custom license wording.
22
+ *
23
+ * See: https://docs.npmjs.com/cli/v9/configuring-npm/package-json#license
24
+ *
25
+ * Example:
26
+ * ```
27
+ * {
28
+ * "license" : "SEE LICENSE IN <filename>"
29
+ * }
30
+ * ```
31
+ */
32
+ const CUSTOM_LICENSE_TEXT = 'SEE LICENSE IN ';
33
+ /**
34
+ * A list of commonly named license files found within packages.
35
+ */
36
+ const LICENSE_FILES = ['LICENSE', 'LICENSE.txt', 'LICENSE.md'];
37
+ /**
38
+ * Header text that will be added to the top of the output license extraction file.
39
+ */
40
+ const EXTRACTION_FILE_HEADER = '';
41
+ /**
42
+ * The package entry separator to use within the output license extraction file.
43
+ */
44
+ const EXTRACTION_FILE_SEPARATOR = '-'.repeat(80) + '\n';
45
+ /**
46
+ * Extracts license information for each node module package included in the output
47
+ * files of the built code. This includes JavaScript and CSS output files. The esbuild
48
+ * metafile generated during the bundling steps is used as the source of information
49
+ * regarding what input files where included and where they are located. A path segment
50
+ * of `node_modules` is used to indicate that a file belongs to a package and its license
51
+ * should be include in the output licenses file.
52
+ *
53
+ * The package name and license field are extracted from the `package.json` file for the
54
+ * package. If a license file (e.g., `LICENSE`) is present in the root of the package, it
55
+ * will also be included in the output licenses file.
56
+ *
57
+ * Vendored from `@angular/build` (`src/tools/esbuild/license-extractor.ts`, identical
58
+ * across the supported Angular majors) since it is not exposed in the package's public
59
+ * or private API. Only the esbuild `Metafile` type import is replaced with the local
60
+ * `LicenseMetafile` interface.
61
+ *
62
+ * @param metafile An esbuild metafile object.
63
+ * @param rootDirectory The root directory of the workspace.
64
+ * @returns A string containing the content of the output licenses file.
65
+ */
66
+ async function extractLicenses(metafile, rootDirectory) {
67
+ let extractedLicenseContent = `${EXTRACTION_FILE_HEADER}\n${EXTRACTION_FILE_SEPARATOR}`;
68
+ const seenPaths = new Set();
69
+ const seenPackages = new Set();
70
+ for (const entry of Object.values(metafile.outputs)) {
71
+ for (const [inputPath, { bytesInOutput }] of Object.entries(entry.inputs)) {
72
+ // Skip if not included in output
73
+ if (bytesInOutput <= 0) {
74
+ continue;
75
+ }
76
+ // Skip already processed paths
77
+ if (seenPaths.has(inputPath)) {
78
+ continue;
79
+ }
80
+ seenPaths.add(inputPath);
81
+ // Skip non-package paths
82
+ if (!inputPath.includes(NODE_MODULE_SEGMENT)) {
83
+ continue;
84
+ }
85
+ // Extract the package name from the path
86
+ let baseDirectory = node_path_1.default.join(rootDirectory, inputPath);
87
+ let nameOrScope, nameOrFile;
88
+ let found = false;
89
+ while (baseDirectory !== node_path_1.default.dirname(baseDirectory)) {
90
+ const segment = node_path_1.default.basename(baseDirectory);
91
+ if (segment === NODE_MODULE_SEGMENT) {
92
+ found = true;
93
+ break;
94
+ }
95
+ nameOrFile = nameOrScope;
96
+ nameOrScope = segment;
97
+ baseDirectory = node_path_1.default.dirname(baseDirectory);
98
+ }
99
+ // Skip non-package path edge cases that are not caught in the includes check above
100
+ if (!found || !nameOrScope) {
101
+ continue;
102
+ }
103
+ const packageName = nameOrScope.startsWith('@')
104
+ ? `${nameOrScope}/${nameOrFile}`
105
+ : nameOrScope;
106
+ const packageDirectory = node_path_1.default.join(baseDirectory, packageName);
107
+ // Load the package's metadata to find the package's name, version, and license type
108
+ const packageJsonPath = node_path_1.default.join(packageDirectory, 'package.json');
109
+ let packageJson;
110
+ try {
111
+ packageJson = JSON.parse(await (0, promises_1.readFile)(packageJsonPath, 'utf-8'));
112
+ }
113
+ catch {
114
+ // Invalid package
115
+ continue;
116
+ }
117
+ // Skip already processed packages
118
+ const packageId = `${packageName}@${packageJson.version}`;
119
+ if (seenPackages.has(packageId)) {
120
+ continue;
121
+ }
122
+ seenPackages.add(packageId);
123
+ // Attempt to find license text inside package
124
+ let licenseText = '';
125
+ if (typeof packageJson.license === 'string' &&
126
+ packageJson.license.toLowerCase().startsWith(CUSTOM_LICENSE_TEXT)) {
127
+ // Attempt to load the package's custom license
128
+ let customLicensePath;
129
+ const customLicenseFile = node_path_1.default.normalize(packageJson.license.slice(CUSTOM_LICENSE_TEXT.length + 1).trim());
130
+ if (customLicenseFile.startsWith('..') ||
131
+ node_path_1.default.isAbsolute(customLicenseFile)) {
132
+ // Path is attempting to access files outside of the package
133
+ // TODO: Issue warning?
134
+ }
135
+ else {
136
+ customLicensePath = node_path_1.default.join(packageDirectory, customLicenseFile);
137
+ try {
138
+ licenseText = await (0, promises_1.readFile)(customLicensePath, 'utf-8');
139
+ break;
140
+ }
141
+ catch { }
142
+ }
143
+ }
144
+ else {
145
+ // Search for a license file within the root of the package
146
+ for (const potentialLicense of LICENSE_FILES) {
147
+ const packageLicensePath = node_path_1.default.join(packageDirectory, potentialLicense);
148
+ try {
149
+ licenseText = await (0, promises_1.readFile)(packageLicensePath, 'utf-8');
150
+ break;
151
+ }
152
+ catch { }
153
+ }
154
+ }
155
+ // Generate the package's license entry in the output content
156
+ extractedLicenseContent += `Package: ${packageJson.name}\n`;
157
+ extractedLicenseContent += `License: ${JSON.stringify(packageJson.license, null, 2)}\n`;
158
+ extractedLicenseContent += `\n${licenseText}\n`;
159
+ extractedLicenseContent += EXTRACTION_FILE_SEPARATOR;
160
+ }
161
+ }
162
+ return extractedLicenseContent;
163
+ }
164
+ const PLUGIN_NAME = 'angular-extract-licenses-plugin';
165
+ /**
166
+ * Emits a licenses file for the node module packages included in the output,
167
+ * mirroring the `@angular/build` application builder's `extractLicenses`
168
+ * behavior and output. Feeds the chunk graph's module paths to the vendored
169
+ * extractor in place of an esbuild metafile.
170
+ */
171
+ class ExtractLicensesPlugin {
172
+ constructor(options) {
173
+ this.options = options;
174
+ this.sharedInputs = options.sharedInputs ?? new Map();
175
+ }
176
+ apply(compiler) {
177
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
178
+ compilation.hooks.processAssets.tapPromise({
179
+ name: PLUGIN_NAME,
180
+ stage: compiler.rspack.Compilation.PROCESS_ASSETS_STAGE_REPORT,
181
+ }, async () => {
182
+ const { outputFilename, rootDirectory } = this.options;
183
+ const inputs = {};
184
+ for (const chunk of compilation.chunks) {
185
+ for (const module of compilation.chunkGraph.getChunkModulesIterable(chunk)) {
186
+ // Concatenated modules (production) wrap the actual source
187
+ // modules, whose paths carry the package information.
188
+ const innerModules = module
189
+ .modules;
190
+ for (const m of innerModules ?? [module]) {
191
+ const resourcePath = m.nameForCondition?.();
192
+ if (!resourcePath) {
193
+ continue;
194
+ }
195
+ inputs[node_path_1.default.relative(rootDirectory, resourcePath)] = {
196
+ bytesInOutput: 1,
197
+ };
198
+ }
199
+ }
200
+ }
201
+ // Packages bundled into component stylesheets never appear in the
202
+ // chunk graph; their inputs are tracked by the Angular plugin.
203
+ const stylesheetInputs = compilation[models_1.NG_RSPACK_SYMBOL_NAME]?.().stylesheetMetafileInputs;
204
+ Object.assign(inputs, stylesheetInputs);
205
+ this.sharedInputs.set(this, inputs);
206
+ const outputs = {};
207
+ let outputIndex = 0;
208
+ for (const compilerInputs of this.sharedInputs.values()) {
209
+ outputs[outputIndex++] = { inputs: compilerInputs };
210
+ }
211
+ const licenses = await extractLicenses({ outputs }, rootDirectory);
212
+ compilation.emitAsset(outputFilename, new compiler.rspack.sources.RawSource(licenses));
213
+ });
214
+ });
215
+ }
216
+ }
217
+ exports.ExtractLicensesPlugin = ExtractLicensesPlugin;
@@ -1,3 +1,3 @@
1
- import { LoaderContext } from '@rspack/core';
2
- export default function loader(this: LoaderContext<unknown>, content: string): void;
1
+ import { LoaderContext, RawSourceMap } from '@rspack/core';
2
+ export default function loader(this: LoaderContext<unknown>, content: string, inputMap?: string | RawSourceMap): void;
3
3
  //# sourceMappingURL=angular-partial-transform.loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"angular-partial-transform.loader.d.ts","sourceRoot":"","sources":["../../../../src/lib/plugins/loaders/angular-partial-transform.loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7C,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,QAyD3E"}
1
+ {"version":3,"file":"angular-partial-transform.loader.d.ts","sourceRoot":"","sources":["../../../../src/lib/plugins/loaders/angular-partial-transform.loader.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAQ3D,MAAM,CAAC,OAAO,UAAU,MAAM,CAC5B,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,EAC5B,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,QAuIjC"}
@@ -1,43 +1,105 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = loader;
4
+ const promises_1 = require("node:fs/promises");
5
+ const angular_rspack_compiler_1 = require("@nx/angular-rspack-compiler");
4
6
  const models_1 = require("../../models");
5
- function loader(content) {
7
+ const inline_source_map_1 = require("./inline-source-map");
8
+ function loader(content, inputMap) {
6
9
  const callback = this.async();
7
10
  if (this._compilation[models_1.NG_RSPACK_SYMBOL_NAME] ===
8
11
  undefined) {
9
- callback(null, content);
12
+ callback(null, content, inputMap);
10
13
  }
11
14
  else {
12
- const { javascriptTransformer, typescriptFileCache, angularCompilationFailed, } = this._compilation[models_1.NG_RSPACK_SYMBOL_NAME]();
15
+ const { javascriptTransformer, typescriptFileCache, babelFileCache, angularCompilationFailed, } = this._compilation[models_1.NG_RSPACK_SYMBOL_NAME]();
16
+ // The sourcemap chained by earlier loaders: source-map-loader runs first
17
+ // and consumes the file's own sourceMappingURL comment (inline or an
18
+ // external .map file) subject to the vendor sourcemap policy. Rspack
19
+ // fails the module build on maps it cannot deserialize, so drop anything
20
+ // unusable instead of forwarding it.
21
+ const chainMap = (0, inline_source_map_1.isForwardableSourceMap)(inputMap) ? inputMap : undefined;
13
22
  if (angularCompilationFailed) {
14
23
  // The build is already failing with the Angular compilation error,
15
24
  // so skip transforming and pass the original content through.
16
- callback(null, content);
25
+ callback(null, content, chainMap);
17
26
  return;
18
27
  }
19
28
  const request = this.resourcePath;
20
29
  if (request.startsWith('data:text/javascript') &&
21
30
  request.includes('__module_federation_bundler_runtime__')) {
22
- callback(null, content);
31
+ callback(null, content, chainMap);
32
+ return;
33
+ }
34
+ const normalizedRequest = (0, angular_rspack_compiler_1.toTypeScriptFileCacheKey)(request);
35
+ const cached = typescriptFileCache.get(normalizedRequest);
36
+ if (cached !== undefined) {
37
+ if (typeof cached !== 'string') {
38
+ callback(null, cached.code, cached.map);
39
+ return;
40
+ }
41
+ // A string entry is raw JavaScript emitted by the Angular compilation
42
+ // (allowJs); it always needs the JavaScript transformations applied.
43
+ javascriptTransformer
44
+ .transformData(normalizedRequest, cached, true, false)
45
+ .then((transformed) => {
46
+ const text = Buffer.from(transformed).toString('utf8');
47
+ const { code, map } = (0, inline_source_map_1.extractInlineSourceMap)(text);
48
+ // A newer emit may have replaced the entry while transforming;
49
+ // only store the result for the emit it was produced from.
50
+ if (typescriptFileCache.get(normalizedRequest) === cached) {
51
+ typescriptFileCache.set(normalizedRequest, { code, map });
52
+ }
53
+ callback(null, code, map);
54
+ }, (error) => {
55
+ // Fail the module instead of leaving the loader callback pending,
56
+ // which would hang the build.
57
+ callback(error instanceof Error ? error : new Error(String(error)));
58
+ });
23
59
  return;
24
60
  }
25
61
  if (!content.includes('@angular')) {
26
- callback(null, content);
62
+ callback(null, content, chainMap);
27
63
  return;
28
64
  }
29
- const existingTransform = typescriptFileCache.get(request);
30
- if (existingTransform) {
31
- const existingContents = typeof existingTransform === 'string'
32
- ? existingTransform
33
- : Buffer.from(existingTransform).toString('utf8');
34
- callback(null, existingContents);
65
+ // The chained map takes part in cache validity: the transformer reads
66
+ // the file's external map file itself and merges it into its output, so
67
+ // a cached transform is stale once that map changes, even though the
68
+ // file itself did not.
69
+ const serializedChainMap = chainMap ? JSON.stringify(chainMap) : undefined;
70
+ const cachedTransform = babelFileCache.get(normalizedRequest);
71
+ if (cachedTransform !== undefined &&
72
+ cachedTransform.chainedMap === serializedChainMap) {
73
+ callback(null, cachedTransform.code, cachedTransform.map);
35
74
  return;
36
75
  }
37
- javascriptTransformer.transformFile(request, false, false).then((contents) => {
38
- const transformedCode = Buffer.from(contents).toString('utf8');
39
- typescriptFileCache.set(request, transformedCode);
40
- callback(null, transformedCode);
76
+ // A mismatched entry means the chained map changed since the entry was
77
+ // produced. The transformer's persistent cache is keyed on the file
78
+ // bytes only, so `transformFile` would return the same stale output;
79
+ // hand it the current file data instead, which always runs the worker
80
+ // and re-reads the changed map file. A cold process has no entry to
81
+ // compare against, so a stale persistent entry can still be served
82
+ // there, matching the esbuild application builder's behavior.
83
+ const transformed = cachedTransform !== undefined
84
+ ? (0, promises_1.readFile)(request, 'utf8').then((data) => javascriptTransformer.transformData(request, data, false, false))
85
+ : javascriptTransformer.transformFile(request, false, false);
86
+ transformed.then((contents) => {
87
+ const text = Buffer.from(contents).toString('utf8');
88
+ const { code, map: extractedMap, strippedComment, } = (0, inline_source_map_1.extractInlineSourceMap)(text);
89
+ // The transformer's output carries a trailing sourcemap comment only
90
+ // when sourcemaps apply to this file: its own inline map for
91
+ // transformed output, the file's original comment when passed
92
+ // through untouched. A stripped comment without a usable inline map
93
+ // references an external map file, which source-map-loader has
94
+ // already read, so chain its map. No comment at all means sourcemaps
95
+ // are off for this file and nothing must be forwarded.
96
+ const map = extractedMap ?? (strippedComment ? serializedChainMap : undefined);
97
+ babelFileCache.set(normalizedRequest, {
98
+ code,
99
+ map,
100
+ chainedMap: serializedChainMap,
101
+ });
102
+ callback(null, code, map);
41
103
  }, (error) => {
42
104
  // Fail the module instead of leaving the loader callback pending,
43
105
  // which would hang the build.
@@ -1 +1 @@
1
- {"version":3,"file":"angular-transform.loader.d.ts","sourceRoot":"","sources":["../../../../src/lib/plugins/loaders/angular-transform.loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,6BAA6B,CAAC;AAKrC,MAAM,CAAC,OAAO,UAAU,MAAM,CAC5B,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,EAC5B,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE;IACJ,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,oBAAoB,EAAE,oBAAoB,CAAC;CAC5C,QAkDF"}
1
+ {"version":3,"file":"angular-transform.loader.d.ts","sourceRoot":"","sources":["../../../../src/lib/plugins/loaders/angular-transform.loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EAErB,MAAM,6BAA6B,CAAC;AAMrC,MAAM,CAAC,OAAO,UAAU,MAAM,CAC5B,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,EAC5B,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE;IACJ,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,oBAAoB,EAAE,oBAAoB,CAAC;CAC5C,QAuFF"}
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = loader;
4
- const path_1 = require("path");
5
4
  const models_1 = require("../../models");
6
5
  const angular_rspack_compiler_1 = require("@nx/angular-rspack-compiler");
6
+ const inline_source_map_1 = require("./inline-source-map");
7
7
  const _styleUrlsResolver = new angular_rspack_compiler_1.StyleUrlsResolver();
8
8
  const _templateUrlsResolver = new angular_rspack_compiler_1.TemplateUrlsResolver();
9
9
  function loader(content, opt) {
@@ -14,7 +14,7 @@ function loader(content, opt) {
14
14
  callback(null, content);
15
15
  }
16
16
  else {
17
- const { typescriptFileCache, angularCompilationFailed } = this._compilation[models_1.NG_RSPACK_SYMBOL_NAME]();
17
+ const { typescriptFileCache, javascriptTransformer, useTypeScriptTranspilation, angularCompilationFailed, resourceDependencies, } = this._compilation[models_1.NG_RSPACK_SYMBOL_NAME]();
18
18
  if (angularCompilationFailed) {
19
19
  // The Angular compilation failure is already reported as a compilation
20
20
  // error. Emit empty modules instead of raw TS sources that would only
@@ -22,26 +22,55 @@ function loader(content, opt) {
22
22
  callback(null, '');
23
23
  return;
24
24
  }
25
- const request = this.resourcePath.replace(/^[A-Z]:/, '');
26
- const normalizedRequest = (0, path_1.normalize)(request);
27
- const templateUrls = templateUrlsResolver.resolve(content, normalizedRequest);
28
- const styleUrls = styleUrlsResolver.resolve(content, normalizedRequest);
29
- for (const urlSet of [...templateUrls, ...styleUrls]) {
30
- // `urlSet` is a string where a relative path is joined with an
31
- // absolute path using the `|` symbol.
32
- // For example: `./app.component.html|/home/projects/analog/src/app/app.component.html`.
33
- const [, absoluteFileUrl] = urlSet.split('|');
34
- this.addDependency(absoluteFileUrl);
35
- }
36
- const contents = typescriptFileCache.get(normalizedRequest);
37
- if (contents === undefined) {
38
- callback(null, content);
39
- }
40
- else if (typeof contents === 'string') {
41
- callback(null, contents);
25
+ const normalizedRequest = (0, angular_rspack_compiler_1.toTypeScriptFileCacheKey)(this.resourcePath);
26
+ const resourceDeps = resourceDependencies?.get(normalizedRequest);
27
+ if (resourceDeps !== undefined) {
28
+ // The compiler tracked this file's template and stylesheet
29
+ // dependencies; registering them keeps the module rebuilding when a
30
+ // resource changes, at no parsing cost.
31
+ for (const dependency of resourceDeps) {
32
+ this.addDependency(dependency);
33
+ }
42
34
  }
43
35
  else {
44
- callback(null, Buffer.from(contents));
36
+ const templateUrls = templateUrlsResolver.resolve(content, normalizedRequest);
37
+ const styleUrls = styleUrlsResolver.resolve(content, normalizedRequest);
38
+ for (const urlSet of [...templateUrls, ...styleUrls]) {
39
+ // `urlSet` is a string where a relative path is joined with an
40
+ // absolute path using the `|` symbol.
41
+ // For example: `./app.component.html|/home/projects/analog/src/app/app.component.html`.
42
+ const [, absoluteFileUrl] = urlSet.split('|');
43
+ this.addDependency(absoluteFileUrl);
44
+ }
45
45
  }
46
+ const cached = typescriptFileCache.get(normalizedRequest);
47
+ if (cached !== undefined) {
48
+ if (typeof cached !== 'string') {
49
+ callback(null, cached.code, cached.map);
50
+ return;
51
+ }
52
+ // A string entry is a raw emit from the Angular compilation. Without
53
+ // TypeScript transpilation it is Angular-transformed TypeScript that
54
+ // the bundler transpiles as-is, matching `@angular/build`.
55
+ if (!useTypeScriptTranspilation) {
56
+ callback(null, cached);
57
+ return;
58
+ }
59
+ javascriptTransformer
60
+ .transformData(normalizedRequest, cached, true, false)
61
+ .then((transformed) => {
62
+ const text = Buffer.from(transformed).toString();
63
+ const { code, map } = (0, inline_source_map_1.extractInlineSourceMap)(text);
64
+ // A newer emit may have replaced the entry while transforming;
65
+ // only store the result for the emit it was produced from.
66
+ if (typescriptFileCache.get(normalizedRequest) === cached) {
67
+ typescriptFileCache.set(normalizedRequest, { code, map });
68
+ }
69
+ callback(null, code, map);
70
+ })
71
+ .catch((err) => callback(err));
72
+ return;
73
+ }
74
+ callback(null, content);
46
75
  }
47
76
  }
@@ -0,0 +1,34 @@
1
+ import type { RawSourceMap } from '@rspack/core';
2
+ export interface ExtractedSourceMap {
3
+ code: string;
4
+ /** The decoded sourcemap as a JSON string, when inline and usable. */
5
+ map: string | undefined;
6
+ /**
7
+ * Whether a trailing sourceMappingURL comment was stripped from the code,
8
+ * even when it yielded no usable map (an external file reference or a
9
+ * payload rspack would reject).
10
+ */
11
+ strippedComment: boolean;
12
+ }
13
+ /**
14
+ * Splits a trailing sourcemap comment off the given code so the map can be
15
+ * passed to the loader callback and chained into the final sourcemaps,
16
+ * mirroring how the esbuild pipeline consumes the inline sourcemaps of the
17
+ * Angular compilation and the JavaScript transformer. Comments that carry no
18
+ * usable inline map are still stripped: esbuild never emits input
19
+ * sourceMappingURL comments into the bundle.
20
+ */
21
+ export declare function extractInlineSourceMap(code: string): ExtractedSourceMap;
22
+ /**
23
+ * Whether rspack's sourcemap deserializer accepts this value as a
24
+ * loader-provided map. Rspack fails the module build on any map it cannot
25
+ * deserialize, even when sourcemaps are disabled, so externally authored maps
26
+ * (a package's own sourcemap) must pass this check before being handed to
27
+ * the loader callback. Mirrors rspack's deserialization rules: `mappings`
28
+ * must be a string; every other field is optional and may be null, but a
29
+ * present value must have the expected type, including array elements
30
+ * (`sources`, `names` and `sourcesContent` entries may be null; `ignoreList`
31
+ * entries are deserialized as unsigned 32-bit integers).
32
+ */
33
+ export declare function isForwardableSourceMap(value: unknown): value is RawSourceMap;
34
+ //# sourceMappingURL=inline-source-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-source-map.d.ts","sourceRoot":"","sources":["../../../../src/lib/plugins/loaders/inline-source-map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAiBjD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAqBvE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CA8B5E"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractInlineSourceMap = extractInlineSourceMap;
4
+ exports.isForwardableSourceMap = isForwardableSourceMap;
5
+ // A `sourceMappingURL` value: an inline base64 data URI (captured) or any
6
+ // other reference, e.g. an external `.js.map` file.
7
+ const SOURCE_MAP_URL_SOURCE = 'sourceMappingURL=(?:data:application\\/json[^,]*;base64,([A-Za-z0-9+/=]+)|[^\\r\\n]*?)';
8
+ // Matches a trailing sourcemap comment in the forms source-map-loader
9
+ // consumes: `//#`, the legacy `//@`, and their single-line block variants
10
+ // `/*# ... */` and `/*@ ... */`. The leading `\r?\n` consumes the newline
11
+ // preceding the comment so no stray `\r` is left on the stripped code. The
12
+ // inline base64 payload lands in group 1 (line comment) or group 2 (block
13
+ // comment).
14
+ const TRAILING_SOURCE_MAP_COMMENT_REGEX = new RegExp(`(?:\\r?\\n)?(?:\\/\\/[#@][ \\t]+${SOURCE_MAP_URL_SOURCE}|\\/\\*[#@][ \\t]+${SOURCE_MAP_URL_SOURCE}[ \\t]*\\*\\/)\\s*$`);
15
+ /**
16
+ * Splits a trailing sourcemap comment off the given code so the map can be
17
+ * passed to the loader callback and chained into the final sourcemaps,
18
+ * mirroring how the esbuild pipeline consumes the inline sourcemaps of the
19
+ * Angular compilation and the JavaScript transformer. Comments that carry no
20
+ * usable inline map are still stripped: esbuild never emits input
21
+ * sourceMappingURL comments into the bundle.
22
+ */
23
+ function extractInlineSourceMap(code) {
24
+ const match = code.match(TRAILING_SOURCE_MAP_COMMENT_REGEX);
25
+ if (match?.index === undefined) {
26
+ return { code, map: undefined, strippedComment: false };
27
+ }
28
+ const stripped = code.slice(0, match.index);
29
+ const inlinePayload = match[1] ?? match[2];
30
+ if (inlinePayload === undefined) {
31
+ // A reference to an external map file; there is nothing to decode.
32
+ return { code: stripped, map: undefined, strippedComment: true };
33
+ }
34
+ const map = Buffer.from(inlinePayload, 'base64').toString('utf8');
35
+ try {
36
+ if (!isForwardableSourceMap(JSON.parse(map))) {
37
+ return { code: stripped, map: undefined, strippedComment: true };
38
+ }
39
+ }
40
+ catch {
41
+ // A malformed map is unusable; still strip its comment from the code.
42
+ return { code: stripped, map: undefined, strippedComment: true };
43
+ }
44
+ return { code: stripped, map, strippedComment: true };
45
+ }
46
+ /**
47
+ * Whether rspack's sourcemap deserializer accepts this value as a
48
+ * loader-provided map. Rspack fails the module build on any map it cannot
49
+ * deserialize, even when sourcemaps are disabled, so externally authored maps
50
+ * (a package's own sourcemap) must pass this check before being handed to
51
+ * the loader callback. Mirrors rspack's deserialization rules: `mappings`
52
+ * must be a string; every other field is optional and may be null, but a
53
+ * present value must have the expected type, including array elements
54
+ * (`sources`, `names` and `sourcesContent` entries may be null; `ignoreList`
55
+ * entries are deserialized as unsigned 32-bit integers).
56
+ */
57
+ function isForwardableSourceMap(value) {
58
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
59
+ return false;
60
+ }
61
+ const map = value;
62
+ const isNullableString = (v) => v === null || typeof v === 'string';
63
+ const isOptional = (v, check) => v === undefined || v === null || check(v);
64
+ const isArrayOf = (v, check) => Array.isArray(v) && v.every(check);
65
+ return (typeof map.mappings === 'string' &&
66
+ isOptional(map.sources, (v) => isArrayOf(v, isNullableString)) &&
67
+ isOptional(map.names, (v) => isArrayOf(v, isNullableString)) &&
68
+ isOptional(map.sourcesContent, (v) => isArrayOf(v, isNullableString)) &&
69
+ isOptional(map.file, (v) => typeof v === 'string') &&
70
+ isOptional(map.sourceRoot, (v) => typeof v === 'string') &&
71
+ isOptional(map.debugId, (v) => typeof v === 'string') &&
72
+ isOptional(map.ignoreList, (v) => isArrayOf(v, (el) => typeof el === 'number' &&
73
+ Number.isInteger(el) &&
74
+ el >= 0 &&
75
+ el <= 0xffffffff)));
76
+ }
@@ -1,12 +1,30 @@
1
1
  import { Compiler, RspackPluginInstance } from '@rspack/core';
2
2
  import type { I18nOptions, NormalizedAngularRspackPluginOptions } from '../models';
3
+ import { AngularRspackPlugin } from './angular-rspack-plugin';
4
+ import { type SharedLicenseInputs } from './extract-licenses-plugin';
3
5
  export declare class NgRspackPlugin implements RspackPluginInstance {
4
6
  readonly pluginOptions: NormalizedAngularRspackPluginOptions;
5
7
  readonly isPlatformServer: boolean;
6
8
  readonly i18n: I18nOptions;
9
+ private readonly sharedLicenseInputs;
10
+ private readonly sharedAngularPlugin;
7
11
  constructor(pluginOptions: NormalizedAngularRspackPluginOptions, extraOptions: {
8
12
  platform: 'browser' | 'server';
9
13
  i18nOptions: I18nOptions;
14
+ /**
15
+ * License extraction inputs shared across the compilers of an SSR
16
+ * build, so the browser and server compilers emit the union instead of
17
+ * overwriting each other's licenses file.
18
+ */
19
+ sharedLicenseInputs?: SharedLicenseInputs;
20
+ /**
21
+ * Angular compilation shared across the compilers of an SSR build: the
22
+ * browser compiler owns it (its program includes the server entry
23
+ * points) and the server compiler consumes its output instead of
24
+ * running a second compilation. When absent, each compiler runs its
25
+ * own compilation.
26
+ */
27
+ sharedAngularPlugin?: AngularRspackPlugin;
10
28
  });
11
29
  apply(compiler: Compiler): void;
12
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ng-rspack.d.ts","sourceRoot":"","sources":["../../../src/lib/plugins/ng-rspack.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EAGR,oBAAoB,EACrB,MAAM,cAAc,CAAC;AAGtB,OAAO,KAAK,EACV,WAAW,EACX,oCAAoC,EACrC,MAAM,WAAW,CAAC;AAUnB,qBAAa,cAAe,YAAW,oBAAoB;IACzD,QAAQ,CAAC,aAAa,EAAE,oCAAoC,CAAC;IAC7D,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAE3B,YACE,aAAa,EAAE,oCAAoC,EACnD,YAAY,EAAE;QACZ,QAAQ,EAAE,SAAS,GAAG,QAAQ,CAAC;QAC/B,WAAW,EAAE,WAAW,CAAC;KAC1B,EAKF;IAED,KAAK,CAAC,QAAQ,EAAE,QAAQ,QA2JvB;CACF"}
1
+ {"version":3,"file":"ng-rspack.d.ts","sourceRoot":"","sources":["../../../src/lib/plugins/ng-rspack.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EAGR,oBAAoB,EACrB,MAAM,cAAc,CAAC;AAGtB,OAAO,KAAK,EACV,WAAW,EACX,oCAAoC,EACrC,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE9D,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AAMnC,qBAAa,cAAe,YAAW,oBAAoB;IACzD,QAAQ,CAAC,aAAa,EAAE,oCAAoC,CAAC;IAC7D,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAkC;IACtE,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAkC;IAEtE,YACE,aAAa,EAAE,oCAAoC,EACnD,YAAY,EAAE;QACZ,QAAQ,EAAE,SAAS,GAAG,QAAQ,CAAC;QAC/B,WAAW,EAAE,WAAW,CAAC;QACzB;;;;WAIG;QACH,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;QAC1C;;;;;;WAMG;QACH,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;KAC3C,EAOF;IAED,KAAK,CAAC,QAAQ,EAAE,QAAQ,QA2MvB;CACF"}