diff --git a/src/js_parser/parse/parse_entry.rs b/src/js_parser/parse/parse_entry.rs index 87c89136c8a7..264529792941 100644 --- a/src/js_parser/parse/parse_entry.rs +++ b/src/js_parser/parse/parse_entry.rs @@ -18,6 +18,7 @@ use crate::parser::{ Jest, ParseStatementOptions, RuntimeFeatures, RuntimeImports, ScanPassResult, StatementScope, WrapMode, }; +use crate::scan::scan_side_effects::SideEffects; use bun_ast as js_ast; use bun_ast::DeclaredSymbol; use bun_ast::{B, E, Expr, G, S, Stmt}; @@ -1252,7 +1253,47 @@ impl<'a> Parser<'a> { found }; if let Some(found) = stmt_and_part { - let stmt = found.stmt; + let mut stmt = found.stmt; + // Without --minify-syntax the NODE_ENV if/else above survives + // as `if () {} else { X }` after DCE empties the dead + // arm; peel to the live arm's single statement. `to_boolean` is + // the same predicate `s_if` used to pick the dead arm. + while let js_ast::StmtData::SIf(s_if) = &stmt.data { + let Some(effects) = SideEffects::to_boolean(p, &s_if.test.data) else { + break; + }; + if effects.side_effects != SideEffects::NoSideEffects { + break; + } + let (live, dead) = if effects.value { + (Some(s_if.yes), s_if.no) + } else { + (s_if.no, Some(s_if.yes)) + }; + let dead_is_empty = match dead { + None => true, + Some(d) => match d.data { + js_ast::StmtData::SEmpty(_) => true, + js_ast::StmtData::SBlock(block) => block.stmts.len() == 0, + _ => false, + }, + }; + if !dead_is_empty { + break; + } + let Some(live_stmt) = live else { break }; + match live_stmt.data { + js_ast::StmtData::SBlock(block) => { + let body = block.stmts.slice(); + if body.len() != 1 { + break; + } + stmt = body[0]; + } + js_ast::StmtData::SEmpty(_) => break, + _ => stmt = live_stmt, + } + } let part = &mut parts[found.part_idx]; if p.symbols.as_slice()[p.module_ref.inner_index() as usize].use_count_estimate == 1 { diff --git a/test/bundler/bundler_cjs2esm.test.ts b/test/bundler/bundler_cjs2esm.test.ts index c0860af1dfec..e2b2ffeb63f6 100644 --- a/test/bundler/bundler_cjs2esm.test.ts +++ b/test/bundler/bundler_cjs2esm.test.ts @@ -200,6 +200,193 @@ describe("bundler", () => { stdout: "development", }, }); + // https://github.com/oven-sh/bun/issues/12726 + itBundled("cjs2esm/ModuleExportsBasedOnNodeEnvProductionNoMinify", { + files: { + "/entry.js": /* js */ ` + import { foo } from 'lib'; + console.log(foo); + `, + "/node_modules/lib/index.js": /* js */ ` + 'use strict'; + if (process.env.NODE_ENV === 'production') { + module.exports = require('./library.prod.js') + } else { + module.exports = require('./library.dev.js') + } + `, + "/node_modules/lib/library.prod.js": /* js */ ` + module.exports.foo = 'production'; + `, + "/node_modules/lib/library.dev.js": /* js */ ` + module.exports.foo = 'FAILED'; + `, + }, + cjs2esm: true, + env: { + NODE_ENV: "production", + }, + run: { + stdout: "production", + }, + }); + itBundled("cjs2esm/ModuleExportsBasedOnNodeEnvDevelopmentNoMinify", { + files: { + "/entry.js": /* js */ ` + import { foo } from 'lib'; + console.log(foo); + `, + "/node_modules/lib/index.js": /* js */ ` + 'use strict'; + if (process.env.NODE_ENV === 'production') { + module.exports = require('./library.prod.js') + } else { + module.exports = require('./library.dev.js') + } + `, + "/node_modules/lib/library.prod.js": /* js */ ` + module.exports.foo = 'FAILED'; + `, + "/node_modules/lib/library.dev.js": /* js */ ` + module.exports.foo = 'development'; + `, + }, + cjs2esm: true, + env: { + NODE_ENV: "development", + }, + run: { + stdout: "development", + }, + }); + itBundled("cjs2esm/ModuleExportsBasedOnNodeEnvNoBracesNoMinify", { + files: { + "/entry.js": /* js */ ` + import { foo } from 'lib'; + console.log(foo); + `, + "/node_modules/lib/index.js": /* js */ ` + if (process.env.NODE_ENV === 'production') + module.exports = require('./library.prod.js') + else + module.exports = require('./library.dev.js') + `, + "/node_modules/lib/library.prod.js": /* js */ ` + module.exports.foo = 'FAILED'; + `, + "/node_modules/lib/library.dev.js": /* js */ ` + module.exports.foo = 'development'; + `, + }, + cjs2esm: true, + env: { + NODE_ENV: "development", + }, + run: { + stdout: "development", + }, + }); + itBundled("cjs2esm/ModuleExportsBasedOnDefineNumberNoMinify", { + files: { + "/entry.js": /* js */ ` + import { foo } from 'lib'; + console.log(foo); + `, + "/node_modules/lib/index.js": /* js */ ` + if (__DEV__) { + module.exports = require('./library.dev.js') + } else { + module.exports = require('./library.prod.js') + } + `, + "/node_modules/lib/library.prod.js": /* js */ ` + module.exports.foo = 'production'; + `, + "/node_modules/lib/library.dev.js": /* js */ ` + module.exports.foo = 'FAILED'; + `, + }, + cjs2esm: true, + define: { + __DEV__: "0", + }, + run: { + stdout: "production", + }, + }); + itBundled("cjs2esm/ModuleExportsBasedOnNodeEnvElseIfChainNoMinify", { + files: { + "/entry.js": /* js */ ` + import { foo } from 'lib'; + console.log(foo); + `, + "/node_modules/lib/index.js": /* js */ ` + if (process.env.NODE_ENV === 'production') { + module.exports = require('./library.prod.js') + } else if (process.env.NODE_ENV === 'test') { + module.exports = require('./library.test.js') + } else { + module.exports = require('./library.dev.js') + } + `, + "/node_modules/lib/library.prod.js": /* js */ ` + module.exports.foo = 'FAILED'; + `, + "/node_modules/lib/library.test.js": /* js */ ` + module.exports.foo = 'FAILED'; + `, + "/node_modules/lib/library.dev.js": /* js */ ` + module.exports.foo = 'development'; + `, + }, + cjs2esm: true, + env: { + NODE_ENV: "development", + }, + run: { + stdout: "development", + }, + }); + itBundled("cjs2esm/ReactIndexNodeEnvRedirectNoMinify", { + files: { + "/entry.js": /* js */ ` + const react = require("react"); + console.log(react.version); + `, + "/node_modules/react/package.json": /* json */ ` + { "name": "react", "version": "18.3.1", "main": "./index.js" } + `, + "/node_modules/react/index.js": /* js */ ` + 'use strict'; + + if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/react.production.min.js'); + } else { + module.exports = require('./cjs/react.development.js'); + } + `, + "/node_modules/react/cjs/react.production.min.js": /* js */ ` + exports.version = 'FAILED'; + `, + "/node_modules/react/cjs/react.development.js": /* js */ ` + exports.version = '18.3.1-dev'; + `, + }, + target: "bun", + env: { + NODE_ENV: "development", + }, + onAfterBundle(api) { + const code = api.readFile("out.js"); + expect(code).not.toContain("react/index.js"); + expect(code).not.toContain("require_react "); + expect(code).not.toContain("require_react("); + expect(code).not.toContain("react.production.min.js"); + }, + run: { + stdout: "18.3.1-dev", + }, + }); itBundled("cjs2esm/ModuleExportsEqualsRuntimeCondition", { files: { "/entry.js": /* js */ ` diff --git a/test/bundler/transpiler/jsx-dev/jsx-dev.tsx b/test/bundler/transpiler/jsx-dev/jsx-dev.tsx index ee854a800268..03d23c878d5c 100644 --- a/test/bundler/transpiler/jsx-dev/jsx-dev.tsx +++ b/test/bundler/transpiler/jsx-dev/jsx-dev.tsx @@ -21,13 +21,13 @@ if (!process.env.NO_BUILD) { let shouldHaveJSX = process.env.CHILD_NODE_ENV === "production"; if (shouldHaveJSXDev) { - if (!code.includes("jsx_dev_runtime.jsxDEV")) { + if (!code.includes("jsx-dev-runtime.development") || !code.includes("jsxDEV(")) { throw new Error("jsxDEV is not included"); } } if (shouldHaveJSX) { - if (!code.includes("jsx_runtime.jsx")) { + if (!code.includes("jsx-runtime.production") || code.includes("jsxDEV(")) { throw new Error("Jsx is not included"); } } diff --git a/test/bundler/transpiler/jsx-production.tsx b/test/bundler/transpiler/jsx-production.tsx index ee854a800268..03d23c878d5c 100644 --- a/test/bundler/transpiler/jsx-production.tsx +++ b/test/bundler/transpiler/jsx-production.tsx @@ -21,13 +21,13 @@ if (!process.env.NO_BUILD) { let shouldHaveJSX = process.env.CHILD_NODE_ENV === "production"; if (shouldHaveJSXDev) { - if (!code.includes("jsx_dev_runtime.jsxDEV")) { + if (!code.includes("jsx-dev-runtime.development") || !code.includes("jsxDEV(")) { throw new Error("jsxDEV is not included"); } } if (shouldHaveJSX) { - if (!code.includes("jsx_runtime.jsx")) { + if (!code.includes("jsx-runtime.production") || code.includes("jsxDEV(")) { throw new Error("Jsx is not included"); } }