@dfosco/storyboard-react 1.17.3 → 1.20.0
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.
- package/package.json +1 -1
- package/src/hooks/useFeatureFlag.js +16 -0
- package/src/index.js +1 -0
- package/src/vite/data-plugin.js +52 -5
package/package.json
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react'
|
|
2
|
+
import { getFlag, subscribeToHash, getHashSnapshot, subscribeToStorage, getStorageSnapshot } from '@dfosco/storyboard-core'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React hook for reading a feature flag value.
|
|
6
|
+
* Re-renders when the flag changes (via hash or localStorage).
|
|
7
|
+
*
|
|
8
|
+
* @param {string} key - Flag key (without "flag." prefix)
|
|
9
|
+
* @returns {boolean} Current resolved flag value
|
|
10
|
+
*/
|
|
11
|
+
export function useFeatureFlag(key) {
|
|
12
|
+
// Subscribe to both hash and storage changes for reactivity
|
|
13
|
+
useSyncExternalStore(subscribeToHash, getHashSnapshot)
|
|
14
|
+
useSyncExternalStore(subscribeToStorage, getStorageSnapshot)
|
|
15
|
+
return getFlag(key)
|
|
16
|
+
}
|
package/src/index.js
CHANGED
|
@@ -19,6 +19,7 @@ export { useRecordOverride } from './hooks/useRecordOverride.js'
|
|
|
19
19
|
export { useLocalStorage } from './hooks/useLocalStorage.js'
|
|
20
20
|
export { useHideMode } from './hooks/useHideMode.js'
|
|
21
21
|
export { useUndoRedo } from './hooks/useUndoRedo.js'
|
|
22
|
+
export { useFeatureFlag } from './hooks/useFeatureFlag.js'
|
|
22
23
|
|
|
23
24
|
// React Router integration
|
|
24
25
|
export { installHashPreserver } from './hashPreserver.js'
|
package/src/vite/data-plugin.js
CHANGED
|
@@ -59,7 +59,21 @@ function buildIndex(root) {
|
|
|
59
59
|
* Reads each data file, parses JSONC at build time, and emits pre-parsed
|
|
60
60
|
* JavaScript objects — no runtime parsing needed.
|
|
61
61
|
*/
|
|
62
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Read storyboard.config.json from the project root (if it exists).
|
|
64
|
+
* Returns the parsed config object, or null if not found.
|
|
65
|
+
*/
|
|
66
|
+
function readConfig(root) {
|
|
67
|
+
const configPath = path.resolve(root, 'storyboard.config.json')
|
|
68
|
+
try {
|
|
69
|
+
const raw = fs.readFileSync(configPath, 'utf-8')
|
|
70
|
+
return { config: parseJsonc(raw), configPath }
|
|
71
|
+
} catch {
|
|
72
|
+
return { config: null, configPath }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function generateModule(index, root) {
|
|
63
77
|
const declarations = []
|
|
64
78
|
const entries = { scene: [], object: [], record: [] }
|
|
65
79
|
let i = 0
|
|
@@ -74,8 +88,24 @@ function generateModule(index) {
|
|
|
74
88
|
}
|
|
75
89
|
}
|
|
76
90
|
|
|
91
|
+
const imports = [`import { init } from '@dfosco/storyboard-core'`]
|
|
92
|
+
const initCalls = [`init({ scenes, objects, records })`]
|
|
93
|
+
|
|
94
|
+
// Feature flags from storyboard.config.json
|
|
95
|
+
const { config } = readConfig(root)
|
|
96
|
+
if (config?.featureFlags && Object.keys(config.featureFlags).length > 0) {
|
|
97
|
+
imports.push(`import { initFeatureFlags } from '@dfosco/storyboard-core'`)
|
|
98
|
+
initCalls.push(`initFeatureFlags(${JSON.stringify(config.featureFlags)})`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Plugin configuration from storyboard.config.json
|
|
102
|
+
if (config?.plugins && Object.keys(config.plugins).length > 0) {
|
|
103
|
+
imports.push(`import { initPlugins } from '@dfosco/storyboard-core'`)
|
|
104
|
+
initCalls.push(`initPlugins(${JSON.stringify(config.plugins)})`)
|
|
105
|
+
}
|
|
106
|
+
|
|
77
107
|
return [
|
|
78
|
-
|
|
108
|
+
imports.join('\n'),
|
|
79
109
|
'',
|
|
80
110
|
declarations.join('\n'),
|
|
81
111
|
'',
|
|
@@ -83,7 +113,7 @@ function generateModule(index) {
|
|
|
83
113
|
`const objects = {\n${entries.object.join(',\n')}\n}`,
|
|
84
114
|
`const records = {\n${entries.record.join(',\n')}\n}`,
|
|
85
115
|
'',
|
|
86
|
-
|
|
116
|
+
initCalls.join('\n'),
|
|
87
117
|
'',
|
|
88
118
|
`export { scenes, objects, records }`,
|
|
89
119
|
`export const index = { scenes, objects, records }`,
|
|
@@ -126,7 +156,7 @@ export default function storyboardDataPlugin() {
|
|
|
126
156
|
load(id) {
|
|
127
157
|
if (id !== RESOLVED_ID) return null
|
|
128
158
|
if (!index) index = buildIndex(root)
|
|
129
|
-
return generateModule(index)
|
|
159
|
+
return generateModule(index, root)
|
|
130
160
|
},
|
|
131
161
|
|
|
132
162
|
configureServer(server) {
|
|
@@ -145,9 +175,26 @@ export default function storyboardDataPlugin() {
|
|
|
145
175
|
}
|
|
146
176
|
}
|
|
147
177
|
|
|
178
|
+
// Watch storyboard.config.json for changes
|
|
179
|
+
const { configPath } = readConfig(root)
|
|
180
|
+
watcher.add(configPath)
|
|
181
|
+
const invalidateConfig = (filePath) => {
|
|
182
|
+
if (path.resolve(filePath) === configPath) {
|
|
183
|
+
index = null
|
|
184
|
+
const mod = server.moduleGraph.getModuleById(RESOLVED_ID)
|
|
185
|
+
if (mod) {
|
|
186
|
+
server.moduleGraph.invalidateModule(mod)
|
|
187
|
+
server.ws.send({ type: 'full-reload' })
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
148
192
|
watcher.on('add', invalidate)
|
|
149
193
|
watcher.on('unlink', invalidate)
|
|
150
|
-
watcher.on('change',
|
|
194
|
+
watcher.on('change', (filePath) => {
|
|
195
|
+
invalidate(filePath)
|
|
196
|
+
invalidateConfig(filePath)
|
|
197
|
+
})
|
|
151
198
|
},
|
|
152
199
|
|
|
153
200
|
// Rebuild index on each build start
|