Scoped HTML is a DOM feature that let's an element establish its own naming context for descendant elements. It makes it possible to keep IDs out of HTML's global namespace and gives us a document that is structured as a hierarchy of scopes and subscopes.
Scopes are designated with the namespace
Boolean attribute.
The following ID is scoped:
<div namespace>
<div>
<div scoped:id="some-id"></div>
</div>
</div>
At scale, what we get is a hierarchy of scopes and subscopes.
<article namespace>
<section scoped:id="europe" namespace>
<div scoped:id="about">About Europe</b></div>
<div scoped:id="countries">Countries in Europe</div>
</section>
<section scoped:id="asia" namespace>
<div scoped:id="about">About Asia</b></div>
<div scoped:id="countries">Countries in Asia</div>
</section>
</article>
A mental model of the hierarchy would be:
continents
|- europe
| |- about
| |- countries
|- asia
|- about
|- countries
Scoped HTML comes with a namespace API that models scope hierarchies.
let continents = document.querySelector('#continents');
let europe = continents.namespace.europe;
let asia = continents.namespace.asia;
let aboutAfrica = continents.namespace.asia.namespace.about;
This gives an application a more bankable tree than the DOM tree as it lets a UI block hide its implementation details while exposing its relevant parts by role.
An element's .namespace
property is implemented as a live object that reflects the element's namespace tree in real time. CHTML also supports the Observer API for change detection; Obs.observe()
can thus be used to observe when IDs enter or exit the namespace.
Obs.observe(continents.namespace, changes => {
console.log(changes.map(change => change.name));
});
With the code below, our observer above should report having added a new ID africa
to the namespace.
continents.append('<section id="africa"></section>');
Scoped CSS is currently a subject of discussion for CHTML. Please submit an issue on our github repo should you have a suggestion.
Scoped JS is a DOM feature that makes it possible to scope a script to its immediate host element and completely out of the global browser scope.
Scoped scripts have their this
variable implicitly bound to their host element. They are defined with the scoped
MIME type.
<div id="alert">
<script type="scoped">
</script>
</div>
This lets us place behaviours just where we need them! This way, we are able to keep the main application layer void of the implementation details of the UI.
Here's an #alert component with a "remove* feature.
<div id="alert">
<div class="message"></div>
<div class="exit" title="Close this message.">X</div>
<script type="scoped">
// details of how the #alert block should behave...
this.querySelector('.exit').addEventListener('click', () => {
this.remove();
});
</script>
</div>
Besides the this
variable being implicitly bound to the script's host element, other variables in a scoped script are to be explicitly-bound to external values; variables are bound by name.
Below, we're implementing a message
variable in our #alert component.
<body>
<div id="alert">
<div class="message"></div>
<div class="exit" title="Close this message.">X</div>
<script type="scoped">
// where to place the message within the alert block...
this.querySelector('.message').innerHTML = message;
// details of how the alert block should behave...
this.querySelector('.exit').addEventListener('click', () => {
this.remove();
});
</script>
</div>
<script>
document.querySelector('#alert').bind({
message: 'This task is now complete!',
});
</script>
</body>
As shown above, an application simply binds its hard-earned values and is done!
Scoped JS follows the normal top-down execution of a script. Calling the .bind()
method with different variable-bindings reruns the script top-down. But as a UI binding langauge, it also features Selective Execution where an update to a variable gets to rerun only the corresponding statements within the script - skipping the other statements. This makes for the most-efficient way to keep a block of the UI in sync with little updates from an application.
To update a variable or multiple variables, call .bind()
with a params
object as a second paremeter and set params.update
to true
.
alertEl.bind({
variable2: 'New value',
variable5: 'New value',
}, {update:true});
Also, Scoped JS exposes a new DOM property .bindings
for selectively updating an element's bindings.
alertEl.bindings.variable5 = 'New value',
This is illustrated in the clock below.
<body>
<div id="clock">
<div class="greeting"></div>
<div class="current-time"></div>
<script type="scoped">
this.querySelector('.greeting').innerHTML = greeting;
this.querySelector('.current-time').innerHTML = currentTime;
</script>
</div>
<script>
let clockEl = document.querySelector('#clock');
clockEl.bind({
greeting: 'Good Afternoon!',
currentTime: '00:00:00',
});
setInterval(() => {
clockEl.bindings.currentTime = (new Date).toLocaleString();
}, 100);
</script>
</body>
Scoped JS also supports the Observer API for object observability. With Observer, Scoped JS is able to respond to mutations made directly to the bound data object. So, the #clock above could be ticked by directly updating the data object.
<script>
let clockState = {
greeting: 'Good Afternoon!',
currentTime: '00:00:00',
};
document.querySelector('#clock').bind(clockState);
setInterval(() => {
Obs.set(clockState, 'currentTime', (new Date).toLocaleString());
}, 100);
</script>
Statements may also reference deep mutations made on the bound data object, as in the clock.currentTime
reference below.
<body>
<div id="clock">
<div class="greeting"></div>
<div class="current-time"></div>
<script type="scoped">
this.querySelector('.greeting').innerHTML = clock.greeting;
this.querySelector('.current-time').innerHTML = clock.currentTime;
</script>
</div>
<script>
let state = {
clock: {
greeting: 'Good Afternoon!',
currentTime: '00:00:00',
},
};
document.querySelector('#clock').bind(state);
setTimeout(() => {
Obs.set(state.clock, 'currentTime', (new Date).toLocaleString());
}, 100);
</script>
</body>
Within the script, the dependency chain is followed even when broken into local variables. Below, a change to clock.currentTime
will still propagate through variable1
and variable2
. (The first and last statements in the script are left untouched touched, as expected.)
<body>
<div id="clock">
<div class="greeting"></div>
<div class="current-time"></div>
<script type="scoped">
this.querySelector('.greeting').innerHTML = clock.greeting;
let variable1 = clock.currentTime;
let variable2 = variable1;
this.querySelector('.current-time').innerHTML = variable2;
this.style.color = 'blue';
</script>
</div>
</body>
By default, scoped scripts have no access to anything besides what is explicitly bound into the scope. But they also have an idea of a global scope - that is, bindings seen by every scoped script. This global scope is created by binding on the document
object itself, using a new document.bind()
method.
document.bind({
greeting: 'Good Afternoon!',
});
To update a global or multiple globals, call document.bind()
with a params
object as a second paremeter and set params.update
to true
.
document.bind({
greeting: 'Good Afternoon!',
}, {update:true});
There is also the document.bindings
property for selectively updating globals.
document.bindings.greeting = 'Good Evening!';
By design, Scoped JS parses scoped scripts immediately they land on the DOM, but runs them only after the global scope has been initialized with document.bind()
or the document.bindings
property. Newer scipts are run immediately after this global runtime initilization. But the runtime of an individual script will begin before the global one on calling the element's .bind()
method or assigning to its .bindings
property.
Alternatively, the autorun=true
directive may be set on the CHTML META tag. The autorun
Boolean attribute may also be set on individual script elements.
<html>
<head>
<meta name="chtml" content="autorun=true;" />
</head>
<body>
<div id="alert">
<script type="scoped" autorun>
...
</script>
</div>
</body>
</html>
Also, it is allowed for an element to receive bindings before its scoped script is appended or is ready to run. The element's runtime begins the first time both are available.
alertEl.bind({
message: 'This task is now complete!',
});
alertEl.append('<script scoped>this.innerHTML = message</script>');
Scoped JS features a way to handle syntax or reference errors that may occur with scoped scripts. Normally, these are shown in the console as warnings. But they can be silently ignored by setting a directive on the CHTML META tag. Individual scripts may also be given a directive, to override whatever the global directive is.
<html>
<head>
<meta name="chtml" content="script-errors=0;" />
</head>
<body>
<h1></h1>
<script type="scoped" errors="1">
this.querySelectorSelectorSelector('h1').innerHTML = headline;
</script>
</body>
</html>
The script tag of a scoped script is not always needed for the lifetime of the page. They are discarded by default after parsing. But when a page is rendered on the server and has to be hydrated by the browser, it becomes necessary to retain these scripts for revival on the browser. This feature is designed to be explicitly turned on with a directive on the CHTML META tag.
<html>
<head>
<meta name="chtml" content="isomorphic=true;" />
</head>
<body>
<h1></h1>
<script type="scoped">
this.querySelector('h1').innerHTML = headline;
</script>
</body>
</html>
Now, this binding will always be there for when we run the code document.bind({headline: 'Hello World'})
- whether on the server and on the browser.
Environment-Specific Bindings
Sometimes, we want certain bindings to apply only on the server; sometimes, only on the browser. For example, animation is only a thing in the browser. This is the perfect use-case for conditionals.
<div>
<script type="scoped">
if (condition) {
this.animate(...);
}
</script>
</div>
Above, condition
could be a simple question about the current environment, and this can be acheived by simply binding a global variable, env
, for example: document.bind({env:'server', headline: 'Hello World'})
.
<div>
<script type="scoped">
if (env !== 'server') {
this.anumate([
{color:'red'},
{color:'blue'},
], {duration:600,});
}
</script>
</div>
HTML Partials is a DOM feature that lets us define, import, access, and compose with reusable HTML snippets using the template, partials, and slots paradigm.
A template is a collection of independent partials that can be consumed from anywhere in the main document.
<head>
<template name="template1">
<div id="partial-1"></div>
<div id="partial-2"></div>
</template>
</head>
An element in the main document, called the implementation block or the composition area, can define <partials-slot>
s, and then, point to a <template>
to have the template's partials mapped to its slots.
<html>
<head>
<template name="template1">
<div id="partial-2" partials-slot="slot-1"></div>
<div id="partial-2" partials-slot="slot-2"></div>
</template>
</head>
<body>
<div template="template1">
<h2>I have slots</h2>
<partials-slot name="slot-1"></partials-slot>
<div>
<partials-slot name="slot-2"></partials-slot>
</div>
</div>
</body>
</html>
Composition takes place and the slots are replaced by the template's partials. The block is said to have implemented the <template>
.
<html>
<head>
<template name="template1">
<div id="partial-2" partials-slot="slot-1"></div>
<div id="partial-2" partials-slot="slot-2"></div>
</template>
</head>
<body>
<div template="template1">
<h2>I have slots</h2>
<div id="partial-2" partials-slot="slot-1"></div>
<div>
<div id="partial-2" partials-slot="slot-2"></div>
</div>
</div>
</body>
</html>
An implementation block can implement another <template>
by simply pointing to it; <partials-slot>
s are disposed off of their previous slotted contents and recomposed from the new <template>
.
The <partials-slot>
element, even though replaced, is never really destroyed. It returns to its exact position whenever the last of its slotted elements get deleted, or whenever the slot has no corresponding partial in the next implemented <template>
.
HTML Partials also supports Default Slots. A template's direct children without an explicit partials-slot
attribute are slotted into the Default Slot in the implementation block.
Universal Slots
By default, slots are scoped to their containing implementation block. But the <partials-slot>
element may also be used independent of an implementation block to point to its own <template>
.
<html>
<head>
<template name="template1">
<div partials-slot="slot-1"></div>
<div partials-slot="slot-2"></div>
</template>
<template name="template2">
<div partials-slot="slot-1"></div>
<div partials-slot="slot-2"></div>
</template>
</head>
<body>
<div template="template1">
<h2>I have slots</h2>
<partials-slot name="slot-1"></partials-slot>
<div>
<partials-slot name="slot-1" template="template2"></partials-slot>
</div>
</div>
<partials-slot name="slot-2" template="template1"></partials-slot>
</body>
</html>
In HTML Partials, slots may be defined with extra properties that a slotted element can inherit. Every element slotted in its place will take on these properties.
Inherittable properties can be both attributes and content.
Attributes
A <partials-slot>
's attributes (other than the name
and template
attributes) are inheritted by every slotted element.
When a slotted element inherits attributes from a <partials-slot>
, inheritted attributes are made to take priority over any existing attributes. On inheriting single-value attributes, like the id
attribute, any such attribute is replaced on the slotted element. On inheriting space-delimitted attributes, like the class
attribute, new and non-duplicate values are placed after any existing values on the slotted element. On inheriting key/value attributes, like the style
attribute, new declarations are placed after any existing declarations on the slotted element (making CSS cascading work on the style
attribute).
Below, we are using Slot Attributes inheritance to recompose the same partial differently on each slotting - to adapt it for each usecase.
<html>
<head>
<template name="template1">
<div partials-slot="slot-1"></div>
<div partials-slot="slot-2"></div>
</template>
</head>
<body>
<div template="template1">
<partials-slot name="slot-1" id="headline" style="color:red"></partials-slot>
</div>
<partials-slot name="slot-1" template="template1" style="color:blue"></partials-slot>
</body>
</html>
Content
Normally, a <partials-slot>
can have default content that renders before slotting takes place. But this content can instead be defined as a new set of partials that can be implemented by slotted elements. This time, the <partials-slot>
element gets to act as the <template>
and the slotted element as the implementation block. (In the light/shadow terminology, this is the <partials-slot>
element acting as an element's Light DOM and the slotted element as its Shadow DOM.)
To implement a <partials-slot>
, a partial would set its template
attribute to the keyword @slot
instead of pointing to an actual <template>
element.
<html>
<head>
<template name="template2">
<!-- I am a recomposable partial. My ideal slot provides the partials for me -->
<div partials-slot="slot-1" template="@slot">
<partials-slot name="slot-1-1"></partials-slot>
</div>
<!-- I am a regular partial -->
<div partials-slot="slot-2"></div>
</template>
</head>
<body>
<div template="template1">
<!-- I am an implementable slot. My ideal partial defines slots -->
<partials-slot name="slot-1">
<div partials-slot="slot-1-1"></div>
</partials-slot>
</div>
</body>
</html>
Templates may be nested for organizational purposes.
<template name="template1">
<div partials-slot="slot1"></div>
<div partials-slot="slot2"></div>
<template name="nested1">
<div partials-slot="slot3"></div>
<div partials-slot="slot4"></div>
</template>
<template name="nested2">
<div partials-slot="slot5"></div>
<div partials-slot="slot6"></div>
</template>
</template>
Nested templates are referenced using a path notation:
<div template="template1/nested1">
</div>
Templates may reference remote content using the src
attribute.
**Remote file: http://localhost/templates.html**
<div partials-slot="slot-1"></div>
<div partials-slot="slot-2"></div>
<template name="extended">
<div partials-slot="slot-3"></div>
<div partials-slot="slot-4"></div>
</template>
<p></p>
Document: http://localhost
<head>
<template name="template1" src="/templates.html"></template>
</head>
Where remote templates are detected in a document, <partials-slot>
s are resolved after all <template>
s have loaded their content.
When rendering happens on the server and has to be serialized for the browser to take over, the browser must still be able to maintain references to all <partials-slot>
s, even those replaced on the server. HTML Partials addresses this by serializing <partials-slot>
elements as comment nodes (<!-- <partials-slot></partials-slot> -->
) with a view to recreating the original slot elements from these comments on getting to the browser. This way, composition is able to continue. Now in the browser, deleting a server-slotted element, for example, should trigger the restoration of the original <partials-slot>
element; changing the template
attribute of any element should dispose off all its server-slotted elements and recompose the block from the new referenced <template>
.
Before Rendering on the Server
<html>
<head>
<template name="template2">
<div partials-slot="slot-1"></div>
<div partials-slot="slot-2"></div>
</template>
</head>
<body>
<div template="template1">
<partials-slot name="slot-1" id="headline" style="color:red">Default Headline</partials-slot>
</div>
<partials-slot template="template1" name="slot-1" style="color:blue"></partials-slot>
</body>
</html>
After Rendering on the Server
<html>
<head>
<template name="template2">
<div partials-slot="slot-1"></div>
<div partials-slot="slot-2"></div>
</template>
</head>
<body>
<div template="template1">
<div partials-slot="slot-1" id="headline" style="color:red"></div>
<!-- <partials-slot name="slot-1" id="headline" style="color:red">Default Headline</partials-slot> -->
</div>
<div partials-slot="slot-1" style="color:blue"></div>
<!-- <partials-slot template="template1" name="slot-1" style="color:blue"></partials-slot> -->
</body>
</html>
Now on the Browser
Find and delete the server-slotted element with ID #headline
. The original <partials-slot>
element should now be restored and ready to be replaced the next time composition takes place.
<html>
<head>
<template name="template2">
<div partials-slot="slot-1"></div>
<div partials-slot="slot-2"></div>
</template>
</head>
<body>
<div template="template1">
<partials-slot name="slot-1" id="headline" style="color:red">Default Headline</partials-slot>
<!-- <partials-slot name="slot-1" id="headline" style="color:red">Default Headline</partials-slot> -->
</div>
<div partials-slot="slot-1" style="color:blue"></div>
<!-- <partials-slot template="template1" name="slot-1" style="color:blue"></partials-slot> -->
</body>
</html>
Enabliing Slots Serialization
Since slots serialization is only necessary for isomorphic pages, this feature is designed to be explicitly turned on on the CHTML META tag.
<html>
<head>
<meta name="chtml" content="isomorphic=true;" />
</head>
<body></body>
</html>
HTML Partials introduces a few new DOM properties for working with composition.
For the document object:
document.templatesReadyState
- (Much like the document.readyState
property.) This property reflects the document's loading status of remote templates:
loading
- This is the initial value of this property.complete
- This is the value of this property when templates are done loading, or when there are no remote templates at all.When the state of this property changes, the templatesreadystatechange
event is fired on the document object.
document.templates
- This property represents the list of <template>
s in the document. References to templates are maintained here by name. So document.templates.template1
should return the <template>
element used in the examples above.
For the <template>
element:
<template>.partials
- This property represents the list of partials defined by the <template>
. References to partials are maintained here by name. Unnamed partials are treated as having the name default. So, for the <template>
below,
<template name="template1">
<div partials-slot="one"></div>
<div partials-slot="two"></div>
<div partials-slot="default"></div>
<p></p>
</template>
accessing document.templates.template1.partials.one
should return an array containing the first <div>
; while document.templates.template1.partials.default
should return an array containing the last <div>
and <p>
.
<template>.templates
- This property represents the list of <template>
s nested within the <template>
. References to templates are maintained here by name.
<template name="template1">
<template name="nested1"></template>
<template name="nested2">
<div partials-slot="one"></div>
</template>
</template>
accessing document.templates.template1.templates.nested1
should return the first nested <template>
, while document.templates.template1.templates.nested2
the second nested <template>
. And the nesting can go on as much as code organization requires.
For every element:
element.template
- This property is a reference to the <template>
element pointed to by an element. So if an element implements a template as in <div template="html/temp"></div>
, then element.template
should be a reference to the <template>
at the module/temp
namespace; element.template.partials.default
should thus return an array like the above.For the <partials-slot>
element:
<partials-slot>.slottedElements
- (Much like the HTMLSlotElement.assignedElements()
method.) This property represents the list of partials slotted into a slot.<partials-slot>.resolve()
- This method, without arguments, is used to programatically resolve a <partials-slot>
from the appropriate <template>
given in context.<slot>.empty([silently = false])
- This method is used to programatically empty the slot of its partials, thereby triggering the restoration of the <partials-slot>
element itself. To empty the slot silently without restoring the original <partials-slot>
element, provide true
on the first parameter.For slotted elements:
element.slotReference
- (Much like the Slottable.assignedSlot
property.) This property gives a reference to the <partials-slot>
element an element was assigned to.Being a foundational technology, CHTML gives us every room to bring our own tooling. This example shows how we could use a DOM abstraction library, like jQuery, from scoped scripts.
Below, we're simply binding the $
variable globally for use in every scoped script.
<body>
<div namespace id="alert">
<div scoped:id="message"></div>
<script type="scoped">
$(this.namespace.message).html(message);
</script>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
document.bind({$: window.jQuery});
document.querySelector('#alert').bind({
message: 'This task is now complete!',
});
</script>
</body>
Tooling can also help us acheive more efficient DOM manipulation. Generally, surgically updating the DOM may have performance implications on the UI, as arising from layout thrashing (see this article on Web Fundamentals). But we also don't need as much as a Virtual DOM for this. A technique like that of fast DOM could just suffice.
This technique is natively implemented by the PlayUI library which has a jQuery-like API. We will now use PlayUI as a drop-in replacement for jQuery.
<body>
<div namespace id="alert">
<div scoped:id="message"></div>
<script type="scoped">
$(this.namespace.message).html(message).then(() => {
});
</script>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="//unpkg.com/@web-native-js/play-ui/dist/main.js"></script>
<script>
document.bind({$: window.WebNative.PlayUI});
document.querySelector('#alert').bind({
message: 'This task is now complete!',
});
</script>
</body>
Here are a few examples that you can try right now. You can simply copy and paste these codes to view on your browser. And you may edit your page live from the browser console.
Be sure to include the CHTML polyfill from the installation page.
This example makes an SPA of templates and slots composition. Below, we're using the two <template>
elements to each represent a route - each is holding partials that are unique to a route. Then we point the <body>
element to implement the <template>
whose namespace matches the current URL.
<html>
<head>
<template name="route">
<template name="home">
<h1 partials-slot="headline">
Welcome Home!
</h1>
<p partials-slot="content">
<a href="#/about">About Me</a>
</p>
</template>
<template name="about">
<h1 partials-slot="headline">
About Me!
</h1>
<p partials-slot="content">
<a href="#/home">Back to Home</a>
</p>
</template>
</template>
</head>
<body template="route/home">
<header></header>
<main>
<div id="banner">
<partials-slot name="headline">404</partials-slot>
</div>
<div>
<partials-slot name="content">Page not Found!</partials-slot>
</div>
</main>
<footer></footer>
<script>
window.addEventListener('popstate', e => {
let path = document.location.hash.substr(1);
document.body.setAttribute('template', 'route' + path);
});
</script>
</body>
</html>
Navigate to a route that does not begin with #/home
or #/about
, you should see the default content showing 404.
Below is a TODO list composed from a JavaScript array using Scoped HTML, Scoped JS in combination with the HTML Partials API.
It features the ability to add/remove items. For the remove feature, we'd let the <li>
element expose a remover button that the main <ul>
logic can bind to the removeItem()
method of the TODO application. For the add feature, we'd add a button to the TODO container that calls the addItem()
method of the TODO application.
We've also decided to use the Observer API and PlayUI's .itemize()
method that provides a simple way to keep the list container in sync with application items.
<html>
<head>
<title>A TODO Example</title>
<template name="items">
<li namespace>
<span scoped:id="desc"></span>
<button scoped:id="remover">Remove</button>
<script type="scoped">
this.namespace.desc.innerHTML = desc;
</script>
</li>
</template>
</head>
<body>
<div namespace id="todo">
<h2 scoped:id="title"></h2>
<ol scoped:id="items" template="items"></ol>
<button scoped:id="adder">Add</button>
<br />
<br />
<div>Ypu can also add items from the console. Open your console and type: <code>todoItems.push({desc:"New Item"})</code></div>
<script type="scoped">
this.namespace.title.innerHTML = title;
$(this.namespace.items).itemize(items, (el, data, index, isUpdate) => {
el.bind(data);
$(el).attr('data-index', index);
if (!isUpdate) {
el.namespace.remover.addEventListener('click', () => removeItem(el.getAttribute('data-index')));
}
});
this.namespace.adder.addEventListener('click', () => addItem());
</script>
</div>
<script src="//unpkg.com/@web-native-js/observer/dist/main.js"></script>
<script src="//unpkg.com/@web-native-js/play-ui/dist/main.js"></script>
<script src="//unpkg.com/@web-native-js/chtml/dist/main.js"></script>
<script>
let Obs = window.WN.Observer;
let $ = window.WN.PlayUI;
let todo = {
$,
title: 'My TODO',
items: [
{desc: 'Task-1'},
{desc: 'Task-2'},
{desc: 'Task-3'},
],
addItem() {
window.todoItems.push({desc: prompt('Task description'),});
},
removeItem(index) {
window.todoItems.splice(index, 1);
},
};
document.querySelector('#todo').bind(todo);
window.todoItems = Obs.proxy(todo.items);
</script>
</body>
</html>
This library is a polyfill for the proposed CHTML suite.
<script src="https://unpkg.com/@web-native-js/chtml/dist/main.js"></script>
Embed Individual Features - Find a build below for a specific CHTML feature.
Scoped HTML - <script src="https://unpkg.com/@web-native-js/chtml/dist/scoped-html.js"></script>
Scoped CSS - <script src="https://unpkg.com/@web-native-js/chtml/dist/scoped-css.js"></script>
Scoped JS - <script src="https://unpkg.com/@web-native-js/chtml/dist/scoped-js.js"></script>
HTML Partials - <script src="https://unpkg.com/@web-native-js/chtml/dist/html-partials.js"></script>
$ npm i -g npm
$ npm i --save @web-native-js/chtml
The installed package is designed to be initialized with the window object of the current browser or server evironment. To do this, import the ENV
object and assign the window object to it, then call the initializer.
import init, { ENV } from '@web-native-js/chtml';
ENV.window = window;
init();
Initialize Individual Features - Find a module below for a specific CHTML feature.
import init, { ENV } from '@web-native-js/chtml/src/scoped-html/index.js';
ENV.window = window;
init();
import init, { ENV } from '@web-native-js/chtml/src/scoped-css/index.js';
ENV.window = window;
init();
import init, { ENV } from '@web-native-js/chtml/src/scoped-js/index.js';
ENV.window = window;
init();
import init, { ENV } from '@web-native-js/chtml/src/html-partials/index.js';
ENV.window = window;
init();
Here is how CHTML could be initialized on DOM instances created on the server with a library like jsdom (using the window
object from the DOM instance.)
import init, { ENV } from '@web-native-js/chtml';
import jsdom from 'jsdom';
import fs from 'fs';
import path from 'path';
const documentFile = fs.readFileSync(path.resolve('./index.html'));
const JSDOM = new jsdom.JSDOM(documentFile.toString());
ENV.window = JSDOM.window;
init();
Let's now learn the core concepts of CHTML.
CHTML is a suite of new DOM features that brings native support for modern UI development paradigms: a component-based architecture, data binding, and reactivity. This lets us build elegant user interfaces using the web platform itself.
CHTML is being proposed as a W3C standard at the Web Platform Incubator Community Group based on this explainer.
Check this project out on GitHub.
To report bugs or request features, please submit an issue.
MIT.