Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: extract hmr chunk from chunk.modules #6

Open
wants to merge 11 commits into
base: feat-rolldown-dev
Choose a base branch
from
Prev Previous commit
Next Next commit
wip: asset url hmr
hi-ogawa committed Dec 3, 2024

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
commit 8773916797f8c0f53f3d7eef0542f1c8ffbb9121
5 changes: 4 additions & 1 deletion packages/vite/src/node/plugins/asset.ts
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@ const assetCache = new WeakMap<Environment, Map<string, string>>()
export interface GeneratedAssetMeta {
originalFileName: string | undefined
isEntry?: boolean
content?: Buffer
}
export const generatedAssetsMap = new WeakMap<
Environment,
@@ -421,7 +422,9 @@ async function fileToBuiltUrl(
originalFileName,
source: content,
})
generatedAssetsMap.get(environment)!.set(referenceId, { originalFileName })
generatedAssetsMap
.get(environment)!
.set(referenceId, { originalFileName, content })

url = `__VITE_ASSET__${referenceId}__${postfix ? `$_${postfix}__` : ``}`
}
31 changes: 31 additions & 0 deletions packages/vite/src/node/server/environments/rolldown.ts
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ import type {
import { CLIENT_ENTRY, VITE_PACKAGE_DIR } from '../../constants'
import { injectEnvironmentToHooks } from '../../build'
import { cleanUrl } from '../../../shared/utils'
import { generatedAssetsMap } from '../../plugins/asset'

const require = createRequire(import.meta.url)

@@ -142,6 +143,7 @@ class RolldownEnvironment extends DevEnvironment {
outputOptions!: rolldown.OutputOptions
lastModules: Record<string, string | null> = {}
newModules: Record<string, string | null> = {}
lastAssets: Record<string, string> = {}
fileModuleIds = new Set<string>()
buildPromise?: Promise<void>

@@ -232,6 +234,10 @@ class RolldownEnvironment extends DevEnvironment {
moduleTypes: {
'.css': 'js',
},
// TODO: isolating finalizer doesn't rewrite yet
// experimental: {
// resolveNewUrlToAsset: true,
// },
}
this.instance = await rolldown.rolldown(this.inputOptions)

@@ -254,6 +260,23 @@ class RolldownEnvironment extends DevEnvironment {
// `generate` should work but we use `write` so it's easier to see output and debug
this.result = await this.instance.write(this.outputOptions)

// find changed assets
const changedAssets: string[] = []
for (const [id, { content }] of generatedAssetsMap.get(this) ?? []) {
if (content) {
const data = content.toString('utf8')
if (this.lastAssets[id] !== data) {
changedAssets.push(id)
}
this.lastAssets[id] = data
}
}
// detect change of content of assert url placeholder __VITE_ASSET__xxx
const changedAssetsRegex = new RegExp(
// eslint-disable-next-line
`__VITE_ASSET__(${changedAssets.join('|')})__`,
)

// extract hmr chunk
// cf. https://github.com/web-infra-dev/rspack/blob/5a967f7a10ec51171a304a1ce8d741bd09fa8ed5/crates/rspack_plugin_hmr/src/lib.rs#L60
const chunk = this.result.output[0]
@@ -262,6 +285,12 @@ class RolldownEnvironment extends DevEnvironment {
for (const [id, mod] of Object.entries(chunk.modules)) {
const current = mod.code
const last = this.lastModules?.[id]
if (current?.match(changedAssetsRegex)) {
// TODO:
// need to replace __VITE_ASSET__xxx
// we should property run `renderChunk` to hmr chunk too
this.newModules[id] = current
}
if (current !== last) {
this.newModules[id] = current
}
@@ -426,6 +455,8 @@ function patchRuntimePlugin(environment: RolldownEnvironment): rolldown.Plugin {
},
},
renderChunk(code, chunk) {
// TODO: this magic string is heavy

// silly but we can do `render_app` on our own for now
// https://github.com/rolldown/rolldown/blob/a29240168290e45b36fdc1a6d5c375281fb8dc3e/crates/rolldown/src/ecmascript/format/app.rs#L28-L55
const output = new MagicString(code)
4 changes: 4 additions & 0 deletions playground/rolldown-dev-react/src/app.tsx
Original file line number Diff line number Diff line change
@@ -9,6 +9,10 @@ import testStyleInline from './test-style-inline.css?inline'
// TODO: hmr for url assets?
import testStyleUrl from './test-style-url.css?url'

// TODO: isolating finalizer doesn't rewrite yet
// const testAssetTxt = new URL('./test-asset.txt', import.meta.url).href;
// console.log(testAssetTxt);

export function App() {
const [count, setCount] = React.useState(0)

1 change: 1 addition & 0 deletions playground/rolldown-dev-react/src/test-asset.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello

Unchanged files with check annotations Beta

expect(
chunk.code,
`the ${chunk.fileName} chunk has the same hash but different contents between builds`,
).toEqual(chunk2.code)

Check failure on line 748 in packages/vite/src/node/__tests__/build.spec.ts

GitHub Actions / Build&Test: node-22, ubuntu-latest

packages/vite/src/node/__tests__/build.spec.ts > build > file hash should change when pure css chunk changes

AssertionError: the assets/index-DhXWseme.js chunk has the same hash but different contents between builds: expected 'const __vite__mapDeps=(i,m=__vite__ma…' to deeply equal 'const __vite__mapDeps=(i,m=__vite__ma…' - Expected + Received - const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_foo-DE7z1b4O.js","assets/_foo-3otn9bEy.css","assets/_baz-XbyN5Pu5.css","assets/_baz-8tIYWJw7.css","assets/_bar-B3ISlUG8.js","assets/_bar-1BxK95df.css"])))=>i.map(i=>d[i]); + const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/_foo-DAiqd_VM.js","assets/_foo-3otn9bEy.css","assets/_baz-XbyN5Pu5.css","assets/_baz-8tIYWJw7.css","assets/_bar-DeM4UPAy.js","assets/_bar-1BxK95df.css"])))=>i.map(i=>d[i]); - (function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))l(e);new MutationObserver(e=>{for(const t of e)if(t.type==="childList")for(const n of t.addedNodes)n.tagName==="LINK"&&n.rel==="modulepreload"&&l(n)}).observe(document,{childList:!0,subtree:!0});function c(e){const t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?t.credentials="include":e.crossOrigin==="anonymous"?t.credentials="omit":t.credentials="same-origin",t}function l(e){if(e.ep)return;e.ep=!0;const t=c(e);fetch(e.href,t)}})();const v="modulepreload",E=function(f){return"/"+f},h={},p=function(s,c,l){let e=Promise.resolve();if(c&&c.length>0){const n=document.getElementsByTagName("link"),r=document.querySelector("meta[property=csp-nonce]"),m=(r==null?void 0:r.nonce)||(r==null?void 0:r.getAttribute("nonce"));e=Promise.allSettled(c.map(o=>{if(o=E(o,l),o in h)return;h[o]=!0;const u=o.endsWith(".css"),y=u?'[rel="stylesheet"]':"";if(!!l)for(let a=n.length-1;a>=0;a--){const d=n[a];if(d.href===o&&(!u||d.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${o}"]${y}`))return;const i=document.createElement("link");if(i.rel=u?"stylesheet":v,u||(i.as="script"),i.crossOrigin="",i.href=o,m&&i.setAttribute("nonce",m),document.head.appendChild(i),u)return new Promise((a,d)=>{i.addEventListener("load",a),i.addEventListener("error",()=>d(new Error(`Unable to preload CSS for ${o}`)))})}))}function t(n){const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=n,window.dispatchEvent(r),!r.defaultPrevented)throw n}return e.then(n=>{for(const r of n||[])r.status==="rejected"&&t(r.reason);return s().catch(t)})};window.addEventListener("click",()=>{p(()=>import("./_foo-DE7z1b4O.js"),__vite__mapDeps([0,1,2,3]))});window.addEventListener("click",()=>{p(()=>import("./_bar-B3ISlUG8.js"),__vite__mapDeps([4,5,2,3]))}); + (function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))l(e);new MutationObserver(e=>{for(const t of e)if(t.type==="childList")for(const n of t.addedNodes)n.tagName==="LINK"&&n.rel==="modulepreload"&&l(n)}).observe(document,{childList:!0,subtree:!0});function c(e){const t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?t.credentials="include":e.crossOrigin==="anonymous"?t.credentials="omit":t.credentials="same-origin",t}function l(e){if(e.ep)return;e.ep=!0;const t=c(e);fetch(e.href,t)}})();const v="modulepreload",E=function(f){return"/"+f},h={},p=function(s,c,l){let e=Promise.resolve();if(c&&c.length>0){const n=document.getElementsByTagName("link"),r=document.querySelector("meta[property=csp-nonce]"),m=(r==null?void 0:r.nonce)||(r==null?void 0:r.getAttribute("nonce"));e=Promise.allSettled(c.map(o=>{if(o=E(o,l),o in h)return;h[o]=!0;const u=o.endsWith(".css"),y=u?'[rel="stylesheet"]':"";if(!!l)for(let a=n.length-1;a>=0;a--){const d=n[a];if(d.href===o&&(!u||d.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${o}"]${y}`))return;const i=document.createElement("link");if(i.rel=u?"stylesheet":v,u||(i.
}
}
}