astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
100 lines (99 loc) • 3.48 kB
JavaScript
import MagicString from "magic-string";
const VIRTUAL_ISLAND_MAP_ID = "@astro-server-islands";
const RESOLVED_VIRTUAL_ISLAND_MAP_ID = "\0" + VIRTUAL_ISLAND_MAP_ID;
const serverIslandPlaceholder = "'$$server-islands$$'";
function vitePluginServerIslands({ settings, logger }) {
let command = "serve";
let viteServer = null;
const referenceIdMap = /* @__PURE__ */ new Map();
return {
name: "astro:server-islands",
enforce: "post",
config(_config, { command: _command }) {
command = _command;
},
configureServer(_server) {
viteServer = _server;
},
resolveId(name) {
if (name === VIRTUAL_ISLAND_MAP_ID) {
return RESOLVED_VIRTUAL_ISLAND_MAP_ID;
}
},
load(id) {
if (id === RESOLVED_VIRTUAL_ISLAND_MAP_ID) {
return { code: `export const serverIslandMap = ${serverIslandPlaceholder};` };
}
},
transform(_code, id) {
if (id.endsWith(".astro")) {
const info = this.getModuleInfo(id);
if (info?.meta) {
const astro = info.meta.astro;
if (astro?.serverComponents.length) {
for (const comp of astro.serverComponents) {
if (!settings.serverIslandNameMap.has(comp.resolvedPath)) {
if (!settings.adapter) {
logger.error(
"islands",
"You tried to render a server island without an adapter added to your project. An adapter is required to use the `server:defer` attribute on any component. Your project will fail to build unless you add an adapter or remove the attribute."
);
}
let name = comp.localName;
let idx = 1;
while (true) {
if (!settings.serverIslandMap.has(name)) {
break;
}
name += idx++;
}
settings.serverIslandNameMap.set(comp.resolvedPath, name);
settings.serverIslandMap.set(name, () => {
return viteServer?.ssrLoadModule(comp.resolvedPath);
});
if (command === "build") {
let referenceId = this.emitFile({
type: "chunk",
id: comp.specifier,
importer: id,
name: comp.localName
});
referenceIdMap.set(comp.resolvedPath, referenceId);
}
}
}
}
}
}
},
renderChunk(code) {
if (code.includes(serverIslandPlaceholder)) {
if (referenceIdMap.size === 0) {
return {
code: code.replace(serverIslandPlaceholder, "new Map();"),
map: null
};
}
let mapSource = "new Map([";
for (let [resolvedPath, referenceId] of referenceIdMap) {
const fileName = this.getFileName(referenceId);
const islandName = settings.serverIslandNameMap.get(resolvedPath);
mapSource += `
['${islandName}', () => import('./${fileName}')],`;
}
mapSource += "\n]);";
referenceIdMap.clear();
const ms = new MagicString(code);
ms.replace(serverIslandPlaceholder, mapSource);
return {
code: ms.toString(),
map: ms.generateMap({ hires: "boundary" })
};
}
}
};
}
export {
VIRTUAL_ISLAND_MAP_ID,
vitePluginServerIslands
};