astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
171 lines (168 loc) • 6.07 kB
JavaScript
import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
import { AstroError, AstroErrorData } from "../errors/index.js";
const SERVER_ISLAND_MANIFEST = "virtual:astro:server-island-manifest";
const RESOLVED_SERVER_ISLAND_MANIFEST = "\0" + SERVER_ISLAND_MANIFEST;
const serverIslandPlaceholderMap = "'$$server-islands-map$$'";
const serverIslandPlaceholderNameMap = "'$$server-islands-name-map$$'";
const SERVER_ISLAND_MAP_MARKER = "$$server-islands-map$$";
const serverIslandMapReplaceExp = /['"]\$\$server-islands-map\$\$['"]/g;
const serverIslandNameMapReplaceExp = /['"]\$\$server-islands-name-map\$\$['"]/g;
function vitePluginServerIslands({
settings,
serverIslandsState
}) {
let command = "serve";
let serverEnvironments = [];
function ensureServerIslandReferenceIds(ctx) {
for (const [resolvedPath, island] of serverIslandsState.getDiscoveredIslandEntries()) {
if (serverIslandsState.hasReferenceId(resolvedPath)) continue;
const referenceId = ctx.emitFile({
type: "chunk",
id: island.specifier,
importer: island.importer,
name: island.islandName
});
serverIslandsState.setReferenceId(resolvedPath, referenceId);
}
}
return {
name: "astro:server-islands",
enforce: "post",
config(_config, { command: _command }) {
command = _command;
},
buildStart() {
if (command !== "build" || this.environment?.name !== ASTRO_VITE_ENVIRONMENT_NAMES.ssr) {
return;
}
ensureServerIslandReferenceIds(this);
},
configureServer(server) {
serverEnvironments = [];
for (const name of [
ASTRO_VITE_ENVIRONMENT_NAMES.ssr,
ASTRO_VITE_ENVIRONMENT_NAMES.prerender,
ASTRO_VITE_ENVIRONMENT_NAMES.astro
]) {
const env = server.environments[name];
if (env) {
serverEnvironments.push(env);
}
}
},
resolveId: {
filter: {
id: new RegExp(`^${SERVER_ISLAND_MANIFEST}$`)
},
handler() {
return RESOLVED_SERVER_ISLAND_MANIFEST;
}
},
load: {
filter: {
id: new RegExp(`^${RESOLVED_SERVER_ISLAND_MANIFEST}$`)
},
handler() {
return {
code: `export const serverIslandMap = ${serverIslandPlaceholderMap};
export const serverIslandNameMap = ${serverIslandPlaceholderNameMap};`
};
}
},
transform: {
filter: {
id: {
include: [/\.(astro|mdx)$/, new RegExp(`^${RESOLVED_SERVER_ISLAND_MANIFEST}$`)]
}
},
async handler(_code, id) {
const info = this.getModuleInfo(id);
const astro = info ? info.meta.astro : void 0;
const isBuildSsr = command === "build" && this.environment?.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr;
if (astro) {
for (const comp of astro.serverComponents) {
if (!settings.adapter) {
throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands);
}
const island = serverIslandsState.discover({
resolvedPath: comp.resolvedPath,
localName: comp.localName,
specifier: comp.specifier ?? comp.resolvedPath,
importer: id
});
if (isBuildSsr && !serverIslandsState.hasReferenceId(comp.resolvedPath)) {
const referenceId = this.emitFile({
type: "chunk",
id: island.specifier,
importer: island.importer,
name: island.islandName
});
serverIslandsState.setReferenceId(comp.resolvedPath, referenceId);
}
}
}
if (serverIslandsState.hasIslands()) {
for (const env of serverEnvironments) {
const mod = env.moduleGraph.getModuleById(RESOLVED_SERVER_ISLAND_MANIFEST);
if (mod) {
env.moduleGraph.invalidateModule(mod);
}
}
}
if (id === RESOLVED_SERVER_ISLAND_MANIFEST) {
if (command === "build" && settings.buildOutput) {
const hasServerIslands = serverIslandsState.hasIslands();
if (hasServerIslands && settings.buildOutput !== "server") {
throw new AstroError(AstroErrorData.NoAdapterInstalledServerIslands);
}
}
if (command !== "build" && serverIslandsState.hasIslands()) {
const mapSource = serverIslandsState.createImportMapSourceFromDiscovered(
(fileName) => fileName
);
const nameMapSource = serverIslandsState.createNameMapSource();
return {
code: `
export const serverIslandMap = ${mapSource};
export const serverIslandNameMap = ${nameMapSource};
`
};
}
}
}
},
renderChunk(code, chunk) {
if (!code.includes(SERVER_ISLAND_MAP_MARKER)) return;
if (command === "build") {
const envName = this.environment?.name;
let mapSource;
if (envName === ASTRO_VITE_ENVIRONMENT_NAMES.ssr) {
const isRelativeChunk = !chunk.isEntry;
const dots = isRelativeChunk ? ".." : ".";
mapSource = serverIslandsState.createImportMapSourceFromReferences(
(referenceId) => this.getFileName(referenceId),
(fileName) => `${dots}/${fileName}`
);
} else {
mapSource = serverIslandsState.createImportMapSourceFromDiscovered(
(fileName) => fileName
);
}
const nameMapSource = serverIslandsState.createNameMapSource();
return {
code: code.replace(serverIslandMapReplaceExp, mapSource).replace(serverIslandNameMapReplaceExp, nameMapSource),
map: null
};
}
return {
code: code.replace(serverIslandMapReplaceExp, "new Map();").replace(serverIslandNameMapReplaceExp, "new Map()"),
map: null
};
}
};
}
export {
SERVER_ISLAND_MANIFEST,
SERVER_ISLAND_MAP_MARKER,
vitePluginServerIslands
};