astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
135 lines (134 loc) • 5.11 kB
JavaScript
import MagicString from "magic-string";
import { transformWithOxc } from "vite";
import { CSS_LANGS_RE } from "../core/viteUtils.js";
import { isAstroClientEnvironment } from "../environments.js";
// Match `import.meta.env` directly without trailing property access
const importMetaEnvOnlyRe = /\bimport\.meta\.env\b(?!\.)/;
function getReferencedPrivateKeys(source, privateEnv) {
const references = new Set();
for (const key in privateEnv) {
if (source.includes(key)) {
references.add(key);
}
}
return references;
}
/**
* Use `transformWithOxc` to perform replacements like Vite
* https://github.com/vitejs/vite/blob/5ea9edbc9ceb991e85f893fe62d68ed028677451/packages/vite/src/node/plugins/define.ts#L130
*/
async function replaceDefine(code, id, define, config) {
// Since Oxc doesn't support replacing complex expressions, we replace `import.meta.env`
// with a marker string first, then postprocess and apply the `Object.assign` code.
const replacementMarkers = {};
const env = define["import.meta.env"];
if (env) {
// Compute the marker from the length of the replaced code. We do this so that Oxc generates
// the sourcemap with the right column offset when we do the postprocessing.
const marker = `__astro_import_meta_env${"_".repeat(env.length - 23)}`;
replacementMarkers[marker] = env;
define = {
...define,
"import.meta.env": marker
};
}
const result = await transformWithOxc(code, id, {
lang: "js",
define,
sourcemap: config.command === "build" ? !!config.build.sourcemap : true,
// Define replacement doesn't depend on tsconfig, and per-file discovery can reach
// unrelated tsconfigs that fail to load (https://github.com/withastro/astro/issues/18404)
tsconfig: false
});
for (const marker in replacementMarkers) {
result.code = result.code.replaceAll(marker, replacementMarkers[marker]);
}
return {
code: result.code,
map: result.map || null
};
}
export function importMetaEnv({ envLoader }) {
let privateEnv;
let defaultDefines;
let isDev;
let devImportMetaEnvPrepend;
let viteConfig;
return {
name: "astro:vite-plugin-env",
config(_, { command }) {
isDev = command !== "build";
},
configResolved(resolvedConfig) {
viteConfig = resolvedConfig;
// HACK: move ourselves before Vite's define plugin to apply replacements at the right time (before Vite normal plugins)
const viteDefinePluginIndex = resolvedConfig.plugins.findIndex((p) => p.name === "vite:define");
if (viteDefinePluginIndex !== -1) {
const myPluginIndex = resolvedConfig.plugins.findIndex((p) => p.name === "astro:vite-plugin-env");
if (myPluginIndex !== -1) {
const myPlugin = resolvedConfig.plugins[myPluginIndex];
// @ts-ignore-error ignore readonly annotation
resolvedConfig.plugins.splice(viteDefinePluginIndex, 0, myPlugin);
// @ts-ignore-error ignore readonly annotation
resolvedConfig.plugins.splice(myPluginIndex, 1);
}
}
},
transform: {
filter: {
id: { exclude: [/.*\.(html|htm|json)$/, CSS_LANGS_RE] },
code: /import\.meta\.env/
},
handler(source, id) {
if (isAstroClientEnvironment(this.environment) || viteConfig.assetsInclude(id)) {
return;
}
// Find matches for *private* env and do our own replacement.
// Env is retrieved before process.env is populated by astro:env
// so that import.meta.env is first replaced by values, not process.env
privateEnv ??= envLoader.getPrivateEnv();
// In dev, we can assign the private env vars to `import.meta.env` directly for performance
if (isDev) {
const s = new MagicString(source);
if (!devImportMetaEnvPrepend) {
devImportMetaEnvPrepend = `Object.assign(import.meta.env,{`;
for (const key in privateEnv) {
devImportMetaEnvPrepend += `${key}:${privateEnv[key]},`;
}
devImportMetaEnvPrepend += "});";
}
s.prepend(devImportMetaEnvPrepend);
return {
code: s.toString(),
map: s.generateMap({ hires: "boundary" })
};
}
// In build, use Oxc to perform replacements. Compute the default defines for Oxc here as a
// separate object as it could be extended by `import.meta.env` later.
if (!defaultDefines) {
defaultDefines = {};
for (const key in privateEnv) {
defaultDefines[`import.meta.env.${key}`] = privateEnv[key];
}
}
let defines = defaultDefines;
// If reference the `import.meta.env` object directly, we want to inject private env vars
// into Vite's injected `import.meta.env` object. To do this, we use `Object.assign` and keeping
// the `import.meta.env` identifier so Vite sees it.
if (importMetaEnvOnlyRe.test(source)) {
const references = getReferencedPrivateKeys(source, privateEnv);
let replacement = `(Object.assign(import.meta.env,{`;
for (const key of references.values()) {
replacement += `${key}:${privateEnv[key]},`;
}
replacement += "}))";
defines = {
...defaultDefines,
"import.meta.env": replacement
};
}
return replaceDefine(source, id, defines, viteConfig);
}
}
};
}