@weaverclub/render 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/entrypoint +0 -0
  2. package/package.json +6 -2
  3. package/.github/workflows/publish.yml +0 -44
  4. package/biome.json +0 -42
  5. package/build.ts +0 -62
  6. package/bun.lock +0 -733
  7. package/bunfig.toml +0 -3
  8. package/ideas.md +0 -11
  9. package/publish.ts +0 -62
  10. package/src/cli/command/renderCommand.ts +0 -112
  11. package/src/cli/entrypoint.ts +0 -25
  12. package/src/core/bundler.ts +0 -180
  13. package/src/core/control/arrayControl.ts +0 -15
  14. package/src/core/control/booleanControl.ts +0 -15
  15. package/src/core/control/control.ts +0 -7
  16. package/src/core/control/controlBuilder.ts +0 -87
  17. package/src/core/control/numberControl.ts +0 -15
  18. package/src/core/control/stringControl.ts +0 -15
  19. package/src/core/control/variantControl.ts +0 -18
  20. package/src/core/css/css.ts +0 -50
  21. package/src/core/css/tailwind.ts +0 -172
  22. package/src/core/html.ts +0 -63
  23. package/src/core/pkg.ts +0 -92
  24. package/src/core/story.ts +0 -52
  25. package/src/core/tsconfig.ts +0 -46
  26. package/src/react/react.ts +0 -2
  27. package/src/react/reactControlBuilder.ts +0 -130
  28. package/src/react/reactStory.ts +0 -36
  29. package/src/server/api/getStories.ts +0 -44
  30. package/src/server/api/renderIframe.ts +0 -66
  31. package/src/server/backend.ts +0 -104
  32. package/src/server/streaming.ts +0 -16
  33. package/src/ui/api.ts +0 -16
  34. package/src/ui/app.tsx +0 -23
  35. package/src/ui/cn.ts +0 -6
  36. package/src/ui/components/appSidebar.tsx +0 -76
  37. package/src/ui/components/button.stories.tsx +0 -32
  38. package/src/ui/components/button.tsx +0 -55
  39. package/src/ui/components/command.tsx +0 -187
  40. package/src/ui/components/contextMenu.tsx +0 -261
  41. package/src/ui/components/dialog.tsx +0 -153
  42. package/src/ui/components/input.tsx +0 -23
  43. package/src/ui/components/inputGroup.tsx +0 -157
  44. package/src/ui/components/kdb.tsx +0 -26
  45. package/src/ui/components/searchCommand.tsx +0 -5
  46. package/src/ui/components/separator.tsx +0 -22
  47. package/src/ui/components/sheet.tsx +0 -131
  48. package/src/ui/components/sidebar.tsx +0 -725
  49. package/src/ui/components/skeleton.tsx +0 -13
  50. package/src/ui/components/spinner.tsx +0 -15
  51. package/src/ui/components/tabButton.tsx +0 -80
  52. package/src/ui/components/tabContent.tsx +0 -20
  53. package/src/ui/components/tabList.tsx +0 -53
  54. package/src/ui/components/textarea.tsx +0 -17
  55. package/src/ui/components/tooltip.tsx +0 -67
  56. package/src/ui/frontend.tsx +0 -68
  57. package/src/ui/hooks/useMobile.ts +0 -23
  58. package/src/ui/index.html +0 -12
  59. package/src/ui/routeTree.gen.ts +0 -35
  60. package/src/ui/routes/__root.tsx +0 -9
  61. package/src/ui/styles.css +0 -123
  62. package/src/ui/tabs.tsx +0 -89
  63. package/tsconfig.json +0 -25
  64. package/tsr.config.json +0 -6
@@ -1,172 +0,0 @@
1
- import { Array as Arr, Effect, Option } from 'effect'
2
-
3
- // Path to the CLI bundled with this package (relative to this file in src/core/css/)
4
- const BUNDLED_CLI = new URL(
5
- '../../../node_modules/@tailwindcss/cli/dist/index.mjs',
6
- import.meta.url
7
- ).pathname
8
-
9
- // Cache for compiled CSS output - always kept updated
10
- let cachedCssOutput: string | null = null
11
- let tailwindWatcher: {
12
- proc: ReturnType<typeof Bun.spawn>
13
- outputFile: string
14
- } | null = null
15
-
16
- export const compileTailwindCss = Effect.fn(function* (projectRoot: string) {
17
- const glob = new Bun.Glob('**/*.css')
18
-
19
- const files = Arr.fromIterable(
20
- glob.scanSync({ absolute: true, cwd: projectRoot })
21
- )
22
-
23
- const entrypoint = Arr.head(files)
24
-
25
- if (Option.isNone(entrypoint))
26
- return yield* Effect.fail(new Error('No CSS files found for Tailwind CSS.'))
27
-
28
- // Use bundled @tailwindcss/cli - runs via bun for speed
29
- const proc = Bun.spawn(
30
- ['bun', BUNDLED_CLI, '-i', entrypoint.value, '--minify'],
31
- {
32
- cwd: projectRoot,
33
- stdout: 'pipe',
34
- stderr: 'pipe'
35
- }
36
- )
37
-
38
- const [stdout, stderr] = yield* Effect.all(
39
- [
40
- Effect.tryPromise(() => new Response(proc.stdout).text()),
41
- Effect.tryPromise(() => new Response(proc.stderr).text())
42
- ],
43
- {
44
- concurrency: 'unbounded'
45
- }
46
- )
47
- const exitCode = yield* Effect.tryPromise(() => proc.exited)
48
-
49
- if (exitCode !== 0)
50
- return yield* Effect.fail(
51
- new Error(`Tailwind CSS compilation failed: ${stderr}`)
52
- )
53
-
54
- cachedCssOutput = stdout
55
-
56
- return {
57
- paths: files,
58
- compiledOutput: stdout
59
- }
60
- })
61
-
62
- // Start Tailwind in watch mode for faster incremental builds
63
- export const startTailwindWatchMode = Effect.fn(function* (
64
- projectRoot: string
65
- ) {
66
- const glob = new Bun.Glob('**/*.css')
67
- const files = Arr.fromIterable(
68
- glob.scanSync({ absolute: true, cwd: projectRoot })
69
- )
70
- const entrypoint = Arr.head(files)
71
-
72
- if (Option.isNone(entrypoint))
73
- return yield* Effect.fail(new Error('No CSS files found for Tailwind CSS.'))
74
-
75
- // Create a temp output file
76
- const outputFile = `${projectRoot}/node_modules/.cache/render/tailwind.css`
77
-
78
- // Ensure the cache directory exists
79
- yield* Effect.tryPromise(async () => {
80
- await Bun.write(outputFile, '')
81
- })
82
-
83
- // Start Tailwind in watch mode - use 'inherit' for stderr to see errors
84
- const proc = Bun.spawn(
85
- [
86
- 'bunx',
87
- '@tailwindcss/cli',
88
- '-i',
89
- entrypoint.value,
90
- '-o',
91
- outputFile,
92
- '--minify',
93
- '--watch'
94
- ],
95
- {
96
- cwd: projectRoot,
97
- stdout: 'ignore',
98
- stderr: 'inherit'
99
- }
100
- )
101
-
102
- tailwindWatcher = { proc, outputFile }
103
-
104
- // Wait for initial compilation and poll for file to have content
105
- let attempts = 0
106
- while (attempts < 20) {
107
- yield* Effect.sleep('100 millis')
108
- const file = Bun.file(outputFile)
109
- const size = file.size
110
- if (size > 0) {
111
- const content = yield* Effect.tryPromise(() => file.text())
112
- if (content.length > 0) {
113
- cachedCssOutput = content
114
- console.log(
115
- ` [Tailwind] Initial compilation: ${content.length} bytes`
116
- )
117
- break
118
- }
119
- }
120
- attempts++
121
- }
122
-
123
- return { outputFile }
124
- })
125
-
126
- // Read the latest CSS from the watch mode output with retry
127
- export const getWatchedCss = Effect.fn(function* () {
128
- if (!tailwindWatcher) {
129
- console.log(
130
- ' [CSS Debug] No tailwind watcher, using cached:',
131
- (cachedCssOutput ?? '').length,
132
- 'bytes'
133
- )
134
- return cachedCssOutput ?? ''
135
- }
136
-
137
- // Try to read with retries (Tailwind might be mid-write)
138
- let attempts = 0
139
- while (attempts < 10) {
140
- const css = yield* Effect.tryPromise(async () => {
141
- if (!tailwindWatcher) return ''
142
- const file = Bun.file(tailwindWatcher.outputFile)
143
- return file.text()
144
- })
145
-
146
- if (css.length > 0) {
147
- cachedCssOutput = css // Update cache
148
- console.log(' [CSS Debug] Read from watcher:', css.length, 'bytes')
149
- return css
150
- }
151
-
152
- // File is empty, Tailwind might be recompiling - wait and retry
153
- yield* Effect.sleep('50 millis')
154
- attempts++
155
- }
156
-
157
- // Fall back to cached if file is still empty
158
- console.log(
159
- ' [CSS Debug] File empty, using cached:',
160
- (cachedCssOutput ?? '').length,
161
- 'bytes'
162
- )
163
- return cachedCssOutput ?? ''
164
- })
165
-
166
- // Stop the Tailwind watcher
167
- export const stopTailwindWatcher = () => {
168
- if (tailwindWatcher) {
169
- tailwindWatcher.proc.kill()
170
- tailwindWatcher = null
171
- }
172
- }
package/src/core/html.ts DELETED
@@ -1,63 +0,0 @@
1
- import { Effect } from 'effect'
2
- import type { CSS } from './css/css'
3
-
4
- export const mountHTML = ({ element, css, bundledScript }: MountHTMLArgs) =>
5
- Effect.sync(
6
- () =>
7
- `<!DOCTYPE html>
8
- <html lang="en">
9
- <head>
10
- <meta charset="UTF-8">
11
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
- <title>Component Preview</title>
13
- ${css.map((style) => `<style>${style.compiledOutput}</style>`).join('\n')}
14
- <style>
15
- body { margin: 0; padding: 1rem; }
16
- </style>
17
- </head>
18
- <body>
19
- <div id="root">${element}</div>
20
- ${bundledScript ? `<script type="module">${bundledScript.replace(/<\/script>/gi, '<\\/script>')}</script>` : ''}
21
- <script>
22
- // HMR client for iframe
23
- (function() {
24
- let ws;
25
- let reconnectAttempts = 0;
26
-
27
- function connect() {
28
- ws = new WebSocket('ws://' + location.host + '/__hmr');
29
-
30
- ws.onopen = function() {
31
- reconnectAttempts = 0;
32
- };
33
-
34
- ws.onmessage = function(event) {
35
- const data = JSON.parse(event.data);
36
- if (data.type === 'reload') {
37
- location.reload();
38
- }
39
- };
40
-
41
- ws.onclose = function() {
42
- if (reconnectAttempts < 10) {
43
- reconnectAttempts++;
44
- setTimeout(connect, 1000 * Math.min(reconnectAttempts, 5));
45
- }
46
- };
47
-
48
- ws.onerror = function() { ws.close(); };
49
- }
50
-
51
- connect();
52
- })();
53
- </script>
54
- </body>
55
- </html>
56
- `
57
- )
58
-
59
- type MountHTMLArgs = {
60
- element: string
61
- css: CSS[]
62
- bundledScript?: string
63
- }
package/src/core/pkg.ts DELETED
@@ -1,92 +0,0 @@
1
- import { FileSystem, Path } from '@effect/platform'
2
- import { Effect, Option, Schema } from 'effect'
3
-
4
- const packageJsonSchema = Schema.Struct({
5
- dependencies: Schema.Record({
6
- key: Schema.String,
7
- value: Schema.String
8
- }).pipe(Schema.optional),
9
- devDependencies: Schema.Record({
10
- key: Schema.String,
11
- value: Schema.String
12
- }).pipe(Schema.optional),
13
- peerDependencies: Schema.Record({
14
- key: Schema.String,
15
- value: Schema.String
16
- }).pipe(Schema.optional)
17
- })
18
-
19
- export const findNearestPackageJson = Effect.fn(function* (startPath: string) {
20
- let aux: string | null = null
21
- const path = yield* Path.Path
22
- const fs = yield* FileSystem.FileSystem
23
-
24
- while (startPath) {
25
- const candidatePath = path.join(startPath, 'package.json')
26
-
27
- const exists = yield* fs.exists(candidatePath)
28
-
29
- if (exists) {
30
- const content = yield* fs.readFileString(candidatePath)
31
-
32
- const contentAsJSON = yield* Effect.try(() => JSON.parse(content))
33
-
34
- const parsed =
35
- yield* Schema.decodeUnknown(packageJsonSchema)(contentAsJSON)
36
-
37
- return Option.some(parsed)
38
- }
39
-
40
- const parentDir = path.dirname(startPath)
41
-
42
- startPath = parentDir
43
-
44
- if (parentDir === aux) break
45
-
46
- aux = startPath
47
- }
48
-
49
- return Option.none()
50
- })
51
-
52
- export const findNearestNodeModules = Effect.fn(function* (startPath: string) {
53
- let aux: string | null = null
54
- const path = yield* Path.Path
55
- const fs = yield* FileSystem.FileSystem
56
-
57
- while (startPath) {
58
- const candidatePath = path.join(startPath, 'node_modules')
59
-
60
- const exists = yield* fs.exists(candidatePath)
61
-
62
- if (exists) {
63
- return Option.some(candidatePath)
64
- }
65
-
66
- const parentDir = path.dirname(startPath)
67
-
68
- startPath = parentDir
69
-
70
- if (parentDir === aux) break
71
-
72
- aux = startPath
73
- }
74
-
75
- return Option.none()
76
- })
77
-
78
- export const getProjectDependencies = Effect.fn(function* (
79
- projectRoot: string
80
- ) {
81
- const pkg = yield* findNearestPackageJson(projectRoot)
82
-
83
- if (Option.isNone(pkg)) return []
84
-
85
- const deps = [
86
- ...Object.keys(pkg.value.dependencies || {}),
87
- ...Object.keys(pkg.value.devDependencies || {}),
88
- ...Object.keys(pkg.value.peerDependencies || {})
89
- ]
90
-
91
- return deps
92
- })
package/src/core/story.ts DELETED
@@ -1,52 +0,0 @@
1
- import { Array as Arr, Effect } from 'effect'
2
- import type { ReactStory } from '#react/reactStory'
3
- import { importStoryWithBundler } from './bundler'
4
-
5
- export const loadStories = Effect.fn(function* (absolutePath: string) {
6
- const glob = new Bun.Glob('**/*.stories.{ts,tsx,js,jsx,mts,cts}')
7
-
8
- const files = Arr.fromIterable(
9
- glob.scanSync({ absolute: true, cwd: absolutePath })
10
- )
11
-
12
- const tasks = Arr.map(files, (file) =>
13
- importStoryWithBundler({
14
- storyPath: file,
15
- projectRoot: absolutePath
16
- }).pipe(
17
- Effect.scoped,
18
- Effect.map((mod) => ({ mod, sourcePath: file }))
19
- )
20
- )
21
-
22
- const results = yield* Effect.all(tasks, { concurrency: 'unbounded' })
23
-
24
- const stories = Arr.flatMap(results, ({ mod, sourcePath }) =>
25
- Object.values(mod).map((story) => {
26
- if (
27
- story !== null &&
28
- typeof story === 'object' &&
29
- '~type' in story &&
30
- story['~type'] === 'ReactStory'
31
- ) {
32
- // Attach the source path for browser bundling
33
- ;(story as ReactStory<React.ComponentType>).sourcePath = sourcePath
34
- }
35
- return story
36
- })
37
- )
38
-
39
- return stories.filter(
40
- // biome-ignore lint/suspicious/noExplicitAny: Required for story filtering
41
- (story): story is ReactStory<any> =>
42
- story !== null &&
43
- typeof story === 'object' &&
44
- '~type' in story &&
45
- story['~type'] === 'ReactStory'
46
- )
47
- })
48
-
49
- export interface Story {
50
- // biome-ignore lint/suspicious/noExplicitAny: Required for generic story execution
51
- render: (...args: any[]) => unknown
52
- }
@@ -1,46 +0,0 @@
1
- import { FileSystem, Path } from '@effect/platform'
2
- import { Effect, Option, Schema } from 'effect'
3
-
4
- const tsconfigSchema = Schema.Struct({
5
- compilerOptions: Schema.Struct({
6
- baseUrl: Schema.String.pipe(Schema.optional),
7
- paths: Schema.Record({
8
- key: Schema.String,
9
- value: Schema.Array(Schema.String)
10
- }).pipe(Schema.optional)
11
- })
12
- })
13
-
14
- const candidates = ['tsconfig.app.json', 'tsconfig.base.json', 'tsconfig.json']
15
-
16
- export const findNearestTsconfig = Effect.fn(function* (startPath: string) {
17
- let aux: string | null = null
18
- const path = yield* Path.Path
19
- const fs = yield* FileSystem.FileSystem
20
-
21
- while (startPath) {
22
- for (const candidate of candidates) {
23
- const tsconfigPath = path.join(startPath, candidate)
24
-
25
- const exists = yield* fs.exists(tsconfigPath)
26
-
27
- if (exists) {
28
- const content = yield* Effect.tryPromise(() => import(tsconfigPath))
29
-
30
- const parsed = yield* Schema.decodeUnknown(tsconfigSchema)(content)
31
-
32
- if (parsed.compilerOptions?.paths) return Option.some(tsconfigPath)
33
- }
34
- }
35
-
36
- const parentDir = path.dirname(startPath)
37
-
38
- startPath = parentDir
39
-
40
- if (parentDir === aux) break
41
-
42
- aux = startPath
43
- }
44
-
45
- return Option.none()
46
- })
@@ -1,2 +0,0 @@
1
- export { controls } from './reactControlBuilder'
2
- export { story } from './reactStory'
@@ -1,130 +0,0 @@
1
- import type { ComponentType } from 'react'
2
- import { ArrayControl } from '#core/control/arrayControl'
3
- import { BooleanControl } from '#core/control/booleanControl'
4
- import type { Control } from '#core/control/control'
5
- import type {
6
- ArrayPropNames,
7
- BooleanPropNames,
8
- ControlBuilder,
9
- ExtractStringLiteralUnion,
10
- NumberPropNames,
11
- StringPropNames,
12
- VariantPropNames
13
- } from '#core/control/controlBuilder'
14
- import { NumberControl } from '#core/control/numberControl'
15
- import { StringControl } from '#core/control/stringControl'
16
- import { VariantControl } from '#core/control/variantControl'
17
- import type { ReactStory } from './reactStory'
18
-
19
- export function controls<
20
- // biome-ignore lint/suspicious/noExplicitAny: Required for React component prop inference
21
- Component extends React.ComponentType<any>
22
- >(): ReactControlBuilder<React.ComponentProps<Component>, never> {
23
- return new ReactControlBuilder<React.ComponentProps<Component>, never>()
24
- }
25
-
26
- const CONTROLS_SYMBOL = Symbol.for('@renderweaver/controls')
27
-
28
- export class ReactControlBuilder<Props, Used extends keyof Props>
29
- implements ControlBuilder<Props, Used>
30
- {
31
- private controls: Control[] = []
32
-
33
- public [CONTROLS_SYMBOL](): Control[] {
34
- return this.controls
35
- }
36
-
37
- public array<
38
- Name extends Exclude<ArrayPropNames<Props>, Used> & string,
39
- Type = Props[Name] extends Array<infer U> ? U : never
40
- >(
41
- name: Name,
42
- options: {
43
- defaultValue: Props[Name]
44
- }
45
- ): ControlBuilder<Props, Used | Name> {
46
- this.controls.push(
47
- new ArrayControl<Type>({
48
- name,
49
- defaultValue: options.defaultValue as Type[]
50
- })
51
- )
52
-
53
- return this as unknown as ControlBuilder<Props, Used | Name>
54
- }
55
-
56
- public bool<Name extends Exclude<BooleanPropNames<Props>, Used> & string>(
57
- name: Name,
58
- options: {
59
- defaultValue: boolean
60
- }
61
- ): ControlBuilder<Props, Used | Name> {
62
- this.controls.push(
63
- new BooleanControl({
64
- name,
65
- defaultValue: options.defaultValue
66
- })
67
- )
68
-
69
- return this as unknown as ControlBuilder<Props, Used | Name>
70
- }
71
-
72
- public number<Name extends Exclude<NumberPropNames<Props>, Used> & string>(
73
- name: Name,
74
- options: {
75
- defaultValue: number
76
- }
77
- ): ControlBuilder<Props, Used | Name> {
78
- this.controls.push(
79
- new NumberControl({
80
- name,
81
- defaultValue: options.defaultValue
82
- })
83
- )
84
-
85
- return this as unknown as ControlBuilder<Props, Used | Name>
86
- }
87
-
88
- public string<Name extends Exclude<StringPropNames<Props>, Used> & string>(
89
- name: Name,
90
- options: { defaultValue: string }
91
- ): ControlBuilder<Props, Used | Name> {
92
- this.controls.push(
93
- new StringControl({
94
- name,
95
- defaultValue: options.defaultValue
96
- })
97
- )
98
-
99
- return this as unknown as ControlBuilder<Props, Used | Name>
100
- }
101
-
102
- public variant<Name extends Exclude<VariantPropNames<Props>, Used> & string>(
103
- name: Name,
104
- options: {
105
- defaultValue: ExtractStringLiteralUnion<Props[Name]>
106
- options: ExtractStringLiteralUnion<Props[Name]>[]
107
- }
108
- ): ControlBuilder<Props, Used | Name> {
109
- this.controls.push(
110
- new VariantControl({
111
- name: name,
112
- options: options.options as string[],
113
- defaultValue: options.defaultValue as string
114
- })
115
- )
116
-
117
- return this as unknown as ControlBuilder<Props, Used | Name>
118
- }
119
- }
120
-
121
- export function getDefaultPropsForReactComponent<
122
- // biome-ignore lint/suspicious/noExplicitAny: Required for React component prop inference
123
- Component extends ComponentType<any>
124
- >(story: ReactStory<Component>): React.ComponentProps<Component> {
125
- if (!story.controls) {
126
- return {} as React.ComponentProps<Component>
127
- }
128
-
129
- return {} as React.ComponentProps<Component>
130
- }
@@ -1,36 +0,0 @@
1
- // biome-ignore-all lint/suspicious/noExplicitAny: Required for React component prop inference
2
- import type { UnfinishedControls } from '#core/control/controlBuilder'
3
- import type { Story } from '#core/story'
4
-
5
- export function story<Component extends React.ComponentType<any>>(
6
- args: StoryArgs<Component>
7
- ) {
8
- return new ReactStory(args)
9
- }
10
-
11
- export class ReactStory<Component extends React.ComponentType<any>>
12
- implements Story
13
- {
14
- public name: string
15
- public id: string
16
- public component: React.ComponentType<any> | undefined
17
- public render: (props: React.ComponentProps<Component>) => React.ReactElement
18
- public controls: UnfinishedControls | undefined
19
- public '~type' = 'ReactStory'
20
- public sourcePath?: string // Path to the story source file for browser bundling
21
-
22
- public constructor(args: StoryArgs<Component>) {
23
- this.name = args.name
24
- this.component = args.component
25
- this.render = args.render
26
- this.controls = args.controls
27
- this.id = this.name.replace(/\s+/g, '-').toLowerCase()
28
- }
29
- }
30
-
31
- type StoryArgs<Component extends React.ComponentType<any>> = {
32
- name: string
33
- render: (props: React.ComponentProps<Component>) => React.ReactElement
34
- component?: Component
35
- controls?: UnfinishedControls
36
- }
@@ -1,44 +0,0 @@
1
- import { Effect } from 'effect'
2
- import type { ReactStory } from '#react/reactStory'
3
-
4
- export const getStories = ({ stories }: GetStoriesArgs) =>
5
- Effect.sync(() => {
6
- const categorizedStories: Record<string, SimpleStory[]> = {}
7
-
8
- for (const story of stories) {
9
- const [category, storyName] = story.name.split('/')
10
-
11
- if (!category || !storyName) continue
12
-
13
- if (!categorizedStories[category]) {
14
- categorizedStories[category] = []
15
- }
16
-
17
- categorizedStories[category].push({
18
- name: storyName,
19
- path: story.id
20
- })
21
- }
22
-
23
- const sortedCategories = Object.keys(categorizedStories).sort()
24
-
25
- for (const category of sortedCategories) {
26
- categorizedStories[category]?.sort((a, b) => a.name.localeCompare(b.name))
27
- }
28
-
29
- return new Response(JSON.stringify(categorizedStories), {
30
- status: 200,
31
- headers: { 'Content-Type': 'application/json' }
32
- })
33
- })
34
-
35
- type SimpleStory = {
36
- name: string
37
- path: string
38
- }
39
-
40
- type GetStoriesArgs = {
41
- request: Request
42
- // biome-ignore lint/suspicious/noExplicitAny: Required for ReactStory type
43
- stories: ReactStory<any>[]
44
- }
@@ -1,66 +0,0 @@
1
- import type { BunRequest } from 'bun'
2
- import { Effect } from 'effect'
3
- import { renderToReadableStream } from 'react-dom/server'
4
- import { bundleStoryForBrowser } from '#core/bundler'
5
- import type { CSS } from '#core/css/css'
6
- import { mountHTML } from '#core/html'
7
- import { getDefaultPropsForReactComponent } from '#react/reactControlBuilder'
8
- import type { ReactStory } from '#react/reactStory'
9
- import { streamToString } from '#server/streaming'
10
-
11
- export const renderIframe = Effect.fn(function* ({
12
- request,
13
- css,
14
- stories,
15
- projectRoot
16
- }: RenderIframeArgs) {
17
- const storyId = request.url.split('/iframe/')[1]?.split('?')[0] // Strip query params
18
- const story = stories.find((s) => s.id === storyId)
19
-
20
- if (!story) return new Response('Story not found', { status: 404 })
21
-
22
- const defaultProps = getDefaultPropsForReactComponent(story)
23
-
24
- const element = story.render(defaultProps)
25
-
26
- const stream = yield* Effect.tryPromise(() => renderToReadableStream(element))
27
-
28
- const html = yield* streamToString(stream)
29
-
30
- // Bundle the story for browser hydration
31
- let bundledScript: string | undefined
32
- if (story.sourcePath) {
33
- const bundleResult = yield* bundleStoryForBrowser({
34
- storyPath: story.sourcePath,
35
- projectRoot,
36
- storyId: story.id
37
- }).pipe(Effect.option)
38
-
39
- if (bundleResult._tag === 'Some') {
40
- bundledScript = bundleResult.value
41
- }
42
- }
43
-
44
- const mountedHTML = yield* mountHTML({
45
- css,
46
- element: html,
47
- bundledScript
48
- })
49
-
50
- return new Response(mountedHTML, {
51
- headers: {
52
- 'Content-Type': 'text/html',
53
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
54
- Pragma: 'no-cache',
55
- Expires: '0'
56
- }
57
- })
58
- })
59
-
60
- type RenderIframeArgs = {
61
- request: BunRequest<'iframe/*'>
62
- // biome-ignore lint/suspicious/noExplicitAny: Required for ReactStory type
63
- stories: ReactStory<any>[]
64
- css: CSS[]
65
- projectRoot: string
66
- }