Skip to content

Commit

Permalink
refactor: emit pages as physical entry points
Browse files Browse the repository at this point in the history
  • Loading branch information
ematipico committed May 24, 2023
1 parent 7851f92 commit 7a8a394
Show file tree
Hide file tree
Showing 14 changed files with 208 additions and 74 deletions.
8 changes: 4 additions & 4 deletions packages/astro/src/core/app/index.ts
Expand Up @@ -32,8 +32,6 @@ export { deserializeManifest } from './common.js';

const clientLocalsSymbol = Symbol.for('astro.locals');

export const pagesVirtualModuleId = '@astrojs-pages-virtual-entry';
export const resolvedPagesVirtualModuleId = '\0' + pagesVirtualModuleId;
const responseSentSymbol = Symbol.for('astro.responseSent');

export interface MatchOptions {
Expand Down Expand Up @@ -139,7 +137,8 @@ export class App {
defaultStatus = 404;
}

let mod = await this.#manifest.pageMap.get(routeData.component)!();
let page = await this.#manifest.pageMap.get(routeData.component)!();
let mod = await page.page();

if (routeData.type === 'page') {
let response = await this.#renderPage(request, routeData, mod, defaultStatus);
Expand All @@ -148,7 +147,8 @@ export class App {
if (response.status === 500 || response.status === 404) {
const errorPageData = matchRoute('/' + response.status, this.#manifestData);
if (errorPageData && errorPageData.route !== routeData.route) {
mod = await this.#manifest.pageMap.get(errorPageData.component)!();
page = await this.#manifest.pageMap.get(errorPageData.component)!();
mod = await page.page();
try {
let errorResponse = await this.#renderPage(
request,
Expand Down
3 changes: 2 additions & 1 deletion packages/astro/src/core/app/types.ts
Expand Up @@ -8,6 +8,7 @@ import type {
SSRLoadedRenderer,
SSRResult,
} from '../../@types/astro';
import type { SinglePageBuiltModule } from '../build/types';

export type ComponentPath = string;

Expand All @@ -31,7 +32,7 @@ export interface RouteInfo {
export type SerializedRouteInfo = Omit<RouteInfo, 'routeData'> & {
routeData: SerializedRouteData;
};
type ImportComponentInstance = () => Promise<ComponentInstance>;
type ImportComponentInstance = () => Promise<SinglePageBuiltModule>;

export interface SSRManifest {
adapterName: string;
Expand Down
32 changes: 21 additions & 11 deletions packages/astro/src/core/build/generate.ts
Expand Up @@ -20,7 +20,11 @@ import {
generateImage as generateImageInternal,
getStaticImageList,
} from '../../assets/generate.js';
import { hasPrerenderedPages, type BuildInternals } from '../../core/build/internal.js';
import {
hasPrerenderedPages,
type BuildInternals,
eachPageDataFromEntryPoint,
} from '../../core/build/internal.js';
import {
prependForwardSlash,
removeLeadingForwardSlash,
Expand All @@ -47,11 +51,12 @@ import { getOutDirWithinCwd, getOutFile, getOutFolder } from './common.js';
import { cssOrder, eachPageData, getPageDataByComponent, mergeInlineCss } from './internal.js';
import type {
PageBuildData,
SingleFileBuiltModule,
SinglePageBuiltModule,
StaticBuildOptions,
StylesheetAsset,
} from './types';
import { getTimeStat } from './util.js';
import { ASTRO_PAGE_MODULE_ID } from './plugins/plugin-pages';

function shouldSkipDraft(pageModule: ComponentInstance, settings: AstroSettings): boolean {
return (
Expand Down Expand Up @@ -99,18 +104,23 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn
const verb = ssr ? 'prerendering' : 'generating';
info(opts.logging, null, `\n${bgGreen(black(` ${verb} static routes `))}`);

const ssrEntryURL = new URL('./' + serverEntry + `?time=${Date.now()}`, outFolder);
const ssrEntry = await import(ssrEntryURL.toString());
const builtPaths = new Set<string>();

if (ssr) {
for (const pageData of eachPageData(internals)) {
if (pageData.route.prerender)
await generatePage(opts, internals, pageData, ssrEntry, builtPaths);
for (const [pageData, filePath] of eachPageDataFromEntryPoint(internals)) {
if (pageData.route.prerender) {
const ssrEntryURLPage = new URL('./' + filePath + `?time=${Date.now()}`, outFolder);
const ssrEntryPage = await import(ssrEntryURLPage.toString());

await generatePage(opts, internals, pageData, ssrEntryPage, builtPaths);
}
}
} else {
for (const pageData of eachPageData(internals)) {
await generatePage(opts, internals, pageData, ssrEntry, builtPaths);
for (const [pageData, filePath] of eachPageDataFromEntryPoint(internals)) {
const ssrEntryURLPage = new URL('./' + filePath + `?time=${Date.now()}`, outFolder);
const ssrEntryPage = await import(ssrEntryURLPage.toString());

await generatePage(opts, internals, pageData, ssrEntryPage, builtPaths);
}
}

Expand Down Expand Up @@ -153,7 +163,7 @@ async function generatePage(
opts: StaticBuildOptions,
internals: BuildInternals,
pageData: PageBuildData,
ssrEntry: SingleFileBuiltModule,
ssrEntry: SinglePageBuiltModule,
builtPaths: Set<string>
) {
let timeStart = performance.now();
Expand All @@ -169,7 +179,7 @@ async function generatePage(
.map(({ sheet }) => sheet)
.reduce(mergeInlineCss, []);

const pageModulePromise = ssrEntry.pageMap?.get(pageData.component);
const pageModulePromise = ssrEntry.page;
const middleware = ssrEntry.middleware;

if (!pageModulePromise) {
Expand Down
6 changes: 3 additions & 3 deletions packages/astro/src/core/build/graph.ts
@@ -1,6 +1,6 @@
import type { GetModuleInfo, ModuleInfo } from 'rollup';

import { resolvedPagesVirtualModuleId } from '../app/index.js';
import { ASTRO_PAGE_RESOLVED_MODULE_ID } from './plugins/plugin-pages.js';

// This walks up the dependency graph and yields out each ModuleInfo object.
export function* walkParentInfos(
Expand Down Expand Up @@ -43,8 +43,8 @@ export function* walkParentInfos(
// it is imported by the top-level virtual module.
export function moduleIsTopLevelPage(info: ModuleInfo): boolean {
return (
info.importers[0] === resolvedPagesVirtualModuleId ||
info.dynamicImporters[0] == resolvedPagesVirtualModuleId
info.importers[0]?.includes(ASTRO_PAGE_RESOLVED_MODULE_ID) ||
info.dynamicImporters[0]?.includes(ASTRO_PAGE_RESOLVED_MODULE_ID)
);
}

Expand Down
23 changes: 21 additions & 2 deletions packages/astro/src/core/build/internal.ts
@@ -1,10 +1,10 @@
import type { Rollup } from 'vite';
import type { PageBuildData, StylesheetAsset, ViteID } from './types';

import type { SSRResult } from '../../@types/astro';
import type { PageOptions } from '../../vite-plugin-astro/types';
import { prependForwardSlash, removeFileExtension } from '../path.js';
import { viteID } from '../util.js';
import { ASTRO_PAGE_EXTENSION_POST_PATTERN, ASTRO_PAGE_MODULE_ID } from './plugins/plugin-pages.js';

export interface BuildInternals {
/**
Expand Down Expand Up @@ -97,7 +97,6 @@ export function createBuildInternals(): BuildInternals {
hoistedScriptIdToPagesMap,
entrySpecifierToBundleMap: new Map<string, string>(),
pageToBundleMap: new Map<string, string>(),

pagesByComponent: new Map(),
pageOptionsByPage: new Map(),
pagesByViteID: new Map(),
Expand Down Expand Up @@ -215,6 +214,26 @@ export function* eachPageData(internals: BuildInternals) {
yield* internals.pagesByComponent.values();
}

export function* eachPageDataFromEntryPoint(
internals: BuildInternals
): Generator<[PageBuildData, string]> {
for (const [entryPoint, filePath] of internals.entrySpecifierToBundleMap) {
if (entryPoint.includes(ASTRO_PAGE_MODULE_ID)) {
const [, pageName] = entryPoint.split(':');
const pageData = internals.pagesByComponent.get(
`${pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}`
);
if (!pageData) {
throw new Error(
"Build failed. Astro couldn't find the emitted page from " + pageName + ' pattern'
);
}

yield [pageData, filePath];
}
}
}

export function hasPrerenderedPages(internals: BuildInternals) {
for (const pageData of eachPageData(internals)) {
if (pageData.route.prerender) {
Expand Down
33 changes: 33 additions & 0 deletions packages/astro/src/core/build/plugins/README.md
@@ -0,0 +1,33 @@
# Plugin directory (WIP)

This file serves as developer documentation to explain how the internal plugins work


## `plugin-middleware`

This plugin is responsible to retrieve the `src/middleware.{ts.js}` file and emit an entry point during the SSR build.

The final file is emitted only if the user has the middleware file. The final name of the file is `middleware.mjs`.

The file emitted has this content, more or less:

```js
import { onRequest } from "@astro-middleware";
export { onRequest }
```

## `plugin-renderers`

This plugin is responsible to collect all the renderers inside an Astro application and emit them in a single file.

The emitted file is called `renderers.mjs`.

The emitted file has content similar to:

```js
const renderers = [Object.assign({"name":"astro:jsx","serverEntrypoint":"astro/jsx/server.js","jsxImportSource":"astro"}, { ssr: server_default }),];

export { renderers };
```

## `plugin-pages`
4 changes: 4 additions & 0 deletions packages/astro/src/core/build/plugins/plugin-middleware.ts
Expand Up @@ -43,6 +43,10 @@ export function vitePluginMiddleware(
return result.join('\n');
}
},

outputOptions(options) {
options.entryFileNames = 'middleware.mjs';
},
};
}

Expand Down
87 changes: 62 additions & 25 deletions packages/astro/src/core/build/plugins/plugin-pages.ts
@@ -1,56 +1,93 @@
import type { Plugin as VitePlugin } from 'vite';
import { pagesVirtualModuleId, resolvedPagesVirtualModuleId } from '../../app/index.js';
import { addRollupInput } from '../add-rollup-input.js';
import { eachPageData, type BuildInternals } from '../internal.js';
import { type BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin';
import type { StaticBuildOptions } from '../types';
import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js';
import { RENDERERS_MODULE_ID } from './plugin-renderers.js';
import { extname } from 'node:path';

export const ASTRO_PAGE_MODULE_ID = '@astro-page:';
export const ASTRO_PAGE_RESOLVED_MODULE_ID = '\0@astro-page:';

// This is an arbitrary string that we are going to replace the dot of the extension
export const ASTRO_PAGE_EXTENSION_POST_PATTERN = '@_@';

function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): VitePlugin {
return {
name: '@astro/plugin-build-pages',

options(options) {
if (opts.settings.config.output === 'static') {
return addRollupInput(options, [pagesVirtualModuleId]);
const inputs: Set<string> = new Set();

for (const path of Object.keys(opts.allPages)) {
const extension = extname(path);

// we mask the extension, so this virtual file
// so rollup won't trigger other plugins in the process
const virtualModuleName = `${ASTRO_PAGE_MODULE_ID}${path.replace(
extension,
extension.replace('.', ASTRO_PAGE_EXTENSION_POST_PATTERN)
)}`;

inputs.add(virtualModuleName);
}

return addRollupInput(options, Array.from(inputs));
}
},

resolveId(id) {
if (id === pagesVirtualModuleId) {
return resolvedPagesVirtualModuleId;
if (id.startsWith(ASTRO_PAGE_MODULE_ID)) {
return '\0' + id;
}
},

async load(id) {
if (id === resolvedPagesVirtualModuleId) {
let importMap = '';
if (id.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
const imports: string[] = [];
const exports: string[] = [];
const content: string[] = [];
let i = 0;
imports.push(`import { renderers } from "${RENDERERS_MODULE_ID}";`);
exports.push(`export { renderers };`);
for (const pageData of eachPageData(internals)) {
const variable = `_page${i}`;
imports.push(
`const ${variable} = () => import(${JSON.stringify(pageData.moduleSpecifier)});`
);
importMap += `[${JSON.stringify(pageData.component)}, ${variable}],`;
i++;
}
// split by ":", the second element is the page name, which will start with "src/..."
const [, pageName] = id.split(':');
// We replaced the `.` of the extension with ASTRO_PAGE_EXTENSION_POST_PATTERN, let's replace it back
const pageData = internals.pagesByComponent.get(
`${pageName.replace(ASTRO_PAGE_EXTENSION_POST_PATTERN, '.')}`
);
if (pageData) {
const resolvedPage = await this.resolve(pageData.moduleSpecifier);
if (resolvedPage) {
imports.push(`const page = () => import(${JSON.stringify(pageData.moduleSpecifier)});`);
exports.push(`export { page }`);

if (opts.settings.config.experimental.middleware) {
imports.push(`import * as _middleware from "${MIDDLEWARE_MODULE_ID}";`);
exports.push(`export const middleware = _middleware;`);
}
imports.push(`import { renderers } from "${RENDERERS_MODULE_ID}";`);
exports.push(`export { renderers };`);

content.push(`export const pageMap = new Map([${importMap}]);`);
if (opts.settings.config.experimental.middleware) {
imports.push(`import * as _middleware from "${MIDDLEWARE_MODULE_ID}";`);
exports.push(`export const middleware = _middleware;`);
}

return `${imports.join('\n')}${content.join('\n')}${exports.join('\n')}`;
return `${imports.join('\n')}${exports.join('\n')}`;
}
}
}
},

outputOptions(options) {
options.entryFileNames = (chunkInfo) => {
if (chunkInfo.facadeModuleId?.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) {
return `entry.${chunkInfo.facadeModuleId
.replace(ASTRO_PAGE_RESOLVED_MODULE_ID, '')
.replace('src/', '')
.replaceAll('[', '_')
.replaceAll(']', '_')
.replaceAll('.', '_')}.mjs`;
} else {
return '[name].mjs';
}
};
},
};
}

Expand Down
4 changes: 4 additions & 0 deletions packages/astro/src/core/build/plugins/plugin-renderers.ts
Expand Up @@ -46,6 +46,10 @@ export function vitePluginRenderers(
}
}
},

outputOptions(options) {
options.entryFileNames = 'renderers.mjs';
},
};
}

Expand Down

0 comments on commit 7a8a394

Please sign in to comment.