diff --git a/.changeset/modern-emus-share.md b/.changeset/modern-emus-share.md new file mode 100644 index 00000000000..6f688456d58 --- /dev/null +++ b/.changeset/modern-emus-share.md @@ -0,0 +1,5 @@ +--- +'@qwik.dev/core': minor +--- + +FEAT: `getClientManifest()` is now the way to get the client build manifest. Importing from `@qwik-client-manifest` is deprecated. diff --git a/packages/docs/src/routes/api/qwik/api.json b/packages/docs/src/routes/api/qwik/api.json index f2b7f880af8..c44fae71cb1 100644 --- a/packages/docs/src/routes/api/qwik/api.json +++ b/packages/docs/src/routes/api/qwik/api.json @@ -731,6 +731,20 @@ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/jsx/types/jsx-node.ts", "mdFile": "core.functioncomponent.md" }, + { + "name": "getClientManifest", + "id": "getclientmanifest", + "hierarchy": [ + { + "name": "getClientManifest", + "id": "getclientmanifest" + } + ], + "kind": "Function", + "content": "Returns the client build manifest, which includes the mappings from symbols to bundles, the bundlegraph etc.\n\n\n```typescript\ngetClientManifest: () => ServerQwikManifest\n```\n**Returns:**\n\nServerQwikManifest", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/get-client-manifest.ts", + "mdFile": "core.getclientmanifest.md" + }, { "name": "getDomContainer", "id": "getdomcontainer", diff --git a/packages/docs/src/routes/api/qwik/index.mdx b/packages/docs/src/routes/api/qwik/index.mdx index fb0fc8db94a..cc2206a599d 100644 --- a/packages/docs/src/routes/api/qwik/index.mdx +++ b/packages/docs/src/routes/api/qwik/index.mdx @@ -1684,6 +1684,20 @@ export type FunctionComponent

= { [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/jsx/types/jsx-node.ts) +

getClientManifest

+ +Returns the client build manifest, which includes the mappings from symbols to bundles, the bundlegraph etc. + +```typescript +getClientManifest: () => ServerQwikManifest; +``` + +**Returns:** + +ServerQwikManifest + +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/get-client-manifest.ts) +

getDomContainer

```typescript diff --git a/packages/insights/tsconfig.json b/packages/insights/tsconfig.json index 4392154a0cd..a6ce4f91100 100644 --- a/packages/insights/tsconfig.json +++ b/packages/insights/tsconfig.json @@ -19,8 +19,7 @@ "noEmit": true, "types": ["node", "vite/client"], "paths": { - "~/*": ["./src/*"], - "@qwik-client-manifest": ["../../qwik/src/server/server-modules.d.ts"] + "~/*": ["./src/*"] } }, "exclude": ["./dist", "eslint.config.mjs"] diff --git a/packages/qwik-router/src/buildtime/vite/plugin.ts b/packages/qwik-router/src/buildtime/vite/plugin.ts index 227bb555150..716b0e35b49 100644 --- a/packages/qwik-router/src/buildtime/vite/plugin.ts +++ b/packages/qwik-router/src/buildtime/vite/plugin.ts @@ -24,6 +24,10 @@ import { validatePlugin } from './validate-plugin'; import { getRouterIndexTags, makeRouterDevMiddleware } from './dev-middleware'; export const QWIK_ROUTER_CONFIG_ID = '@qwik-router-config'; +/** + * This virtual module is used to generate dynamic entries for user route files, which are added as + * dynamic imports to the qwik-router-config as a way to create new entry points for the build. + */ const QWIK_ROUTER_ENTRIES_ID = '@qwik-router-entries'; const QWIK_ROUTER = '@qwik.dev/router'; const QWIK_ROUTER_SW_REGISTER = '@qwik-router-sw-register'; diff --git a/packages/qwik/package.json b/packages/qwik/package.json index e805724b23c..2d22fa117d0 100644 --- a/packages/qwik/package.json +++ b/packages/qwik/package.json @@ -10,6 +10,7 @@ "dependencies": { "csstype": "^3.2.3", "launch-editor": "^2.12.0", + "magic-string": "0.30.21", "rollup": "^4.59.0", "ts-morph": "27.0.2" }, diff --git a/packages/qwik/src/core/index.ts b/packages/qwik/src/core/index.ts index f77ec1a5451..fd4162f187d 100644 --- a/packages/qwik/src/core/index.ts +++ b/packages/qwik/src/core/index.ts @@ -54,6 +54,7 @@ export { getPlatform, setPlatform } from './shared/platform/platform'; export type { CorePlatform } from './shared/platform/types'; export type { ClientContainer } from './client/types'; export type { DomContainer } from './client/dom-container'; +export { getClientManifest } from './shared/get-client-manifest'; ////////////////////////////////////////////////////////////////////////////////////////// // JSX Runtime diff --git a/packages/qwik/src/core/qwik.core.api.md b/packages/qwik/src/core/qwik.core.api.md index ad8b364534d..3c1afc2766b 100644 --- a/packages/qwik/src/core/qwik.core.api.md +++ b/packages/qwik/src/core/qwik.core.api.md @@ -8,6 +8,7 @@ import * as CSS_2 from 'csstype'; import { isBrowser } from '@qwik.dev/core/build'; import { isDev } from '@qwik.dev/core/build'; import { isServer } from '@qwik.dev/core/build'; +import type { ServerQwikManifest } from '@qwik.dev/core/optimizer'; // @public export const $: (expression: T) => QRL; @@ -401,6 +402,9 @@ export type FunctionComponent

= { renderFn(props: P, key: string | null, flags: number, dev?: DevJSX): JSXOutput; }['renderFn']; +// @public +export const getClientManifest: () => ServerQwikManifest; + // Warning: (ae-forgotten-export) The symbol "PropsProxy" needs to be exported by the entry point index.d.ts // // @internal diff --git a/packages/qwik/src/core/shared/get-client-manifest.ts b/packages/qwik/src/core/shared/get-client-manifest.ts new file mode 100644 index 00000000000..16d6f0c4448 --- /dev/null +++ b/packages/qwik/src/core/shared/get-client-manifest.ts @@ -0,0 +1,17 @@ +import type { ServerQwikManifest } from '@qwik.dev/core/optimizer'; + +/** + * Returns the client build manifest, which includes the mappings from symbols to bundles, the + * bundlegraph etc. + * + * @public + */ +export const getClientManifest = (): ServerQwikManifest => { + const manifest = (globalThis as any).__QWIK_MANIFEST__ as ServerQwikManifest; + if (!(globalThis as any).__QWIK_MANIFEST__) { + throw new Error( + `Client manifest is not available. It should have been automatically injected during the build process. Make sure that @qwik.dev/core is internal to the build.` + ); + } + return manifest; +}; diff --git a/packages/qwik/src/optimizer/src/plugins/plugin.ts b/packages/qwik/src/optimizer/src/plugins/plugin.ts index 3add4e61d96..66dbaa76ba3 100644 --- a/packages/qwik/src/optimizer/src/plugins/plugin.ts +++ b/packages/qwik/src/optimizer/src/plugins/plugin.ts @@ -22,6 +22,7 @@ import type { import { convertManifestToBundleGraph } from './bundle-graph'; import { createLinter, type QwikLinter } from './eslint-plugin'; import { isVirtualId, isWin, parseId } from './vite-utils'; +import MagicString from 'magic-string'; const REG_CTX_NAME = ['server']; @@ -539,6 +540,9 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) { }; } } else if (pathId.endsWith(QWIK_CLIENT_MANIFEST_ID)) { + console.error( + `${importerId}: importing ${QWIK_CLIENT_MANIFEST_ID} is deprecated. Use \`getClientManifest()\` instead.` + ); debug(`resolveId(${count})`, 'Resolved', QWIK_CLIENT_MANIFEST_ID); result = { id: QWIK_CLIENT_MANIFEST_ID, @@ -662,7 +666,7 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) { debug(`load(${count})`, QWIK_CLIENT_MANIFEST_ID, opts.buildMode); return { moduleSideEffects: false, - code: await getQwikServerManifestModule(isServer), + code: `export const manifest = globalThis.${globalManifestKey}'};\n`, }; } /** @@ -715,6 +719,7 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) { return null; }; + let theManifest: string | null | undefined; let transformCount = 0; const transform = async function ( ctx: Rollup.PluginContext, @@ -725,7 +730,6 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) { if (isVirtualId(id)) { return; } - const count = transformCount++; const isServer = getIsServer(transformOpts); const currentOutputs = isServer ? serverTransformedOutputs : clientTransformedOutputs; if (currentOutputs.has(id)) { @@ -733,6 +737,10 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) { return; } + let shouldReturn = false; + + const count = transformCount++; + const optimizer = getOptimizer(); const path = getPath(); @@ -741,6 +749,8 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) { const dir = parsedPathId.dir; const base = parsedPathId.base; const ext = parsedPathId.ext.toLowerCase(); + let map; + let meta; if (ext in TRANSFORM_EXTS || TRANSFORM_REGEX.test(pathId)) { /** Strip client|server code from qwik server|client, but not in lib/test */ const strip = opts.target === 'client' || opts.target === 'ssr'; @@ -846,19 +856,35 @@ export function createQwikPlugin(optimizerOptions: OptimizerOptions = {}) { ctx.addWatchFile(id); - return { - code: module.code, - map: module.map, - meta: { - segment: module.segment, - qwikdeps: Array.from(deps), - }, + code = module.code; + map = module.map; + meta = { + segment: module.segment, + qwikdeps: Array.from(deps), }; + shouldReturn = true; } - debug(`transform(${count})`, 'Not transforming', id); + if (code.includes(`globalThis.${globalManifestKey}`)) { + if (theManifest === undefined) { + theManifest = await getQwikServerManifest(getIsServer(transformOpts)); + } + const s = new MagicString(code); + // Always replace the check + s.replace(`!globalThis.${globalManifestKey}`, 'false'); + if (theManifest) { + s.replace(`globalThis.${globalManifestKey}`, theManifest); + } + code = s.toString(); + map ||= s.generateMap({ source: id, includeContent: false }); + shouldReturn = true; + debug(`transform(${count})`, `Replaced globalThis.${globalManifestKey} with manifest input`); + } - return null; + if (shouldReturn) { + return { code, map, meta }; + } + debug(`transform(${count})`, 'Not transforming', id); }; type OutputAnalyzer = { @@ -957,7 +983,7 @@ export const isDev = ${JSON.stringify(isDev)}; `; } - async function getQwikServerManifestModule(isServer: boolean) { + async function getQwikServerManifest(isServer: boolean) { if ( !opts.manifestInput && opts.target === 'ssr' && @@ -1001,8 +1027,7 @@ export const isDev = ${JSON.stringify(isDev)}; bundleGraph: manifest.bundleGraph, }; } - return `// @qwik-client-manifest -export const manifest = ${serverManifest ? JSON.stringify(serverManifest) : 'globalThis.__QWIK_MANIFEST__'};\n`; + return serverManifest && JSON.stringify(serverManifest); } function setSourceMapSupport(sourcemap: boolean) { @@ -1207,10 +1232,11 @@ export const QWIK_JSX_DEV_RUNTIME_ID = '@qwik.dev/core/jsx-dev-runtime'; export const QWIK_CORE_SERVER = '@qwik.dev/core/server'; +/** Internal use only - use `getClientManifest()` instead */ export const QWIK_CLIENT_MANIFEST_ID = '@qwik-client-manifest'; export const QWIK_PRELOADER_ID = '@qwik.dev/core/preloader'; - +/** @internal virtual import to ensure the _run etc handlers are exported as-is */ export const QWIK_HANDLERS_ID = '@qwik-handlers'; export const SRC_DIR_DEFAULT = 'src'; @@ -1289,3 +1315,5 @@ export type QwikBuildTarget = 'client' | 'ssr' | 'lib' | 'test'; /** @public */ export type QwikBuildMode = 'production' | 'development'; + +const globalManifestKey = '__QWIK_MANIFEST__'; diff --git a/packages/qwik/src/optimizer/src/plugins/plugin.unit.ts b/packages/qwik/src/optimizer/src/plugins/plugin.unit.ts index 1f54cf23d11..2d372435c74 100644 --- a/packages/qwik/src/optimizer/src/plugins/plugin.unit.ts +++ b/packages/qwik/src/optimizer/src/plugins/plugin.unit.ts @@ -296,7 +296,7 @@ describe('resolveId', () => { 'id', '@qwik.dev/core/build' ); - expect(await plugin.resolveId(null!, '@qwik-client-manifest', '/foo/bar')).toHaveProperty( + expect(await plugin.resolveId(null!, '@qwik-client-manifest', '/foo/bar/core')).toHaveProperty( 'id', '@qwik-client-manifest' ); diff --git a/packages/qwik/src/server/server-modules.d.ts b/packages/qwik/src/server/server-modules.d.ts index 081815adccc..4a8b961ccad 100644 --- a/packages/qwik/src/server/server-modules.d.ts +++ b/packages/qwik/src/server/server-modules.d.ts @@ -1,4 +1,5 @@ declare module '@qwik-client-manifest' { + /** @deprecated Use `getClientManifest()` instead */ const manifest: import('.').ServerQwikManifest; export { manifest }; } diff --git a/packages/qwik/src/server/ssr-render.ts b/packages/qwik/src/server/ssr-render.ts index d00398b0e6e..ed7c47ded21 100644 --- a/packages/qwik/src/server/ssr-render.ts +++ b/packages/qwik/src/server/ssr-render.ts @@ -1,5 +1,8 @@ +import { getClientManifest } from '@qwik.dev/core'; import { getSymbolHash, setServerPlatform } from './platform'; -import type { JSXOutput, ResolvedManifest, SymbolMapper, StreamWriter } from './qwik-types'; +import type { JSXOutput, ResolvedManifest, StreamWriter, SymbolMapper } from './qwik-types'; +import { ssrCreateContainer } from './ssr-container'; +import { StreamHandler } from './ssr-stream-handler'; import type { QwikManifest, RenderToStreamOptions, @@ -8,9 +11,6 @@ import type { RenderToStringResult, } from './types'; import { getBuildBase } from './utils'; -import { ssrCreateContainer } from './ssr-container'; -import { manifest as builtManifest } from '@qwik-client-manifest'; -import { StreamHandler } from './ssr-stream-handler'; /** * Creates a server-side `document`, renders to root node to the document, then serializes the @@ -102,6 +102,7 @@ export const renderToStream = async ( export function resolveManifest( manifest?: Partial | undefined ): ResolvedManifest | undefined { + const builtManifest = getClientManifest(); const mergedManifest = (manifest ? { ...builtManifest, ...manifest } : builtManifest) as | ResolvedManifest | QwikManifest; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c8631df19b..167a18e15e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -591,6 +591,9 @@ importers: launch-editor: specifier: ^2.12.0 version: 2.12.0 + magic-string: + specifier: 0.30.21 + version: 0.30.21 rollup: specifier: ^4.59.0 version: 4.59.0 diff --git a/scripts/api.ts b/scripts/api.ts index 4abe8f6a1e4..094c6f67933 100644 --- a/scripts/api.ts +++ b/scripts/api.ts @@ -292,6 +292,7 @@ function generateServerReferenceModules(config: BuildConfig) { // server-modules.d.ts const referenceDts = `/// declare module '@qwik-client-manifest' { + /** @deprecated Use \`getClientManifest()\` instead */ const manifest: import('./optimizer').QwikManifest; export { manifest }; } diff --git a/scripts/submodule-core.ts b/scripts/submodule-core.ts index 8509b01e080..5a47a228ed9 100644 --- a/scripts/submodule-core.ts +++ b/scripts/submodule-core.ts @@ -39,7 +39,12 @@ async function submoduleCoreProd(config: BuildConfig): Promise { const input: InputOptions = { input: join(config.tscDir, 'packages', 'qwik', 'src', 'core', 'index.js'), onwarn: rollupOnWarn, - external: ['@qwik.dev/core/build', '@qwik.dev/core/preloader', 'node:async_hooks'], + external: [ + '@qwik.dev/core/build', + '@qwik.dev/core/preloader', + '@qwik-client-manifest', + 'node:async_hooks', + ], plugins: [ { name: 'setVersion', @@ -78,7 +83,7 @@ async function submoduleCoreProd(config: BuildConfig): Promise { const inputCore = join(config.distQwikPkgDir, 'core.mjs'); const inputMin: InputOptions = { - external: ['@qwik.dev/core/preloader', 'node:async_hooks'], + external: ['@qwik.dev/core/preloader', '@qwik-client-manifest', 'node:async_hooks'], input: inputCore, onwarn: rollupOnWarn, plugins: [ @@ -339,7 +344,12 @@ async function submoduleCoreDev(config: BuildConfig) { const esm = await build({ ...opts, - external: ['@qwik.dev/core/build', '@qwik.dev/core/preloader', 'node:async_hooks'], + external: [ + '@qwik.dev/core/build', + '@qwik.dev/core/preloader', + '@qwik-client-manifest', + 'node:async_hooks', + ], format: 'esm', outExtension: { '.js': '.mjs' }, }); diff --git a/scripts/submodule-server.ts b/scripts/submodule-server.ts index 737eb48651f..da24e3367f3 100644 --- a/scripts/submodule-server.ts +++ b/scripts/submodule-server.ts @@ -37,7 +37,6 @@ export async function submoduleServer(config: BuildConfig, nameCache?: object) { '@qwik.dev/core', '@qwik.dev/core/build', '@qwik.dev/core/preloader', - '@qwik-client-manifest', ], };