Skip to content

Commit 85da837

Browse files
refactor: address pr comments.
1 parent 3ff049a commit 85da837

3 files changed

Lines changed: 130 additions & 14 deletions

File tree

playwright/rendering-modes/core.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,65 @@ test('top-level @import in user css is applied in preview iframe', async ({ page
351351
.toBe('rgb(11, 22, 33)')
352352
})
353353

354+
test('non-first style tab keeps top-level @import valid', async ({ page }) => {
355+
await waitForInitialRender(page)
356+
357+
const importedCss = encodeURIComponent(
358+
'.imported-tab-button { color: rgb(21, 31, 41); }',
359+
)
360+
361+
await addWorkspaceTab(page, { type: 'style' })
362+
363+
await setWorkspaceTabSource(page, {
364+
fileName: 'app.css',
365+
kind: 'styles',
366+
source: ['.first-style-sentinel { color: rgb(1, 2, 3); }'].join('\n'),
367+
})
368+
369+
await setWorkspaceTabSource(page, {
370+
fileName: 'module.css',
371+
kind: 'styles',
372+
source: [
373+
`@import url("data:text/css,${importedCss}");`,
374+
'.imported-tab-button { font-weight: 700; }',
375+
].join('\n'),
376+
})
377+
378+
await setComponentEditorSource(
379+
page,
380+
[
381+
"import '../styles/app.css'",
382+
"import '../styles/module.css'",
383+
'',
384+
'const App = () => <button class="imported-tab-button">Imported from second tab</button>',
385+
'',
386+
].join('\n'),
387+
)
388+
389+
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
390+
391+
await expect
392+
.poll(async () => {
393+
return getPreviewFrame(page)
394+
.locator('html')
395+
.evaluate(() => {
396+
const userStyleElements = document.querySelectorAll(
397+
'style[id^="knighted-preview-user-styles"]',
398+
)
399+
return userStyleElements.length
400+
})
401+
})
402+
.toBe(2)
403+
404+
await expect
405+
.poll(async () => {
406+
return getPreviewFrame(page)
407+
.getByRole('button', { name: 'Imported from second tab' })
408+
.evaluate(element => getComputedStyle(element).color)
409+
})
410+
.toBe('rgb(21, 31, 41)')
411+
})
412+
354413
test('preview iframe keeps one base and one user style node across rerenders', async ({
355414
page,
356415
}) => {

src/modules/preview-runtime/iframe-preview-executor.js

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const createIframeShellDocument = ({ channelId, parentOrigin, importMap }) => {
5858
renderedNodes: [],
5959
visualConfig: {
6060
cssText: '',
61+
userStyleSheets: [],
6162
hostPadding: '',
6263
backgroundColor: '',
6364
},
@@ -114,9 +115,26 @@ const createIframeShellDocument = ({ channelId, parentOrigin, importMap }) => {
114115
].join('\\n')
115116
}
116117
117-
const __knightedApplyVisualConfig = ({ cssText = '', hostPadding = '', backgroundColor = '' }) => {
118+
const __knightedApplyVisualConfig = ({
119+
cssText = '',
120+
userStyleSheets = [],
121+
hostPadding = '',
122+
backgroundColor = '',
123+
}) => {
124+
const normalizedUserStyleSheets = Array.isArray(userStyleSheets)
125+
? userStyleSheets
126+
.filter(styleText => typeof styleText === 'string')
127+
.map(styleText => String(styleText))
128+
: []
129+
const fallbackUserStyleText = typeof cssText === 'string' ? cssText : ''
130+
const desiredUserStyleSheets =
131+
normalizedUserStyleSheets.length > 0
132+
? normalizedUserStyleSheets
133+
: [fallbackUserStyleText]
134+
118135
__knightedState.visualConfig = {
119-
cssText: typeof cssText === 'string' ? cssText : '',
136+
cssText: fallbackUserStyleText,
137+
userStyleSheets: desiredUserStyleSheets,
120138
hostPadding: typeof hostPadding === 'string' ? hostPadding : '',
121139
backgroundColor: typeof backgroundColor === 'string' ? backgroundColor : '',
122140
}
@@ -128,25 +146,55 @@ const createIframeShellDocument = ({ channelId, parentOrigin, importMap }) => {
128146
document.head.append(baseStyleElement)
129147
}
130148
131-
let userStyleElement = document.getElementById('knighted-preview-user-styles')
132-
if (!(userStyleElement instanceof HTMLStyleElement)) {
133-
userStyleElement = document.createElement('style')
134-
userStyleElement.id = 'knighted-preview-user-styles'
135-
document.head.append(userStyleElement)
149+
const desiredUserStyleElementIds = __knightedState.visualConfig.userStyleSheets.map(
150+
(_styleText, index) =>
151+
index === 0
152+
? 'knighted-preview-user-styles'
153+
: 'knighted-preview-user-styles-' + index,
154+
)
155+
156+
const desiredUserStyleElementIdSet = new Set(desiredUserStyleElementIds)
157+
const existingUserStyleElements = Array.from(
158+
document.head.querySelectorAll('style[id^="knighted-preview-user-styles"]'),
159+
)
160+
for (const existingUserStyleElement of existingUserStyleElements) {
161+
if (!desiredUserStyleElementIdSet.has(existingUserStyleElement.id)) {
162+
existingUserStyleElement.remove()
163+
}
136164
}
137165
166+
const userStyleElements = desiredUserStyleElementIds.map(styleElementId => {
167+
let userStyleElement = document.getElementById(styleElementId)
168+
if (!(userStyleElement instanceof HTMLStyleElement)) {
169+
userStyleElement = document.createElement('style')
170+
userStyleElement.id = styleElementId
171+
document.head.append(userStyleElement)
172+
}
173+
174+
return userStyleElement
175+
})
176+
177+
const firstUserStyleElement = userStyleElements[0]
138178
const isBaseAfterUser =
139-
(baseStyleElement.compareDocumentPosition(userStyleElement) &
179+
firstUserStyleElement instanceof HTMLStyleElement &&
180+
(baseStyleElement.compareDocumentPosition(firstUserStyleElement) &
140181
Node.DOCUMENT_POSITION_PRECEDING) !==
141182
0
142-
if (isBaseAfterUser) {
143-
document.head.insertBefore(baseStyleElement, userStyleElement)
183+
if (isBaseAfterUser && firstUserStyleElement instanceof HTMLStyleElement) {
184+
document.head.insertBefore(baseStyleElement, firstUserStyleElement)
144185
}
145186
146187
baseStyleElement.textContent = __knightedToBaseStyles(
147188
__knightedState.visualConfig.hostPadding,
148189
)
149-
userStyleElement.textContent = String(__knightedState.visualConfig.cssText)
190+
191+
for (let index = 0; index < userStyleElements.length; index += 1) {
192+
const userStyleElement = userStyleElements[index]
193+
userStyleElement.textContent = String(
194+
__knightedState.visualConfig.userStyleSheets[index] ?? '',
195+
)
196+
document.head.append(userStyleElement)
197+
}
150198
151199
if (__knightedState.visualConfig.hostPadding.trim().length > 0) {
152200
document.documentElement.style.setProperty(
@@ -574,6 +622,7 @@ export const createWorkspaceIframePreviewBridge = ({
574622
entryExportName,
575623
importMap,
576624
cssText,
625+
userStyleSheets = [],
577626
hostPadding = '',
578627
backgroundColor = '',
579628
runtimeSpecifiers,
@@ -616,6 +665,7 @@ export const createWorkspaceIframePreviewBridge = ({
616665
entryExportName,
617666
runtimeSpecifiers,
618667
cssText,
668+
userStyleSheets,
619669
hostPadding,
620670
backgroundColor,
621671
importMap,

src/modules/preview/render-runtime.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ export const createRenderRuntimeController = ({
411411

412412
if (!entryTab) {
413413
clearStyleDiagnostics()
414-
return { css: '', styleModuleExportsByTabId: {} }
414+
return { css: '', userStyleSheets: [], styleModuleExportsByTabId: {} }
415415
}
416416

417417
const runtimeSpecifiers = getWorkspaceRuntimeSpecifiers()
@@ -427,7 +427,7 @@ export const createRenderRuntimeController = ({
427427

428428
if (!virtualModulePlan) {
429429
clearStyleDiagnostics()
430-
return { css: '', styleModuleExportsByTabId: {} }
430+
return { css: '', userStyleSheets: [], styleModuleExportsByTabId: {} }
431431
}
432432

433433
const workspaceTabById = new Map(
@@ -481,7 +481,7 @@ export const createRenderRuntimeController = ({
481481

482482
if (styleInputs.length === 0) {
483483
clearStyleDiagnostics()
484-
const output = { css: '', styleModuleExportsByTabId: {} }
484+
const output = { css: '', userStyleSheets: [], styleModuleExportsByTabId: {} }
485485
compiledStylesCache = {
486486
key: cacheKey,
487487
value: output,
@@ -560,13 +560,15 @@ export const createRenderRuntimeController = ({
560560

561561
const styleModuleExportsByTabId = {}
562562
const compiledCssParts = []
563+
const userStyleSheets = []
563564

564565
for (let index = 0; index < styleInputs.length; index += 1) {
565566
const input = styleInputs[index]
566567
const part = compiledStyleParts[index]
567568

568569
if (part && typeof part.css === 'string') {
569570
compiledCssParts.push(part.css)
571+
userStyleSheets.push(part.css)
570572
}
571573

572574
if (input?.dialect !== 'module' || !part?.moduleExports) {
@@ -594,6 +596,7 @@ export const createRenderRuntimeController = ({
594596

595597
const output = {
596598
css: compiledCssParts.join('\n\n'),
599+
userStyleSheets,
597600
styleModuleExportsByTabId,
598601
}
599602
if (styleWarningLines.length > 0) {
@@ -708,6 +711,7 @@ export const createRenderRuntimeController = ({
708711
const renderWorkspaceInIframe = async ({
709712
mode,
710713
cssText,
714+
userStyleSheets = [],
711715
styleModuleExportsByTabId = {},
712716
}) => {
713717
const workspaceTabs = getWorkspaceTabsForPreview()
@@ -784,6 +788,7 @@ export const createRenderRuntimeController = ({
784788
entryExportName: virtualModulePlan.entryExportName,
785789
importMap: virtualModulePlan.importMap,
786790
cssText,
791+
userStyleSheets,
787792
hostPadding,
788793
backgroundColor: getPreviewBackgroundColor(),
789794
runtimeSpecifiers,
@@ -816,6 +821,7 @@ export const createRenderRuntimeController = ({
816821
await renderWorkspaceInIframe({
817822
mode: 'dom',
818823
cssText: compiledStyles.css,
824+
userStyleSheets: compiledStyles.userStyleSheets,
819825
styleModuleExportsByTabId: compiledStyles.styleModuleExportsByTabId,
820826
})
821827
}
@@ -834,6 +840,7 @@ export const createRenderRuntimeController = ({
834840
await renderWorkspaceInIframe({
835841
mode: 'react',
836842
cssText: compiledStyles.css,
843+
userStyleSheets: compiledStyles.userStyleSheets,
837844
styleModuleExportsByTabId: compiledStyles.styleModuleExportsByTabId,
838845
})
839846
}

0 commit comments

Comments
 (0)