UNPKG

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
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); } } }; }