diff --git a/packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts b/packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts index 0a3ae3059..17b31167a 100644 --- a/packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts +++ b/packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts @@ -153,6 +153,7 @@ export function angular(options?: PluginOptions): Plugin[] { let watchMode = false; let testWatchMode = false; let inlineComponentStyles: Map | undefined; + let externalComponentStyles: Map | undefined; const sourceFileCache = new SourceFileCache(); const isTest = process.env['NODE_ENV'] === 'test' || !!process.env['VITEST']; const isStackBlitz = !!process.versions['webcontainer']; @@ -387,7 +388,7 @@ export function angular(options?: PluginOptions): Plugin[] { { type: 'css-update', timestamp: Date.now(), - path: isDirect.id, + path: isDirect.url, acceptedPath: isDirect.file, }, ], @@ -460,15 +461,24 @@ export function angular(options?: PluginOptions): Plugin[] { )}?raw`; } + // Map angular external styleUrls to the source file + if (isComponentStyleSheet(id)) { + const componentStyles = externalComponentStyles?.get( + getFilenameFromPath(id) + ); + if (componentStyles) { + return componentStyles + new URL(id, 'http://localhost').search; + } + } + return undefined; }, async load(id, options) { + // Map angular inline styles to the source text if (isComponentStyleSheet(id)) { - const filename = new URL(id, 'http://localhost').pathname.replace( - /^\//, - '' + const componentStyles = inlineComponentStyles?.get( + getFilenameFromPath(id) ); - const componentStyles = inlineComponentStyles?.get(filename); if (componentStyles) { return componentStyles; } @@ -781,12 +791,16 @@ export function angular(options?: PluginOptions): Plugin[] { inlineComponentStyles = tsCompilerOptions['externalRuntimeStyles'] ? new Map() : undefined; + externalComponentStyles = tsCompilerOptions['externalRuntimeStyles'] + ? new Map() + : undefined; augmentHostWithResources(host, styleTransform, { inlineStylesExtension: pluginOptions.inlineStylesExtension, supportAnalogFormat: pluginOptions.supportAnalogFormat, isProd, markdownTemplateTransforms: pluginOptions.markdownTemplateTransforms, inlineComponentStyles, + externalComponentStyles, }); } } @@ -1009,3 +1023,12 @@ function getComponentStyleSheetMeta(id: string): { ] as 'emulated' | 'shadow' | 'none', }; } + +/** + * Removes leading / and query string from a url path + * e.g. /foo.scss?direct&ngcomp=ng-c3153525609&e=0 returns foo.scss + * @param id + */ +function getFilenameFromPath(id: string): string { + return new URL(id, 'http://localhost').pathname.replace(/^\//, ''); +} diff --git a/packages/vite-plugin-angular/src/lib/host.ts b/packages/vite-plugin-angular/src/lib/host.ts index 8b0ac807b..281a00b0d 100644 --- a/packages/vite-plugin-angular/src/lib/host.ts +++ b/packages/vite-plugin-angular/src/lib/host.ts @@ -12,6 +12,7 @@ import { MarkdownTemplateTransform } from './authoring/markdown-transform.js'; import { createRequire } from 'node:module'; import { createHash } from 'node:crypto'; +import path from 'node:path'; const require = createRequire(import.meta.url); @@ -33,6 +34,7 @@ export function augmentHostWithResources( isProd?: boolean; markdownTemplateTransforms?: MarkdownTemplateTransform[]; inlineComponentStyles?: Map; + externalComponentStyles?: Map; } ) { const ts = require('typescript'); @@ -40,7 +42,6 @@ export function augmentHostWithResources( const baseGetSourceFile = ( resourceHost as ts.CompilerHost ).getSourceFile.bind(resourceHost); - const externalStylesheets = new Map(); if (options.supportAnalogFormat) { (resourceHost as ts.CompilerHost).getSourceFile = ( @@ -187,6 +188,33 @@ export function augmentHostWithResources( return null; }; + + resourceHost.resourceNameToFileName = function ( + resourceName, + containingFile + ) { + const resolvedPath = path.join(path.dirname(containingFile), resourceName); + + // All resource names that have template file extensions are assumed to be templates + if ( + !options.externalComponentStyles || + hasTemplateExtension(resolvedPath) + ) { + return resolvedPath; + } + + // For external stylesheets, create a unique identifier and store the mapping + let externalId = options.externalComponentStyles.get(resolvedPath); + if (externalId === undefined) { + externalId = createHash('sha256').update(resolvedPath).digest('hex'); + } + + const filename = externalId + path.extname(resolvedPath); + + options.externalComponentStyles.set(filename, resolvedPath); + + return filename; + }; } export function augmentProgramWithVersioning(program: ts.Program): void { @@ -261,3 +289,16 @@ export function mergeTransformers( return result; } + +function hasTemplateExtension(file: string): boolean { + const extension = path.extname(file).toLowerCase(); + + switch (extension) { + case '.htm': + case '.html': + case '.svg': + return true; + } + + return false; +}