Repository navigation
feat: precompress the client build for the start.nodeServer entry - #526
Merged
Merged
Conversation
🦋 Changeset detectedLatest commit: 00354b7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
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.
The
dist/server/node.jsthatstart.nodeServeremits sends every client file uncompressed. In the e18e framework-tracker benchmark (e18e/framework-tracker#709), the Solid 2.0 app's/initial-loadpage transfers 110,431 B of JS and CSS, while the Nitro-based apps send the same kind of bundle compressed because they setcompressPublicAssets.sendFilein the Node entry streamed the file from disk and never looked atAccept-Encoding. Nothing in the build wrote compressed copies either, so there was nothing to serve.What changed
nodeServeris set, the client build'swriteBundlewrites a.br(quality 11) and a.gz(level 9) next to each text file of 1 KiB or more: JS, CSS, HTML, JSON, SVG, XML, wasm, ico and fonts other than woff/woff2. A variant is kept only when it is smaller than the file. Dot directories such as.vite/are skipped. Files copied frompublic/are included, because Vite copies them beforewriteBundle. In client start mode, the prerenderedindex.htmlis compressed after it is written. Source maps are skipped, because brotli at quality 11 runs at about 0.9 MB/s and only devtools fetch maps.Accept-Encodingwith q-values,q=0and*, and picks br, then gzip. A coding ranked below an explicitidentityweight is not used. If the preferred variant is missing, it falls back to the next accepted one. The response getsContent-Encoding, the variant'sContent-Length, andVary: Accept-Encoding.Varyis set on every compressible type, including uncompressed responses, so a shared cache does not hand an uncompressed copy to a client that accepts br. HEAD returns the same headers with no body.node.jsis meant to be the server that serves these files.nodeServer: { precompress: false }turns it off for setups where a proxy or CDN compresses. With it off, the generated config setsprecompressed: falseand the entry ignores any variants left from an earlier build. The newNodeServerOptionstype is exported.This is the same approach as Nitro's
compressPublicAssets: compress once at build time, then pick a file per request. It adds no per-request CPU cost and no dependency to the emitted entry, which still imports onlynode:*and./server.js.Verification
pnpm test:unit: 123 tests pass. The newsrc/node-entry-compression.test.tsbuilds the real entry with rolldown next to a stubserver.jsand sends raw HTTP requests to cover br, gzip, identity (no header,identity, andq=0on both codings), HEAD, a missing br variant falling back to gzip, files under the size floor, PNG, handler responses, andprecompressed: false. It also checks theAccept-Encodingparser and the build step: variants decompress to the original, small files and binary types are skipped, stale variants are removed, and.vite/is skipped.CHROME_BIN=… node examples/start-ssr/test/run.mjs node: 93/93 pass. New checks: variants are written but not inside.vite, an asset is served as br withVaryand the variant's length, the rendered page is not compressed, andprecompress: falsewrites no variants and bakes inprecompressed: false.node examples/start-client/test/run.mjs node: 21/21 pass.app-solid, built in a scratch copy against this branch (start.noderenamed tostart.nodeServer), with headless Chromium loading/initial-load:.gzfiles)Build time for that app did not change (about 1.0 s).
Open questions
/assets/x.js.britself are served asapplication/octet-stream, the same as any other unknown file. Nitro behaves the same way.build.emptyOutDir: false, files left by earlier builds are compressed again on every build.🤖 Generated with Claude Code