Skip to content

Commit

Permalink
perf(docs): move bundled repl to assets
Browse files Browse the repository at this point in the history
The previous situation was a hack, this is way cleaner and allows caching repl dependencies between releases.
  • Loading branch information
wmertens committed Aug 1, 2024
1 parent 99d6db4 commit 38ff681
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 77 deletions.
5 changes: 5 additions & 0 deletions packages/docs/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// handled by raw-source plugin in vite.repl-apps.ts
declare module '*?raw-source' {
const url: string;
export default url;
}
1 change: 1 addition & 0 deletions packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.37",
"prettier": "^3.2.5",
Expand Down
74 changes: 27 additions & 47 deletions packages/docs/src/repl/bundled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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: {
Expand All @@ -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;
26 changes: 9 additions & 17 deletions packages/docs/src/repl/monaco.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
};

Expand All @@ -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',
Expand All @@ -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) => {
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/repl/repl-detail-panel.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -31,7 +31,7 @@ export const ReplDetailPanel = ({ input, store }: ReplDetailPanelProps) => {
<ReplOptions
input={input}
versions={store.versions}
qwikVersion={getBundled()[QWIK_PKG_NAME].version}
qwikVersion={bundled[QWIK_PKG_NAME].version}
/>
) : null}
</div>
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/repl/repl-version.ts
Original file line number Diff line number Diff line change
@@ -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(','));
Expand Down
3 changes: 1 addition & 2 deletions packages/docs/src/repl/repl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -165,7 +165,6 @@ export const receiveMessageFromReplServer = (
}
};

const bundled = getBundled();
const getDependencies = (input: ReplAppInput) => {
const out = { ...bundled };
if (input.version !== 'bundled') {
Expand Down
5 changes: 3 additions & 2 deletions packages/docs/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -58,6 +58,7 @@ export default defineConfig(async () => {
},

plugins: [
rawSource(),
qwikCity({
mdxPlugins: {
rehypeSyntaxHighlight: false,
Expand Down Expand Up @@ -138,7 +139,7 @@ export default defineConfig(async () => {
defaultHandler(level, log);
},
output: {
assetFileNames: 'assets/[hash].[ext]',
assetFileNames: 'assets/[hash]-[name].[ext]',
},
},
},
Expand Down
106 changes: 106 additions & 0 deletions packages/docs/vite.repl-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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 = /^(?<path>.*)\?(|(?<before>.+)&)raw-source($|&(?<after>.*))/.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;
},
};
}
Loading

0 comments on commit 38ff681

Please sign in to comment.