Repository navigation
fix(vite-dev-server): answer missing files within the preview base path with 404 - #69
Merged
Merged
Conversation
…th with 404 The Service Worker passed its Hono app to handle() from hono/service-worker without options. With the default options, the adapter fetches a 404 response again from the network. So a request within the preview base path that the virtual project could not serve reached the host server, and the host's response was returned to the preview. With @vrowzer/vite-plugin on the host, such requests got its guard page: a 503 "Waiting for Service Worker..." page that reloads itself, although the Service Worker controlled the page. When the preview HTML itself was missing, the preview reloaded every second and reported a load error each time. handle() now gets options without fetch, so the app's 404 is returned as it is, as Vite does. Requests outside the base path are still left to the browser.
A missing file within the preview base path that the SPA fallback does not answer (a GET with a narrow Accept header, a POST and /favicon.ico) gets a 404 with an empty body from the Service Worker. A new preview server plugin records the requests within the base path that reach the host. It runs before the guard of @vrowzer/vite-plugin, and the test checks that none of these requests reach the host. A preview session whose HTML request ends in a 404 reports the load error once, with status 404, and reports nothing more after 2.5 seconds. The session loads /favicon.ico as its HTML, because the SPA fallback answers other missing paths with index.html, and removing index.html would break the other sessions. Both tests fail without the previous commit: the host's guard answers with 503, and the preview reloads and reports the error every second.
The README section on the Service Worker scope says that requests within basePath which the virtual project cannot serve get a 404 response from the Service Worker, and are not sent to the host server.
This was referenced Oct 1, 2026
kazupon
added a commit
that referenced
this pull request
Oct 1, 2026
…s none (#71) * fix(vrowzer): give both Workers a default index.html when ready() gets none The Web Worker wrote a default /index.html during setup, but the Service Worker, which serves the preview HTML, never got one. A ready() without /index.html, as in the README usage example, therefore left the preview without HTML: since #69 it reported one 404 load error and stayed blank. initialize() now adds a default /index.html to the files it copies from ready() when they have none, and sends it to both Workers. The default is minimal: an empty #app element and a module script that loads /main.js. An /index.html given to ready() is used as before. * refactor(vite-dev-server): leave the default index.html to the runtime setupVirtualFiles() wrote a default /index.html in the Web Worker. The Service Worker serves the preview HTML and never had this default, so it was never served. The vrowzer runtime now sends the same default /index.html to both Workers when ready() gets none, so the Web Worker no longer writes its own. /public/.gitkeep is still written. * test(vrowzer): cover the README usage example in the browser Opened with ?files=readme-example, the playground gives ready() only the /main.js of the README usage example. A new test opens it in a new browser context, which has a Service Worker of its own, and checks that the preview shows "Hello!", as the example expects. Without the default /index.html, the preview stays blank and the test times out. * docs(vrowzer): describe the default index.html The README section of ready() and the JSDoc of VrowzerConfig.files say that, without /index.html, Vrowzer uses a default one: an empty #app element and a module script that loads /main.js. The API references are regenerated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #68
A request within the preview base path that the virtual project cannot serve now gets a 404 from the Service Worker, as with Vite, instead of the host server's response.
Cause
The Service Worker passed its Hono app to
handle()fromhono/service-workerwithout options. With the default options, the adapter fetches the request again from the network whenever the app answers with 404, and returns the host's response.Most ordinary loads were not affected: for a
GETorHEADrequest whoseAcceptheader hastext/htmlor*/*, the SPA fallback answers a missing file with/index.html. The other requests reached the host:POST;/favicon.ico;Acceptheaders;/index.htmlitself was missing.With
@vrowzer/vite-pluginon the host, these requests got its guard page: a 503 "Waiting for Service Worker..." page that reloads itself after one second, although the Service Worker did control the page. When the preview HTML itself was missing, the bootstrap rendered that page, so the preview reloaded every second and reported apreviewLoadErroreach time.Fix
handle()now gets options withoutfetch, so the adapter returns the app's 404 as it is. Requests outside the base path are still left to the browser. The README states that requests withinbasePaththat the virtual project cannot serve get a 404 from the Service Worker.The fallback does not look intended:
handle(middlewares)has been called without options since the Service Worker module was first split out, with no recorded reason.basePath.@vrowzer/vite-plugin, which the integration tests and the e2e fixtures use, the fallback could only ever return the guard's 503.Behavior change
A request within the preview base path that the virtual project cannot serve gets a 404 instead of the host's response. With
@vrowzer/vite-pluginon the host, that response was always the guard's 503, so no working setup depends on it. A host without the plugin that served files under the preview base path would now get 404s for them. The README never described that use.Not included
/index.htmlin the Service Worker: the Web Worker writes a default/index.html, but the Service Worker does not, so aready()without/index.htmlhas no preview HTML. With this change, the preview reports one 404 load error instead of reloading. Whether to provide a default is a separate question.fetch()rejects: the error handler catches middleware errors, and fix(vite-dev-server): use Vite's error handler for Service Worker requests #58 removed the case where it threw.Tests
Integration tests in the shared playground:
GETwithAccept: application/json, aPOST, and/favicon.ico.enforce: 'pre')./favicon.icoas its HTML: the SPA fallback answers other missing paths withindex.html, and removingindex.htmlwould break the other sessions.Before the change, both tests failed. The three requests got the guard's 503 and were recorded at the host, and the preview reported a 503 three times within 2.5 seconds. Reverting the fix makes both tests fail again.
Validated locally with:
vp run test:unit: 119 files, 1,914 testsvp run test:integration:vrowzer: 69 passed and 1 conditional skip, in three runsvp run test:e2e: 46 passed, 6 existingtest.skip