fix: bind :thisSlide per style block instead of per compile pass
Every slide's :thisSlide rules leaked into every other slide: the whole joined source was recompiled once per slide with all occurrences rebound, so each pass attached every slide's scoped rules to that pass's slide and the concatenated output applied them all to everyone. Bind the token while each block still belongs to one slide (global block -> any slide) and compile the project once - also drops the O(n) duplicated CSS output.
This commit is contained in:
parent
97ae327415
commit
a05c860c6e
1 changed files with 30 additions and 39 deletions
|
|
@ -109,6 +109,9 @@ function slideSelector(id: string): string {
|
|||
return `[data-ps-slide="${id}"]`
|
||||
}
|
||||
|
||||
/** What `:thisSlide` means inside the global slide's styles: any slide. */
|
||||
const ANY_SLIDE_SELECTOR = '[data-ps-slide]'
|
||||
|
||||
const THIS_SLIDE = ':thisSlide'
|
||||
|
||||
// `<?= … ?>` is a bogus comment to the HTML parser, so a placeholder would not
|
||||
|
|
@ -206,52 +209,40 @@ function isGlobalTemplate(global: string): boolean {
|
|||
* Compile a project into runtime-ready data.
|
||||
*
|
||||
* Every `<style>` tag in the deck contributes to one project stylesheet (the
|
||||
* global slide first, then each slide in order). That stylesheet is compiled
|
||||
* once per slide, with `:thisSlide` replaced by the selector of the slide being
|
||||
* compiled — so Less keeps behaving like Less, and the output never depends on
|
||||
* which slide happens to be visible.
|
||||
* global slide first, then each slide in order), and `:thisSlide` is rewritten
|
||||
* per style block *before* the blocks are joined: the global block to any
|
||||
* slide, each slide's block to that slide's own section. The joined source
|
||||
* then compiles exactly once — so Less keeps behaving like Less, the output
|
||||
* never depends on which slide is visible, and one slide's scoped rules can
|
||||
* never bind to its neighbours.
|
||||
*/
|
||||
export async function compilePresentation(input: CompileInput): Promise<CompiledPresentation> {
|
||||
const global = extractStyles(input.global ?? '')
|
||||
const globalIsTemplate = isGlobalTemplate(global.html)
|
||||
const sources: string[] = [global.style]
|
||||
const extracted = input.slides.map((markup) => extractStyles(markup))
|
||||
|
||||
const slides: CompiledSlide[] = input.slides.map((markup, index) => {
|
||||
const extracted = extractStyles(markup)
|
||||
sources.push(extracted.style)
|
||||
return {
|
||||
id: slideId(index),
|
||||
index,
|
||||
html: globalIsTemplate ? extracted.html : buildSlideHtml(extracted.html, global.html)
|
||||
}
|
||||
})
|
||||
const slides: CompiledSlide[] = extracted.map((slide, index) => ({
|
||||
id: slideId(index),
|
||||
index,
|
||||
html: globalIsTemplate ? slide.html : buildSlideHtml(slide.html, global.html)
|
||||
}))
|
||||
|
||||
const css = await compileStyles(
|
||||
sources.join('\n'),
|
||||
slides.map((slide) => slide.id)
|
||||
)
|
||||
// Bind `:thisSlide` while each block still belongs to exactly one slide:
|
||||
// the global block means "whoever is showing", a slide block means that
|
||||
// slide. Rebinding after the join (the old per-slide passes) rebound every
|
||||
// block on every pass, so each slide's scoped rules matched all slides.
|
||||
const sources = [
|
||||
bindThisSlide(global.style, ANY_SLIDE_SELECTOR),
|
||||
...extracted.map((slide, index) =>
|
||||
bindThisSlide(slide.style, slideSelector(slideId(index)))
|
||||
)
|
||||
]
|
||||
const source = sources.join('\n')
|
||||
const css = source.trim() ? await renderLess(source) : ''
|
||||
return { slides, css, template: global.html }
|
||||
}
|
||||
|
||||
async function compileStyles(source: string, ids: string[]): Promise<string> {
|
||||
if (!source.trim()) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// Fast path: without `:thisSlide` every slide resolves to the same CSS, so
|
||||
// compile once instead of once per slide.
|
||||
if (!source.includes(THIS_SLIDE)) {
|
||||
return renderLess(source)
|
||||
}
|
||||
|
||||
const perSlide = await Promise.all(
|
||||
ids.map(async (id) => {
|
||||
const selector = slideSelector(id)
|
||||
const scoped = source.split(THIS_SLIDE).join(selector)
|
||||
const css = await renderLess(scoped)
|
||||
return `/* ${selector} */\n${css}`
|
||||
})
|
||||
)
|
||||
|
||||
return perSlide.join('\n')
|
||||
/** Rewrite the `:thisSlide` authoring token to a concrete selector. */
|
||||
function bindThisSlide(source: string, selector: string): string {
|
||||
return source.split(THIS_SLIDE).join(selector)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue