A Babel plugin that injects lightweight metadata into JSX elements for visual editor integration. This plugin processes JSX elements during compilation to inject minimal data attributes that enable component tracking, authorship detection, and stable element identification in visual editing tools.
npm install babel-plugin-jsx-metadata
Add the plugin to your Babel configuration:
// babel.config.js
module.exports = {
plugins: [
['babel-plugin-jsx-metadata', {
filename: 'src/Component.js', // Current file being processed
skipFiles: ['SkipMe.jsx'] // Optional: files to skip
}]
]
};
The plugin identifies JSX component return values by traversing:
FunctionDeclaration
nodes with JSX returnsVariableDeclarator
nodes with arrow functions returning JSX
For each JSX component, the plugin:
-
Adds component metadata to root elements:
data-component-file
: Source file pathdata-component-name
: Component name (e.g., "Button", "Hero")data-editor-id
: Stable 12-character hash for persistent element tracking
-
Adds ownership tracking to child elements:
data-rendered-by
: File path of the authoring componentdata-editor-id
: Stable 12-character hash for persistent element tracking
The plugin wraps text content in components to enable selection:
// Before
<div>
Hello World
</div>
// After
<div data-rendered-by="src/Component.js" data-editor-id="7ca930b58636">
Hello World
</div>
// In Hero.js - Hero passes text to Button
<Button variant="primary">Get Started Today</Button>
// Button receives text from Hero and preserves authorship
<button data-component-file="src/components/Button.js" data-component-name="Button" data-editor-id="418a66f72141">
<span data-rendered-by="src/Hero.js" data-editor-id="8f4207890d1b"> {children} </span>
</button>
The plugin uses PascalCase detection to identify JSX components vs HTML elements:
-
JSX Components (PascalCase):
Button
,Header
,Card
- Skip adding
data-rendered-by
(they have their own component metadata)
- Skip adding
-
HTML Elements (lowercase):
div
,button
,span
- Add
data-rendered-by
pointing to the file that authored them - Add
data-editor-id
for stable element identification
- Add
The current file being processed. Used to set data-component-file
and data-rendered-by
attributes.
Array of filenames or patterns to skip processing. Defaults to []
.
{
filename: 'src/components/Button.tsx',
skipFiles: ['generated-components.js']
}
data-component-file
: File path where the component is defined (e.g.,"src/Button.js"
)data-component-name
: Component name (e.g.,"Button"
,"Hero"
)data-editor-id
: Stable 12-character hash for persistent element tracking (e.g.,"418a66f72141"
)
data-rendered-by
: File path of the component that authored this elementdata-editor-id
: Stable 12-character hash for persistent element tracking
Automatically wrapped text nodes get:
data-rendered-by
: File path of the authoring componentdata-editor-id
: Stable 12-character hash for persistent element tracking
The plugin generates stable, collision-resistant IDs for each element:
- Hash-based: Uses MD5 hash of element path and position for consistency
- Collision-safe: Automatically resolves ID conflicts within the same file
- Preservation: Keeps existing unique IDs when possible to avoid unnecessary changes
- File-scoped: IDs are unique within each file to prevent conflicts
The injected metadata enables:
- Element Selection: Click handlers use
data-component-name
anddata-editor-id
to identify components - File Navigation:
data-component-file
determines which file to open for editing - Persistent Tracking:
data-editor-id
provides stable element identification across code changes - Ownership Tracking:
data-rendered-by
determines which file authored each element - Text Editing: Wrapped text nodes can be selected and modified while preserving authorship
- Collision-free Editing: Hash-based IDs prevent conflicts when multiple editors work simultaneously
function componentDataPlugin(
api: ConfigAPI,
options: { filename?: string; skipFiles?: string[] }
): PluginObj
- Only processes direct JSX returns from components
- PascalCase detection may miss edge cases
- Adds spans that could affect styling
- Cross-component text authorship requires careful
{children}
handling - IDs are generated deterministically but may change if element structure changes significantly
# Build the plugin
npm run build
# Run tests
npm test
# Clean build artifacts
npm run clean
MIT