Repository navigation
Conversation
… runs `node ./site` with a package.json "main" of `index.html` printed nothing and exited 0. `boot` read the flag from the extension of the given path, so the module loader imported the HTML file as a module. `Run::start` now sets the flag from the resolved path. An HTML entry point does not keep the given path in `process.argv[1]`, because `internal/html.ts` reads its entry from there. A script behind a symlink named `.html` now runs as a script.
|
Status: fix pushed ( How I reproduced it (Linux x64, bun mkdir -p /tmp/html/site /tmp/shim && cd /tmp/html
echo '{"main":"index.html"}' > site/package.json
echo '<html><body><script>console.log(1)</script></body></html>' > site/index.html
ln -sf "$(which bun)" /tmp/shim/node # bun as node
/tmp/shim/node ./site ; echo "exit=$?" # no output, exit=0 (before this PR)
bun ./site # Bun v1.4.3 dev server ready ... url: http://localhost:3000/
/tmp/shim/node ./site/index.html # starts the dev server too
node ./site # Node: SyntaxError: Unexpected token '<', exit 1The reverse case:
|
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🟣
src/runtime/cli/run_command.rs— Users runningnode --watch ./siteornode --hot ./sitewith an HTMLmainget a file watcher whose main file is the directory/x/site, not theindex.htmlthat now runs. The reloader is enabled at src/runtime/cli/run_command.rs:1437 and :1447 with the unresolvedentry, before resolve_entry_path at :1469 replaces it. MainFile::init (hot_reloader.rs:410) then hashes the directory and its parent; the rename/replace detection at hot_reloader.rs:1068 keys on that basename, so an editor that atomically replacesindex.htmlmay not trigger a restart. Fix: enable the reloader after the entry is resolved, or re-point MainFile at the resolved path once resolution succeeds, for both the Hot and Watch arms.Extended reasoning...
Before this PR
node --watch ./sitewith an HTML main did nothing useful (the base loads index.html as a module and exits), so the mismatch had no visible consequence. After this PR the same command starts the HTML dev server, so the watcher's behaviour is now user-visible for the first time. Steps: exec_as_if_node normalizes./siteto/x/site(run_command.rs:3036). Run::start enables the WatchReloader with Some(entry) =/x/siteat :1447. MainFile::init stores file =/x/site, dir =/x/, hashes both. Only then does :1469 resolve to/x/site/index.htmland :1472 mark it HTML. The module loader adds/x/site/index.htmlto the watch list when html.ts imports it, so in-place edits do reload, but the special main-file path at hot_reloader.rs:960-986 and :1068-1093 (which handles the main file being deleted and recreated by an atomic-save editor, and the kqueue directory fallback) is bound to/x/site, whose basename never changes.bun --watch ./sitedoes not have this gap because exec_with_cfg resolves before boot and passes the HTML file. The PR only verified by hand that the…Verification: pre-existing — the mismatch the candidate describes is real, but the base branch (d62e10a, the #43409 ordering) already installs the same wrong MainFile for the same command and for every other
EntryPath::Unresolvedentry that resolves elsewhere (node --watch ./sitewith"main": "index.js",node --watch ./bin/link), where it is already user-visible; this PR does not touch the…
|
Confirmed in part. I ran it on Linux x64 with a debug build of this PR, 3 saves of
I do not change the reloader in this PR. The fix belongs with the resolve on #43409: the reloader has to get the resolved path, and the watcher thread reads |
|
Updated 12:25 PM PT - Sep 19th, 2026
✅ @robobun, your commit d56c366b9f30e01fb5a2afcc678abaac07ad6b70 passed in 🧪 To try this PR locally: bunx bun-pr 43495That installs a local version of the PR into your bun-43495 --bun |
Stacked on #43409. The base of this PR is that branch.
Problem
node(the shim thatbun runputs inPATH) prints nothing and exits 0 fornode ./sitewhensite/package.jsonhas"main": "index.html".bun ./siteandnode ./site/index.htmlstart the HTML dev server.RunCommand::boot(src/runtime/cli/run_command.rs:1111) setsvm.main_is_html_entrypointfrom the extension of the given path./x/sitehas none, soindex.htmlloads as a module and nothing runs.page.htmltoscript.jsruns the script, then starts an HTML server that never exits.Fix
Run::startsets the flag from the path that Make the entry point of thenodeshim the main module #43409 resolves, asbun <entry>does. If the resolve fails, the flag frombootstays, sonode './*.html'still works.process.argv[1]is the resolved file, as forbun ./site.src/js/internal/html.tsfinds its pages inprocess.argvby the.htmlsuffix.test/cli/run/as-node.test.ts(3 new tests, all fail on the base branch and on release 1.4.3-canary). Alsotest/js/bun/http/bun-serve-html-entry.test.ts.Background
reload_entry_pointrunsinternal/html.ts, which serves the pages withBun.serve.HTMLBundleobject. That is correct forimport page from "./index.html".SyntaxErrorhere. The shim has served a direct.htmlpath since Introducebun ./index.html#16993.Notes
Why the dev server and not an error
node ./site/index.html. Ifnode ./sitefailed, the spelling of the path would decide the result, not the file that runs.bun ./sitestarts the server.exec_with_cfgresolves first and passes the loader of the resolved path toboot..htmlpath would stay a special case.nodeshim the main module #43409 found this case and proposed the same remedy: take the flag from the resolved entry.Node v26.3.0, the base (#43409), and this PR (Linux x64, cwd
/x)node ./site("main": "index.html")SyntaxError, exit 1node ./bin/link(symlink toindex.html)SyntaxError, exit 1node ./site/index.htmlSyntaxError, exit 1node './site/*.html'MODULE_NOT_FOUND, exit 1node ./page.html(symlink toscript.js)argv[1]is/x/page.html, exit 0node ./missing.htmlMODULE_NOT_FOUND, exit 1error: Cannot find module, exit 1The released binary
1.4.3-canary.1+367d939d9(main, without #43409) gives the same results as the base column.What else I ran by hand (debug build)
node --hot ./siteandnode --watch ./sitestart the server.node --require ./pre.js ./siteandbun --preload ./pre.js ./siteprint the sameprocess.argv[1]andBun.mainfrom the preload:/x/site/index.html.Tests
test/cli/run/as-node.test.ts: an HTML file behind a directory with a package.jsonmain, an HTML file behind a symlink, and a script behind a symlink named.html.argv0: "node"and do not go throughbun --bun node. Then the process that the test stops is the HTML server, with no wrapper process between them. They do not touch the directory of thenodeshim, so they are concurrent.as-node.test.ts28 pass. With thesrc/of the base branch: the 3 new tests fail. Released binary: the 3 new tests fail.test/js/bun/http/bun-serve-html-entry.test.ts(7 pass),test/cli/run/env.test.ts -t "node shim"(4 pass),test/cli/run/run-extensionless.test.ts.Self-review
main_is_html_entrypointruns betweenbootand the new assignment. Its readers arereload_entry_pointandBun__VM__entryRootKey.process.argvis built on first access. Nothing reads it beforereload_entry_pointsetsvm.main(), sointernal/html.tsalways sees the resolved path.--hotreload passesvm.main(), which is the resolved path. Cron mode and-eseteval_source, so they skip the block.describesaid that none of its tests is concurrent. It now names the tests that spawnbun --bun.bun-serve-html-entry.test.tsare concurrent in the same way, and a hang is a failure in any case.node ./node_modules/pkgwith an HTMLmainnow fails withNo HTML files found matching ...and exit 1, asbun ./node_modules/pkgdoes.internal/html.tsskips a path undernode_modules. Before, the shim printed nothing and exited 0.--hotafter a delete (Linux x64, debug build of this PR, 3 saves ofsite/index.html)index.htmlrestartsnode --watch ./siteand reloadsnode --hot ./site, 3 of 3, as forbun.index.html,node --hot ./sitedoes not reload (0 of 3).bun --hot ./siteandnode --hot ./site/index.htmlreload 3 of 3.Run::startgives the reloader the given path, so itsMainFileis/x/siteand not the file that runs. Make the entry point of thenodeshim the main module #43409 alone has the same result fornode --hot ./pkgwith a script, wheremainreloads 5 of 5. It is reported there: Make the entry point of thenodeshim the main module #43409 (comment)nodeshim the main module #43409.Not in this PR
node ./x.cssandnode ./x.txtalso print nothing and exit 0. That is a different decision, because there is no server to start. cli: reportCannot runfor a path to a data file, for every spelling (bun ./x.css) #43480 handlesbun ./x.cssand leaves the shim as it is.