![Create React App Officially Deprecated Amid React 19 Compatibility Issues](https://cdn.sanity.io/images/cgdhsj6q/production/04fa08cf844d798abc0e1a6391c129363cc7e2ab-1024x1024.webp?w=400&fit=max&auto=format)
Security News
Create React App Officially Deprecated Amid React 19 Compatibility Issues
Create React App is officially deprecated due to React 19 issues and lack of maintenance—developers should switch to Vite or other modern alternatives.
data-double-dash
Advanced tools
A lightweight, HTML-first and type-safe front-end framework.
data-double-dash
is a great MPA front-end solution when working with templating engines (such as ejs
, handlebars
, blade
, twig
, jinja
, etc) or plain HTML. Once a component is defined it is automatically mounted wherever you need it in your HTML (using attributes starting with "data--"
).
Each component is self-contained, having its own state, event handlers, lifecycle hooks and more.
When a DOM element which you mounted your component on is deleted (with DOM APIs such as element.remove()
), all of its attached event listeners, intervals and timeouts are automatically removed.
A very basic example showing how to create a simple counter :
ddd({
name: "counter",
// type validation schema for the component's props
props: ddd.object({
step: ddd.number(),
}),
// initial state
state: {
count: 0,
},
// event handlers
handlers: {
increment(event, { element, props, state }) {
state.count += props.step // increment count
element.textContent = state.count.toString() // update UI
},
},
})
<button
data--counter='{ "step": 1 }'
data--counter--click='{ "handler": "increment" }'
>
0
</button>
# npm
npm install data-double-dash
# yarn
yarn add data-double-dash
# pnpm
pnpm add data-double-dash
The manager monitors DOM changes and is responsible for registering/unregistering templates and mounting/unmounting components. Unless you have a specific use case that requires to manually control these steps you probably don't need to interact with it.
Templates are registered using the ddd
function, they are used by the manager as a blueprint to automatically create its corresponding components in the DOM.
The name is a unique identifier for your template, it must only contain lower case alphanumeric characters and single hyphens. It is the only mandatory property for a template. If a template with the same name has already been registered, the registration will be ignored.
Components will be mounted when they match the name of their template, for example if the name of a template is "foo-bar"
all elements with a data--foo-bar
attribute name will be mounted.
ddd({
name: "carousel",
})
<div data--carousel></div>
When declaring a component in your HTML, you can configure it by giving it properties, for example data--carousel='{ "loop": true, "autoplay": true }'
. Providing a type schema will validate the props, this will ensure they are of the expected type. You can also choose to not provide any schema, however if you do so you won't have type safety.
ddd({
name: "carousel",
props: ddd.object({
loop: ddd.boolean(),
autoplay: ddd.boolean({ optional: true }),
}),
})
<div data--carousel='{ "loop": true }'></div>
The structure is an element schema allowing you to enforce a certain element structure (mandatory attributes, required children, etc) when the component is created.
ddd({
name: "carousel",
element: {
tag: "ul",
attributes: {
class: {
value: /carousel/
optional: true
}
}
children: [
tag: "li"
min: 0,
children: [
{
tag: "img",
max: 1
}
]
]
}
})
Refs are a way to quickly access key elements. You declare them in the template using the following syntax in an array:
"<ul>"
or "<div>"
."foo-bar"
."[]"
which will transform your ref to an element into a ref to an array of elements or "?"
to make the ref optional.For example this is an array of valid refs ["foo", "bar?", "baz[]", "<section>fizz", "<div>buzz[]"]
ddd({
name: "carousel",
refs: ["<ul>image-list", "<ul>dot-list", "<img>images[]"],
handlers: {
display(event, component, params) {
component.refs["image-list"] satisfies HTMLUListElement
component.refs["dot-list"] satisfies HTMLUListElement
component.refs.images satisfies HTMLImageElement[]
},
},
})
<div data--carousel>
<ul data--carousel.image-list>
<li><img data--carousel.images src="..." /></li>
<li><img data--carousel.images src="..." /></li>
</ul>
<ul data--carousel.dot-list>
<li>
<button
data--carousel--click='{ "handler": "display", "params": { "slide": 0 } }'
>
1
</button>
<button
data--carousel--click='{ "handler": "display", "params": { "slide": 1 } }'
>
2
</button>
</li>
</ul>
</div>
The state of a component only refers to its variables and methods. Unlike frameworks like React data-double-dash
is not reactive, state changes will not automatically update the DOM.
When creating a template you can provide its initial state, either directly as an object or using a callback which receives the current component as a parameter including props.
If the typeof
your state is "object"
, then you can use it. In other words you can have regular objects, but also arrays, classes (including anonymous classes new (class { /* ... */ })()
) or your favourite state library.
ddd({
name: "carousel",
props: ddd.object({
loop: ddd.boolean(),
autoplay: ddd.boolean({ optional: true }),
}),
state: ({ props }) => {
return {
currentSlide: 0,
pauseOnHover: props.autoplay ?? false,
}
},
})
Handlers are methods which handle all the component associated events.
Events are defined in the HTML and may or may not have an associated handler, for example data--carousel--click='{ "handler": "display", "params": { "slide": 3 } }'
.
An handler can be defined as a function or as an object, when defining it as an object you can have validation on the event params
and detail
property for CustomEvent
s.
ddd({
name: "carousel",
props: ddd.object({
loop: ddd.boolean(),
autoplay: ddd.boolean({ optional: true }),
}),
state: ({ props }) => {
return {
currentSlide: 0,
pauseOnHover: props.autoplay ?? false,
}
},
handlers: {
display: {
params: ddd.object({
slide: ddd.number(),
}),
handler(event, component, params) {
component.state.currentSlide = params.slide
// ...
},
},
},
})
<div data--carousel='{ "loop": true, "autoplay": false }'>
<ul>
<li><img src="..." /></li>
<li><img src="..." /></li>
</ul>
<ul>
<li>
<button
data--carousel--click='{ "handler": "display", "params": { "slide": 0 } }'
>
1
</button>
<button
data--carousel--click='{ "handler": "display", "params": { "slide": 1 } }'
>
2
</button>
</li>
</ul>
</div>
Lifecycle hooks are methods allowing you to run custom code at a certain point of the lifecycle of a template/component. These hooks are :
register
: This hook executes right after the template has succesfully been registered/created.
mount
: This hook executes right after a component has been mounted, it receives that component as a parameter.
unmount
: This hook executes right after a component's event listeners, intervals and timeouts have been removed and just before it gets unmounted, it receives that component as a parameter.
unregister
: This hook executes right after the template has succesfully been unregistered/removed.
ddd({
name: "foo-bar",
register() {
// ...
},
mount(component) {
// ...
},
unmount(component) {
// ...
},
unregister() {
// ...
},
})
As seen in the templates documentation, components are instanciated in every DOM element with a corresponding attribute. When handling a component you will have access to the following properties.
The template is a string refering to the unique name of the template this component originated from. This is the name you use to declare a new component in your HTML.
<div data--template-name>
This is a component for the "template-name" template.
</div>
The element property is a reference to the element on which the component is mounted.
<div data--component>
Here the component's element property will refer to this div.
</div>
The props of a component is an object passed when declaring your component.
<div data--component='{ "foo": true, "bar": false }'>
This component will have access to a props object with two properties, one
being `foo` which will be `true` and the other being `bar` which will be
`false`.
</div>
You can access the refs declared in the template by using the component.refs
object
The state is the current state of your component. The initial state will be equal to what has been defined in the template, however you are free to mutate it over time as needed.
You can declare component events in your HTML using the following syntax :
data--
: Start with the framework identifier.template--
: Then the template name and two dashes to indicate an event for that component.target.
: This part is optional, this is the target to put the event listener on. The available event targets are : ""
(default target corresponding to the current element), "window"
, "document"
or the name of a template (this will attach the event listener to the closest element with a component of that type).type
: The event type, for example "click"
.<div data--component data--component--window.resize='{ "handler": "onResize" }'>
This will call the "onResize" handler on window resize on the "component"
template.
</div>
<div data--file-drop>
<ul
data--file-list
data--file-list--file-drop.drop='{ "handler": "onFileDrop" }'
>
<li>
When the parent "file-drop" component emits a "drop" event the
"onFileDrop" handler will be executed.
</li>
</ul>
</div>
ddd
function ddd(
template: Template,
options: { target?: Node; control?: "auto" | "manual" | "full" },
): () => void // returns a function to remove the template and all its components
template.name
: The unique name of the template.
template.props
: An optional schema to validate the props.
template.state
: An optional object or a method returning an object as the initial state of a component.
template.element
: An optional element schema to enforce a certain element structure when the component is created.
// Initial state providing function
function (
component: {
template: string,
element: Element,
// schema validated props found in the component's HTML attribute :
props: Record<string, unknown>
}
): object
template.handlers
: An optional object containing either event listener methods or objects containing the event listener method and validation schemas.// Handler as a method :
function (
event: Event,
component: {
template: string
element: Element
// schema validated props found in the component's HTML attribute :
props: Record<string, unknown>
// current state for the component :
state: object
// dispatch a CustomEvent on `element` that bubbles up the DOM tree :
dispatch: (type: string, detail?: { [key: string]: unknown }, options?: { cancelable?: boolean; composed?: boolean }) => boolean
// returns a function to remove the timeout :
timeout: (handler: (...args: any[]) => unknown, timeout: number) => () => void
// returns a function to remove the interval :
interval: (handler: (...args: any[]) => unknown, timeout: number) => () => void
},
// params found in the event's HTML attribute :
params: Record<string, unknown>
): any
// Handler as an object :
{
// constructor or array of constructors extending EventTarget used to validate the handler's event.target
event?: EventTargetConstructor | EventTargetConstructor[],
// constructor or array of constructors extending Event used to validate the handler's event
event?: EventConstructor | EventConstructor[],
// schema used to validate CustomEvent's detail property :
detail?: Schema,
// schema to validate the params found in the event's HTML attribute :
params?: Schema,
handler: function (
event: Event,
component: {
template: string
element: Element
// schema validated props found in the component's HTML attribute :
props: Record<string, unknown>
// current state for the component :
state: object
// dispatch a CustomEvent on `element` that bubbles up the DOM tree :
dispatch: (type: string, detail?: { [key: string]: unknown }, options?: { cancelable?: boolean; composed?: boolean }) => boolean
// returns a function to remove the timeout :
timeout: (handler: (...args: any[]) => unknown, timeout: number) => () => void
// returns a function to remove the interval :
interval: (handler: (...args: any[]) => unknown, timeout: number) => () => void
},
// params found in the event's HTML attribute :
params: Record<string, unknown>
): any
}
template.register
: The optional register lifecycle hook method. Executes on succesful template creation.
template.mount
: The optional mount lifecycle hook method. Executes on succesful component mount.
function (component: Component): any
template.unmount
: The optional unmount lifecycle hook method. Executes on succesful component unmount.function (component: Component): any
template.unregister
: The optional unregister lifecycle hook method. Executes on succesful template removal.ddd.get
function (): Manager
ddd.get.components
function (element: Element): { [template: string]: Component }
ddd.get.component
function (element: Element, template: string): Component | null
ddd.element
function (
html: string,
options?: {
// allows you to replace substrings in `html` before converting it to a node
// this is useful for placeholders such as <div id="__ID__">...</div>
replace?: Record<string, string>;
// defaults to true, removes everything that can execute code from the HTML
sanitize?: boolean;
}
): DocumentFragment
ddd.element.validate
function (
element: Element,
schema: ElementSchema<U>,
): asserts element is GetElementFromHtmlTag<U>
ddd.validate
function (data: unknown, schema: Schema): asserts data is ResolvedSchema<Schema>
ddd.string
// creates a schema to validate strings
function (
options?: {
optional: true // makes this schema optional in an object
predicate: (data: string) => boolean // additional custom validation function
}
): StringSchema
ddd.number
// creates a schema to validate numbers
function (
options?: {
optional: true // makes this schema optional in an object
predicate: (data: number) => boolean // additional custom validation function
}
): NumberSchema
ddd.boolean
// creates a schema to validate booleans
function (
options?: {
optional: true // makes this schema optional in an object
predicate: (data: boolean) => boolean // additional custom validation function
}
): BooleanSchema
ddd.undefined
// creates a schema to validate undefined values
function (
options?: {
optional: true // makes this schema optional in an object
predicate: (data: undefined) => boolean // additional custom validation function
}
): UndefinedSchema
ddd.null
// creates a schema to validate null values
function (
options?: {
optional: true // makes this schema optional in an object
predicate: (data: null) => boolean // additional custom validation function
}
): NullSchema
ddd.nullish
// creates a schema to validate nullish values
function (
options?: {
optional: true // makes this schema optional in an object
predicate: (data: null | undefined) => boolean // additional custom validation function
}
): NullishSchema
ddd.function
// creates a schema to validate functions
function (
options?: {
optional: true // makes this schema optional in an object
predicate: (data: (...args: any[]) => unknown) => boolean // additional custom validation function
}
): FunctionSchema
ddd.instance
// creates a schema to validate instances of a constructor
function (
constructor: new (...args: any[]) => Object // for example `File` or `Element`
options?: {
optional: true // makes this schema optional in an object
predicate: (data: CorrespondingInstance) => boolean // additional custom validation function
}
): InstanceSchema
ddd.object
// creates a schema to validate object
function (
object: { [property: string]: Schema }
options?: {
optional: true // makes this schema optional in an object
predicate: (data: CorrespondingObject) => boolean // additional custom validation function
}
): ObjectSchema
ddd.array
// creates a schema to validate arrays
function (
types: Schema | Schema[] // allowed types in the array
options?: {
optional: true // makes this schema optional in an object
predicate: (data: CorrespondingArray) => boolean // additional custom validation function
}
): ArraySchema
ddd.union
// creates a schema to validate union (an union is when there's more than a single valid type)
function (
types: Schema | Schema[] // allowed types
options?: {
optional: true // makes this schema optional in an object
predicate: (data: CorrespondingUnion) => boolean // additional custom validation function
}
): UnionSchema
ddd.record
// creates a schema to validate records (records are objects with arbitrary keys)
function (
types: Schema | Schema[] // allowed types in the record values
options?: {
optional: true // makes this schema optional in an object
predicate: (data: CorrespondingRecord) => boolean // additional custom validation function
}
): ArraySchema
ddd.set
// creates a schema to validate sets
function (
types: Schema | Schema[] // allowed types in the set
options?: {
optional: true // makes this schema optional in an object
predicate: (data: CorrespondingSet) => boolean // additional custom validation function
}
): SetSchema
ddd.map
// creates a schema to validate maps
function (
keyTypes: Schema | Schema[], // allowed types for keys in the map
valueTypes: Schema | Schema[] // allowed types for values in the map
options?: {
optional: true // makes this schema optional in an object
predicate: (data: CorrespondingSet) => boolean // additional custom validation function
}
): MapSchema
ddd.any
// creates a schema to accept any value
function (
options?: {
optional: true // makes this schema optional in an object
predicate: (data: unknown) => boolean // additional custom validation function
}
): AnySchema
![]() Alexandre Fernandez |
FAQs
A lightweight, HTML-first and type-safe front-end framework.
The npm package data-double-dash receives a total of 775 weekly downloads. As such, data-double-dash popularity was classified as not popular.
We found that data-double-dash demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Create React App is officially deprecated due to React 19 issues and lack of maintenance—developers should switch to Vite or other modern alternatives.
Security News
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.