mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-17 11:03:53 +08:00
fix(@angular-devkit/build-angular): update sourcemaps when rebasing Sass url() functions in esbuild builder
When using the experimental esbuild-based browser application builder with Sass and sourcemaps, the final sourcemap for an input Sass stylesheet will now contain the original content for any `url` functions that were rebased to support bundling. This required generating internal intermediate source maps for each imported stylesheet that was modified with rebased URLs and then merging these intermediate source maps with the final Sass generated source map. This process only occurs when stylesheet sourcemaps are enabled.
This commit is contained in:
parent
0cff3e09cd
commit
f7ad20c465
@ -154,6 +154,7 @@ ts_library(
|
||||
"@npm//less-loader",
|
||||
"@npm//license-webpack-plugin",
|
||||
"@npm//loader-utils",
|
||||
"@npm//magic-string",
|
||||
"@npm//mini-css-extract-plugin",
|
||||
"@npm//minimatch",
|
||||
"@npm//ng-packagr",
|
||||
|
@ -41,6 +41,7 @@
|
||||
"less-loader": "11.1.0",
|
||||
"license-webpack-plugin": "4.0.2",
|
||||
"loader-utils": "3.2.0",
|
||||
"magic-string": "0.26.7",
|
||||
"mini-css-extract-plugin": "2.6.1",
|
||||
"minimatch": "5.1.0",
|
||||
"open": "8.4.0",
|
||||
|
@ -6,6 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import { RawSourceMap } from '@ampproject/remapping';
|
||||
import MagicString from 'magic-string';
|
||||
import { Dirent, readFileSync, readdirSync } from 'node:fs';
|
||||
import { basename, dirname, extname, join, relative } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
@ -31,8 +33,13 @@ const URL_REGEXP = /url(?:\(\s*(['"]?))(.*?)(?:\1\s*\))/g;
|
||||
abstract class UrlRebasingImporter implements Importer<'sync'> {
|
||||
/**
|
||||
* @param entryDirectory The directory of the entry stylesheet that was passed to the Sass compiler.
|
||||
* @param rebaseSourceMaps When provided, rebased files will have an intermediate sourcemap added to the Map
|
||||
* which can be used to generate a final sourcemap that contains original sources.
|
||||
*/
|
||||
constructor(private entryDirectory: string) {}
|
||||
constructor(
|
||||
private entryDirectory: string,
|
||||
private rebaseSourceMaps?: Map<string, RawSourceMap>,
|
||||
) {}
|
||||
|
||||
abstract canonicalize(url: string, options: { fromImport: boolean }): URL | null;
|
||||
|
||||
@ -46,6 +53,7 @@ abstract class UrlRebasingImporter implements Importer<'sync'> {
|
||||
|
||||
let match;
|
||||
URL_REGEXP.lastIndex = 0;
|
||||
let updatedContents;
|
||||
while ((match = URL_REGEXP.exec(contents))) {
|
||||
const originalUrl = match[2];
|
||||
|
||||
@ -60,10 +68,21 @@ abstract class UrlRebasingImporter implements Importer<'sync'> {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/CSS/url#syntax
|
||||
const rebasedUrl = './' + rebasedPath.replace(/\\/g, '/').replace(/[()\s'"]/g, '\\$&');
|
||||
|
||||
contents =
|
||||
contents.slice(0, match.index) +
|
||||
`url(${rebasedUrl})` +
|
||||
contents.slice(match.index + match[0].length);
|
||||
updatedContents ??= new MagicString(contents);
|
||||
updatedContents.update(match.index, match.index + match[0].length, `url(${rebasedUrl})`);
|
||||
}
|
||||
|
||||
if (updatedContents) {
|
||||
contents = updatedContents.toString();
|
||||
if (this.rebaseSourceMaps) {
|
||||
// Generate an intermediate source map for the rebasing changes
|
||||
const map = updatedContents.generateMap({
|
||||
hires: true,
|
||||
includeContent: true,
|
||||
source: canonicalUrl.href,
|
||||
});
|
||||
this.rebaseSourceMaps.set(canonicalUrl.href, map as RawSourceMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,8 +113,12 @@ abstract class UrlRebasingImporter implements Importer<'sync'> {
|
||||
* the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file.
|
||||
*/
|
||||
export class RelativeUrlRebasingImporter extends UrlRebasingImporter {
|
||||
constructor(entryDirectory: string, private directoryCache = new Map<string, Dirent[]>()) {
|
||||
super(entryDirectory);
|
||||
constructor(
|
||||
entryDirectory: string,
|
||||
private directoryCache = new Map<string, Dirent[]>(),
|
||||
rebaseSourceMaps?: Map<string, RawSourceMap>,
|
||||
) {
|
||||
super(entryDirectory, rebaseSourceMaps);
|
||||
}
|
||||
|
||||
canonicalize(url: string, options: { fromImport: boolean }): URL | null {
|
||||
@ -238,9 +261,10 @@ export class ModuleUrlRebasingImporter extends RelativeUrlRebasingImporter {
|
||||
constructor(
|
||||
entryDirectory: string,
|
||||
directoryCache: Map<string, Dirent[]>,
|
||||
rebaseSourceMaps: Map<string, RawSourceMap> | undefined,
|
||||
private finder: FileImporter<'sync'>['findFileUrl'],
|
||||
) {
|
||||
super(entryDirectory, directoryCache);
|
||||
super(entryDirectory, directoryCache, rebaseSourceMaps);
|
||||
}
|
||||
|
||||
override canonicalize(url: string, options: { fromImport: boolean }): URL | null {
|
||||
@ -263,9 +287,10 @@ export class LoadPathsUrlRebasingImporter extends RelativeUrlRebasingImporter {
|
||||
constructor(
|
||||
entryDirectory: string,
|
||||
directoryCache: Map<string, Dirent[]>,
|
||||
rebaseSourceMaps: Map<string, RawSourceMap> | undefined,
|
||||
private loadPaths: Iterable<string>,
|
||||
) {
|
||||
super(entryDirectory, directoryCache);
|
||||
super(entryDirectory, directoryCache, rebaseSourceMaps);
|
||||
}
|
||||
|
||||
override canonicalize(url: string, options: { fromImport: boolean }): URL | null {
|
||||
|
@ -145,7 +145,7 @@ export class SassWorkerImplementation {
|
||||
|
||||
const callback: RenderCallback = (error, result) => {
|
||||
if (error) {
|
||||
const url = error?.span.url as string | undefined;
|
||||
const url = error.span?.url as string | undefined;
|
||||
if (url) {
|
||||
error.span.url = pathToFileURL(url);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import mergeSourceMaps, { RawSourceMap } from '@ampproject/remapping';
|
||||
import { Dirent } from 'node:fs';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
@ -82,6 +83,7 @@ parentPort.on('message', (message: RenderRequestMessage) => {
|
||||
| undefined;
|
||||
try {
|
||||
const directoryCache = new Map<string, Dirent[]>();
|
||||
const rebaseSourceMaps = options.sourceMap ? new Map<string, RawSourceMap>() : undefined;
|
||||
if (hasImporter) {
|
||||
// When a custom importer function is present, the importer request must be proxied
|
||||
// back to the main thread where it can be executed.
|
||||
@ -105,6 +107,7 @@ parentPort.on('message', (message: RenderRequestMessage) => {
|
||||
new ModuleUrlRebasingImporter(
|
||||
entryDirectory,
|
||||
directoryCache,
|
||||
rebaseSourceMaps,
|
||||
proxyImporter.findFileUrl,
|
||||
),
|
||||
)
|
||||
@ -116,7 +119,12 @@ parentPort.on('message', (message: RenderRequestMessage) => {
|
||||
options.importers ??= [];
|
||||
options.importers.push(
|
||||
sassBindWorkaround(
|
||||
new LoadPathsUrlRebasingImporter(entryDirectory, directoryCache, options.loadPaths),
|
||||
new LoadPathsUrlRebasingImporter(
|
||||
entryDirectory,
|
||||
directoryCache,
|
||||
rebaseSourceMaps,
|
||||
options.loadPaths,
|
||||
),
|
||||
),
|
||||
);
|
||||
options.loadPaths = undefined;
|
||||
@ -125,7 +133,7 @@ parentPort.on('message', (message: RenderRequestMessage) => {
|
||||
let relativeImporter;
|
||||
if (rebase) {
|
||||
relativeImporter = sassBindWorkaround(
|
||||
new RelativeUrlRebasingImporter(entryDirectory, directoryCache),
|
||||
new RelativeUrlRebasingImporter(entryDirectory, directoryCache, rebaseSourceMaps),
|
||||
);
|
||||
}
|
||||
|
||||
@ -151,6 +159,17 @@ parentPort.on('message', (message: RenderRequestMessage) => {
|
||||
: undefined,
|
||||
});
|
||||
|
||||
if (result.sourceMap && rebaseSourceMaps?.size) {
|
||||
// Merge the intermediate rebasing source maps into the final Sass generated source map.
|
||||
// Casting is required due to small but compatible differences in typings between the packages.
|
||||
result.sourceMap = mergeSourceMaps(
|
||||
result.sourceMap as unknown as RawSourceMap,
|
||||
// To prevent an infinite lookup loop, skip getting the source when the rebasing source map
|
||||
// is referencing its original self.
|
||||
(file, context) => (file !== context.importer ? rebaseSourceMaps.get(file) : null),
|
||||
) as unknown as typeof result.sourceMap;
|
||||
}
|
||||
|
||||
parentPort.postMessage({
|
||||
id,
|
||||
warnings,
|
||||
|
Loading…
x
Reference in New Issue
Block a user