@dfosco/storyboard-react 1.17.3 → 1.18.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 +46 -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,18 @@ 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
|
+
|
|
77
101
|
return [
|
|
78
|
-
|
|
102
|
+
imports.join('\n'),
|
|
79
103
|
'',
|
|
80
104
|
declarations.join('\n'),
|
|
81
105
|
'',
|
|
@@ -83,7 +107,7 @@ function generateModule(index) {
|
|
|
83
107
|
`const objects = {\n${entries.object.join(',\n')}\n}`,
|
|
84
108
|
`const records = {\n${entries.record.join(',\n')}\n}`,
|
|
85
109
|
'',
|
|
86
|
-
|
|
110
|
+
initCalls.join('\n'),
|
|
87
111
|
'',
|
|
88
112
|
`export { scenes, objects, records }`,
|
|
89
113
|
`export const index = { scenes, objects, records }`,
|
|
@@ -126,7 +150,7 @@ export default function storyboardDataPlugin() {
|
|
|
126
150
|
load(id) {
|
|
127
151
|
if (id !== RESOLVED_ID) return null
|
|
128
152
|
if (!index) index = buildIndex(root)
|
|
129
|
-
return generateModule(index)
|
|
153
|
+
return generateModule(index, root)
|
|
130
154
|
},
|
|
131
155
|
|
|
132
156
|
configureServer(server) {
|
|
@@ -145,9 +169,26 @@ export default function storyboardDataPlugin() {
|
|
|
145
169
|
}
|
|
146
170
|
}
|
|
147
171
|
|
|
172
|
+
// Watch storyboard.config.json for changes
|
|
173
|
+
const { configPath } = readConfig(root)
|
|
174
|
+
watcher.add(configPath)
|
|
175
|
+
const invalidateConfig = (filePath) => {
|
|
176
|
+
if (path.resolve(filePath) === configPath) {
|
|
177
|
+
index = null
|
|
178
|
+
const mod = server.moduleGraph.getModuleById(RESOLVED_ID)
|
|
179
|
+
if (mod) {
|
|
180
|
+
server.moduleGraph.invalidateModule(mod)
|
|
181
|
+
server.ws.send({ type: 'full-reload' })
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
148
186
|
watcher.on('add', invalidate)
|
|
149
187
|
watcher.on('unlink', invalidate)
|
|
150
|
-
watcher.on('change',
|
|
188
|
+
watcher.on('change', (filePath) => {
|
|
189
|
+
invalidate(filePath)
|
|
190
|
+
invalidateConfig(filePath)
|
|
191
|
+
})
|
|
151
192
|
},
|
|
152
193
|
|
|
153
194
|
// Rebuild index on each build start
|