Merge pull request #5534 from deepseek-harness/turtle/plugin-toggle-css-c341cac49368
fix(client-modules): keep other owners' styles when a plugin toggles
This commit is contained in:
commit
95a7a0cae3
2 files changed
+43
-5
No files matched your search
@@ -63,15 +63,20 @@ function chunkUrl(row: BootModuleRow, fileName: string, rev: string): string {
|
||||
return `${url.slice(0, resourceStart)}/${row.id}/${fileName}?${url.slice(revisionStart + 1)}`
|
||||
}
|
||||
|
||||
/** Untagged <style> elements present before a factory runs; the factory must not claim them. */
|
||||
const untaggedStyles = (): ReadonlySet<Element> =>
|
||||
typeof document === 'undefined' ? new Set() : new Set(document.querySelectorAll('style:not([data-plugin])'))
|
||||
|
||||
/**
|
||||
* Claim and inventory the <style> tags a factory injected during
|
||||
* materialization: preset-emitted tags arrive pre-tagged with data-plugin;
|
||||
* any untagged tag is claimed for the materializing plugin (HMR bookkeeping).
|
||||
* an untagged tag that was absent from `before` is claimed for the
|
||||
* materializing plugin (HMR bookkeeping).
|
||||
*/
|
||||
const claimStyles = (id: string): string[] => {
|
||||
const claimStyles = (id: string, before: ReadonlySet<Element>): string[] => {
|
||||
if (typeof document === 'undefined') return []
|
||||
for (const el of document.querySelectorAll('style:not([data-plugin])')) {
|
||||
el.setAttribute('data-plugin', id)
|
||||
for (const el of untaggedStyles()) {
|
||||
if (!before.has(el)) el.setAttribute('data-plugin', id)
|
||||
}
|
||||
const owned: string[] = []
|
||||
for (const el of document.querySelectorAll(`style[data-plugin=${JSON.stringify(id)}]`)) {
|
||||
@@ -300,13 +305,15 @@ export class ClientModuleSystem implements ClientModuleLoader {
|
||||
throw new Error(`client-modules: require cycle through "${id}" (factory-form CJS cannot deliver partial exports)`)
|
||||
}
|
||||
this.materializing.add(id)
|
||||
const before = untaggedStyles()
|
||||
try {
|
||||
const edges = new Set<string>()
|
||||
const exports = registered.factory(this.makeRequire(ownerId, edges))
|
||||
const record: ClientModuleRecord = { id, exports, styles: claimStyles(ownerId), edges }
|
||||
const record: ClientModuleRecord = { id, exports, styles: claimStyles(ownerId, before), edges }
|
||||
this.loadCache.set(id, record)
|
||||
return record
|
||||
} catch (error) {
|
||||
claimStyles(ownerId, before)
|
||||
removeOwnedStyles(ownerId)
|
||||
throw error
|
||||
} finally {
|
||||
|
||||
@@ -832,6 +832,37 @@ describe('style claiming', () => {
|
||||
expect(foreign.getAttribute('data-plugin')).toBe('other')
|
||||
})
|
||||
|
||||
it('leaves untagged style tags inserted before materialization unclaimed', async () => {
|
||||
const earlier = document.createElement('style')
|
||||
document.head.appendChild(earlier)
|
||||
const b = bench([row('a')], { a: () => ({}) })
|
||||
await b.loader.import('a', '', {})
|
||||
removeOwnedStyles('a')
|
||||
expect(earlier.isConnected).toBe(true)
|
||||
expect(earlier.hasAttribute('data-plugin')).toBe(false)
|
||||
})
|
||||
|
||||
it('assigns an untagged style to the factory that inserted it before a nested require', async () => {
|
||||
const outer = document.createElement('style')
|
||||
const b = bench([row('b'), row('a', { external: ['b'] })], {
|
||||
a: (require) => {
|
||||
document.head.appendChild(outer)
|
||||
require('b')
|
||||
return {}
|
||||
},
|
||||
b: () => ({}),
|
||||
})
|
||||
await b.loader.import('a', '', {})
|
||||
expect(outer.getAttribute('data-plugin')).toBe('a')
|
||||
})
|
||||
|
||||
it('removes untagged styles inserted by a factory that throws', async () => {
|
||||
const leaked = document.createElement('style')
|
||||
const b = bench([row('a')], { a: () => { document.head.appendChild(leaked); throw new Error('factory exploded') } })
|
||||
await expect(b.loader.import('a', '', {})).rejects.toThrow('factory exploded')
|
||||
expect(leaked.isConnected).toBe(false)
|
||||
})
|
||||
|
||||
it('materialization without a document skips the style inventory', async () => {
|
||||
const b = bench([row('a')], { a: () => ({}) })
|
||||
vi.stubGlobal('document', undefined)
|
||||
|
||||
Reference in new issue
Block a user