-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a77230a
commit 776f06f
Showing
9 changed files
with
253 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,3 +14,6 @@ node_modules | |
# build | ||
dist | ||
bench | ||
|
||
# vite | ||
vite.config.ts.timestamp-* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,6 @@ | |
"vite-plugin-solid": "^2.8.2" | ||
}, | ||
"dependencies": { | ||
"solid-js": "^1.9.2" | ||
"solid-js": "^1.9.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
|
||
// @babel/standalone does not export types, | ||
// so this file is a mess of anys. | ||
|
||
import type { StoryContext, PartialStoryFn } from '@storybook/types'; | ||
import { SolidRenderer } from '../types'; | ||
|
||
import { SNIPPET_RENDERED, SourceType } from '@storybook/docs-tools'; | ||
import { addons, useEffect } from '@storybook/preview-api'; | ||
|
||
// @ts-expect-error Types are not up to date | ||
import * as Babel from '@babel/standalone'; | ||
const parser = Babel.packages.parser; | ||
const generate = Babel.packages.generator.default; | ||
const t = Babel.packages.types; | ||
|
||
function skipSourceRender(context: StoryContext<SolidRenderer>): boolean { | ||
const sourceParams = context?.parameters.docs?.source; | ||
const isArgsStory = context?.parameters.__isArgsStory; | ||
|
||
// always render if the user forces it | ||
if (sourceParams?.type === SourceType.DYNAMIC) { | ||
return false; | ||
} | ||
|
||
// never render if the user is forcing the block to render code, or | ||
// if the user provides code, or if it's not an args story. | ||
return ( | ||
!isArgsStory || sourceParams?.code || sourceParams?.type === SourceType.CODE | ||
); | ||
} | ||
|
||
/** | ||
* Generate JSX source code from stories. | ||
*/ | ||
export const sourceDecorator = ( | ||
storyFn: PartialStoryFn<SolidRenderer>, | ||
ctx: StoryContext<SolidRenderer>, | ||
) => { | ||
// Strategy: Since SolidJS doesn't have a VDOM, | ||
// it isn't possible to get information directly about inner components. | ||
// Instead, there needs to be an altered render function | ||
// that records information about component properties, | ||
// or source code extraction from files. | ||
// This decorator uses the latter technique. | ||
// By using the source code string generated by CSF-tools, | ||
// we can then parse the properties of the `args` object, | ||
// and return the source slices. | ||
|
||
// Note: this also means we are limited in how we can | ||
// get the component name. | ||
// Since Storybook doesn't do source code extraction for | ||
// story metas (yet), we can use the title for now. | ||
const channel = addons.getChannel(); | ||
const story = storyFn(); | ||
const skip = skipSourceRender(ctx); | ||
|
||
// eslint-disable-next-line prefer-const | ||
let source: string | null; | ||
|
||
useEffect(() => { | ||
if (!skip && source) { | ||
const { id, unmappedArgs } = ctx; | ||
channel.emit(SNIPPET_RENDERED, { id, args: unmappedArgs, source }); | ||
} | ||
}); | ||
|
||
if (skip) return story; | ||
|
||
const docs = ctx?.parameters?.docs; | ||
const src = docs?.source?.originalSource; | ||
const name = ctx.title.split('/').at(-1)!; | ||
|
||
source = generateSolidSource(name, src); | ||
console.log(source); | ||
|
||
return story; | ||
}; | ||
|
||
/** | ||
* Generate Solid JSX from story source. | ||
*/ | ||
function generateSolidSource(name: string, src: string): string | null { | ||
try { | ||
const { attributes, children } = parseProps(src); | ||
|
||
const selfClosing = children == null || children.length == 0; | ||
|
||
const component = { | ||
type: 'JSXElement', | ||
openingElement: { | ||
type: 'JSXOpeningElement', | ||
name: { | ||
type: 'JSXIdentifier', | ||
name, | ||
}, | ||
attributes: attributes, | ||
selfClosing, | ||
}, | ||
children: children ?? [], | ||
closingElement: selfClosing | ||
? undefined | ||
: { | ||
type: 'JSXClosingElement', | ||
name: { | ||
type: 'JSXIdentifier', | ||
name, | ||
}, | ||
}, | ||
}; | ||
|
||
console.log(component); | ||
|
||
return generate(component, { compact: false }).code; | ||
} catch (e) { | ||
console.error(e); | ||
return null; | ||
} | ||
} | ||
|
||
function toJSXChild(node: any): object { | ||
if ( | ||
t.isJSXElement(node) || | ||
t.isJSXText(node) || | ||
t.isJSXExpressionContainer(node) || | ||
t.isJSXSpreadChild(node) || | ||
t.isJSXFragment(node) | ||
) { | ||
return node; | ||
} | ||
|
||
if (t.isStringLiteral(node)) { | ||
return { | ||
type: 'JSXText', | ||
value: node.value, | ||
}; | ||
} | ||
|
||
if (t.isExpression(node)) { | ||
return { | ||
type: 'JSXExpressionContainer', | ||
value: node, | ||
}; | ||
} | ||
|
||
return { | ||
type: 'JSXExpressionContainer', | ||
value: t.jsxEmptyExpression(), | ||
}; | ||
} | ||
|
||
interface SolidProps { | ||
attributes: object[]; | ||
children: object[] | null; | ||
} | ||
|
||
/** | ||
* Parses component properties from source expression. | ||
* | ||
* The source code will be in the form of a `Story` object. | ||
*/ | ||
function parseProps(src: string): SolidProps { | ||
const ast = parser.parseExpression(src, { plugins: ['jsx'] }); | ||
console.log(ast); | ||
if (ast.type != 'ObjectExpression') throw 'Expected `ObjectExpression` type'; | ||
// Find args property. | ||
const args_prop = ast.properties.find((v: any) => { | ||
if (v.type != 'ObjectProperty') return false; | ||
if (v.key.type != 'Identifier') return false; | ||
return v.key.name == 'args'; | ||
}) as any | undefined; | ||
// No args just there aren't any properties or children. | ||
if (!args_prop) | ||
return { | ||
attributes: [], | ||
children: null, | ||
}; | ||
// Get arguments. | ||
const args = args_prop.value; | ||
if (args.type != 'ObjectExpression') throw 'Expected `ObjectExpression` type'; | ||
|
||
// Construct props object, where values are source code slices. | ||
const attributes: object[] = []; | ||
let children: object[] | null = null; | ||
for (const el of args.properties) { | ||
if (el.type != 'ObjectProperty') continue; | ||
if (el.key.type != 'Identifier') continue; | ||
|
||
if (el.key.name == 'children') { | ||
children = [toJSXChild(el.value)]; | ||
continue; | ||
} | ||
|
||
let value: any = { | ||
type: 'JSXExpressionContainer', | ||
expression: el.value, | ||
}; | ||
|
||
if (el.value.type == 'BooleanLiteral' && el.value.value == true) { | ||
value = undefined; | ||
} | ||
|
||
attributes.push({ | ||
type: 'JSXAttribute', | ||
name: { | ||
type: 'JSXIdentifier', | ||
name: el.key.name, | ||
}, | ||
value, | ||
}); | ||
} | ||
|
||
return { attributes, children }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.