Skip to content

fix: set correct NODE_ENV for build#247

Merged
southpolesteve merged 2 commits intocloudflare:mainfrom
hyoban:3-4-set-NODE_ENV
Mar 4, 2026
Merged

fix: set correct NODE_ENV for build#247
southpolesteve merged 2 commits intocloudflare:mainfrom
hyoban:3-4-set-NODE_ENV

Conversation

@hyoban
Copy link
Copy Markdown
Contributor

@hyoban hyoban commented Mar 4, 2026

Fixes #246

@southpolesteve
Copy link
Copy Markdown
Collaborator

/bigbonk review

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix — setting NODE_ENV correctly during build is important. The approach is sound overall: align process.env.NODE_ENV with Next.js semantics and inject it as a define for static replacement. A few things to tighten up below.

Comment thread packages/vinext/src/index.ts Outdated
Comment on lines +1700 to +1701
// Align NODE_ENV with Next.js semantics: build -> production, serve -> development.
const resolvedNodeEnv = mode === "test"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ternary chain works but is a bit hard to parse at a glance. Consider using a local helper or an if/else if/else block for clarity:

Suggested change
// Align NODE_ENV with Next.js semantics: build -> production, serve -> development.
const resolvedNodeEnv = mode === "test"
// Align NODE_ENV with Next.js semantics: build -> production, serve -> development.
let resolvedNodeEnv: string;
if (mode === "test") {
resolvedNodeEnv = "test";
} else if (env?.command === "build") {
resolvedNodeEnv = "production";
} else {
resolvedNodeEnv = "development";
}

Nit — feel free to ignore.

Comment on lines +1747 to +1750
if (
!config.define ||
typeof config.define !== "object" ||
!("process.env.NODE_ENV" in config.define)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard typeof config.define !== "object" is redundant — Vite's UserConfig["define"] is typed as Record<string, any> | undefined, so if it's truthy it's always an object. But more importantly, this check has a subtle issue: if the user passes config.define as an empty object {}, the in check returns false and vinext still injects its value — which is the correct behavior. That's fine.

However, the typeof config.define !== "object" branch would also catch null, which while unlikely, would cause "process.env.NODE_ENV" in null to throw. So the typeof guard does serve a defensive purpose. It's fine as-is — just noting for clarity that the key protection here is really the !config.define check.

const mockConfig = { root: tmpDir, build: {}, plugins: [] };
const result = await (mainPlugin as any).config(
mockConfig,
{ command: "build", mode: "production" },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test only covers the { command: "build", mode: "production" } case. It would be valuable to also test:

  1. command: "serve" — verify process.env.NODE_ENV is "development" (or not set as "production")
  2. command: "build" without mode — the existing tests in this file call config(mockConfig, { command: "build" }) (no mode). With the new code, mode falls back to "development" via env?.mode ?? "development" on line 1692, yet env?.command is "build" so resolvedNodeEnv becomes "production". This is correct Next.js behavior but isn't explicitly tested.
  3. User already defined process.env.NODE_ENV — verify the guard at line 1747-1751 respects user overrides.

At minimum, case (2) would be good to add since it matches what the other tests in this file actually do (pass { command: "build" } with no mode).

Comment on lines +15 to +27
let originalNodeEnv: string | undefined;

beforeEach(() => {
originalNodeEnv = process.env.NODE_ENV;
});

afterEach(() => {
if (originalNodeEnv === undefined) {
delete process.env.NODE_ENV;
} else {
process.env.NODE_ENV = originalNodeEnv;
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The beforeEach/afterEach for saving and restoring NODE_ENV is good practice since the production code now mutates process.env.NODE_ENV. However, these hooks are at the file top level (outside any describe block), which means they run for every test in the file — including unrelated tests like clientTreeshakeConfig, clientManualChunks, and computeLazyChunks.

Consider scoping these hooks inside the process.env.NODE_ENV define describe block instead, so the save/restore only runs for tests that actually need it. Or, if you're concerned about the existing tests being affected by the new process.env.NODE_ENV mutation in the config hook, that's a fair reason to keep them global — but it would be worth adding a comment explaining why.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Mar 4, 2026

Review posted successfully. Here's a summary of my feedback on PR #247:

Overall: The approach is correct — aligning process.env.NODE_ENV with Next.js semantics (build = production, serve = development) and injecting it as a define for static replacement in client bundles. This is safe and won't conflict with Vite's built-in NODE_ENV handling since user/plugin defines take precedence.

Comments posted (4):

  1. Ternary readability (index.ts:1700-1701) — Minor style nit suggesting if/else if/else over nested ternaries for clarity.

  2. Guard condition (index.ts:1747-1750) — Noted that the typeof config.define !== "object" check is mostly redundant given Vite's types, though it does defensively handle the null case. Fine as-is.

  3. Test coverage gaps (build-optimization.test.ts:205) — The test only covers { command: "build", mode: "production" }. Suggested adding cases for: command: "serve" (should be "development"), command: "build" without mode (matches what other tests in the file do), and user-defined process.env.NODE_ENV override.

  4. beforeEach/afterEach scope (build-optimization.test.ts:15-27) — The NODE_ENV save/restore hooks are at the file top level, affecting all tests including unrelated ones. Suggested scoping them inside the relevant describe block or adding a comment explaining why they're global.

github run

…nv guard

- Replace nested ternary with if/else for NODE_ENV resolution
- Add comment explaining why beforeEach/afterEach is file-scoped
- Add test cases: serve (development), build without mode, user override
- Skip NODE_ENV key in nextConfig.env loop to prevent silent override
- Extract setupTmpProject helper to reduce test boilerplate
Copy link
Copy Markdown
Collaborator

@southpolesteve southpolesteve left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed all review feedback and added an additional fix:

Review feedback addressed:

  1. Ternary readability - Replaced nested ternary with if/else block for the NODE_ENV resolution logic.
  2. beforeEach/afterEach scope - Kept file-scoped (necessary because other test blocks like optimizeDeps and treeshake also call mainPlugin.config(), which now mutates process.env.NODE_ENV). Added a comment explaining why.
  3. Test coverage - Added three new test cases:
    • command: "serve" verifies "development"
    • command: "build" without explicit mode verifies "production"
    • User-defined config.define with process.env.NODE_ENV verifies the guard skips injection
  4. Guard condition - Left as-is; the typeof check defensively handles the null case.

Additional fix (from my own review):

  • The nextConfig.env loop (which injects env vars from next.config.js) could silently override the NODE_ENV define we just set. If a user had env: { NODE_ENV: "development" } in their next.config, it would overwrite "production" during build. Added a if (key === "NODE_ENV") continue guard, matching Next.js behavior (Next.js also ignores NODE_ENV in the env config key).

Also extracted a setupTmpProject() helper in the test file to reduce boilerplate across the NODE_ENV test cases.

All checks pass locally: typecheck clean, lint 0 warnings, 37/37 build-optimization tests pass, 1041/1041 core tests pass.

@southpolesteve southpolesteve enabled auto-merge (squash) March 4, 2026 21:56
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 4, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@247

commit: a46d664

@southpolesteve southpolesteve merged commit 5b9d775 into cloudflare:main Mar 4, 2026
14 checks passed
@nunofgs
Copy link
Copy Markdown

nunofgs commented Mar 21, 2026

👋 thanks for the fix. Has this been included in a release?

@james-elicx
Copy link
Copy Markdown
Collaborator

👋 thanks for the fix. Has this been included in a release?

Hi @nunofgs, it was first released in v0.0.20.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Set correct NODE_ENV for build

4 participants