Support programmatic TypeScript config (cf.config.ts)#12575
Support programmatic TypeScript config (cf.config.ts)#12575penalosa wants to merge 1 commit intopenalosa/async-read-configfrom
Conversation
… readConfig Make experimental_readRawConfig async to support loading programmatic config files. Propagate async through all consumers: readConfig, readPagesConfig, unstable_readConfig, unstable_getMiniflareWorkerOptions, and the vite-plugin/vitest-pool-workers integration layers. - Add programmatic config loading (loadProgrammaticConfig) and workerConfigToConfig conversion in workers-utils - Wire programmatic config detection into register-yargs-command - Update all ~40 call sites across wrangler, vite-plugin, and vitest-pool-workers to await the now-async config functions - Update test assertions for async throwing (rejects.toThrow) - Fix pre-existing yargs YError snapshot mismatches in dev/init tests
|
| const fileUrl = `file://${file}`; | ||
| const module = await import(fileUrl); |
There was a problem hiding this comment.
🔴 ESM module cache prevents watch mode from picking up config changes
In watchProgrammaticConfig, the rebuilt config file is always written to a fixed path (cf-config-watch.mjs at line 152), and loadFunctionOrObjectConfig loads it via import(fileUrl) at packages/workers-utils/src/config/programmatic.ts:34. Node.js caches ESM modules by URL — once a file:// URL has been imported, subsequent import() calls to the same URL return the cached module. This means only the first config load in watch mode will read the actual file; all subsequent rebuilds will silently return the stale cached module.
Root Cause and Impact
The one-shot loadProgrammaticConfig function avoids this problem by generating a unique filename per invocation using Date.now() and Math.random() (packages/workers-utils/src/config/programmatic.ts:104-107). However, watchProgrammaticConfig uses a static filename:
const tmpFile = path.join(tmpDir, `cf-config-watch.mjs`); // line 152When the esbuild watch plugin's onEnd callback fires on a rebuild (line 164), it calls:
const workerConfig = await loadFunctionOrObjectConfig(tmpFile, { env }); // line 187which internally does:
const fileUrl = `file://${file}`;
const module = await import(fileUrl); // line 34 — returns cached module!Since the fileUrl is always file://<tmpDir>/cf-config-watch.mjs, Node.js returns the cached module from the first import. The onChange callback will be invoked with the original config object, not the updated one.
Impact: wrangler dev with a programmatic config file (e.g. cf.config.ts) will never reload config changes during watch mode. Users editing their config will see no effect until they restart wrangler dev.
The fix is to append a cache-busting query parameter to the URL, e.g.:
const fileUrl = `file://${file}?t=${Date.now()}`;| const fileUrl = `file://${file}`; | |
| const module = await import(fileUrl); | |
| const fileUrl = `file://${file}?t=${Date.now()}`; | |
Was this helpful? React with 👍 or 👎 to provide feedback.
create-cloudflare
@cloudflare/kv-asset-handler
miniflare
@cloudflare/pages-shared
@cloudflare/unenv-preset
@cloudflare/vite-plugin
@cloudflare/vitest-pool-workers
@cloudflare/workers-editor-shared
@cloudflare/workers-utils
wrangler
commit: |
One step along the road of supporting programmatic config in Wrangler. This part adds support for programmatic config with the existing config file structure, to make the diff semi easy to read.
A picture of a cute animal (not mandatory, but encouraged)