vike
Version:
The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.
90 lines (89 loc) • 4.85 kB
JavaScript
export { pluginEnvVars };
import MagicString from 'magic-string';
import { loadEnv } from 'vite';
import { assert, assertPosixPath, assertUsage, assertWarning, escapeRegex, isArray, isNotNullish, lowerFirst, } from '../utils.js';
import { getModuleFilePathAbsolute } from '../shared/getFilePath.js';
import { normalizeId } from '../shared/normalizeId.js';
import { isViteServerBuild_safe } from '../shared/isViteServerBuild.js';
import { applyRegExpWithMagicString } from '../shared/applyRegExWithMagicString.js';
// TODO/enventually:
// - Make import.meta.env work inside +config.js
// - For it to work, we'll probably need the user to define the settings (e.g. `envDir`) for loadEnv() inside vike.config.js instead of vite.config.js
// - Or stop using Vite's `mode` implementation and have Vike implement its own `mode` feature? (So that the only dependencies are `$ vike build --mode staging` and `$ MODE=staging vike build`.)
const PUBLIC_ENV_PREFIX = 'PUBLIC_ENV__';
const PUBLIC_ENV_ALLOWLIST = [
// https://github.com/vikejs/vike/issues/1724
'STORYBOOK',
];
function pluginEnvVars() {
let envsAll;
let config;
return {
name: 'vike:pluginEnvVars',
enforce: 'post',
configResolved(config_) {
config = config_;
envsAll = loadEnv(config.mode, config.envDir || config.root, '');
config.plugins.sort(lowerFirst((plugin) => (plugin.name === 'vite:define' ? 1 : 0)));
},
transform(code, id, options) {
id = normalizeId(id);
assertPosixPath(id);
if (id.includes('/node_modules/'))
return;
assertPosixPath(config.root);
if (!id.startsWith(config.root))
return;
if (!code.includes('import.meta.env.'))
return;
const isBuild = config.command === 'build';
const isClientSide = !isViteServerBuild_safe(config, options);
const magicString = new MagicString(code);
// Find & check
const replacements = Object.entries(envsAll)
.filter(([key]) => {
// Already handled by Vite
const envPrefix = !config.envPrefix ? [] : isArray(config.envPrefix) ? config.envPrefix : [config.envPrefix];
return !envPrefix.some((prefix) => key.startsWith(prefix));
})
.map(([envName, envVal]) => {
const envStatement = `import.meta.env.${envName}`;
const envStatementRegExpStr = escapeRegex(envStatement) + '\\b';
// Security check
{
const isPrivate = !envName.startsWith(PUBLIC_ENV_PREFIX) && !PUBLIC_ENV_ALLOWLIST.includes(envName);
if (isPrivate && isClientSide) {
if (!new RegExp(envStatementRegExpStr).test(code))
return;
const modulePath = getModuleFilePathAbsolute(id, config);
const errMsgAddendum = isBuild ? '' : ' (Vike will prevent your app from building for production)';
const keyPublic = `${PUBLIC_ENV_PREFIX}${envName}`;
const errMsg = `${envStatement} is used in client-side file ${modulePath} which means that the environment variable ${envName} will be included in client-side bundles and, therefore, ${envName} will be publicly exposed which can be a security leak${errMsgAddendum}. Use ${envStatement} only in server-side files, or rename ${envName} to ${keyPublic}, see https://vike.dev/env`;
if (isBuild) {
assertUsage(false, errMsg);
}
else {
// - Only a warning for faster development DX (e.g. when user toggles `ssr: boolean` or `onBeforeRenderIsomorph: boolean`).
// - But only showing a warning can be confusing: https://github.com/vikejs/vike/issues/1641
assertWarning(false, errMsg, { onlyOnce: true });
}
}
// Double check
assert(!(isPrivate && isClientSide) || !isBuild);
}
return { regExpStr: envStatementRegExpStr, replacement: envVal };
})
.filter(isNotNullish);
// Apply
replacements.forEach(({ regExpStr, replacement }) => {
applyRegExpWithMagicString(magicString, regExpStr, replacement);
});
if (!magicString.hasChanged())
return null;
return {
code: magicString.toString(),
map: magicString.generateMap({ hires: true, source: id }),
};
},
};
}