diff --git a/examples/docs/src/images/twitter.png b/examples/docs/src/images/twitter.png new file mode 100644 index 000000000..ad4cae1e9 Binary files /dev/null and b/examples/docs/src/images/twitter.png differ diff --git a/examples/docs/src/layouts/MainLayout.astro b/examples/docs/src/layouts/MainLayout.astro index cdadebe61..e1f268b64 100644 --- a/examples/docs/src/layouts/MainLayout.astro +++ b/examples/docs/src/layouts/MainLayout.astro @@ -118,5 +118,7 @@ const githubEditUrl = CONFIG.GITHUB_EDIT_URL && (CONFIG.GITHUB_EDIT_URL + curren + + diff --git a/examples/docs/src/layouts/script.js b/examples/docs/src/layouts/script.js new file mode 100644 index 000000000..e7fa9eb9f --- /dev/null +++ b/examples/docs/src/layouts/script.js @@ -0,0 +1 @@ +console.log("HERE I AM"); \ No newline at end of file diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 1842afc22..eefff24ba 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -1,6 +1,6 @@ import type { AstroConfig, ComponentInstance, GetStaticPathsResult, ManifestData, RouteCache, RouteData, RSSResult } from '../../@types/astro'; import type { LogOptions } from '../logger'; -import type { AllPagesData } from './types'; +import type { AllPagesData, PageBuildData } from './types'; import type { RenderedChunk } from 'rollup'; import { rollupPluginAstroBuildHTML } from '../../vite-plugin-build-html/index.js'; @@ -12,7 +12,7 @@ import vite, { ViteDevServer } from '../vite.js'; import { fileURLToPath } from 'url'; import { createVite, ViteConfigWithSSR } from '../create-vite.js'; import { debug, defaultLogOptions, info, levels, timerMessage, warn } from '../logger.js'; -import { preload as ssrPreload } from '../ssr/index.js'; +import { preload as ssrPreload, renderComponent, getParamsAndProps } from '../ssr/index.js'; import { generatePaginateFunction } from '../ssr/paginate.js'; import { createRouteManifest, validateGetStaticPathsModule, validateGetStaticPathsResult } from '../ssr/routing.js'; import { generateRssFunction } from '../ssr/rss.js'; @@ -162,20 +162,26 @@ class AstroBuilder { const pageNames: string[] = []; + // Blah + const facadeIdToPageDataMap = new Map(); + // Bundle the assets in your final build: This currently takes the HTML output // of every page (stored in memory) and bundles the assets pointed to on those pages. timer.buildStart = performance.now(); - await vite.build({ + let result = await vite.build({ logLevel: 'error', mode: 'production', build: { emptyOutDir: true, - minify: 'esbuild', // significantly faster than "terser" but may produce slightly-bigger bundles + minify: false,// 'esbuild', // significantly faster than "terser" but may produce slightly-bigger bundles outDir: fileURLToPath(this.config.dist), + ssr: true, rollupOptions: { // The `input` will be populated in the build rollup plugin. input: [], - output: { format: 'esm' }, + output: { + format: 'cjs' + }, }, target: 'es2020', // must match an esbuild target }, @@ -192,6 +198,7 @@ class AstroBuilder { pageNames, routeCache: this.routeCache, viteServer, + facadeIdToPageDataMap, }), rollupPluginAstroBuildCSS({ astroPageStyleMap, @@ -209,6 +216,13 @@ class AstroBuilder { }); debug(logging, 'build', timerMessage('Vite build finished', timer.buildStart)); + /* TODO REMOVE THIS NEW HACKY CODE */ + console.log('End build step, now generating'); + for(let out of (result as any).output) { + if(out.facadeModuleId) + await this.doTheRest(out, facadeIdToPageDataMap); + } + // Write any additionally generated assets to disk. timer.assetsStart = performance.now(); Object.keys(assets).map((k) => { @@ -237,6 +251,35 @@ class AstroBuilder { } } + private async doTheRest(out: any, facadeIdToPageDataMap: Map) { + let url = new URL('./' + out.fileName, this.config.dist); + let pageData = facadeIdToPageDataMap.get(out.facadeModuleId)!; + let compiledModule = await import(url.toString()); + let Component = compiledModule.default.default; + + const [renderers, mod] = pageData.preload; + + for(let path of pageData.paths) { + try { + const [params, pageProps] = await getParamsAndProps({ + route: pageData.route, + routeCache: this.routeCache, + logging: this.logging, + pathname: path, + mod + }) + console.log(`Generating: ${path}`); + let html = await renderComponent(renderers, Component, this.config, path, this.origin, params, pageProps); + let outFolder = new URL('.' + path + '/', this.config.dist); + let outFile = new URL('./index.html', outFolder); + await fs.promises.mkdir(outFolder, { recursive: true }); + await fs.promises.writeFile(outFile, html, 'utf-8'); + } catch(err) { + console.error("did not work", err); + } + } + } + /** Extract all static paths from a dynamic route */ private async getStaticPathsForRoute(route: RouteData): Promise<{ paths: string[]; rss?: RSSResult }> { if (!this.viteServer) throw new Error(`vite.createServer() not called!`); diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts index 4b979a182..9acd3869b 100644 --- a/packages/astro/src/core/ssr/index.ts +++ b/packages/astro/src/core/ssr/index.ts @@ -17,6 +17,7 @@ import type { SSRResult, } from '../../@types/astro'; import type { LogOptions } from '../logger'; +import type { AstroComponentFactory } from '../../runtime/server/index'; import eol from 'eol'; import fs from 'fs'; @@ -138,6 +139,89 @@ export async function preload({ astroConfig, filePath, viteServer }: SSROptions) return [renderers, mod]; } +export async function renderComponent(renderers: Renderer[], Component: AstroComponentFactory, astroConfig: AstroConfig, pathname: string, origin: string, params: Params, pageProps: Props): Promise { + const result: SSRResult = { + styles: new Set(), + scripts: new Set(), + /** This function returns the `Astro` faux-global */ + createAstro(astroGlobal: AstroGlobalPartial, props: Record, slots: Record | null) { + const site = new URL(origin); + const url = new URL('.' + pathname, site); + const canonicalURL = getCanonicalURL('.' + pathname, astroConfig.buildOptions.site || origin); + return { + __proto__: astroGlobal, + props, + request: { + canonicalURL, + params, + url, + }, + slots: Object.fromEntries(Object.entries(slots || {}).map(([slotName]) => [slotName, true])), + // This is used for but shouldn't be used publicly + privateRenderSlotDoNotUse(slotName: string) { + return renderSlot(result, slots ? slots[slotName] : null); + }, + // also needs the same `astroConfig.markdownOptions.render` as `.md` pages + async privateRenderMarkdownDoNotUse(content: string, opts: any) { + let mdRender = astroConfig.markdownOptions.render; + let renderOpts = {}; + if (Array.isArray(mdRender)) { + renderOpts = mdRender[1]; + mdRender = mdRender[0]; + } + if (typeof mdRender === 'string') { + ({ default: mdRender } = await import(mdRender)); + } + const { code } = await mdRender(content, { ...renderOpts, ...(opts ?? {}) }); + return code; + }, + } as unknown as AstroGlobal; + }, + _metadata: { + renderers, + pathname, + experimentalStaticBuild: astroConfig.buildOptions.experimentalStaticBuild + }, + }; + + let html = await renderPage(result, Component, pageProps, null); + + return html; +} + +export async function getParamsAndProps({route, routeCache, logging, pathname, mod}: {route: RouteData | undefined, routeCache: RouteCache, pathname: string, mod: ComponentInstance, logging: LogOptions}): Promise<[Params, Props]> { + // Handle dynamic routes + let params: Params = {}; + let pageProps: Props = {}; + if (route && !route.pathname) { + if (route.params.length) { + const paramsMatch = route.pattern.exec(pathname); + if (paramsMatch) { + params = getParams(route.params)(paramsMatch); + } + } + validateGetStaticPathsModule(mod); + if (!routeCache[route.component]) { + routeCache[route.component] = await ( + await mod.getStaticPaths!({ + paginate: generatePaginateFunction(route), + rss: () => { + /* noop */ + }, + }) + ).flat(); + } + validateGetStaticPathsResult(routeCache[route.component], logging); + const routePathParams: GetStaticPathsResult = routeCache[route.component]; + const matchedStaticPath = routePathParams.find(({ params: _params }) => JSON.stringify(_params) === JSON.stringify(params)); + if (!matchedStaticPath) { + throw new Error(`[getStaticPaths] route pattern matched, but no matching static path found. (${pathname})`); + } + pageProps = { ...matchedStaticPath.props } || {}; + } + return [params, pageProps]; +} + /** use Vite to SSR */ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrOpts: SSROptions): Promise { const { astroConfig, filePath, logging, mode, origin, pathname, route, routeCache, viteServer } = ssrOpts; @@ -225,6 +309,7 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO _metadata: { renderers, pathname, + experimentalStaticBuild: astroConfig.buildOptions.experimentalStaticBuild }, }; diff --git a/packages/astro/src/vite-plugin-build-html/index.ts b/packages/astro/src/vite-plugin-build-html/index.ts index cdc5c1877..cb7f8f7eb 100644 --- a/packages/astro/src/vite-plugin-build-html/index.ts +++ b/packages/astro/src/vite-plugin-build-html/index.ts @@ -2,7 +2,7 @@ import type { AstroConfig, RouteCache } from '../@types/astro'; import type { LogOptions } from '../core/logger'; import type { ViteDevServer, Plugin as VitePlugin } from '../core/vite'; import type { OutputChunk, PreRenderedChunk, RenderedChunk } from 'rollup'; -import type { AllPagesData } from '../core/build/types'; +import type { AllPagesData, PageBuildData } from '../core/build/types'; import parse5 from 'parse5'; import srcsetParse from 'srcset-parse'; import * as npath from 'path'; @@ -36,6 +36,8 @@ interface PluginOptions { origin: string; routeCache: RouteCache; viteServer: ViteDevServer; + + facadeIdToPageDataMap: Map; } export function rollupPluginAstroBuildHTML(options: PluginOptions): VitePlugin { @@ -72,10 +74,16 @@ export function rollupPluginAstroBuildHTML(options: PluginOptions): VitePlugin { // Hydrated components are statically identified. for (const path of mod.$$metadata.getAllHydratedComponentPaths()) { - jsInput.add(path); + //jsInput.add(path); } + let astroModuleId = new URL('./' + component, astroConfig.projectRoot).pathname; + jsInput.add(astroModuleId); + options.facadeIdToPageDataMap.set(astroModuleId, pageData); + for (const pathname of pageData.paths) { + + /* pageNames.push(pathname.replace(/\/?$/, '/index.html').replace(/^\//, '')); const id = ASTRO_PAGE_PREFIX + pathname; const html = await ssrRender(renderers, mod, { @@ -183,6 +191,8 @@ export function rollupPluginAstroBuildHTML(options: PluginOptions): VitePlugin { if (!pageStyleImportOrder.includes(assetHref)) pageStyleImportOrder.push(assetHref); } } + + */ } } @@ -233,10 +243,13 @@ export function rollupPluginAstroBuildHTML(options: PluginOptions): VitePlugin { if (!pageName) { pageName = 'index'; } - return `assets/${pageName}.[hash].js`; + return `assets/${pageName}.[hash].cjs`; } - return 'assets/[name].[hash].js'; + return 'assets/[name].[hash].cjs'; }, + chunkFileNames(chunk: PreRenderedChunk) { + return 'assets/[name].[hash].cjs'; + } }); return outputOptions; }, diff --git a/packages/astro/test/astro-basic.test.js b/packages/astro/test/astro-basic.test.js index fc8c43cca..242d92c55 100644 --- a/packages/astro/test/astro-basic.test.js +++ b/packages/astro/test/astro-basic.test.js @@ -18,7 +18,7 @@ describe('Astro basics', () => { }); describe('build', () => { - it('Can load page', async () => { + it.only('Can load page', async () => { const html = await fixture.readFile(`/index.html`); const $ = cheerio.load(html);