From 87464b42d53e237e12b8e002ec15e3961de21704 Mon Sep 17 00:00:00 2001 From: MeAkib Date: Mon, 19 Oct 2026 07:29:16 +0600 Subject: [PATCH] fix(@angular/build): support quotes in `styles` and `polyfills` paths The application builder generated the global styles and polyfills entry modules by wrapping each configured path in single quotes without escaping. A path containing a single quote, such as `src/dev's theme/theme.css`, produced an invalid import and the build failed with "Could not resolve" or a syntax error. Generate the import specifiers with `JSON.stringify` so that such paths are quoted correctly. --- .../application/tests/options/polyfills_spec.ts | 16 ++++++++++++++++ .../application/tests/options/styles_spec.ts | 17 +++++++++++++++++ .../tools/esbuild/application-code-bundle.ts | 12 +++++++----- .../build/src/tools/esbuild/global-styles.ts | 4 +++- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/packages/angular/build/src/builders/application/tests/options/polyfills_spec.ts b/packages/angular/build/src/builders/application/tests/options/polyfills_spec.ts index 5fff9bd33a7c..ad68443a2e03 100644 --- a/packages/angular/build/src/builders/application/tests/options/polyfills_spec.ts +++ b/packages/angular/build/src/builders/application/tests/options/polyfills_spec.ts @@ -89,5 +89,21 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { }); }); } + + it('uses a provided file with a single quote in its path', async () => { + await harness.writeFile('src/main.ts', 'console.log("TEST");'); + await harness.writeFile(`src/dev's polyfills/polyfills.js`, `console.log('main');`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + polyfills: [`src/dev's polyfills/polyfills.js`], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness.expectFile('dist/browser/polyfills.js').content.toContain(`console.log("main")`); + }); }); }); diff --git a/packages/angular/build/src/builders/application/tests/options/styles_spec.ts b/packages/angular/build/src/builders/application/tests/options/styles_spec.ts index 311b0037daa5..7c5b3d52eb8c 100644 --- a/packages/angular/build/src/builders/application/tests/options/styles_spec.ts +++ b/packages/angular/build/src/builders/application/tests/options/styles_spec.ts @@ -62,6 +62,23 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { .content.toContain(''); }); + it('processes a style with a single quote in its path', async () => { + await harness.writeFile(`src/dev's theme/test-style-a.css`, '.test-a {color: red}'); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: [`src/dev's theme/test-style-a.css`], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + + harness + .expectFile('dist/browser/styles.css') + .content.toMatch(/\.test-a {\s*color: red;?\s*}/); + }); + it('processes multiple styles into a single output', async () => { await harness.writeFiles({ 'src/test-style-a.css': '.test-a {color: red}', diff --git a/packages/angular/build/src/tools/esbuild/application-code-bundle.ts b/packages/angular/build/src/tools/esbuild/application-code-bundle.ts index af53e04f58af..c98ad3bcae28 100644 --- a/packages/angular/build/src/tools/esbuild/application-code-bundle.ts +++ b/packages/angular/build/src/tools/esbuild/application-code-bundle.ts @@ -364,8 +364,8 @@ export function createServerMainCodeBundleOptions( `export { ɵresetCompiledComponents } from '@angular/core';`, // Re-export all symbols including default export from 'main.server.ts' - `export { default } from '${mainServerEntryPointJsImport}';`, - `export * from '${mainServerEntryPointJsImport}';`, + `export { default } from ${JSON.stringify(mainServerEntryPointJsImport)};`, + `export * from ${JSON.stringify(mainServerEntryPointJsImport)};`, ]; return { @@ -496,8 +496,8 @@ export function createSsrEntryCodeBundleOptions( `import '${ssrInjectManifestNamespace}';`, // Re-export all symbols including default export - `import * as server from '${serverEntryPointJsImport}';`, - `export * from '${serverEntryPointJsImport}';`, + `import * as server from ${JSON.stringify(serverEntryPointJsImport)};`, + `export * from ${JSON.stringify(serverEntryPointJsImport)};`, // The below is needed to avoid // `Import "default" will always be undefined because there is no matching export` warning when no default is present. `const defaultExportName = 'default';`, @@ -740,7 +740,9 @@ function getEsBuildCommonPolyfillsOptions( } // Generate module contents with an import statement per defined polyfill - let contents = polyfillPaths.map((file) => `import '${toPosixPath(file)}';`).join('\n'); + let contents = polyfillPaths + .map((file) => `import ${JSON.stringify(toPosixPath(file))};`) + .join('\n'); // The below should be done after loading `$localize` as otherwise the locale will be overridden. if (i18nOptions.shouldInline) { diff --git a/packages/angular/build/src/tools/esbuild/global-styles.ts b/packages/angular/build/src/tools/esbuild/global-styles.ts index fd2cb13fa7b2..79afff2008af 100644 --- a/packages/angular/build/src/tools/esbuild/global-styles.ts +++ b/packages/angular/build/src/tools/esbuild/global-styles.ts @@ -92,7 +92,9 @@ export function createGlobalStylesBundleOptions( assert(files, `global style name should always be found [${args.path}]`); return { - contents: files.map((file) => `@import '${toPosixPath(file)}';`).join('\n'), + contents: files + .map((file) => `@import ${JSON.stringify(toPosixPath(file))};`) + .join('\n'), loader: 'css', resolveDir: workspaceRoot, };