Skip to content

Commit 0cc37a4

Browse files
fix: support @knighted/jsx type-only imports in DOM mode. (#39)
1 parent 979c9cf commit 0cc37a4

4 files changed

Lines changed: 98 additions & 4 deletions

File tree

playwright/diagnostics.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,45 @@ test('typecheck error reports diagnostics count in button and details in drawer'
208208
await expect(page.getByText(/TS\d+/)).toBeVisible()
209209
})
210210

211+
test('dom mode typecheck resolves @knighted/jsx type-only imports', async ({ page }) => {
212+
await waitForInitialRender(page)
213+
214+
await ensurePanelToolsVisible(page, 'component')
215+
216+
await page.getByRole('combobox', { name: 'Render mode' }).selectOption('dom')
217+
await setComponentEditorSource(
218+
page,
219+
[
220+
"import type { JsxChildren } from '@knighted/jsx'",
221+
'',
222+
'type DrawerProps = {',
223+
' children?: JsxChildren',
224+
'}',
225+
'',
226+
'const Drawer = ({ children }: DrawerProps) => {',
227+
' return <div className="drawer">{children}</div>',
228+
'}',
229+
'',
230+
'const App = () => {',
231+
' return (',
232+
' <Drawer>',
233+
' <p>drawer</p>',
234+
' </Drawer>',
235+
' )',
236+
'}',
237+
].join('\n'),
238+
)
239+
240+
await runTypecheck(page)
241+
await ensureDiagnosticsDrawerOpen(page)
242+
await expect(page.locator('#diagnostics-component')).toContainText(
243+
'No TypeScript errors found.',
244+
)
245+
246+
const diagnosticsText = await page.locator('#diagnostics-component').innerText()
247+
expect(diagnosticsText).not.toContain("Cannot find module '@knighted/jsx'")
248+
})
249+
211250
test('component diagnostics rows navigate editor to reported line', async ({ page }) => {
212251
await waitForInitialRender(page)
213252

playwright/rendering-modes.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,41 @@ test('transpiles TypeScript annotations in component source', async ({ page }) =
4040
).toContainText('typed')
4141
})
4242

43+
test('dom mode supports type-only imports without runtime export syntax errors', async ({
44+
page,
45+
}) => {
46+
await waitForInitialRender(page)
47+
48+
await ensurePanelToolsVisible(page, 'component')
49+
await page.getByLabel('ShadowRoot').uncheck()
50+
await page.getByRole('combobox', { name: 'Render mode' }).selectOption('dom')
51+
52+
await setComponentEditorSource(
53+
page,
54+
[
55+
"import type { JsxChildren } from '@knighted/jsx'",
56+
'',
57+
'type DrawerProps = {',
58+
' children?: JsxChildren',
59+
'}',
60+
'',
61+
'const Drawer = ({ children }: DrawerProps) => {',
62+
' return <div className="drawer">{children}</div>',
63+
'}',
64+
'',
65+
'const App = () => {',
66+
' return <Drawer><button type="button">typed children import</button></Drawer>',
67+
'}',
68+
].join('\n'),
69+
)
70+
71+
await expect(page.getByRole('status', { name: 'App status' })).toHaveText('Rendered')
72+
await expect(page.locator('#preview-host pre')).toHaveCount(0)
73+
await expect(
74+
page.getByRole('region', { name: 'Preview output' }).getByRole('button'),
75+
).toContainText('typed children import')
76+
})
77+
4378
test('react mode typecheck loads types without malformed URL fetches', async ({
4479
page,
4580
}) => {

src/modules/render-runtime.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,9 @@ export const createRenderRuntimeController = ({
313313
return output
314314
}
315315

316+
const stripEmptyExportStatements = code =>
317+
code.replace(/(?:^|\n)\s*export\s*\{\s*\}\s*;?\s*(?=\n|$)/g, '\n')
318+
316319
const buildRuntimeImportPlan = imports => {
317320
const preamble = []
318321
const unsupportedSources = new Set()
@@ -790,9 +793,10 @@ export const createRenderRuntimeController = ({
790793
transformedResult.code,
791794
importAnalysisResult.imports,
792795
)
796+
const sanitizedRuntimeCode = stripEmptyExportStatements(runtimeCode)
793797
const executableUserCode = runtimeImportPlan.preamble.length
794-
? `${runtimeImportPlan.preamble.join('\n')}\n${runtimeCode}`
795-
: runtimeCode
798+
? `${runtimeImportPlan.preamble.join('\n')}\n${sanitizedRuntimeCode}`
799+
: sanitizedRuntimeCode
796800

797801
const moduleFactory = createUserModuleFactory(executableUserCode)
798802

src/modules/type-diagnostics.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,29 @@ const isAbsoluteUrlReference = reference => {
2222
}
2323

2424
const domJsxTypes =
25+
"declare module '@knighted/jsx' {\n" +
26+
' export type JsxRenderable =\n' +
27+
' | Node\n' +
28+
' | DocumentFragment\n' +
29+
' | string\n' +
30+
' | number\n' +
31+
' | bigint\n' +
32+
' | boolean\n' +
33+
' | null\n' +
34+
' | undefined\n' +
35+
' | Iterable<JsxRenderable>\n' +
36+
' export type JsxChildren = JsxRenderable | JsxRenderable[]\n' +
37+
' export type JsxComponent<Props = Record<string, unknown>> = (\n' +
38+
' props: Props & { children?: JsxChildren }\n' +
39+
' ) => JsxRenderable\n' +
40+
'}\n' +
2541
'declare namespace React {\n' +
2642
' type Key = string | number\n' +
2743
' interface Attributes { key?: Key | null }\n' +
2844
'}\n' +
2945
'declare namespace JSX {\n' +
30-
' type Element = unknown\n' +
31-
' interface ElementChildrenAttribute { children: unknown }\n' +
46+
" type Element = import('@knighted/jsx').JsxRenderable\n" +
47+
" interface ElementChildrenAttribute { children: import('@knighted/jsx').JsxChildren }\n" +
3248
' interface IntrinsicAttributes extends React.Attributes {}\n' +
3349
' interface IntrinsicElements { [elemName: string]: Record<string, unknown> }\n' +
3450
'}\n'

0 commit comments

Comments
 (0)