astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
182 lines (181 loc) • 6.67 kB
JavaScript
import { ASTRO_ACTIONS_INTERNAL_MODULE_ID } from "../../../actions/consts.js";
import { MIDDLEWARE_MODULE_ID } from "../../middleware/vite-plugin.js";
import { routeIsRedirect } from "../../redirects/index.js";
import { VIRTUAL_ISLAND_MAP_ID } from "../../server-islands/vite-plugin-server-islands.js";
import { addRollupInput } from "../add-rollup-input.js";
import { SSR_MANIFEST_VIRTUAL_MODULE_ID } from "./plugin-manifest.js";
import { ASTRO_PAGE_MODULE_ID } from "./plugin-pages.js";
import { RENDERERS_MODULE_ID } from "./plugin-renderers.js";
import { getVirtualModulePageName } from "./util.js";
const SSR_VIRTUAL_MODULE_ID = "@astrojs-ssr-virtual-entry";
const RESOLVED_SSR_VIRTUAL_MODULE_ID = "\0" + SSR_VIRTUAL_MODULE_ID;
const ADAPTER_VIRTUAL_MODULE_ID = "@astrojs-ssr-adapter";
const RESOLVED_ADAPTER_VIRTUAL_MODULE_ID = "\0" + ADAPTER_VIRTUAL_MODULE_ID;
function vitePluginAdapter(adapter) {
return {
name: "@astrojs/vite-plugin-astro-adapter",
enforce: "post",
resolveId(id) {
if (id === ADAPTER_VIRTUAL_MODULE_ID) {
return RESOLVED_ADAPTER_VIRTUAL_MODULE_ID;
}
},
async load(id) {
if (id === RESOLVED_ADAPTER_VIRTUAL_MODULE_ID) {
return { code: `export * from ${JSON.stringify(adapter.serverEntrypoint)};` };
}
}
};
}
function vitePluginSSR(internals, adapter, options) {
return {
name: "@astrojs/vite-plugin-astro-ssr-server",
enforce: "post",
options(opts) {
const inputs = /* @__PURE__ */ new Set();
for (const pageData of Object.values(options.allPages)) {
if (routeIsRedirect(pageData.route)) {
continue;
}
inputs.add(getVirtualModulePageName(ASTRO_PAGE_MODULE_ID, pageData.component));
}
const adapterServerEntrypoint = options.settings.adapter?.serverEntrypoint;
if (adapterServerEntrypoint) {
inputs.add(ADAPTER_VIRTUAL_MODULE_ID);
}
inputs.add(SSR_VIRTUAL_MODULE_ID);
return addRollupInput(opts, Array.from(inputs));
},
resolveId(id) {
if (id === SSR_VIRTUAL_MODULE_ID) {
return RESOLVED_SSR_VIRTUAL_MODULE_ID;
}
},
async load(id) {
if (id === RESOLVED_SSR_VIRTUAL_MODULE_ID) {
const { allPages } = options;
const imports = [];
const contents = [];
const exports = [];
let i = 0;
const pageMap = [];
for (const pageData of Object.values(allPages)) {
if (routeIsRedirect(pageData.route)) {
continue;
}
const virtualModuleName = getVirtualModulePageName(
ASTRO_PAGE_MODULE_ID,
pageData.component
);
let module = await this.resolve(virtualModuleName);
if (module) {
const variable = `_page${i}`;
imports.push(`const ${variable} = () => import("${virtualModuleName}");`);
const pageData2 = internals.pagesByKeys.get(pageData.key);
if (pageData2) {
pageMap.push(`[${JSON.stringify(pageData2.component)}, ${variable}]`);
}
i++;
}
}
contents.push(`const pageMap = new Map([
${pageMap.join(",\n ")}
]);`);
exports.push(`export { pageMap }`);
const middleware = await this.resolve(MIDDLEWARE_MODULE_ID);
const ssrCode = generateSSRCode(adapter, middleware.id);
imports.push(...ssrCode.imports);
contents.push(...ssrCode.contents);
return { code: [...imports, ...contents, ...exports].join("\n") };
}
},
async generateBundle(_opts, bundle) {
for (const [, chunk] of Object.entries(bundle)) {
if (chunk.type === "asset") {
internals.staticFiles.add(chunk.fileName);
}
}
for (const [, chunk] of Object.entries(bundle)) {
if (chunk.type === "asset") {
continue;
}
if (chunk.modules[RESOLVED_SSR_VIRTUAL_MODULE_ID]) {
internals.ssrEntryChunk = chunk;
}
}
}
};
}
function pluginSSR(options, internals) {
const ssr = options.settings.buildOutput === "server";
return {
targets: ["server"],
hooks: {
"build:before": () => {
const adapter = options.settings.adapter;
const ssrPlugin = ssr && vitePluginSSR(internals, adapter, options);
const vitePlugin = [vitePluginAdapter(adapter)];
if (ssrPlugin) {
vitePlugin.unshift(ssrPlugin);
}
return {
enforce: "after-user-plugins",
vitePlugin
};
},
"build:post": async () => {
if (!ssr) {
return;
}
if (!internals.ssrEntryChunk) {
throw new Error(`Did not generate an entry chunk for SSR`);
}
internals.ssrEntryChunk.fileName = options.settings.config.build.serverEntry;
}
}
};
}
function generateSSRCode(adapter, middlewareId) {
const edgeMiddleware = adapter?.adapterFeatures?.edgeMiddleware ?? false;
const imports = [
`import { renderers } from '${RENDERERS_MODULE_ID}';`,
`import * as serverEntrypointModule from '${ADAPTER_VIRTUAL_MODULE_ID}';`,
`import { manifest as defaultManifest } from '${SSR_MANIFEST_VIRTUAL_MODULE_ID}';`,
`import { serverIslandMap } from '${VIRTUAL_ISLAND_MAP_ID}';`
];
const contents = [
edgeMiddleware ? `const middleware = (_, next) => next()` : "",
`const _manifest = Object.assign(defaultManifest, {`,
` pageMap,`,
` serverIslandMap,`,
` renderers,`,
` actions: () => import("${ASTRO_ACTIONS_INTERNAL_MODULE_ID}"),`,
` middleware: ${edgeMiddleware ? "undefined" : `() => import("${middlewareId}")`}`,
`});`,
`const _args = ${adapter.args ? JSON.stringify(adapter.args, null, 4) : "undefined"};`,
adapter.exports ? `const _exports = serverEntrypointModule.createExports(_manifest, _args);` : "",
...adapter.exports?.map((name) => {
if (name === "default") {
return `export default _exports.default;`;
} else {
return `export const ${name} = _exports['${name}'];`;
}
}) ?? [],
// NOTE: This is intentionally obfuscated!
// Do NOT simplify this to something like `serverEntrypointModule.start?.(_manifest, _args)`
// They are NOT equivalent! Some bundlers will throw if `start` is not exported, but we
// only want to silently ignore it... hence the dynamic, obfuscated weirdness.
`const _start = 'start';
if (_start in serverEntrypointModule) {
serverEntrypointModule[_start](_manifest, _args);
}`
];
return {
imports,
contents
};
}
export {
RESOLVED_SSR_VIRTUAL_MODULE_ID,
pluginSSR
};