diff --git a/packages/docs/global.d.ts b/packages/docs/global.d.ts new file mode 100644 index 000000000000..ffeb16c99150 --- /dev/null +++ b/packages/docs/global.d.ts @@ -0,0 +1,5 @@ +// handled by raw-source plugin in vite.repl-apps.ts +declare module '*?raw-source' { + const url: string; + export default url; +} diff --git a/packages/docs/package.json b/packages/docs/package.json index f76dac79c02c..ef4477b95494 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -33,6 +33,7 @@ "fflate": "0.8.2", "gray-matter": "4.0.3", "leaflet": "1.9.4", + "magic-string": "0.30.11", "openai": "3.3.0", "postcss": "8.4.39", "prettier": "3.3.3", diff --git a/packages/docs/src/repl/bundled.tsx b/packages/docs/src/repl/bundled.tsx index 96c1a6782688..6ce706ac5f17 100644 --- a/packages/docs/src/repl/bundled.tsx +++ b/packages/docs/src/repl/bundled.tsx @@ -2,24 +2,22 @@ import { version as qwikVersion } from '@builder.io/qwik'; import type { PkgUrls } from './types'; import prettierPkgJson from 'prettier/package.json'; -import prettierParserHtml from 'prettier/plugins/html.js?raw'; -import prettierStandaloneJs from 'prettier/standalone.js?raw'; +import prettierParserHtml from '../../node_modules/prettier/plugins/html.js?raw-source'; +import prettierStandaloneJs from '../../node_modules/prettier/standalone.js?raw-source'; + import terserPkgJson from 'terser/package.json'; -import terserJs from '../../node_modules/terser/dist/bundle.min.js?raw'; -import qBuild from '../../node_modules/@builder.io/qwik/dist/build/index.d.ts?raw'; -import qCoreCjs from '../../node_modules/@builder.io/qwik/dist/core.cjs?raw'; -import qCoreDts from '../../node_modules/@builder.io/qwik/dist/core.d.ts?raw'; -import qCoreMinMjs from '../../node_modules/@builder.io/qwik/dist/core.min.mjs?raw'; -import qCoreMjs from '../../node_modules/@builder.io/qwik/dist/core.mjs?raw'; -import qJsxDts from '../../node_modules/@builder.io/qwik/dist/jsx-runtime.d.ts?raw'; -import qOptimizerCjs from '../../node_modules/@builder.io/qwik/dist/optimizer.cjs?raw'; -import qServerCjs from '../../node_modules/@builder.io/qwik/dist/server.cjs?raw'; -import qServerDts from '../../node_modules/@builder.io/qwik/dist/server.d.ts?raw'; -import qWasmCjs from '../../node_modules/@builder.io/qwik/bindings/qwik.wasm.cjs?raw'; -// we can use the wasm binary directly, it doesn't get processed -import qWasmBinUrl from '../../node_modules/@builder.io/qwik/bindings/qwik_wasm_bg.wasm?url'; +import terserJs from '../../node_modules/terser/dist/bundle.min.js?raw-source'; -import { isServer } from '@builder.io/qwik/build'; +import qBuild from '../../node_modules/@builder.io/qwik/dist/build/index.d.ts?raw-source'; +import qCoreCjs from '../../node_modules/@builder.io/qwik/dist/core.cjs?raw-source'; +import qCoreDts from '../../node_modules/@builder.io/qwik/dist/core.d.ts?raw-source'; +import qCoreMinMjs from '../../node_modules/@builder.io/qwik/dist/core.min.mjs?raw-source'; +import qCoreMjs from '../../node_modules/@builder.io/qwik/dist/core.mjs?raw-source'; +import qOptimizerCjs from '../../node_modules/@builder.io/qwik/dist/optimizer.cjs?raw-source'; +import qServerCjs from '../../node_modules/@builder.io/qwik/dist/server.cjs?raw-source'; +import qServerDts from '../../node_modules/@builder.io/qwik/dist/server.d.ts?raw-source'; +import qWasmCjs from '../../node_modules/@builder.io/qwik/bindings/qwik.wasm.cjs?raw-source'; +import qWasmBinUrl from '../../node_modules/@builder.io/qwik/bindings/qwik_wasm_bg.wasm?raw-source'; export const QWIK_PKG_NAME = '@builder.io/qwik'; const ROLLUP_VERSION = '2.75.6'; @@ -46,35 +44,24 @@ export const getNpmCdnUrl = ( return `https://cdn.jsdelivr.net/npm/${pkgName}${pkgVersion ? '@' + pkgVersion : ''}${pkgPath}`; }; -// https://github.com/vitejs/vite/issues/15753 -const blobUrl = (code: string, type: string = 'application/javascript') => { - if (isServer) { - return ''; - } - const blob = new Blob([code], { type }); - return URL.createObjectURL(blob); -}; - -const bundled: PkgUrls = { +export const bundled: PkgUrls = { [QWIK_PKG_NAME]: { version: qwikVersion, - '/dist/build/index.d.ts': blobUrl(qBuild), - '/dist/core.cjs': blobUrl(qCoreCjs), - '/dist/core.d.ts': blobUrl(qCoreDts), - '/dist/core.min.mjs': blobUrl(qCoreMinMjs), - '/dist/core.mjs': blobUrl(qCoreMjs), - '/dist/jsx-runtime.d.ts': blobUrl(qJsxDts), - '/dist/jsx-dev-runtime.d.ts': blobUrl(qJsxDts), - '/dist/optimizer.cjs': blobUrl(qOptimizerCjs), - '/dist/server.cjs': blobUrl(qServerCjs), - '/dist/server.d.ts': blobUrl(qServerDts), - '/bindings/qwik.wasm.cjs': blobUrl(qWasmCjs), + '/dist/build/index.d.ts': qBuild, + '/dist/core.cjs': qCoreCjs, + '/dist/core.d.ts': qCoreDts, + '/dist/core.min.mjs': qCoreMinMjs, + '/dist/core.mjs': qCoreMjs, + '/dist/optimizer.cjs': qOptimizerCjs, + '/dist/server.cjs': qServerCjs, + '/dist/server.d.ts': qServerDts, + '/bindings/qwik.wasm.cjs': qWasmCjs, '/bindings/qwik_wasm_bg.wasm': qWasmBinUrl, }, prettier: { version: prettierPkgJson.version, - '/plugins/html.js': blobUrl(prettierParserHtml), - '/standalone.js': blobUrl(prettierStandaloneJs), + '/plugins/html.js': prettierParserHtml, + '/standalone.js': prettierStandaloneJs, }, // v4 of rollup uses wasm etc, need to figure out how to bundle that rollup: { @@ -88,13 +75,6 @@ const bundled: PkgUrls = { }, terser: { version: terserPkgJson.version, - '/dist/bundle.min.js': blobUrl(terserJs), + '/dist/bundle.min.js': terserJs, }, }; - -export const getBundled = () => - isServer - ? { - [QWIK_PKG_NAME]: { version: qwikVersion }, - } - : bundled; diff --git a/packages/docs/src/repl/monaco.tsx b/packages/docs/src/repl/monaco.tsx index e3f0b98822d6..e6698852a620 100644 --- a/packages/docs/src/repl/monaco.tsx +++ b/packages/docs/src/repl/monaco.tsx @@ -4,8 +4,11 @@ import type MonacoTypes from 'monaco-editor'; import type { EditorProps, EditorStore } from './editor'; import type { ReplStore } from './types'; import { getColorPreference } from '../components/theme-toggle/theme-toggle'; -import { getBundled, getNpmCdnUrl } from './bundled'; +import { bundled, getNpmCdnUrl } from './bundled'; import { isServer } from '@builder.io/qwik/build'; +// We cannot use this, it causes the repl to use imports +// import { QWIK_REPL_DEPS_CACHE } from './worker/repl-constants'; +const QWIK_REPL_DEPS_CACHE = 'QwikReplDeps'; export const initMonacoEditor = async ( containerElm: any, @@ -207,7 +210,10 @@ export const addQwikLibs = async (version: string) => { ); } }); - + typescriptDefaults.addExtraLib( + `declare module '@builder.io/qwik/jsx-runtime' { export * from '@builder.io/qwik' }`, + '/node_modules/@builder.io/qwik/dist/jsx-runtime.d.ts' + ); typescriptDefaults.addExtraLib(CLIENT_LIB); }; @@ -226,19 +232,6 @@ const loadDeps = async (qwikVersion: string) => { pkgPath: `${prefix}core.d.ts`, import: '', }, - // JSX runtime - { - pkgName: '@builder.io/qwik', - pkgVersion: qwikVersion, - pkgPath: `${prefix}jsx-runtime.d.ts`, - import: '/jsx-runtime', - }, - { - pkgName: '@builder.io/qwik', - pkgVersion: qwikVersion, - pkgPath: `${prefix}jsx-runtime.d.ts`, - import: '/jsx-dev-runtime', - }, // server API { pkgName: '@builder.io/qwik', @@ -255,7 +248,7 @@ const loadDeps = async (qwikVersion: string) => { }, ]; - const cache = await caches.open('QwikReplResults'); + const cache = await caches.open(QWIK_REPL_DEPS_CACHE); await Promise.all( deps.map(async (dep) => { @@ -286,7 +279,6 @@ const loadDeps = async (qwikVersion: string) => { return monacoCtx.deps; }; -const bundled = getBundled(); const fetchDep = async (cache: Cache, dep: NodeModuleDep) => { const url = getNpmCdnUrl(bundled, dep.pkgName, dep.pkgVersion, dep.pkgPath); const req = new Request(url); diff --git a/packages/docs/src/repl/repl-detail-panel.tsx b/packages/docs/src/repl/repl-detail-panel.tsx index 561dcbea6233..07a9b94ffef9 100644 --- a/packages/docs/src/repl/repl-detail-panel.tsx +++ b/packages/docs/src/repl/repl-detail-panel.tsx @@ -1,4 +1,4 @@ -import { QWIK_PKG_NAME, getBundled } from './bundled'; +import { QWIK_PKG_NAME, bundled } from './bundled'; import { ReplConsole } from './repl-console'; import { ReplOptions } from './repl-options'; import { ReplTabButton } from './repl-tab-button'; @@ -31,7 +31,7 @@ export const ReplDetailPanel = ({ input, store }: ReplDetailPanelProps) => { ) : null} diff --git a/packages/docs/src/repl/repl-version.ts b/packages/docs/src/repl/repl-version.ts index 347ec10adea6..eed2a554bc9a 100644 --- a/packages/docs/src/repl/repl-version.ts +++ b/packages/docs/src/repl/repl-version.ts @@ -1,7 +1,7 @@ /* eslint-disable no-console */ -import { QWIK_PKG_NAME, getBundled } from './bundled'; +import { QWIK_PKG_NAME, bundled } from './bundled'; -const bundledVersion = getBundled()[QWIK_PKG_NAME].version; +const bundledVersion = bundled[QWIK_PKG_NAME].version; // The golden oldies const keepList = new Set('1.0.0,1.1.5,1.2.13,1.4.5'.split(',')); diff --git a/packages/docs/src/repl/repl.tsx b/packages/docs/src/repl/repl.tsx index 83b770ab1f3a..d54792b1a804 100644 --- a/packages/docs/src/repl/repl.tsx +++ b/packages/docs/src/repl/repl.tsx @@ -15,7 +15,7 @@ import type { ReplStore, ReplUpdateMessage, ReplMessage, ReplAppInput } from './ import { ReplDetailPanel } from './repl-detail-panel'; import { getReplVersion } from './repl-version'; import { updateReplOutput } from './repl-output-update'; -import { QWIK_PKG_NAME, getBundled, getNpmCdnUrl } from './bundled'; +import { QWIK_PKG_NAME, bundled, getNpmCdnUrl } from './bundled'; import { isServer } from '@builder.io/qwik/build'; export const Repl = component$((props: ReplProps) => { @@ -165,7 +165,6 @@ export const receiveMessageFromReplServer = ( } }; -const bundled = getBundled(); const getDependencies = (input: ReplAppInput) => { const out = { ...bundled }; if (input.version !== 'bundled') { diff --git a/packages/docs/vite.config.mts b/packages/docs/vite.config.mts index 2833e1206a9f..db8be7e51096 100644 --- a/packages/docs/vite.config.mts +++ b/packages/docs/vite.config.mts @@ -6,7 +6,7 @@ import { qwikVite } from '@builder.io/qwik/optimizer'; import path, { resolve } from 'node:path'; import { defineConfig, loadEnv } from 'vite'; import Inspect from 'vite-plugin-inspect'; -import { examplesData, playgroundData, tutorialData } from './vite.repl-apps'; +import { examplesData, playgroundData, rawSource, tutorialData } from './vite.repl-apps'; import { sourceResolver } from './vite.source-resolver'; export const PUBLIC_QWIK_INSIGHT_KEY = loadEnv('', '.', 'PUBLIC').PUBLIC_QWIK_INSIGHTS_KEY; @@ -58,6 +58,7 @@ export default defineConfig(async () => { }, plugins: [ + rawSource(), qwikCity({ mdxPlugins: { rehypeSyntaxHighlight: false, @@ -138,7 +139,7 @@ export default defineConfig(async () => { defaultHandler(level, log); }, output: { - assetFileNames: 'assets/[hash].[ext]', + assetFileNames: 'assets/[hash]-[name].[ext]', }, }, }, diff --git a/packages/docs/vite.repl-apps.ts b/packages/docs/vite.repl-apps.ts index 441796c923e8..7580acd49cdd 100644 --- a/packages/docs/vite.repl-apps.ts +++ b/packages/docs/vite.repl-apps.ts @@ -7,6 +7,7 @@ import type { PlaygroundApp } from './src/routes/playground/playground-data'; import type { TutorialSection } from './src/routes/tutorial/tutorial-data'; import type { PluginContext } from 'rollup'; import type { ReplModuleInput } from './src/repl/types'; +import MagicString from 'magic-string'; export function playgroundData(routesDir: string): Plugin { const playgroundAppDir = join(routesDir, 'playground', 'app'); @@ -258,3 +259,108 @@ export function tutorialData(routesDir: string): Plugin { }, }; } + +// Workaround for https://github.com/vitejs/vite/issues/15753 +// A vite plugin that implements what `?raw&url` should do +// Use `import url from 'file?raw-source'` to get the url for the raw file content +export function rawSource(): Plugin { + let base: string; + let isDev: boolean = false; + let doSourceMap: boolean = false; + const extToMime = { + js: 'application/javascript', + css: 'text/css', + html: 'text/html', + json: 'application/json', + wasm: 'application/wasm', + }; + return { + name: 'raw-source', + + configResolved(config) { + base = config.base; + isDev = config.command === 'serve'; + doSourceMap = !!config.build.sourcemap; + }, + + configureServer(server) { + // Vite still processes /@fs urls, so we need to run our own static server + server.middlewares.use((req, res, next) => { + if (req.url!.startsWith('/@raw-fs')) { + const filePath = req.url!.slice('/@raw-fs'.length); + if (existsSync(filePath)) { + const ext = filePath.split('.').pop()! as keyof typeof extToMime; + const contentType = extToMime[ext] || 'application/octet-stream'; + res.setHeader('Content-Type', contentType); + res.end(readFileSync(filePath)); + } else { + res.statusCode = 404; + res.end('File not found'); + } + } else { + next(); + } + }); + }, + + resolveId: { + order: 'pre', + async handler(id, importer) { + const match = /^(?.*)\?(|(?.+)&)raw-source($|&(?.*))/.exec(id); + + if (match) { + const newQuery = [match.groups!.before, match.groups!.after].filter(Boolean).join('&'); + const newId = `${match.groups!.path}${newQuery ? `?${newQuery}` : ''}`; + const resolved = await this.resolve(newId, importer, { + skipSelf: true, + }); + if (!resolved) { + throw new Error(`Could not resolve "${id}" from "${importer}"`); + } + return `\0raw-source:${resolved!.id.split('?')[0]}`; + } + }, + }, + + load(id) { + if (id.startsWith('\0raw-source:')) { + let path = id.slice('\0raw-source:'.length); + if (path.startsWith('/@fs/')) { + path = path.slice('/@fs'.length); + } + if (isDev) { + const devUrl = `${base}@raw-fs${path}`; + return `export default "${devUrl}";`; + } + const fileContent = readFileSync(path); + const ref = this.emitFile({ + type: 'asset', + name: basename(path), + source: fileContent, + }); + return { + code: `export default "__RAW-URL_${ref}__";`, + map: { version: 3, sources: [path], mappings: '' }, + }; + } + }, + + renderChunk(code, chunk) { + // Copied from vite assets code and simplified + let s, match; + while ((match = /__RAW-URL_([^_]+)__/g.exec(code))) { + s ||= new MagicString(code); + const ref = match[1]; + const fileName = this.getFileName(ref); + chunk.viteMetadata!.importedAssets.add(fileName); + s.overwrite(match.index, match.index + match[0].length, base + fileName); + } + return s + ? { + code: s ? s.toString() : code, + map: doSourceMap ? s.generateMap({ hires: true }) : null, + } + : null; + }, + }; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 823ccba2b308..a7a5682f013b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -335,6 +335,9 @@ importers: leaflet: specifier: 1.9.4 version: 1.9.4 + magic-string: + specifier: 0.30.11 + version: 0.30.11 openai: specifier: 3.3.0 version: 3.3.0