Skip to content

Commit d392d65

Browse files
clydinalan-agius4
authored andcommitted
fix(@angular-devkit/build-angular): ensure correct web worker URL resolution in vite dev server
When using the application builder with the development server, Web Worker URLs previously may have been incorrectly resolved. This caused Vite to consider the Web Worker URLs as outside the project root and generate a special file system URL. While this worked on Mac/Linux, it would fail on Windows. Since Vite does not appear to support resolve plugins for Web Workers, the virtual project root for the in-memory build has now been adjusted to allow the referencing file to have a path that resolves the Web Worker URL to a project relative location. (cherry picked from commit 09682e5)
1 parent f7f6e97 commit d392d65

File tree

3 files changed

+49
-16
lines changed

3 files changed

+49
-16
lines changed

packages/angular_devkit/build_angular/src/builders/application/tests/behavior/rebuild-web-workers_spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setu
1717
*/
1818
export const BUILD_TIMEOUT = 30_000;
1919

20+
/**
21+
* A regular expression used to check if a built worker is correctly referenced in application code.
22+
*/
23+
const REFERENCED_WORKER_REGEXP =
24+
/new Worker\(new URL\("worker-[A-Z0-9]{8}\.js", import\.meta\.url\)/;
25+
2026
describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
2127
describe('Behavior: "Rebuilds when Web Worker files change"', () => {
2228
it('Recovers from error when directly referenced worker file is changed', async () => {
@@ -59,11 +65,17 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
5965
case 0:
6066
expect(result?.success).toBeTrue();
6167

68+
// Ensure built worker is referenced in the application code
69+
harness
70+
.expectFile('dist/browser/main.js')
71+
.content.toMatch(REFERENCED_WORKER_REGEXP);
72+
6273
// Update the worker file to be invalid syntax
6374
await harness.writeFile('src/app/worker.ts', `asd;fj$3~kls;kd^(*fjlk;sdj---flk`);
6475

6576
break;
6677
case 1:
78+
expect(result?.success).toBeFalse();
6779
expect(logs).toContain(
6880
jasmine.objectContaining<logging.LogEntry>({
6981
message: jasmine.stringMatching(errorText),
@@ -108,6 +120,11 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
108120
}),
109121
);
110122

123+
// Ensure built worker is referenced in the application code
124+
harness
125+
.expectFile('dist/browser/main.js')
126+
.content.toMatch(REFERENCED_WORKER_REGEXP);
127+
111128
// Test complete - abort watch mode
112129
builderAbort?.abort();
113130
break;

packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { json, logging } from '@angular-devkit/core';
1212
import type { Plugin } from 'esbuild';
1313
import { lookup as lookupMimeType } from 'mrmime';
1414
import assert from 'node:assert';
15+
import { randomUUID } from 'node:crypto';
1516
import { readFile } from 'node:fs/promises';
1617
import { ServerResponse } from 'node:http';
1718
import type { AddressInfo } from 'node:net';
@@ -182,7 +183,7 @@ export async function* serveWithVite(
182183
}
183184

184185
if (server) {
185-
handleUpdate(generatedFiles, server, serverOptions, context.logger);
186+
handleUpdate(normalizePath, generatedFiles, server, serverOptions, context.logger);
186187
} else {
187188
const projectName = context.target?.project;
188189
if (!projectName) {
@@ -230,6 +231,7 @@ export async function* serveWithVite(
230231
}
231232

232233
function handleUpdate(
234+
normalizePath: (id: string) => string,
233235
generatedFiles: Map<string, OutputFileRecord>,
234236
server: ViteDevServer,
235237
serverOptions: NormalizedDevServerOptions,
@@ -241,7 +243,9 @@ function handleUpdate(
241243
for (const [file, record] of generatedFiles) {
242244
if (record.updated) {
243245
updatedFiles.push(file);
244-
const updatedModules = server.moduleGraph.getModulesByFile(file);
246+
const updatedModules = server.moduleGraph.getModulesByFile(
247+
normalizePath(path.join(server.config.root, file)),
248+
);
245249
updatedModules?.forEach((m) => server?.moduleGraph.invalidateModule(m));
246250
}
247251
}
@@ -255,9 +259,7 @@ function handleUpdate(
255259
const timestamp = Date.now();
256260
server.ws.send({
257261
type: 'update',
258-
updates: updatedFiles.map((f) => {
259-
const filePath = f.slice(1); // Remove leading slash.
260-
262+
updates: updatedFiles.map((filePath) => {
261263
return {
262264
type: 'css-update',
263265
timestamp,
@@ -298,7 +300,7 @@ function analyzeResultFiles(
298300
// This mimics the Webpack dev-server behavior.
299301
filePath = '/index.html';
300302
} else {
301-
filePath = '/' + normalizePath(file.path);
303+
filePath = normalizePath(file.path);
302304
}
303305
seen.add(filePath);
304306

@@ -365,11 +367,16 @@ export async function setupServer(
365367
// dynamically import Vite for ESM compatibility
366368
const { normalizePath } = await import('vite');
367369

370+
// Path will not exist on disk and only used to provide separate path for Vite requests
371+
const virtualProjectRoot = normalizePath(
372+
path.join(serverOptions.workspaceRoot, `.angular/vite-root/${randomUUID()}/`),
373+
);
374+
368375
const configuration: InlineConfig = {
369376
configFile: false,
370377
envFile: false,
371378
cacheDir: path.join(serverOptions.cacheOptions.path, 'vite'),
372-
root: serverOptions.workspaceRoot,
379+
root: virtualProjectRoot,
373380
publicDir: false,
374381
esbuild: false,
375382
mode: 'development',
@@ -399,7 +406,7 @@ export async function setupServer(
399406
},
400407
ssr: {
401408
// Exclude any provided dependencies (currently build defined externals)
402-
external: externalMetadata.implicit,
409+
external: externalMetadata.explicit,
403410
},
404411
plugins: [
405412
createAngularLocaleDataPlugin(),
@@ -415,27 +422,32 @@ export async function setupServer(
415422
return source;
416423
}
417424

418-
if (importer && source.startsWith('.')) {
425+
if (importer && source[0] === '.' && importer.startsWith(virtualProjectRoot)) {
419426
// Remove query if present
420427
const [importerFile] = importer.split('?', 1);
421428

422-
source = normalizePath(path.join(path.dirname(importerFile), source));
429+
source = normalizePath(
430+
path.join(path.dirname(path.relative(virtualProjectRoot, importerFile)), source),
431+
);
432+
}
433+
if (source[0] === '/') {
434+
source = source.slice(1);
423435
}
424-
425436
const [file] = source.split('?', 1);
426437
if (outputFiles.has(file)) {
427-
return source;
438+
return path.join(virtualProjectRoot, source);
428439
}
429440
},
430441
load(id) {
431442
const [file] = id.split('?', 1);
432-
const codeContents = outputFiles.get(file)?.contents;
443+
const relativeFile = normalizePath(path.relative(virtualProjectRoot, file));
444+
const codeContents = outputFiles.get(relativeFile)?.contents;
433445
if (codeContents === undefined) {
434446
return;
435447
}
436448

437449
const code = Buffer.from(codeContents).toString('utf-8');
438-
const mapContents = outputFiles.get(file + '.map')?.contents;
450+
const mapContents = outputFiles.get(relativeFile + '.map')?.contents;
439451

440452
return {
441453
// Remove source map URL comments from the code if a sourcemap is present.
@@ -532,7 +544,7 @@ export async function setupServer(
532544
return;
533545
}
534546

535-
const rawHtml = outputFiles.get('/index.server.html')?.contents;
547+
const rawHtml = outputFiles.get('index.server.html')?.contents;
536548
if (!rawHtml) {
537549
next();
538550

packages/angular_devkit/build_angular/src/tools/esbuild/angular/compiler-plugin.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,12 @@ export function createCompilerPlugin(
222222
file.path.endsWith('.js'),
223223
);
224224
assert(workerCodeFile, 'Web Worker bundled code file should always be present.');
225+
const workerCodePath = path.relative(
226+
build.initialOptions.outdir ?? '',
227+
workerCodeFile.path,
228+
);
225229

226-
return path.relative(build.initialOptions.outdir ?? '', workerCodeFile.path);
230+
return workerCodePath.replaceAll('\\', '/');
227231
},
228232
};
229233

0 commit comments

Comments
 (0)