From f73a036fcb736bebe8417511f54159c61641220a Mon Sep 17 00:00:00 2001 From: Jordan Paulino Date: Wed, 29 Apr 2026 16:28:53 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=8D=95=20Hydrate=20window.process.env=20b?= =?UTF-8?q?efore=20Vite=20module=20evaluation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generate a shared .clay/_env-init.js runtime module and import it first from both Vite entries (view bootstrap and kiln-edit init). This hydrates window.process.env from the earliest available runtime source (window.kiln.preloadData._envVars when present, otherwise existing window.process.env) before component/model modules execute. Fixes timing-sensitive cases where top-level universal constants like `const AGORA_HOST = process.env.AGORA_HOST` were evaluated before env injection and permanently captured undefined/null values. Made-with: Cursor --- lib/cmd/vite/generate-bootstrap.js | 6 ++++ lib/cmd/vite/generate-env-init.js | 56 ++++++++++++++++++++++++++++++ lib/cmd/vite/generate-kiln-edit.js | 4 +++ 3 files changed, 66 insertions(+) create mode 100644 lib/cmd/vite/generate-env-init.js diff --git a/lib/cmd/vite/generate-bootstrap.js b/lib/cmd/vite/generate-bootstrap.js index 1496fec..22a0385 100644 --- a/lib/cmd/vite/generate-bootstrap.js +++ b/lib/cmd/vite/generate-bootstrap.js @@ -4,6 +4,7 @@ const fs = require('fs-extra'); const path = require('path'); const { globSync } = require('glob'); const { getConfigValue } = require('../../config-file-helpers'); +const { generateViteEnvInit } = require('./generate-env-init'); const CWD = process.cwd(); const CLAY_DIR = path.join(CWD, '.clay'); @@ -106,6 +107,8 @@ mountComponentModules().catch(console.error); * @returns {Promise} absolute path to the written bootstrap file */ async function generateViteBootstrap() { + await generateViteEnvInit(); + const clientFiles = [ ...globSync(path.join(CWD, 'components', '**', 'client.js')), ...globSync(path.join(CWD, 'layouts', '**', 'client.js')), @@ -152,6 +155,8 @@ ${stickyListeners} ? "import './_globals-init.js';\n" : '// no global/js — skipping _globals-init\n'; + const envInitImport = "import './_env-init.js';\n"; + // Clay-kiln's preloader does `Object.keys(window.modules)` to find // component model.js / kiln.js registrations that Browserify's megabundler // assigns there at page load. Under Vite there is no Browserify runtime, @@ -174,6 +179,7 @@ ${stickyListeners} '// present on the current page — keeping initial parse cost low.', '', kilnCompatStub, + envInitImport, globalsImport, stickyShimBlock, 'const _clayClientModules = {', diff --git a/lib/cmd/vite/generate-env-init.js b/lib/cmd/vite/generate-env-init.js new file mode 100644 index 0000000..61f9021 --- /dev/null +++ b/lib/cmd/vite/generate-env-init.js @@ -0,0 +1,56 @@ +'use strict'; + +const fs = require('fs-extra'); +const path = require('path'); + +const CWD = process.cwd(); +const CLAY_DIR = path.join(CWD, '.clay'); +const ENV_INIT_FILE = path.join(CLAY_DIR, '_env-init.js'); + +/** + * Generate .clay/_env-init.js — runtime hydration for window.process.env. + * + * Why: + * - Universal modules can be evaluated before late env injection in some + * browser paths under ESM (especially edit-mode/kiln startup), and any + * top-level `const X = process.env.X` captures undefined permanently. + * - Hydrating window.process.env in a dedicated side-effect module imported + * first by Vite entries ensures env is present before those modules run. + * + * Sources (in priority order): + * 1) window.kiln.preloadData._envVars (authoritative edit-mode payload from amphora-html) + * 2) existing window.process.env (if already injected by any earlier script) + * + * @returns {Promise} absolute path to the generated file + */ +async function generateViteEnvInit() { + const content = [ + '// AUTO-GENERATED — clay vite env init (do not edit)', + `// ${new Date().toISOString()}`, + ';(function clayViteEnvInit() {', + ' if (typeof window === "undefined") return;', + '', + ' var fromKiln = window.kiln && window.kiln.preloadData && window.kiln.preloadData._envVars;', + ' var existing = window.process && window.process.env;', + '', + ' var source = (fromKiln && typeof fromKiln === "object")', + ' ? fromKiln', + ' : ((existing && typeof existing === "object") ? existing : {});', + '', + ' window.process = window.process || {};', + ' window.process.env = Object.assign({}, source, window.process.env || {});', + '}());', + '', + ].join('\n'); + + await fs.ensureDir(CLAY_DIR); + await fs.writeFile(ENV_INIT_FILE, content, 'utf8'); + + return ENV_INIT_FILE; +} + +module.exports = { + generateViteEnvInit, + ENV_INIT_FILE, +}; + diff --git a/lib/cmd/vite/generate-kiln-edit.js b/lib/cmd/vite/generate-kiln-edit.js index 5bb16fc..7b2f65f 100644 --- a/lib/cmd/vite/generate-kiln-edit.js +++ b/lib/cmd/vite/generate-kiln-edit.js @@ -3,6 +3,7 @@ const fs = require('fs-extra'); const path = require('path'); const { globSync } = require('glob'); +const { generateViteEnvInit } = require('./generate-env-init'); const CWD = process.cwd(); const CLAY_DIR = path.join(CWD, '.clay'); @@ -27,6 +28,8 @@ const KILN_EDIT_ENTRY_KEY = '.clay/vite-kiln-edit-init'; * @returns {Promise} absolute path to the generated file */ async function generateViteKilnEditEntry() { + await generateViteEnvInit(); + const modelFiles = [ ...globSync(path.join(CWD, 'components', '**', 'model.js')), ...globSync(path.join(CWD, 'layouts', '**', 'model.js')), @@ -54,6 +57,7 @@ async function generateViteKilnEditEntry() { // Use namespace imports (import * as) so both CJS modules (which @rollup/plugin-commonjs // gives a namespace object with a .default property) and ESM modules work uniformly. // Then resolve the actual export via _resolveDefault() at runtime. + lines.push('import \'./_env-init.js\';'); modelFiles.forEach((f, i) => lines.push(`import * as _m${i} from ${JSON.stringify(toRel(f))};`)); kilnjsFiles.forEach((f, i) => lines.push(`import * as _k${i} from ${JSON.stringify(toRel(f))};`)); if (hasKilnPlugin) {