Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import assert from 'node:assert';
import { randomUUID } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';

Expand Down Expand Up @@ -110,7 +110,7 @@ export function resolve(
});
}

export async function load(url: string, context: { format?: string | null }, nextLoad: Function) {
export function load(url: string, context: { format?: string | null }, nextLoad: Function) {
const { format } = context;

// Load the file from memory if the URL is based in the virtual root
Expand All @@ -137,7 +137,7 @@ export async function load(url: string, context: { format?: string | null }, nex
}

const filePath = fileURLToPath(url);
const fileBytes = await readFile(filePath);
const fileBytes = readFileSync(filePath);
const source = Buffer.concat([NG_SERVER_MODE_INIT_BYTES, fileBytes]);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
* found in the LICENSE file at https://angular.dev/license
*/

import assert from 'node:assert';
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
import { initialize, load, resolve } from './loader-hooks';
import { createSharedServerFiles } from './utils';

Expand Down Expand Up @@ -124,46 +128,46 @@ describe('esm-in-memory-loader loader-hooks', () => {
});

describe('load', () => {
it('should load in-memory file source from SharedArrayBuffer backed Uint8Array', async () => {
it('should load in-memory file source from SharedArrayBuffer backed Uint8Array', () => {
const memoryUrl = new URL('./main.server.mjs', 'memory://').href;
const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {});
const nextLoad = jasmine.createSpy('nextLoad');

const result = await load(rootResolve.url, { format: 'module' }, nextLoad);
const result = load(rootResolve.url, { format: 'module' }, nextLoad);

expect(nextLoad).not.toHaveBeenCalled();
expect(result.format).toBe('module');
expect(result.shortCircuit).toBeTrue();
expect(result.source).toBe('export const main = true;');
});

it('should load in-memory file with multi-byte UTF-8 characters', async () => {
it('should load in-memory file with multi-byte UTF-8 characters', () => {
const memoryUrl = new URL('./utf8.mjs', 'memory://').href;
const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {});
const nextLoad = jasmine.createSpy('nextLoad');

const result = await load(rootResolve.url, { format: 'module' }, nextLoad);
const result = load(rootResolve.url, { format: 'module' }, nextLoad);

expect(nextLoad).not.toHaveBeenCalled();
expect(result.format).toBe('module');
expect(result.shortCircuit).toBeTrue();
expect(result.source).toBe('export const text = "🔥 UTF-8 🚀";');
});

it('should load in-memory file with empty content', async () => {
it('should load in-memory file with empty content', () => {
const memoryUrl = new URL('./empty.mjs', 'memory://').href;
const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {});
const nextLoad = jasmine.createSpy('nextLoad');

const result = await load(rootResolve.url, { format: 'module' }, nextLoad);
const result = load(rootResolve.url, { format: 'module' }, nextLoad);

expect(nextLoad).not.toHaveBeenCalled();
expect(result.format).toBe('module');
expect(result.shortCircuit).toBeTrue();
expect(result.source).toBe('');
});

it('should load in-memory file source with non-zero byteOffset in Uint8Array', async () => {
it('should load in-memory file source with non-zero byteOffset in Uint8Array', () => {
const target = 'export const sliced = 42;';
const fullBuffer = Buffer.from(`__PADDING__${target}__MORE__`);
const offset = Buffer.byteLength('__PADDING__', 'utf-8');
Expand All @@ -181,15 +185,15 @@ describe('esm-in-memory-loader loader-hooks', () => {
const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {});
const nextLoad = jasmine.createSpy('nextLoad');

const result = await load(rootResolve.url, { format: 'module' }, nextLoad);
const result = load(rootResolve.url, { format: 'module' }, nextLoad);

expect(nextLoad).not.toHaveBeenCalled();
expect(result.format).toBe('module');
expect(result.shortCircuit).toBeTrue();
expect(result.source).toBe(target);
});

it('should load in-memory file source when outputFiles contain string values', async () => {
it('should load in-memory file source when outputFiles contain string values', () => {
initialize({
workspaceRoot,
outputFiles: {
Expand All @@ -201,29 +205,54 @@ describe('esm-in-memory-loader loader-hooks', () => {
const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {});
const nextLoad = jasmine.createSpy('nextLoad');

const result = await load(rootResolve.url, { format: 'module' }, nextLoad);
const result = load(rootResolve.url, { format: 'module' }, nextLoad);

expect(nextLoad).not.toHaveBeenCalled();
expect(result.format).toBe('module');
expect(result.shortCircuit).toBeTrue();
expect(result.source).toBe('export const fromString = 1;');
});

it('should reject when in-memory file does not exist in outputFiles', async () => {
it('should throw when in-memory file does not exist in outputFiles', () => {
const memoryUrl = new URL('./main.server.mjs', 'memory://').href;
const rootResolve = resolve(memoryUrl, { parentURL: undefined }, () => {});
const nonExistentVirtualUrl = rootResolve.url.replace('main.server.mjs', 'missing.mjs');
const nextLoad = jasmine.createSpy('nextLoad');

await expectAsync(
load(nonExistentVirtualUrl, { format: 'module' }, nextLoad),
).toBeRejectedWithError(/Resolved in-memory ESM file should always exist/);
expect(() => load(nonExistentVirtualUrl, { format: 'module' }, nextLoad)).toThrowError(
/Resolved in-memory ESM file should always exist/,
);
expect(nextLoad).not.toHaveBeenCalled();
});

it('should delegate to nextLoad for non-angular file URLs', async () => {
const nextLoad = jasmine.createSpy('nextLoad').and.resolveTo({ format: 'module' });
const result = await load(
it('should prepend ngServerMode initialization for @angular/ file URLs', async () => {
const baseTmpDir = process.env['TEST_TMPDIR'];
assert(baseTmpDir, 'TEST_TMPDIR is not set');
const tmpDir = await mkdtemp(join(baseTmpDir, 'loader-hooks-test-'));

try {
const angularDir = join(tmpDir, '@angular/core');
await mkdir(angularDir, { recursive: true });
const filePath = join(angularDir, 'index.mjs');
await writeFile(filePath, 'export const VERSION = "1.0.0";');

const nextLoad = jasmine.createSpy('nextLoad');
const result = load(pathToFileURL(filePath).href, { format: 'module' }, nextLoad);

expect(nextLoad).not.toHaveBeenCalled();
expect(result.format).toBe('module');
expect(result.shortCircuit).toBeTrue();
expect(new TextDecoder().decode(result.source)).toBe(
'var ngServerMode=true;export const VERSION = "1.0.0";',
);
} finally {
await rm(tmpDir, { recursive: true, force: true });
}
});

it('should delegate to nextLoad for non-angular file URLs', () => {
const nextLoad = jasmine.createSpy('nextLoad').and.returnValue({ format: 'module' });
const result = load(
'file:///workspace/node_modules/rxjs/index.js',
{ format: 'module' },
nextLoad,
Expand All @@ -235,9 +264,9 @@ describe('esm-in-memory-loader loader-hooks', () => {
expect(result.format).toBe('module');
});

it('should delegate to nextLoad for non-memory non-file URLs', async () => {
const nextLoad = jasmine.createSpy('nextLoad').and.resolveTo({ format: 'builtin' });
const result = await load('node:fs', { format: 'builtin' }, nextLoad);
it('should delegate to nextLoad for non-memory non-file URLs', () => {
const nextLoad = jasmine.createSpy('nextLoad').and.returnValue({ format: 'builtin' });
const result = load('node:fs', { format: 'builtin' }, nextLoad);

expect(nextLoad).toHaveBeenCalledWith('node:fs');
expect(result.format).toBe('builtin');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
* found in the LICENSE file at https://angular.dev/license
*/

import { register } from 'node:module';
import { pathToFileURL } from 'node:url';
import { registerHooks } from 'node:module';
import { workerData } from 'node:worker_threads';
import { initialize, load, resolve } from './loader-hooks';

register('./loader-hooks.js', { parentURL: pathToFileURL(__filename), data: workerData });
initialize(workerData);
registerHooks({ resolve, load });
Comment thread
alan-agius4 marked this conversation as resolved.
Loading