Skip to content

Commit 0314b61

Browse files
authored
Merge branch 'main' into fix/deno-production-builds
2 parents 03b590f + fe45eb6 commit 0314b61

File tree

4 files changed

+260
-11
lines changed

4 files changed

+260
-11
lines changed

packages/qwik/src/core/platform/platform.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
// keep this import from qwik/build so the cjs build works
22
import { isServer } from '@builder.io/qwik/build';
3-
import { qError, QError_qrlMissingChunk, QError_qrlMissingContainer } from '../error/error';
3+
import {
4+
qError,
5+
QError_dynamicImportFailed,
6+
QError_qrlMissingChunk,
7+
QError_qrlMissingContainer,
8+
} from '../error/error';
49
import { getSymbolHash } from '../qrl/qrl-class';
510
import type { QwikElement } from '../render/dom/virtual-element';
611
import { qDynamicPlatform } from '../util/qdev';
@@ -16,6 +21,8 @@ export const createPlatform = (): CorePlatform => {
1621
if (regSym) {
1722
return regSym;
1823
}
24+
// we never lazy import on the server
25+
throw qError(QError_dynamicImportFailed, symbolName);
1926
}
2027
if (!url) {
2128
throw qError(QError_qrlMissingChunk, symbolName);
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
2+
import { createPlatform, setPlatform } from './platform';
3+
import { getSymbolHash } from '../qrl/qrl-class';
4+
5+
describe('core platform', () => {
6+
beforeEach(() => {
7+
// Initialize a fresh Map for each test to avoid pollution
8+
(globalThis as any).__qwik_reg_symbols = new Map<string, any>();
9+
});
10+
11+
afterEach(() => {
12+
// Clean up global state
13+
delete (globalThis as any).__qwik_reg_symbols;
14+
});
15+
16+
describe('importSymbol - server mode', () => {
17+
test('returns registered symbol without importing', async () => {
18+
const platform = createPlatform();
19+
20+
// Register a mock symbol
21+
const symbolName = 'myComponent_abc123';
22+
const hash = getSymbolHash(symbolName);
23+
const mockFunction = () => 'mock component';
24+
(globalThis as any).__qwik_reg_symbols.set(hash, mockFunction);
25+
26+
// importSymbol should return the registered symbol synchronously
27+
const result = await platform.importSymbol(null as any, '', symbolName);
28+
29+
expect(result).toBe(mockFunction);
30+
});
31+
32+
test('throws error for unregistered symbol without importing', async () => {
33+
const platform = createPlatform();
34+
35+
const symbolName = 'unregisteredSymbol_xyz789';
36+
37+
// importSymbol should throw qError without attempting any dynamic import
38+
expect(() => platform.importSymbol(null as any, '', symbolName)).toThrow();
39+
40+
let didThrow = false;
41+
// Verify it throws with the correct error structure
42+
try {
43+
platform.importSymbol(null as any, '', symbolName);
44+
expect.unreachable('Should have thrown an error');
45+
} catch (e: any) {
46+
didThrow = true;
47+
// The error should be a QError with code for dynamic import failed
48+
expect(e.message).toMatch(/Code\(\d+\)/);
49+
expect(e.message).toContain('Dynamic import not found');
50+
}
51+
expect(didThrow).toBe(true);
52+
});
53+
54+
test('does not call dynamic import on server', async () => {
55+
const platform = createPlatform();
56+
57+
const symbolName = 'testSymbol_test123';
58+
59+
// Verify that the function throws synchronously without any async import
60+
const startTime = Date.now();
61+
try {
62+
platform.importSymbol(null as any, '', symbolName);
63+
} catch (e) {
64+
// Expected to throw
65+
}
66+
const endTime = Date.now();
67+
68+
// Should complete nearly instantly (no network/file I/O)
69+
// If it took more than 100ms, something is probably doing async work
70+
expect(endTime - startTime).toBeLessThan(100);
71+
});
72+
73+
test('works with symbols containing multiple underscores in server mode', async () => {
74+
const platform = createPlatform();
75+
76+
const symbolName = 'my_component_with_underscores_abc123';
77+
const hash = getSymbolHash(symbolName);
78+
const mockFunction = () => 'mock';
79+
(globalThis as any).__qwik_reg_symbols.set(hash, mockFunction);
80+
81+
const result = await platform.importSymbol(null as any, '', symbolName);
82+
83+
expect(result).toBe(mockFunction);
84+
});
85+
});
86+
87+
describe('setPlatform and getPlatform', () => {
88+
test('setPlatform updates the platform', () => {
89+
const customPlatform = createPlatform();
90+
const customImportSymbol = vi.fn();
91+
customPlatform.importSymbol = customImportSymbol;
92+
93+
setPlatform(customPlatform);
94+
95+
// The platform should now use the custom implementation
96+
expect(customPlatform.importSymbol).toBe(customImportSymbol);
97+
});
98+
});
99+
100+
describe('platform utilities', () => {
101+
test('nextTick returns a promise and executes callback', async () => {
102+
const platform = createPlatform();
103+
const mockFn = vi.fn(() => 'result');
104+
105+
const promise = platform.nextTick(mockFn);
106+
107+
expect(promise).toBeInstanceOf(Promise);
108+
const result = await promise;
109+
expect(mockFn).toHaveBeenCalled();
110+
expect(result).toBe('result');
111+
});
112+
113+
test('chunkForSymbol returns symbol and chunk', () => {
114+
const platform = createPlatform();
115+
116+
const result1 = platform.chunkForSymbol('mySymbol', 'chunk.js');
117+
expect(result1).toEqual(['mySymbol', 'chunk.js']);
118+
119+
const result2 = platform.chunkForSymbol('mySymbol', null);
120+
expect(result2).toEqual(['mySymbol', '_']);
121+
});
122+
});
123+
124+
describe('getSymbolHash', () => {
125+
test('extracts hash after last underscore', () => {
126+
expect(getSymbolHash('mySymbol_abc123')).toBe('abc123');
127+
expect(getSymbolHash('component_with_multiple_underscores_xyz789')).toBe('xyz789');
128+
});
129+
130+
test('returns full name if no underscore present', () => {
131+
expect(getSymbolHash('noUnderscore')).toBe('noUnderscore');
132+
expect(getSymbolHash('simpleSymbol')).toBe('simpleSymbol');
133+
});
134+
135+
test('handles edge cases', () => {
136+
expect(getSymbolHash('_leadingUnderscore')).toBe('leadingUnderscore');
137+
expect(getSymbolHash('trailingUnderscore_')).toBe('');
138+
expect(getSymbolHash('_')).toBe('');
139+
});
140+
});
141+
});

packages/qwik/src/server/platform.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { SerializeDocumentOptions } from './types';
22
import { setPlatform } from '@builder.io/qwik';
33
import type { ResolvedManifest } from '@builder.io/qwik/optimizer';
44
import type { CorePlatformServer } from '../core/platform/types';
5+
import { qError, QError_dynamicImportFailed } from '../core/error/error';
56

67
declare const require: (module: string) => Record<string, any>;
78

@@ -45,16 +46,8 @@ export function createPlatform(
4546
if (regSym) {
4647
return regSym;
4748
}
48-
49-
let modulePath = String(url);
50-
if (!modulePath.endsWith('.js')) {
51-
modulePath += '.js';
52-
}
53-
const module = require(modulePath);
54-
if (!(symbolName in module)) {
55-
throw new Error(`Q-ERROR: missing symbol '${symbolName}' in module '${modulePath}'.`);
56-
}
57-
return module[symbolName];
49+
// we never lazy import on the server
50+
throw qError(QError_dynamicImportFailed, symbolName);
5851
},
5952
raf: () => {
6053
console.error('server can not rerender');
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
2+
import { createPlatform, getSymbolHash } from './platform';
3+
4+
describe('server platform', () => {
5+
beforeEach(() => {
6+
// Initialize a fresh Map for each test to avoid pollution
7+
(globalThis as any).__qwik_reg_symbols = new Map<string, any>();
8+
});
9+
10+
afterEach(() => {
11+
// Clean up global state
12+
delete (globalThis as any).__qwik_reg_symbols;
13+
});
14+
15+
describe('importSymbol', () => {
16+
test('returns registered symbol without importing', async () => {
17+
const platform = createPlatform({}, undefined);
18+
19+
// Register a mock symbol
20+
const symbolName = 'myComponent_abc123';
21+
const hash = getSymbolHash(symbolName);
22+
const mockFunction = () => 'mock component';
23+
(globalThis as any).__qwik_reg_symbols.set(hash, mockFunction);
24+
25+
// importSymbol should return the registered symbol synchronously
26+
const result = await platform.importSymbol(null as any, '', symbolName);
27+
28+
expect(result).toBe(mockFunction);
29+
});
30+
31+
test('throws error for unregistered symbol without importing', async () => {
32+
const platform = createPlatform({}, undefined);
33+
34+
const symbolName = 'unregisteredSymbol_xyz789';
35+
36+
// importSymbol should throw qError without attempting any dynamic import
37+
await expect(platform.importSymbol(null as any, '', symbolName)).rejects.toThrow();
38+
39+
let didThrow = false;
40+
// Verify it throws with the correct error structure
41+
try {
42+
await platform.importSymbol(null as any, '', symbolName);
43+
expect.unreachable('Should have thrown an error');
44+
} catch (e: any) {
45+
didThrow = true;
46+
// The error should be a QError with code for dynamic import failed
47+
expect(e.message).toMatch(/Code\(\d+\)/);
48+
expect(e.message).toContain('Dynamic import not found');
49+
}
50+
expect(didThrow).toBe(true);
51+
});
52+
53+
test('does not call dynamic import', async () => {
54+
const platform = createPlatform({}, undefined);
55+
56+
// Spy on console.error to capture the error log (if any)
57+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
58+
59+
const symbolName = 'testSymbol_test123';
60+
61+
// Verify that the function throws synchronously without any async import
62+
const startTime = Date.now();
63+
try {
64+
await platform.importSymbol(null as any, '', symbolName);
65+
} catch (e) {
66+
// Expected to throw
67+
}
68+
const endTime = Date.now();
69+
70+
// Should complete nearly instantly (no network/file I/O)
71+
// If it took more than 100ms, something is probably doing async work
72+
expect(endTime - startTime).toBeLessThan(100);
73+
74+
consoleErrorSpy.mockRestore();
75+
});
76+
77+
test('works with symbols containing multiple underscores', async () => {
78+
const platform = createPlatform({}, undefined);
79+
80+
const symbolName = 'my_component_with_underscores_abc123';
81+
const hash = getSymbolHash(symbolName);
82+
const mockFunction = () => 'mock';
83+
(globalThis as any).__qwik_reg_symbols.set(hash, mockFunction);
84+
85+
const result = await platform.importSymbol(null as any, '', symbolName);
86+
87+
expect(result).toBe(mockFunction);
88+
});
89+
});
90+
91+
describe('getSymbolHash', () => {
92+
test('extracts hash after last underscore', () => {
93+
expect(getSymbolHash('mySymbol_abc123')).toBe('abc123');
94+
expect(getSymbolHash('component_with_multiple_underscores_xyz789')).toBe('xyz789');
95+
});
96+
97+
test('returns full name if no underscore present', () => {
98+
expect(getSymbolHash('noUnderscore')).toBe('noUnderscore');
99+
expect(getSymbolHash('simpleSymbol')).toBe('simpleSymbol');
100+
});
101+
102+
test('handles edge cases', () => {
103+
expect(getSymbolHash('_leadingUnderscore')).toBe('leadingUnderscore');
104+
expect(getSymbolHash('trailingUnderscore_')).toBe('');
105+
expect(getSymbolHash('_')).toBe('');
106+
});
107+
});
108+
});

0 commit comments

Comments
 (0)