UNPKG

vike

Version:

The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.

140 lines (139 loc) 6.99 kB
import '../assertEnvVite.js'; export { pluginReplaceConstantsEnvVars }; import { loadEnv } from 'vite'; import { escapeRegex } from '../../../utils/escapeRegex.js'; import { isNotNullish } from '../../../utils/isNullish.js'; import { assert, assertUsage, assertWarning } from '../../../utils/assert.js'; import { isArray } from '../../../utils/isArray.js'; import { lowerFirst } from '../../../utils/sorter.js'; import { assertPosixPath } from '../../../utils/path.js'; import { getFilePathToShowToUserModule } from '../shared/getFilePath.js'; import { normalizeId } from '../shared/normalizeId.js'; import { isViteServerSide_extraSafe } from '../shared/isViteServerSide.js'; import { getMagicString } from '../shared/getMagicString.js'; const PUBLIC_ENV_PREFIX = 'PUBLIC_ENV__'; const PUBLIC_ENV_ALLOWLIST = [ // https://github.com/vikejs/vike/issues/1724 'STORYBOOK', ]; // TO-DO/eventually: // - 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`.) // === Rolldown filter const skipNodeModules = '/node_modules/'; const skipIrrelevant = 'import.meta.env.'; const filterRolldown = { id: { exclude: `**${skipNodeModules}**`, }, code: { include: skipIrrelevant, }, }; const filterFunction = (id, code) => { if (id.includes(skipNodeModules)) return false; if (!code.includes(skipIrrelevant)) return false; return true; }; // === function pluginReplaceConstantsEnvVars() { let envVarsAll; let envPrefix; let config; return [ { name: 'vike:pluginReplaceConstantsEnvVars', enforce: 'post', configResolved: { handler(config_) { config = config_; envVarsAll = loadEnv(config.mode, config.envDir || config.root, ''); // Add process.env values defined by .env files Object.entries(envVarsAll).forEach(([key, val]) => { var _a; return ((_a = process.env)[key] ?? (_a[key] = val)); }); envPrefix = getEnvPrefix(config); config.plugins.sort(lowerFirst((plugin) => (plugin.name === 'vite:define' ? 1 : 0))); }, }, transform: { filter: filterRolldown, handler(code, id, options) { id = normalizeId(id); assertPosixPath(id); assertPosixPath(config.root); if (!id.startsWith(config.root)) return; // skip linked dependencies assert(filterFunction(id, code)); const isBuild = config.command === 'build'; const isClientSide = !isViteServerSide_extraSafe(config, this.environment, options); const { magicString, getMagicStringResult } = getMagicString(code, id); // Get regex operations const replacements = Object.entries(envVarsAll) // Skip env vars that start with [`config.envPrefix`](https://vite.dev/config/shared-options.html#envprefix) — they are already handled by Vite .filter(([envName]) => !envPrefix.some((prefix) => envName.startsWith(prefix))) // Skip constants like import.meta.env.DEV which are already handled by Vite .filter(([envName]) => !['DEV', 'PROD', 'SSR', 'MODE', 'BASE_URL'].includes(envName)) .map(([envName, envVal]) => { const envStatement = `import.meta.env.${envName}`; const envStatementRegExpStr = escapeRegex(envStatement) + '\\b'; // Show error (warning in dev) if client code contains a private environment variable (one that doesn't start with PUBLIC_ENV__ and that isn't included in `PUBLIC_ENV_ALLOWLIST`). if (isClientSide) { const skip = assertNoClientSideLeak({ envName, envStatement, envStatementRegExpStr, code, id, config, isBuild, }); if (skip) return null; } return { regExpStr: envStatementRegExpStr, replacement: envVal }; }) .filter(isNotNullish); // Apply regex operations replacements.forEach(({ regExpStr, replacement }) => { magicString.replaceAll(new RegExp(regExpStr, 'g'), JSON.stringify(replacement)); }); return getMagicStringResult(); }, }, }, ]; } function getEnvPrefix(config) { const { envPrefix } = config; if (!envPrefix) return []; if (!isArray(envPrefix)) return [envPrefix]; return envPrefix; } function assertNoClientSideLeak({ envName, envStatement, envStatementRegExpStr, code, id, config, isBuild, }) { const isPrivate = !envName.startsWith(PUBLIC_ENV_PREFIX) && !PUBLIC_ENV_ALLOWLIST.includes(envName); // ✅ All good if (!isPrivate) return; if (!new RegExp(envStatementRegExpStr).test(code)) return true; // ❌ Security leak! // - Warning in dev // - assertUsage() and abort when building for production const modulePath = getFilePathToShowToUserModule(id, config); const errMsgAddendum = isBuild ? '' : ' (Vike will prevent your app from building for production)'; const envNameFixed = `${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 ${envNameFixed}, 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`). // - Although only showing a warning can be confusing: https://github.com/vikejs/vike/issues/1641 assertWarning(false, errMsg, { onlyOnce: true }); } assert(!isBuild); // we should abort if building for production }