From f5afaf24984ee7d4d6e908a7eeed17f5ca18c61e Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Tue, 21 Jun 2022 08:32:05 -0400 Subject: [PATCH] Support re-exporting astro components containing client components (#3625) * Support re-exporting astro components containing client components * Include metadata for markdown too * Fix ssr, probably * Inject post-build * Remove tagName custom element test * Allows using the constructor for lit elements * Fix hoisted script scanning * Pass through plugin context * Get edge functions working in the edge tests * Fix types for the edge function integration * Upgrade the compiler * Upgrade compiler version * Better release notes for lit * Update .changeset/unlucky-hairs-camp.md Co-authored-by: Nate Moore * Properly test that the draft was not rendered * Prevent from rendering draft posts * Add a changeset about the build perf improvement. Co-authored-by: Nate Moore --- .changeset/cyan-kids-sleep.md | 11 ++ .changeset/unlucky-hairs-camp.md | 9 ++ packages/astro/package.json | 2 +- packages/astro/src/core/build/generate.ts | 14 ++ packages/astro/src/core/build/graph.ts | 36 +++++ packages/astro/src/core/build/index.ts | 12 -- packages/astro/src/core/build/internal.ts | 28 +++- packages/astro/src/core/build/page-data.ts | 41 ++---- packages/astro/src/core/build/static-build.ts | 87 +++---------- packages/astro/src/core/build/types.ts | 2 - .../src/core/build/vite-plugin-analyzer.ts | 123 ++++++++++++++++++ .../astro/src/core/build/vite-plugin-ssr.ts | 37 ++++-- packages/astro/src/runtime/server/metadata.ts | 34 +---- .../astro/src/vite-plugin-astro/compile.ts | 7 +- packages/astro/src/vite-plugin-astro/index.ts | 13 +- .../astro/src/vite-plugin-astro/styles.ts | 7 +- packages/astro/src/vite-plugin-astro/types.ts | 9 ++ .../astro/src/vite-plugin-build-css/index.ts | 35 +---- .../astro/src/vite-plugin-markdown/index.ts | 14 +- .../astro/test/astro-markdown-drafts.test.js | 3 + packages/astro/test/custom-elements.test.js | 2 +- .../custom-elements/src/pages/load.astro | 15 --- .../lit-element/src/components/my-element.js | 2 - .../lit-element/src/pages/index.astro | 6 +- .../astro.config.mjs | 5 + .../package.json | 7 + .../src/components/One/One.astro | 4 + .../src/components/One/One.jsx | 6 + .../src/components/One/index.js | 1 + .../src/pages/index.astro | 9 ++ packages/astro/test/lit-element.test.js | 14 +- ...-astro-containing-client-component.test.js | 19 +++ packages/integrations/lit/server-shim.js | 6 + packages/integrations/lit/server.js | 8 +- packages/integrations/lit/src/index.ts | 1 + packages/integrations/netlify/package.json | 1 + .../netlify/src/integration-edge-functions.ts | 22 +++- .../edge-functions/dynamic-import.test.js | 2 + .../test/edge-functions/edge-basic.test.ts | 1 + pnpm-lock.yaml | 21 ++- 40 files changed, 434 insertions(+), 242 deletions(-) create mode 100644 .changeset/cyan-kids-sleep.md create mode 100644 .changeset/unlucky-hairs-camp.md create mode 100644 packages/astro/src/core/build/graph.ts create mode 100644 packages/astro/src/core/build/vite-plugin-analyzer.ts create mode 100644 packages/astro/src/vite-plugin-astro/types.ts delete mode 100644 packages/astro/test/fixtures/custom-elements/src/pages/load.astro create mode 100644 packages/astro/test/fixtures/reexport-astro-containing-client-component/astro.config.mjs create mode 100644 packages/astro/test/fixtures/reexport-astro-containing-client-component/package.json create mode 100644 packages/astro/test/fixtures/reexport-astro-containing-client-component/src/components/One/One.astro create mode 100644 packages/astro/test/fixtures/reexport-astro-containing-client-component/src/components/One/One.jsx create mode 100644 packages/astro/test/fixtures/reexport-astro-containing-client-component/src/components/One/index.js create mode 100644 packages/astro/test/fixtures/reexport-astro-containing-client-component/src/pages/index.astro create mode 100644 packages/astro/test/reexport-astro-containing-client-component.test.js diff --git a/.changeset/cyan-kids-sleep.md b/.changeset/cyan-kids-sleep.md new file mode 100644 index 000000000000..e533efa72c11 --- /dev/null +++ b/.changeset/cyan-kids-sleep.md @@ -0,0 +1,11 @@ +--- +'astro': patch +--- + +Significantly improved build performance + +This change reflects in a significantly improved build performance, especially on larger sites. + +With this change Astro is not building everything by statically analyzing `.astro` files. This means it no longer needs to dynamically *run* your code in order to know what JavaScript needs to be built. + +With one particular large site we found it to build __32%__ faster. diff --git a/.changeset/unlucky-hairs-camp.md b/.changeset/unlucky-hairs-camp.md new file mode 100644 index 000000000000..d63d123ba2b5 --- /dev/null +++ b/.changeset/unlucky-hairs-camp.md @@ -0,0 +1,9 @@ +--- +'@astrojs/lit': minor +--- + +Conform to Constructor based rendering + +This changes `@astrojs/lit` to conform to the way rendering happens in all other frameworks. Instead of using the tag name `` you use the imported constructor function, `` like you would do with any other framework. + +Support for `tag-name` syntax had to be removed due to the fact that it was a runtime feature that was not statically analyzable. To improve build performance, we have removed all runtime based component discovery. Using the imported Constructor name allows Astro to discover what components need to be built and bundled for production without ever running your file. diff --git a/packages/astro/package.json b/packages/astro/package.json index 782f439154d2..5165f4e43446 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -78,7 +78,7 @@ "test:e2e:match": "playwright test -g" }, "dependencies": { - "@astrojs/compiler": "^0.15.2", + "@astrojs/compiler": "^0.16.1", "@astrojs/language-server": "^0.13.4", "@astrojs/markdown-remark": "^0.11.2", "@astrojs/prism": "0.4.1", diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index 65f3930dd975..469d04c501b4 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -56,6 +56,15 @@ function* throttle(max: number, inPaths: string[]) { } } +function shouldSkipDraft(pageModule: ComponentInstance, astroConfig: AstroConfig): boolean { + return ( + // Drafts are disabled + !astroConfig.markdown.drafts && + // This is a draft post + ('frontmatter' in pageModule && (pageModule as any).frontmatter.draft === true) + ); +} + // Gives back a facadeId that is relative to the root. // ie, src/pages/index.astro instead of /Users/name..../src/pages/index.astro export function rootRelativeFacadeId(facadeId: string, astroConfig: AstroConfig): string { @@ -124,6 +133,11 @@ async function generatePage( ); } + if(shouldSkipDraft(pageModule, opts.astroConfig)) { + info(opts.logging, null, `${magenta('⚠️')} Skipping draft ${pageData.route.component}`); + return; + } + const generationOptions: Readonly = { pageData, internals, diff --git a/packages/astro/src/core/build/graph.ts b/packages/astro/src/core/build/graph.ts new file mode 100644 index 000000000000..e1b0b62b7077 --- /dev/null +++ b/packages/astro/src/core/build/graph.ts @@ -0,0 +1,36 @@ +import type { GetModuleInfo, ModuleInfo, OutputChunk } from 'rollup'; +import { resolvedPagesVirtualModuleId } from '../app/index.js'; + +// This walks up the dependency graph and yields out each ModuleInfo object. +export function* walkParentInfos( + id: string, + ctx: { getModuleInfo: GetModuleInfo }, + seen = new Set() +): Generator { + seen.add(id); + const info = ctx.getModuleInfo(id); + if (info) { + yield info; + } + const importers = (info?.importers || []).concat(info?.dynamicImporters || []); + for (const imp of importers) { + if (seen.has(imp)) { + continue; + } + yield* walkParentInfos(imp, ctx, seen); + } +} + +// This function walks the dependency graph, going up until it finds a page component. +// This could be a .astro page or a .md page. +export function* getTopLevelPages( + id: string, + ctx: { getModuleInfo: GetModuleInfo } +): Generator { + for (const info of walkParentInfos(id, ctx)) { + const importers = (info?.importers || []).concat(info?.dynamicImporters || []); + if (importers.length <= 2 && importers[0] === resolvedPagesVirtualModuleId) { + yield info.id; + } + } +} diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 70ca80160bd6..137c1e7a9b2d 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -114,18 +114,6 @@ class AstroBuilder { ssr: isBuildingToSSR(this.config), }); - // Filter pages by using conditions based on their frontmatter. - Object.entries(allPages).forEach(([page, data]) => { - if ('frontmatter' in data.preload[1]) { - // TODO: add better type inference to data.preload[1] - const frontmatter = (data.preload[1] as any).frontmatter; - if (Boolean(frontmatter.draft) && !this.config.markdown.drafts) { - debug('build', timerMessage(`Skipping draft page ${page}`, this.timer.loadStart)); - delete allPages[page]; - } - } - }); - debug('build', timerMessage('All pages loaded', this.timer.loadStart)); // The names of each pages diff --git a/packages/astro/src/core/build/internal.ts b/packages/astro/src/core/build/internal.ts index ad838fac8187..2926e8270949 100644 --- a/packages/astro/src/core/build/internal.ts +++ b/packages/astro/src/core/build/internal.ts @@ -1,4 +1,4 @@ -import type { RenderedChunk } from 'rollup'; +import type { OutputChunk, RenderedChunk } from 'rollup'; import type { PageBuildData, ViteID } from './types'; import { prependForwardSlash } from '../path.js'; @@ -31,6 +31,27 @@ export interface BuildInternals { * A map for page-specific information by a client:only component */ pagesByClientOnly: Map>; + + /** + * A list of hydrated components that are discovered during the SSR build + * These will be used as the top-level entrypoints for the client build. + */ + discoveredHydratedComponents: Set; + /** + * A list of client:only components that are discovered during the SSR build + * These will be used as the top-level entrypoints for the client build. + */ + discoveredClientOnlyComponents: Set; + /** + * A list of hoisted scripts that are discovered during the SSR build + * These will be used as the top-level entrypoints for the client build. + */ + discoveredScripts: Set; + + // A list of all static files created during the build. Used for SSR. + staticFiles: Set; + // The SSR entry chunk. Kept in internals to share between ssr/client build steps + ssrEntryChunk?: OutputChunk; } /** @@ -64,6 +85,11 @@ export function createBuildInternals(): BuildInternals { pagesByComponent: new Map(), pagesByViteID: new Map(), pagesByClientOnly: new Map(), + + discoveredHydratedComponents: new Set(), + discoveredClientOnlyComponents: new Set(), + discoveredScripts: new Set(), + staticFiles: new Set(), }; } diff --git a/packages/astro/src/core/build/page-data.ts b/packages/astro/src/core/build/page-data.ts index ebbe50a51aad..371b7bd26284 100644 --- a/packages/astro/src/core/build/page-data.ts +++ b/packages/astro/src/core/build/page-data.ts @@ -71,30 +71,18 @@ export async function collectPagesData( css: new Set(), hoistedScript: undefined, scripts: new Set(), - preload: await ssrPreload({ - astroConfig, - filePath: new URL(`./${route.component}`, astroConfig.root), - viteServer, - }) - .then((routes) => { - clearInterval(routeCollectionLogTimeout); - if (buildMode === 'static') { - const html = `${route.pathname}`.replace(/\/?$/, '/index.html'); - debug( - 'build', - `├── ${colors.bold(colors.green('✔'))} ${route.component} → ${colors.yellow(html)}` - ); - } else { - debug('build', `├── ${colors.bold(colors.green('✔'))} ${route.component}`); - } - return routes; - }) - .catch((err) => { - clearInterval(routeCollectionLogTimeout); - debug('build', `├── ${colors.bold(colors.red('✘'))} ${route.component}`); - throw err; - }), }; + + clearInterval(routeCollectionLogTimeout); + if (buildMode === 'static') { + const html = `${route.pathname}`.replace(/\/?$/, '/index.html'); + debug( + 'build', + `├── ${colors.bold(colors.green('✔'))} ${route.component} → ${colors.yellow(html)}` + ); + } else { + debug('build', `├── ${colors.bold(colors.green('✔'))} ${route.component}`); + } continue; } // dynamic route: @@ -144,12 +132,7 @@ export async function collectPagesData( moduleSpecifier: '', css: new Set(), hoistedScript: undefined, - scripts: new Set(), - preload: await ssrPreload({ - astroConfig, - filePath: new URL(`./${route.component}`, astroConfig.root), - viteServer, - }), + scripts: new Set() }; } diff --git a/packages/astro/src/core/build/static-build.ts b/packages/astro/src/core/build/static-build.ts index ed855e254c30..2300c790a3f2 100644 --- a/packages/astro/src/core/build/static-build.ts +++ b/packages/astro/src/core/build/static-build.ts @@ -7,7 +7,6 @@ import * as vite from 'vite'; import { BuildInternals, createBuildInternals, - trackClientOnlyPageDatas, } from '../../core/build/internal.js'; import { prependForwardSlash } from '../../core/path.js'; import { emptyDir, removeDir } from '../../core/util.js'; @@ -23,7 +22,8 @@ import { getTimeStat } from './util.js'; import { vitePluginHoistedScripts } from './vite-plugin-hoisted-scripts.js'; import { vitePluginInternals } from './vite-plugin-internals.js'; import { vitePluginPages } from './vite-plugin-pages.js'; -import { vitePluginSSR } from './vite-plugin-ssr.js'; +import { vitePluginSSR, injectManifest } from './vite-plugin-ssr.js'; +import { vitePluginAnalyzer } from './vite-plugin-analyzer.js'; export async function staticBuild(opts: StaticBuildOptions) { const { allPages, astroConfig } = opts; @@ -31,16 +31,12 @@ export async function staticBuild(opts: StaticBuildOptions) { // The pages to be built for rendering purposes. const pageInput = new Set(); - // The JavaScript entrypoints. - const jsInput = new Set(); - // A map of each page .astro file, to the PageBuildData which contains information // about that page, such as its paths. const facadeIdToPageDataMap = new Map(); // Build internals needed by the CSS plugin const internals = createBuildInternals(); - const uniqueHoistedIds = new Map(); const timer: Record = {}; @@ -53,66 +49,6 @@ export async function staticBuild(opts: StaticBuildOptions) { // Track the page data in internals trackPageData(internals, component, pageData, astroModuleId, astroModuleURL); - if (pageData.route.type === 'page') { - const [renderers, mod] = pageData.preload; - const metadata = mod.$$metadata; - - const topLevelImports = new Set([ - // The client path for each renderer - ...renderers - .filter((renderer) => !!renderer.clientEntrypoint) - .map((renderer) => renderer.clientEntrypoint!), - ]); - - if (metadata) { - // Any component that gets hydrated - // 'components/Counter.jsx' - // { 'components/Counter.jsx': 'counter.hash.js' } - for (const hydratedComponentPath of metadata.hydratedComponentPaths()) { - topLevelImports.add(hydratedComponentPath); - } - - // Track client:only usage so we can map their CSS back to the Page they are used in. - const clientOnlys = Array.from(metadata.clientOnlyComponentPaths()); - trackClientOnlyPageDatas(internals, pageData, clientOnlys); - - // Client-only components - for (const clientOnly of clientOnlys) { - topLevelImports.add(clientOnly); - } - - // Add hoisted scripts - const hoistedScripts = new Set(metadata.hoistedScriptPaths()); - if (hoistedScripts.size) { - const uniqueHoistedId = JSON.stringify(Array.from(hoistedScripts).sort()); - let moduleId: string; - - // If we're already tracking this set of hoisted scripts, get the unique id - if (uniqueHoistedIds.has(uniqueHoistedId)) { - moduleId = uniqueHoistedIds.get(uniqueHoistedId)!; - } else { - // Otherwise, create a unique id for this set of hoisted scripts - moduleId = `/astro/hoisted.js?q=${uniqueHoistedIds.size}`; - uniqueHoistedIds.set(uniqueHoistedId, moduleId); - } - topLevelImports.add(moduleId); - - // Make sure to track that this page uses this set of hoisted scripts - if (internals.hoistedScriptIdToPagesMap.has(moduleId)) { - const pages = internals.hoistedScriptIdToPagesMap.get(moduleId); - pages!.add(astroModuleId); - } else { - internals.hoistedScriptIdToPagesMap.set(moduleId, new Set([astroModuleId])); - internals.hoistedScriptIdToHoistedMap.set(moduleId, hoistedScripts); - } - } - } - - for (const specifier of topLevelImports) { - jsInput.add(specifier); - } - } - pageInput.add(astroModuleId); facadeIdToPageDataMap.set(fileURLToPath(astroModuleURL), pageData); } @@ -122,10 +58,6 @@ export async function staticBuild(opts: StaticBuildOptions) { // condition, so we are doing it ourselves emptyDir(astroConfig.outDir, new Set('.git')); - timer.clientBuild = performance.now(); - // Run client build first, so the assets can be fed into the SSR rendered version. - await clientBuild(opts, internals, jsInput); - // Build your project (SSR application code, assets, client JS, etc.) timer.ssr = performance.now(); info( @@ -138,6 +70,17 @@ export async function staticBuild(opts: StaticBuildOptions) { const ssrResult = (await ssrBuild(opts, internals, pageInput)) as RollupOutput; info(opts.logging, 'build', dim(`Completed in ${getTimeStat(timer.ssr, performance.now())}.`)); + const clientInput = new Set([ + ...internals.discoveredHydratedComponents, + ...internals.discoveredClientOnlyComponents, + ...astroConfig._ctx.renderers.map(r => r.clientEntrypoint).filter(a => a) as string[], + ...internals.discoveredScripts, + ]); + + // Run client build first, so the assets can be fed into the SSR rendered version. + timer.clientBuild = performance.now(); + await clientBuild(opts, internals, clientInput); + timer.generate = performance.now(); if (opts.buildConfig.staticMode) { try { @@ -146,6 +89,9 @@ export async function staticBuild(opts: StaticBuildOptions) { await cleanSsrOutput(opts); } } else { + // Inject the manifest + await injectManifest(opts, internals) + info(opts.logging, null, `\n${bgMagenta(black(' finalizing server assets '))}\n`); await ssrMoveAssets(opts); } @@ -198,6 +144,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp // SSR needs to be last isBuildingToSSR(opts.astroConfig) && vitePluginSSR(opts, internals, opts.astroConfig._ctx.adapter!), + vitePluginAnalyzer(opts.astroConfig, internals) ], publicDir: ssr ? false : viteConfig.publicDir, root: viteConfig.root, diff --git a/packages/astro/src/core/build/types.ts b/packages/astro/src/core/build/types.ts index 980d3a1702bc..0d018be7d4b6 100644 --- a/packages/astro/src/core/build/types.ts +++ b/packages/astro/src/core/build/types.ts @@ -8,7 +8,6 @@ import type { } from '../../@types/astro'; import type { ViteConfigWithSSR } from '../create-vite'; import type { LogOptions } from '../logger/core'; -import type { ComponentPreload } from '../render/dev/index'; import type { RouteCache } from '../render/route-cache'; export type ComponentPath = string; @@ -17,7 +16,6 @@ export type ViteID = string; export interface PageBuildData { component: ComponentPath; paths: string[]; - preload: ComponentPreload; route: RouteData; moduleSpecifier: string; css: Set; diff --git a/packages/astro/src/core/build/vite-plugin-analyzer.ts b/packages/astro/src/core/build/vite-plugin-analyzer.ts new file mode 100644 index 000000000000..8b8b663e69cb --- /dev/null +++ b/packages/astro/src/core/build/vite-plugin-analyzer.ts @@ -0,0 +1,123 @@ + + +import type { Plugin as VitePlugin } from 'vite'; +import type { PluginContext } from 'rollup'; +import type { AstroConfig } from '../../@types/astro'; +import type { BuildInternals } from '../../core/build/internal.js'; +import type { PluginMetadata as AstroPluginMetadata } from '../../vite-plugin-astro/types'; + +import { prependForwardSlash } from '../../core/path.js'; +import { getPageDataByViteID, trackClientOnlyPageDatas } from './internal.js'; +import { getTopLevelPages } from './graph.js'; + + +export function vitePluginAnalyzer( + astroConfig: AstroConfig, + internals: BuildInternals +): VitePlugin { + + function hoistedScriptScanner() { + const uniqueHoistedIds = new Map(); + const pageScripts = new Map>(); + + return { + scan( + this: PluginContext, + scripts: AstroPluginMetadata['astro']['scripts'], + from: string + ) { + const hoistedScripts = new Set(); + for(let i = 0; i < scripts.length; i++) { + const hid = `${from.replace('/@fs', '')}?astro&type=script&index=${i}`; + hoistedScripts.add(hid); + } + + if (hoistedScripts.size) { + for(const pageId of getTopLevelPages(from, this)) { + for(const hid of hoistedScripts) { + if(pageScripts.has(pageId)) { + pageScripts.get(pageId)?.add(hid); + } else { + pageScripts.set(pageId, new Set([hid])); + } + } + } + } + }, + + finalize() { + for(const [pageId, hoistedScripts] of pageScripts) { + const pageData = getPageDataByViteID(internals, pageId); + if(!pageData) continue; + + const { component } = pageData; + const astroModuleId = prependForwardSlash(component); + + const uniqueHoistedId = JSON.stringify(Array.from(hoistedScripts).sort()); + let moduleId: string; + + // If we're already tracking this set of hoisted scripts, get the unique id + if (uniqueHoistedIds.has(uniqueHoistedId)) { + moduleId = uniqueHoistedIds.get(uniqueHoistedId)!; + } else { + // Otherwise, create a unique id for this set of hoisted scripts + moduleId = `/astro/hoisted.js?q=${uniqueHoistedIds.size}`; + uniqueHoistedIds.set(uniqueHoistedId, moduleId); + } + internals.discoveredScripts.add(moduleId); + + // Make sure to track that this page uses this set of hoisted scripts + if (internals.hoistedScriptIdToPagesMap.has(moduleId)) { + const pages = internals.hoistedScriptIdToPagesMap.get(moduleId); + pages!.add(astroModuleId); + } else { + internals.hoistedScriptIdToPagesMap.set(moduleId, new Set([astroModuleId])); + internals.hoistedScriptIdToHoistedMap.set(moduleId, hoistedScripts); + } + } + } + }; + } + + return { + name: '@astro/rollup-plugin-astro-analyzer', + generateBundle() { + const hoistScanner = hoistedScriptScanner(); + + const ids = this.getModuleIds(); + for(const id of ids) { + const info = this.getModuleInfo(id); + if(!info || !info.meta?.astro) continue; + + const astro = info.meta.astro as AstroPluginMetadata['astro']; + + for(const c of astro.hydratedComponents) { + internals.discoveredHydratedComponents.add(c.resolvedPath || c.specifier); + } + + // Scan hoisted scripts + hoistScanner.scan.call(this, astro.scripts, id); + + if(astro.clientOnlyComponents.length) { + const clientOnlys: string[] = []; + + for(const c of astro.clientOnlyComponents) { + const cid = c.resolvedPath || c.specifier; + internals.discoveredClientOnlyComponents.add(cid); + clientOnlys.push(cid); + } + + for(const pageId of getTopLevelPages(id, this)) { + const pageData = getPageDataByViteID(internals, pageId); + if(!pageData) continue; + + trackClientOnlyPageDatas(internals, pageData, clientOnlys); + } + } + } + + // Finalize hoisting + hoistScanner.finalize(); + } + }; +} diff --git a/packages/astro/src/core/build/vite-plugin-ssr.ts b/packages/astro/src/core/build/vite-plugin-ssr.ts index a7cd3ef4c523..4fc1274c71c9 100644 --- a/packages/astro/src/core/build/vite-plugin-ssr.ts +++ b/packages/astro/src/core/build/vite-plugin-ssr.ts @@ -12,6 +12,7 @@ import { pagesVirtualModuleId } from '../app/index.js'; import { serializeRouteData } from '../routing/index.js'; import { addRollupInput } from './add-rollup-input.js'; import { eachPageData } from './internal.js'; +import * as fs from 'fs'; export const virtualModuleId = '@astrojs-ssr-virtual-entry'; const resolvedVirtualModuleId = '\0' + virtualModuleId; @@ -69,7 +70,7 @@ if(_start in adapter) { return void 0; }, async generateBundle(_opts, bundle) { - const staticFiles = new Set( + internals.staticFiles = new Set( await glob('**/*', { cwd: fileURLToPath(buildOpts.buildConfig.client), }) @@ -78,28 +79,42 @@ if(_start in adapter) { // Add assets from this SSR chunk as well. for (const [_chunkName, chunk] of Object.entries(bundle)) { if (chunk.type === 'asset') { - staticFiles.add(chunk.fileName); + internals.staticFiles.add(chunk.fileName); } } - - const manifest = buildManifest(buildOpts, internals, Array.from(staticFiles)); - await runHookBuildSsr({ config: buildOpts.astroConfig, manifest }); - - for (const [_chunkName, chunk] of Object.entries(bundle)) { + + for (const [chunkName, chunk] of Object.entries(bundle)) { if (chunk.type === 'asset') { continue; } if (chunk.modules[resolvedVirtualModuleId]) { - const code = chunk.code; - chunk.code = code.replace(replaceExp, () => { - return JSON.stringify(manifest); - }); + internals.ssrEntryChunk = chunk; + delete bundle[chunkName]; } } }, }; } +export async function injectManifest(buildOpts: StaticBuildOptions, internals: BuildInternals) { + if(!internals.ssrEntryChunk) { + throw new Error(`Did not generate an entry chunk for SSR`); + } + + const staticFiles = internals.staticFiles; + const manifest = buildManifest(buildOpts, internals, Array.from(staticFiles)); + await runHookBuildSsr({ config: buildOpts.astroConfig, manifest }); + + const chunk = internals.ssrEntryChunk; + const code = chunk.code; + chunk.code = code.replace(replaceExp, () => { + return JSON.stringify(manifest); + }); + const serverEntryURL = new URL(buildOpts.buildConfig.serverEntry, buildOpts.buildConfig.server); + await fs.promises.mkdir(new URL('./', serverEntryURL), { recursive: true }); + await fs.promises.writeFile(serverEntryURL, chunk.code, 'utf-8'); +} + function buildManifest( opts: StaticBuildOptions, internals: BuildInternals, diff --git a/packages/astro/src/runtime/server/metadata.ts b/packages/astro/src/runtime/server/metadata.ts index 548d2bb7d764..11adeb4ead7b 100644 --- a/packages/astro/src/runtime/server/metadata.ts +++ b/packages/astro/src/runtime/server/metadata.ts @@ -50,40 +50,10 @@ export class Metadata { return metadata?.componentExport || null; } - /** - * Gets the paths of all hydrated components within this component - * and children components. - */ - *hydratedComponentPaths() { - const found = new Set(); - for (const metadata of this.deepMetadata()) { - for (const component of metadata.hydratedComponents) { - const path = metadata.getPath(component); - if (path && !found.has(path)) { - found.add(path); - yield path; - } - } - } - } - - *clientOnlyComponentPaths() { - const found = new Set(); - for (const metadata of this.deepMetadata()) { - for (const component of metadata.clientOnlyComponents) { - const path = metadata.resolvePath(component); - if (path && !found.has(path)) { - found.add(path); - yield path; - } - } - } - } - *hoistedScriptPaths() { for (const metadata of this.deepMetadata()) { - let i = 0, - pathname = metadata.mockURL.pathname; + let i = 0, pathname = metadata.mockURL.pathname; + while (i < metadata.hoisted.length) { // Strip off the leading "/@fs" added during compilation. yield `${pathname.replace('/@fs', '')}?astro&type=script&index=${i}`; diff --git a/packages/astro/src/vite-plugin-astro/compile.ts b/packages/astro/src/vite-plugin-astro/compile.ts index 5a6e199d7197..8c4590967838 100644 --- a/packages/astro/src/vite-plugin-astro/compile.ts +++ b/packages/astro/src/vite-plugin-astro/compile.ts @@ -1,5 +1,5 @@ import type { TransformResult } from '@astrojs/compiler'; -import type { SourceMapInput } from 'rollup'; +import type { PluginContext, SourceMapInput } from 'rollup'; import type { AstroConfig } from '../@types/astro'; import type { TransformHook } from './styles'; @@ -33,13 +33,14 @@ function safelyReplaceImportPlaceholder(code: string) { const configCache = new WeakMap(); -interface CompileProps { +export interface CompileProps { config: AstroConfig; filename: string; moduleId: string; source: string; ssr: boolean; viteTransform: TransformHook; + pluginContext: PluginContext; } async function compile({ @@ -49,6 +50,7 @@ async function compile({ source, ssr, viteTransform, + pluginContext, }: CompileProps): Promise { const filenameURL = new URL(`file://${filename}`); const normalizedID = fileURLToPath(filenameURL); @@ -98,6 +100,7 @@ async function compile({ id: normalizedID, transformHook: viteTransform, ssr, + pluginContext, }); let map: SourceMapInput | undefined; diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 4823b6839da0..b0e97c2e0455 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -2,6 +2,7 @@ import type { PluginContext } from 'rollup'; import type * as vite from 'vite'; import type { AstroConfig } from '../@types/astro'; import type { LogOptions } from '../core/logger/core.js'; +import type { PluginMetadata as AstroPluginMetadata } from './types'; import ancestor from 'common-ancestor-path'; import esbuild from 'esbuild'; @@ -12,7 +13,7 @@ import { isRelativePath, startsWithForwardSlash } from '../core/path.js'; import { resolvePages } from '../core/util.js'; import { PAGE_SCRIPT_ID, PAGE_SSR_SCRIPT_ID } from '../vite-plugin-scripts/index.js'; import { getFileInfo } from '../vite-plugin-utils/index.js'; -import { cachedCompilation } from './compile.js'; +import { cachedCompilation, CompileProps } from './compile.js'; import { handleHotUpdate, trackCSSDependencies } from './hmr.js'; import { parseAstroRequest } from './query.js'; import { getViteTransform, TransformHook } from './styles.js'; @@ -105,13 +106,14 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu if (isPage && config._ctx.scripts.some((s) => s.stage === 'page')) { source += `\n