fix: set correct NODE_ENV for build#247
Conversation
|
/bigbonk review |
| // Align NODE_ENV with Next.js semantics: build -> production, serve -> development. | ||
| const resolvedNodeEnv = mode === "test" |
There was a problem hiding this comment.
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:
| // 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.
| if ( | ||
| !config.define || | ||
| typeof config.define !== "object" || | ||
| !("process.env.NODE_ENV" in config.define) |
There was a problem hiding this comment.
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" }, |
There was a problem hiding this comment.
The test only covers the { command: "build", mode: "production" } case. It would be valuable to also test:
command: "serve"— verifyprocess.env.NODE_ENVis"development"(or not set as"production")command: "build"withoutmode— the existing tests in this file callconfig(mockConfig, { command: "build" })(nomode). With the new code,modefalls back to"development"viaenv?.mode ?? "development"on line 1692, yetenv?.commandis"build"soresolvedNodeEnvbecomes"production". This is correct Next.js behavior but isn't explicitly tested.- 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).
| 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; | ||
| } | ||
| }); |
There was a problem hiding this comment.
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.
|
Review posted successfully. Here's a summary of my feedback on PR #247: Overall: The approach is correct — aligning Comments posted (4):
|
…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
southpolesteve
left a comment
There was a problem hiding this comment.
Addressed all review feedback and added an additional fix:
Review feedback addressed:
- Ternary readability - Replaced nested ternary with if/else block for the NODE_ENV resolution logic.
- beforeEach/afterEach scope - Kept file-scoped (necessary because other test blocks like
optimizeDepsandtreeshakealso callmainPlugin.config(), which now mutatesprocess.env.NODE_ENV). Added a comment explaining why. - Test coverage - Added three new test cases:
command: "serve"verifies"development"command: "build"without explicitmodeverifies"production"- User-defined
config.definewithprocess.env.NODE_ENVverifies the guard skips injection
- Guard condition - Left as-is; the
typeofcheck defensively handles thenullcase.
Additional fix (from my own review):
- The
nextConfig.envloop (which injects env vars fromnext.config.js) could silently override the NODE_ENV define we just set. If a user hadenv: { NODE_ENV: "development" }in their next.config, it would overwrite"production"during build. Added aif (key === "NODE_ENV") continueguard, 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.
commit: |
|
👋 thanks for the fix. Has this been included in a release? |
Hi @nunofgs, it was first released in v0.0.20. |
Fixes #246