Skip to content

Commit 571bd01

Browse files
committed
fix(repl): workaround for vite preload
the SSR worker was importing the vite preload from the wrong path, breaking SSR. Maybe there's a problem with how qwik router makes entry files.
1 parent 50c5c35 commit 571bd01

File tree

7 files changed

+90
-57
lines changed

7 files changed

+90
-57
lines changed

packages/docs/src/repl/bundler/repl-ssr-worker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ self.onmessage = async (e: MessageEvent<IncomingMessage>) => {
6565
async function executeSSR(message: InitSSRMessage): Promise<{ html: string; events: any[] }> {
6666
const { baseUrl, manifest, entry } = message;
6767

68-
const module = await import(/* @vite-ignore */ `/repl/${replId}-ssr/${entry}`);
68+
// @ts-expect-error - we prevent Vite from touching this import and replace it later
69+
const module = await DO_NOT_TOUCH_IMPORT(`/repl/${replId}-ssr/${entry}`);
6970
const server = module.default;
7071

7172
const render = typeof server === 'function' ? server : server?.render;

packages/docs/src/repl/ui/repl-instance.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import type {
1010
InitSSRMessage,
1111
OutgoingMessage as SSROutgoingMessage,
1212
} from '../bundler/repl-ssr-worker';
13+
// @ts-expect-error - we don't have types for this yet
14+
import ssrWorkerStringPre from '../bundler/repl-ssr-worker?compiled-string';
15+
16+
const ssrWorkerString = ssrWorkerStringPre.replace(/DO_NOT_TOUCH_IMPORT/g, 'import');
1317

1418
let channel: BroadcastChannel;
1519
let registered = false;
@@ -181,6 +185,13 @@ export class ReplInstance {
181185
throw new Error(`Invalid REPL path ${path}`);
182186
}
183187
const [, , ssrFlag, filePath] = match;
188+
/**
189+
* We must serve the SSR worker string from /repl/ so that the import() inside the worker can be
190+
* intercepted by our repl-sw.js
191+
*/
192+
if (ssrFlag && filePath === 'repl-ssr-worker.js') {
193+
return ssrWorkerString;
194+
}
184195

185196
const ssrPromise = this.ensureBuilt();
186197
// First wait only for the bundles
@@ -228,8 +239,13 @@ export class ReplInstance {
228239
return resolve({ html: errorHtml('No SSR module found', 'SSR') });
229240
}
230241

231-
// This is ../bundler/ssr-worker.ts but we need to go via an entry.ts
232-
const ssrWorker = new Worker(`/repl/repl-ssr-worker.js`, { type: 'module' });
242+
/**
243+
* We could also serve it from /repl/ directly but then we need to special-case the repl-sw
244+
* and then docs.dev mode wouldn't reload the worker
245+
*/
246+
const ssrWorker = new Worker(`/repl/${this.replId}-ssr/repl-ssr-worker.js`, {
247+
type: 'module',
248+
});
233249

234250
const initMessage: InitSSRMessage = {
235251
type: 'run-ssr',

packages/docs/src/routes/repl/repl-ssr-worker.js/entry.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

packages/docs/vite.config.mts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import type { ShikiTransformer } from '@shikijs/types';
1111
import tailwindcss from '@tailwindcss/vite';
1212
import path, { resolve } from 'node:path';
1313
import { defineConfig, loadEnv, type Plugin, type Rollup } from 'vite';
14-
import Inspect from 'vite-plugin-inspect';
1514
import { examplesData, playgroundData, rawSource, tutorialData } from './vite.repl-apps';
1615
import { sourceResolver } from './vite.source-resolver';
16+
import { compiledStringPlugin } from '../../scripts/compiled-string-plugin';
1717

1818
const PUBLIC_QWIK_INSIGHTS_KEY = loadEnv('', '.', 'PUBLIC').PUBLIC_QWIK_INSIGHTS_KEY;
1919
const docsDir = new URL(import.meta.url).pathname;
@@ -181,6 +181,7 @@ export default defineConfig(() => {
181181
['MODULE_LEVEL_DIRECTIVE', 'use client'],
182182
]),
183183
rawSource(),
184+
compiledStringPlugin(),
184185
qwikCity({
185186
mdxPlugins: {
186187
rehypeSyntaxHighlight: false,
@@ -214,7 +215,6 @@ export default defineConfig(() => {
214215
tutorialData(routesDir),
215216
sourceResolver(docsDir),
216217
qwikReact(),
217-
Inspect(),
218218
qwikInsights({ publicApiKey: PUBLIC_QWIK_INSIGHTS_KEY }),
219219
tailwindcss(),
220220
overrideManualChunksForRepl(),

packages/qwik-labs/compiled-string-plugin.ts

Lines changed: 0 additions & 49 deletions
This file was deleted.

packages/qwik-labs/vite.config.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { defineConfig } from 'vite';
22
import { qwikVite } from '@builder.io/qwik/optimizer';
33
import dtsPlugin from 'vite-plugin-dts';
4-
import { compiledStringPlugin } from './compiled-string-plugin';
4+
import { compiledStringPlugin } from '../../scripts/compiled-string-plugin';
55

66
export default defineConfig(() => {
77
return {

scripts/compiled-string-plugin.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import type { Plugin } from 'vite';
2+
3+
const isCompiledStringId = (id: string) => /[?&]compiled-string/.test(id);
4+
5+
/** This returns the source code of a module after transforming */
6+
export function compiledStringPlugin(): Plugin {
7+
let devServer: any;
8+
return {
9+
name: 'compiled-string-plugin',
10+
enforce: 'pre',
11+
12+
configureServer(server) {
13+
devServer = server;
14+
},
15+
16+
resolveId: {
17+
order: 'pre',
18+
async handler(id, importer, options) {
19+
if (isCompiledStringId(id)) {
20+
const cleanId = id.replace(/([?&])compiled-string/, '$1').replace(/[?&]$/, '');
21+
const resolved = await this.resolve(cleanId, importer, { skipSelf: true });
22+
if (resolved) {
23+
return `virtual:compiled-string:${resolved.id}`;
24+
}
25+
} else if (id.startsWith('virtual:compiled-string:')) {
26+
return id;
27+
}
28+
return null;
29+
},
30+
},
31+
32+
load: {
33+
order: 'pre',
34+
async handler(id) {
35+
if (id.startsWith('virtual:compiled-string:')) {
36+
const originalId = id.slice('virtual:compiled-string:'.length);
37+
38+
const result = await this.load({
39+
id: originalId,
40+
moduleSideEffects: true,
41+
});
42+
43+
let code: string;
44+
if (result && 'code' in result && result.code) {
45+
// If this.load provides code, use it
46+
code = result.code;
47+
} else if (devServer) {
48+
// in dev mode, you need to use the dev server to transform the request
49+
const transformResult = await devServer.transformRequest(originalId);
50+
if (transformResult && transformResult.code) {
51+
code = transformResult.code;
52+
}
53+
this.addWatchFile(originalId);
54+
}
55+
if (!code!) {
56+
throw new Error(`Unable to load code for ${originalId}`);
57+
}
58+
return {
59+
code: `export default ${JSON.stringify(code)};`,
60+
map: null,
61+
};
62+
}
63+
return null;
64+
},
65+
},
66+
};
67+
}

0 commit comments

Comments
 (0)