diff --git a/.changeset/fix-template-css-diagnostics-sourcemap.md b/.changeset/fix-template-css-diagnostics-sourcemap.md new file mode 100644 index 0000000000..853d812bb3 --- /dev/null +++ b/.changeset/fix-template-css-diagnostics-sourcemap.md @@ -0,0 +1,3 @@ +--- + +--- diff --git a/packages/webpack/template-webpack-plugin/etc/template-webpack-plugin.api.md b/packages/webpack/template-webpack-plugin/etc/template-webpack-plugin.api.md index 9c04564508..a7ca0cf6db 100644 --- a/packages/webpack/template-webpack-plugin/etc/template-webpack-plugin.api.md +++ b/packages/webpack/template-webpack-plugin/etc/template-webpack-plugin.api.md @@ -124,9 +124,9 @@ export function processTasmCSSDiagnostics(input: ProcessTasmCSSDiagnosticsOption // @public export interface ProcessTasmCSSDiagnosticsOptions { - compilation: Compilation; context: string; cssDiagnostics: unknown; + cssSourceMaps: string[]; emittedWarnings?: Set | undefined; fileExists?: ((path: string) => boolean) | undefined; } @@ -159,6 +159,7 @@ export interface TemplateHooks { beforeEmit: AsyncSeriesWaterfallHook<{ finalEncodeOptions: EncodeOptions; debugInfo: string; + cssDiagnostics?: string; template: Buffer; outputName: string; mainThreadAssets: Asset[]; @@ -180,6 +181,7 @@ export interface TemplateHooks { }, { buffer: Buffer; debugInfo: string; + cssDiagnostics?: string; }>; } diff --git a/packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts b/packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts index b22c898795..3532ea12ad 100644 --- a/packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts +++ b/packages/webpack/template-webpack-plugin/src/LynxEncodePlugin.ts @@ -4,7 +4,10 @@ import type { Chunk, Compiler } from 'webpack'; -import { processTasmCSSDiagnostics } from './cssDiagnostics.js'; +import { + collectCSSSourceMapContents, + processTasmCSSDiagnostics, +} from './cssDiagnostics.js'; import { LynxTemplatePlugin } from './LynxTemplatePlugin.js'; import { getRequireModuleAsyncCachePolyfill } from './polyfill/requireModuleAsync.js'; @@ -234,9 +237,20 @@ export class LynxEncodePluginImpl { encode(encodeOptions), ); + return { + buffer, + debugInfo: lepus_debug, + cssDiagnostics: css_diagnostics as string, + }; + }); + + templateHooks.beforeEmit.tapPromise({ + name: this.name, + stage: LynxEncodePlugin.BEFORE_EMIT_STAGE, + }, async (args) => { const resolvedDiagnostics = processTasmCSSDiagnostics({ - cssDiagnostics: css_diagnostics, - compilation, + cssDiagnostics: args.cssDiagnostics, + cssSourceMaps: collectCSSSourceMapContents(args.cssChunks), context: compiler.context, emittedWarnings: emittedCSSDiagnosticWarnings, }); @@ -245,7 +259,7 @@ export class LynxEncodePluginImpl { const webpackWarning = new compiler.webpack.WebpackError( diagnostic.message, ); - webpackWarning.stack = ''; + webpackWarning.hideStack = true; if ( diagnostic.sourceFile @@ -272,7 +286,7 @@ export class LynxEncodePluginImpl { }); } - return { buffer, debugInfo: lepus_debug }; + return args; }); }); } diff --git a/packages/webpack/template-webpack-plugin/src/LynxTemplatePlugin.ts b/packages/webpack/template-webpack-plugin/src/LynxTemplatePlugin.ts index 8e0db2ebfd..ad7eff0544 100644 --- a/packages/webpack/template-webpack-plugin/src/LynxTemplatePlugin.ts +++ b/packages/webpack/template-webpack-plugin/src/LynxTemplatePlugin.ts @@ -112,7 +112,7 @@ export interface TemplateHooks { encodeOptions: EncodeOptions; intermediate?: string; }, - { buffer: Buffer; debugInfo: string } + { buffer: Buffer; debugInfo: string; cssDiagnostics?: string } >; /** @@ -123,6 +123,7 @@ export interface TemplateHooks { beforeEmit: AsyncSeriesWaterfallHook<{ finalEncodeOptions: EncodeOptions; debugInfo: string; + cssDiagnostics?: string; template: Buffer; outputName: string; mainThreadAssets: Asset[]; @@ -912,7 +913,7 @@ class LynxTemplatePluginImpl { } try { - const { buffer, debugInfo } = await hooks.encode.promise({ + const { buffer, debugInfo, cssDiagnostics } = await hooks.encode.promise({ encodeOptions: resolvedEncodeOptions, intermediate, }); @@ -926,6 +927,7 @@ class LynxTemplatePluginImpl { const { template } = await hooks.beforeEmit.promise({ finalEncodeOptions: resolvedEncodeOptions, debugInfo, + ...(cssDiagnostics === undefined ? {} : { cssDiagnostics }), template: buffer, outputName: filename, mainThreadAssets: [lepusCode.root, ...encodeData.lepusCode.chunks] diff --git a/packages/webpack/template-webpack-plugin/src/cssDiagnostics.ts b/packages/webpack/template-webpack-plugin/src/cssDiagnostics.ts index 57e7294474..40678eeb88 100644 --- a/packages/webpack/template-webpack-plugin/src/cssDiagnostics.ts +++ b/packages/webpack/template-webpack-plugin/src/cssDiagnostics.ts @@ -8,7 +8,7 @@ import { fileURLToPath } from 'node:url'; import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping'; import type { SourceMapInput } from '@jridgewell/trace-mapping'; -import type { Compilation } from 'webpack'; +import type { Asset } from 'webpack'; import type * as CSS from '@lynx-js/css-serializer'; @@ -72,9 +72,9 @@ export interface ProcessTasmCSSDiagnosticsOptions { */ cssDiagnostics: unknown; /** - * The webpack compilation containing the main CSS asset. + * CSS source map contents from the CSS chunks for the current template. */ - compilation: Compilation; + cssSourceMaps: string[]; /** * The webpack compiler context used to resolve relative source paths. */ @@ -96,7 +96,7 @@ export interface ProcessTasmCSSDiagnosticsOptions { */ export function processTasmCSSDiagnostics({ cssDiagnostics, - compilation, + cssSourceMaps, context, emittedWarnings, fileExists, @@ -108,7 +108,7 @@ export function processTasmCSSDiagnostics({ const resolveOptions: Parameters[0] = { cssDiagnostics: diagnostics, - mainCSSSourceMap: getMainCSSSourceMap(compilation), + mainCSSSourceMaps: parseCSSSourceMaps(cssSourceMaps), context, }; if (fileExists !== undefined) { @@ -127,13 +127,13 @@ export function extractTasmCSSDiagnostics(value: unknown): TasmCSSDiagnostic[] { } try { - const parsed = JSON.parse(value) as unknown as TasmCSSDiagnostic[]; + const parsed = JSON.parse(value) as unknown; if (!Array.isArray(parsed)) { return []; } return parsed - .map((element) => normalizeTasmCSSDiagnostic(element)) + .map(element => normalizeTasmCSSDiagnostic(element)) .filter((diagnostic): diagnostic is TasmCSSDiagnostic => ( diagnostic !== null )); @@ -144,60 +144,65 @@ export function extractTasmCSSDiagnostics(value: unknown): TasmCSSDiagnostic[] { export function resolveTasmCSSDiagnostics({ cssDiagnostics, - mainCSSSourceMap, + mainCSSSourceMaps, context, fileExists = existsSync, }: { cssDiagnostics: TasmCSSDiagnostic[]; - mainCSSSourceMap: CSS.CSSSourceMap | undefined; + mainCSSSourceMaps: CSS.CSSSourceMap[]; context: string; fileExists?: (path: string) => boolean; }): ResolvedTasmCSSDiagnostic[] { - if (!mainCSSSourceMap) { + if (mainCSSSourceMaps.length === 0) { return cssDiagnostics.map(diagnostic => ({ ...diagnostic, message: formatTasmCSSDiagnosticMessage(diagnostic), })); } - const traceMap = new TraceMap(mainCSSSourceMap as SourceMapInput); + const traceMaps = mainCSSSourceMaps.map(sourceMap => ({ + sourceMap, + traceMap: new TraceMap(sourceMap as SourceMapInput), + })); return cssDiagnostics.map(diagnostic => { - const mapped = originalPositionFor(traceMap, { - line: diagnostic.line, - column: Math.max(diagnostic.column - 1, 0), - }); - const message = formatTasmCSSDiagnosticMessage(diagnostic); - if ( - mapped.source === null - || mapped.line === null - || mapped.column === null - ) { - return { - ...diagnostic, - message, - }; - } - const sourceFile = normalizeTasmSourcePath( - mapped.source, - mainCSSSourceMap, - context, - ); - if (!sourceFile || !fileExists(sourceFile)) { + for (const { sourceMap, traceMap } of traceMaps) { + const mapped = originalPositionFor(traceMap, { + line: diagnostic.line, + column: Math.max(diagnostic.column - 1, 0), + }); + + if ( + mapped.source === null + || mapped.line === null + || mapped.column === null + ) { + continue; + } + + const sourceFile = normalizeTasmSourcePath( + mapped.source, + sourceMap, + context, + ); + if (!sourceFile || !fileExists(sourceFile)) { + continue; + } + return { ...diagnostic, message, + sourceFile, + sourceLine: mapped.line, + sourceColumn: mapped.column + 1, }; } return { ...diagnostic, message, - sourceFile, - sourceLine: mapped.line, - sourceColumn: mapped.column + 1, }; }); } @@ -225,7 +230,43 @@ export function dedupeTasmCSSDiagnostics( }); } -type Asset = ReturnType[number]; +export function collectCSSSourceMapContents( + cssChunks: Asset[] | undefined, +): string[] { + const sourceMaps: string[] = []; + + if (!cssChunks || cssChunks.length === 0) { + return sourceMaps; + } + + cssChunks.forEach((cssChunk) => { + const sourceMap = normalizeCSSSourceMap(cssChunk.source.map?.()); + if (sourceMap) { + sourceMaps.push(JSON.stringify(sourceMap)); + } + }); + + return Array.from(new Set(sourceMaps)); +} + +function parseCSSSourceMaps(cssSourceMaps: string[]): CSS.CSSSourceMap[] { + return cssSourceMaps.reduce((parsed, cssSourceMap) => { + if (cssSourceMap.trim() === '') { + return parsed; + } + + try { + const sourceMap = JSON.parse(cssSourceMap) as unknown; + if (isCSSSourceMap(sourceMap)) { + parsed.push(sourceMap); + } + } catch { + // Ignore invalid source map payloads. + } + + return parsed; + }, []); +} function normalizeCSSSourceMap( sourceMap: ReturnType | undefined, @@ -237,21 +278,16 @@ function normalizeCSSSourceMap( return sourceMap; } -export function getMainCSSSourceMap( - compilation: Compilation, -): CSS.CSSSourceMap | undefined { - for (const asset of compilation.getAssets()) { - if (!asset.name.endsWith('.css')) { - continue; - } - - const sourceMap = normalizeCSSSourceMap(asset.source.map?.()); - if (sourceMap) { - return sourceMap; - } +function isCSSSourceMap(value: unknown): value is CSS.CSSSourceMap { + if (!isRecord(value)) { + return false; } - return undefined; + return ( + typeof value['version'] === 'number' + && typeof value['mappings'] === 'string' + && Array.isArray(value['sources']) + ); } function normalizeTasmCSSDiagnostic(value: unknown): TasmCSSDiagnostic | null { diff --git a/packages/webpack/template-webpack-plugin/test/basic.test.ts b/packages/webpack/template-webpack-plugin/test/basic.test.ts index e45f214555..0cac2c646c 100644 --- a/packages/webpack/template-webpack-plugin/test/basic.test.ts +++ b/packages/webpack/template-webpack-plugin/test/basic.test.ts @@ -1,11 +1,17 @@ // Copyright 2024 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. -import { dirname } from 'node:path'; +import { readFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { TraceMap, generatedPositionFor } from '@jridgewell/trace-mapping'; +import type { SourceMapInput } from '@jridgewell/trace-mapping'; +import { SyncHook } from '@rspack/lite-tapable'; import { describe, expect, test } from '@rstest/core'; import webpack from 'webpack'; +import type { Asset, WebpackError } from 'webpack'; +import { CssExtractWebpackPlugin } from '../../css-extract-webpack-plugin/lib/index.js'; import { LynxEncodePlugin, LynxTemplatePlugin } from '../src/index.js'; import { getRequireModuleAsyncCachePolyfill } from '../src/polyfill/requireModuleAsync.js'; @@ -51,6 +57,256 @@ globalThis.renderPage = function() { expect(assets?.find(i => i.name === 'main.js')).not.toBeUndefined(); expect(assets?.find(i => i.name === 'main.lepus')).not.toBeUndefined(); }); + + test('emits css diagnostics during beforeEmit with current css chunk source maps', async () => { + const context = dirname(new URL(import.meta.url).pathname); + const compiler = { + context, + options: { + mode: 'production', + }, + hooks: { + thisCompilation: new SyncHook(['compilation']), + }, + webpack, + } as unknown as webpack.Compiler; + const compilation = { + warnings: [], + errors: [], + chunks: [], + outputOptions: {}, + hooks: { + processAssets: { + tap: () => void 0, + }, + }, + deleteAsset: () => void 0, + } as unknown as webpack.Compilation; + const compilationParams = {} as Parameters< + typeof compiler.hooks.thisCompilation.call + >[1]; + + new LynxEncodePlugin().apply(compiler); + compiler.hooks.thisCompilation.call(compilation, compilationParams); + + const hooks = LynxTemplatePlugin.getLynxTemplatePluginHooks(compilation); + + expect(compilation.warnings).toEqual([]); + + await hooks.beforeEmit.promise({ + finalEncodeOptions: { + manifest: {}, + compilerOptions: {}, + lepusCode: { + root: undefined, + lepusChunk: {}, + filename: undefined, + }, + customSections: {}, + }, + debugInfo: '', + cssDiagnostics: + '[{"type":"property","name":"unknown-prop","line":2,"column":10}]', + template: Buffer.from(''), + outputName: 'main.bundle', + mainThreadAssets: [], + cssChunks: [{ + name: 'main.css', + info: {}, + source: { + map: () => ({ + version: 3, + file: '.rspeedy/second/second.css', + sources: ['webpack:/basic.test.ts'], + sourcesContent: [ + '.foo {\n unknown-prop: red;\n}\n', + ], + names: [], + mappings: 'AAAA;EACE,kBAAkB;AACpB', + }), + }, + } as never], + entryNames: ['main'], + }); + + expect(compilation.warnings).toHaveLength(1); + expect(compilation.warnings[0]?.message).toBe( + 'Unsupported property "unknown-prop" was removed during template encode.', + ); + expect((compilation.warnings[0] as WebpackError)?.file).toBe( + `${context}/basic.test.ts`, + ); + expect((compilation.warnings[0] as WebpackError)?.loc).toEqual({ + start: { + line: 2, + column: 3, + }, + }); + }); + + test('maps css diagnostics to the matching source map for each entry', async () => { + const context = join( + dirname(new URL(import.meta.url).pathname), + 'fixtures', + 'css-diagnostics-mpa', + ); + const outputPath = join(context, 'dist'); + const stats = await runWebpack({ + context, + mode: 'development', + devtool: 'source-map', + target: 'node', + entry: { + a: './a.js', + b: './b.js', + }, + output: { + filename: '[name]/[name].js', + iife: false, + publicPath: '/', + path: outputPath, + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + CssExtractWebpackPlugin.loader, + { + loader: 'css-loader', + options: { + sourceMap: true, + }, + }, + ], + }, + ], + }, + plugins: [ + new CssExtractWebpackPlugin({ + filename: '[name]/[name].css', + }), + new LynxEncodePlugin(), + ], + }); + + expect(stats.compilation.errors).toEqual([]); + expect(stats.compilation.warnings).toEqual([]); + + const hooks = LynxTemplatePlugin.getLynxTemplatePluginHooks( + stats.compilation, + ); + const aCssAsset = createCSSAsset( + 'a/a.css', + JSON.parse(await readFile(join(outputPath, 'a/a.css.map'), 'utf-8')), + ); + const bCssAsset = createCSSAsset( + 'b/b.css', + JSON.parse(await readFile(join(outputPath, 'b/b.css.map'), 'utf-8')), + ); + const aSourceMap = aCssAsset.source.map?.(); + const bSourceMap = bCssAsset.source.map?.(); + + expect(aSourceMap).toBeDefined(); + expect(Array.isArray(aSourceMap)).toBe(false); + expect(bSourceMap).toBeDefined(); + expect(Array.isArray(bSourceMap)).toBe(false); + + if (!aSourceMap || Array.isArray(aSourceMap)) { + throw new Error('Missing source map for a/a.css'); + } + if (!bSourceMap || Array.isArray(bSourceMap)) { + throw new Error('Missing source map for b/b.css'); + } + + const aSource = aSourceMap.sources.find(source => source.endsWith('a.css')); + const bSource = bSourceMap.sources.find(source => source.endsWith('b.css')); + + expect(aSource).toBeDefined(); + expect(bSource).toBeDefined(); + + if (!aSource) { + throw new Error('Missing source a.css in a/a.css'); + } + if (!bSource) { + throw new Error('Missing source b.css in b/b.css'); + } + + const aGenerated = generatedPositionFor( + new TraceMap(aSourceMap as SourceMapInput), + { + source: aSource, + line: 2, + column: 2, + }, + ); + const bGenerated = generatedPositionFor( + new TraceMap(bSourceMap as SourceMapInput), + { + source: bSource, + line: 2, + column: 2, + }, + ); + + expect(aGenerated.line).not.toBeNull(); + expect(aGenerated.column).not.toBeNull(); + expect(bGenerated.line).not.toBeNull(); + expect(bGenerated.column).not.toBeNull(); + + if (aGenerated.line === null || aGenerated.column === null) { + throw new Error('Missing generated position for a/a.css'); + } + if (bGenerated.line === null || bGenerated.column === null) { + throw new Error('Missing generated position for b/b.css'); + } + + await hooks.beforeEmit.promise({ + ...createBeforeEmitArgs('a/template.js'), + cssDiagnostics: createCSSDiagnosticsJSON({ + type: 'property', + name: 'unknown-a', + line: aGenerated.line, + column: aGenerated.column + 1, + }), + cssChunks: [aCssAsset], + entryNames: ['a'], + }); + await hooks.beforeEmit.promise({ + ...createBeforeEmitArgs('b/template.js'), + cssDiagnostics: createCSSDiagnosticsJSON({ + type: 'property', + name: 'unknown-b', + line: bGenerated.line, + column: bGenerated.column + 1, + }), + cssChunks: [bCssAsset], + entryNames: ['b'], + }); + + expect( + stats.compilation.warnings.map((warning) => ({ + message: warning.message, + file: (warning as WebpackError).file, + })), + ).toEqual([ + { + message: + 'Unsupported property "unknown-a" was removed during template encode.', + file: `${context}/a.css`, + }, + { + message: + 'Unsupported property "unknown-b" was removed during template encode.', + file: `${context}/b.css`, + }, + ]); + expect(stats.compilation.warnings.map((warning) => warning.message)) + .toEqual([ + 'Unsupported property "unknown-a" was removed during template encode.', + 'Unsupported property "unknown-b" was removed during template encode.', + ]); + }); }); function runWebpack(config: webpack.Configuration): Promise { @@ -72,6 +328,44 @@ function runWebpack(config: webpack.Configuration): Promise { }); } +function createBeforeEmitArgs(outputName: string) { + return { + finalEncodeOptions: { + manifest: {}, + compilerOptions: {}, + lepusCode: { + root: undefined, + lepusChunk: {}, + filename: undefined, + }, + customSections: {}, + }, + debugInfo: '', + template: Buffer.from(''), + outputName, + mainThreadAssets: [], + }; +} + +function createCSSAsset(name: string, sourceMap: unknown): Asset { + return { + name, + info: {}, + source: { + map: () => sourceMap, + }, + } as never; +} + +function createCSSDiagnosticsJSON(diagnostic: { + type: string; + name: string; + line: number; + column: number; +}): string { + return `[{"type":"${diagnostic.type}","name":"${diagnostic.name}","line":${diagnostic.line},"column":${diagnostic.column}}]`; +} + describe('requireModuleAsyncCachePolyfill', () => { const moduleResult: Record = { 'module1': [null, 'module1'], diff --git a/packages/webpack/template-webpack-plugin/test/cssDiagnostics.test.ts b/packages/webpack/template-webpack-plugin/test/cssDiagnostics.test.ts index 9542e2e442..e49e6ae0a0 100644 --- a/packages/webpack/template-webpack-plugin/test/cssDiagnostics.test.ts +++ b/packages/webpack/template-webpack-plugin/test/cssDiagnostics.test.ts @@ -2,11 +2,11 @@ // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import { describe, expect, test } from '@rstest/core'; -import type { Compilation } from 'webpack'; import type { CSSSourceMap } from '@lynx-js/css-serializer'; import { + collectCSSSourceMapContents, dedupeTasmCSSDiagnostics, extractTasmCSSDiagnostics, processTasmCSSDiagnostics, @@ -52,7 +52,7 @@ describe('cssDiagnostics', () => { column: 10, }, ], - mainCSSSourceMap: sourceMap, + mainCSSSourceMaps: [sourceMap], context: '/workspace/app', fileExists: () => true, }); @@ -72,6 +72,55 @@ describe('cssDiagnostics', () => { ]); }); + test('resolve tasm css diagnostics with first mappable source map', () => { + const firstSourceMap: CSSSourceMap = { + version: 3, + file: '.rspeedy/first/first.css', + sources: ['webpack:/src/first.css'], + sourcesContent: ['.foo {}\n'], + names: [], + mappings: 'AAAA', + }; + const secondSourceMap: CSSSourceMap = { + version: 3, + file: '.rspeedy/second/second.css', + sources: ['webpack:/src/second.css'], + sourcesContent: [ + '.foo {\n unknown-prop: red;\n}\n', + ], + names: [], + mappings: 'AAAA;EACE,kBAAkB;AACpB', + }; + + const resolved = resolveTasmCSSDiagnostics({ + cssDiagnostics: [ + { + type: 'property', + name: 'unknown-prop', + line: 2, + column: 10, + }, + ], + mainCSSSourceMaps: [firstSourceMap, secondSourceMap], + context: '/workspace/app', + fileExists: () => true, + }); + + expect(resolved).toEqual([ + { + type: 'property', + name: 'unknown-prop', + line: 2, + column: 10, + message: + 'Unsupported property "unknown-prop" was removed during template encode.', + sourceFile: '/workspace/app/src/second.css', + sourceLine: 2, + sourceColumn: 3, + }, + ]); + }); + test('skip mapped source when file does not exist', () => { const sourceMap: CSSSourceMap = { version: 3, @@ -93,7 +142,7 @@ describe('cssDiagnostics', () => { column: 10, }, ], - mainCSSSourceMap: sourceMap, + mainCSSSourceMaps: [sourceMap], context: '/workspace/app', fileExists: () => false, }); @@ -191,16 +240,7 @@ describe('cssDiagnostics', () => { expect( processTasmCSSDiagnostics({ cssDiagnostics: rawDiagnostics, - compilation: { - getAssets: () => [ - { - name: 'main.css', - source: { - map: () => sourceMap, - }, - }, - ], - } as Compilation, + cssSourceMaps: [JSON.stringify(sourceMap)], context: '/workspace/app', emittedWarnings: seen, fileExists: () => true, @@ -219,4 +259,46 @@ describe('cssDiagnostics', () => { }, ]); }); + + test('process tasm css diagnostics ignores invalid css source maps', () => { + const rawDiagnostics = + '[{"type":"property","name":"unknown-prop","line":2,"column":10}]'; + + expect( + processTasmCSSDiagnostics({ + cssDiagnostics: rawDiagnostics, + cssSourceMaps: ['', 'not json', '{}'], + context: '/workspace/app', + }), + ).toEqual([ + { + type: 'property', + name: 'unknown-prop', + line: 2, + column: 10, + message: + 'Unsupported property "unknown-prop" was removed during template encode.', + }, + ]); + }); + + test('collect css source maps from chunk source.map()', () => { + const sourceMap = { + version: 3, + mappings: 'AAAA', + sources: ['webpack:/src/app.css'], + }; + + const result = collectCSSSourceMapContents( + [{ + name: 'main.css', + info: {}, + source: { + map: () => sourceMap, + }, + } as never], + ); + + expect(result).toEqual([JSON.stringify(sourceMap)]); + }); }); diff --git a/packages/webpack/template-webpack-plugin/test/fixtures/css-diagnostics-mpa/a.css b/packages/webpack/template-webpack-plugin/test/fixtures/css-diagnostics-mpa/a.css new file mode 100644 index 0000000000..8cc54c0f31 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/fixtures/css-diagnostics-mpa/a.css @@ -0,0 +1,3 @@ +.a { + unknown-a: red; +} diff --git a/packages/webpack/template-webpack-plugin/test/fixtures/css-diagnostics-mpa/a.js b/packages/webpack/template-webpack-plugin/test/fixtures/css-diagnostics-mpa/a.js new file mode 100644 index 0000000000..fe7ff3d844 --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/fixtures/css-diagnostics-mpa/a.js @@ -0,0 +1,3 @@ +import './a.css'; + +console.info('entry-a'); diff --git a/packages/webpack/template-webpack-plugin/test/fixtures/css-diagnostics-mpa/b.css b/packages/webpack/template-webpack-plugin/test/fixtures/css-diagnostics-mpa/b.css new file mode 100644 index 0000000000..45f72ceb4c --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/fixtures/css-diagnostics-mpa/b.css @@ -0,0 +1,3 @@ +.b { + unknown-b: blue; +} diff --git a/packages/webpack/template-webpack-plugin/test/fixtures/css-diagnostics-mpa/b.js b/packages/webpack/template-webpack-plugin/test/fixtures/css-diagnostics-mpa/b.js new file mode 100644 index 0000000000..a5b73ec9ed --- /dev/null +++ b/packages/webpack/template-webpack-plugin/test/fixtures/css-diagnostics-mpa/b.js @@ -0,0 +1,3 @@ +import './b.css'; + +console.info('entry-b');