@symbo.ls/frank
v3.14.787
Published
Bidirectional transformation between Symbols project JSON and filesystem formats
Maintainers
Keywords
Readme
@symbo.ls/frank
Bidirectional transformation between Symbols project JSON and filesystem formats. Bundles a symbols/ directory into a JSON object (toJSON), or converts a JSON project back into a filesystem structure (toFS).
API
toJSON(projectDir, options?)
Bundles a Symbols filesystem project into a JSON-serializable object. Uses esbuild to bundle all project modules into a single CJS file, loads it, and returns the resolved data with functions stringified.
import { toJSON } from '@symbo.ls/frank'
const project = await toJSON('/path/to/symbols')
// project.components -> all components
// project.pages -> all pages
// project.designSystem -> design system config
// project.state -> app state
// project.functions -> all functions (stringified)Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| entry | string | auto-detected | Custom entry file (defaults to context.js) |
| stringify | boolean | true | Stringify functions for JSON transport |
| tmpDir | string | .symbols_local/frank-tmp/ | Custom temp directory for bundled output |
| external | string[] | [] | Additional packages to externalize |
| scanAndFix | boolean | false | Free-var scan + scope rewrite (the publish/serve emission pipeline) |
| verifySerialization | boolean | true | Verify-or-fail publish gate on the emitted output (scanAndFix mode only) |
If no context.js exists, frank auto-generates one by discovering available project modules (state, components, pages, etc.).
The verify-or-fail publish gate
With scanAndFix: true (what smbls push, smbls frank to-json and the runner use), the emitted
output is validated AFTER all rewriting (verifyEmission.js). Findings throw an error with
code: 'FRANK_VERIFY_FAILED' and a message naming every defect — file, export, identifier, line —
so a publish that would ship broken code fails loudly at build time instead of succeeding into a
silently-broken runtime. The checks:
- No unresolvable identifiers — every emitted function string must parse, and every free
identifier (nested function/arrow bodies included, with full scope tracking) must resolve to a
param/local, an injected alias (
__scope,__mod, …) or a known runtime global. The runtime revives these strings with a bare eval — anything else is a guaranteedReferenceError. __scope.Xreads resolve —Xmust exist in the emittedglobalScope, in a statically-declared elementscopeon the path, or be assigned in-function (npm injection).- Signature parity — the emitted parameter list must equal the source parameter list.
Exception: the documented
()→(el)injection for positionally-invoked factories. Forfunctions.*/methods.*(invokedfn.call(element, ...args)) ANY drift is an error. - No dropped exports — every named export declared in
functions/,components/,snippets/,methods/source files must be present in the emitted bundle (the directory is scanned, not the index — missing-index-re-export drops are caught too).
Escape hatch (emergencies only): toJSON(dir, { verifySerialization: false }), or set
SMBLS_NO_VERIFY_SERIALIZATION=1 in the environment (works through smbls push unchanged).
If the gate blocked you and the finding is wrong, that is a gate bug — file it; don't leave the
hatch enabled.
toFS(data, distDir, options?)
Converts a JSON project object into a Symbols filesystem structure with proper directory layout, index files, and a generated context.js.
import { toFS } from '@symbo.ls/frank'
await toFS(projectData, '/path/to/output/symbols')Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| overwrite | boolean | false | Overwrite existing files |
Generated structure:
symbols/
index.js # re-exports all modules
context.js # aggregated default export for bundling
config.js # design system config flags
state.js # app state
dependencies.js # project dependencies
components/
index.js # export * from each component
Button.js
Card.js
pages/
index.js # default export with route mapping
main.js # / route
about.js # /about route
functions/
index.js
initApp.js
methods/
index.js
snippets/
index.js
designSystem/
index.js # default export merging all sub-modules
color.js
theme.js
typography.js
files/
index.jsstringifyFunctions(value)
Recursively clones a value, converting all functions to their string representations for JSON serialization. Handles circular references via WeakMap and skips internal metadata keys (__fn, __fnMeta, __handler, __meta).
import { stringifyFunctions } from '@symbo.ls/frank'
const serializable = stringifyFunctions({
onClick: (el) => el.update({ active: true }),
nested: { handler: function () { return 42 } }
})
// { onClick: '(el) => el.update({ active: true })', nested: { handler: 'function () { return 42 }' } }How bundling works
toJSON uses esbuild to bundle the project entry into a single CJS module:
- Detects entry file (
context.js, or generates one from discovered modules) - Bundles with esbuild — resolves all local imports, externalizes runtime packages (smbls, domql, etc.)
- Injects browser API stubs (location, history, document, etc.) so modules that reference browser globals don't crash in Node.js
- Loads the bundled CJS module, strips empty
default: {}artifacts from CJS bundling - Optionally stringifies functions, returns the plain object
- Cleans up temp files
Project modules
Frank recognizes these standard project modules:
| Module | Path | Export style |
|--------|------|-------------|
| state | ./state.js | default |
| dependencies | ./dependencies.js | default |
| sharedLibraries | ./sharedLibraries.js | default |
| components | ./components/index.js | namespace |
| snippets | ./snippets/index.js | namespace |
| pages | ./pages/index.js | default |
| functions | ./functions/index.js | namespace |
| methods | ./methods/index.js | namespace |
| designSystem | ./designSystem/index.js | default |
| files | ./files/index.js | default |
| config | ./config.js | default |
