A fast, secure, modern, light-weight, and simple JS library for creating web components and more!
The preferred method of using Aegis is via import
from https://unpkg.com/aegisjsproject/core[@:version]
.
You may use Subresource Integrity,
but this is only recommended if you include a version in the script URL. For
convenience, you maybe desire using <script type="importmap">
in your development setup and a plug-in such as
@shgysk8zer0/rollup-import
when bundling for production.
Of course, you could always stick with the the more familiar package installation:
npm install aegisjsproject/core
That works just fine, should you prefer.
A final option is installing via a git submodule.
git submodule add https://github.com/AegisJSProject/core.git [:destination_path]
Submodules ultimately make little difference compared to the CDN or installing the
package, should you prefer that option. There are no dependencies, and you probably
just end up using a different source/URL for your <script>
or import
. The one
and only difference to consider is that this will not include the generated CommonJS
(use for require()
) version.
This library is designed with security, and specifically strict Content-Security-Policy and TrustedTypes
, in mind.
However, since the Sanitizer API is an
in-development API and new CSStyleSheet()
is not universally supported, the polyfills require some minimal adjustments to
any Content-Security-Policy:
For the recommended polyfills, allowing script from https://unpkg.com/@shgysk8zer0/
(you can be more restrictive/specific to https://unkg.com/@shgysk8zer0/polyfills@:version
) will
be necessary.
The recommended polyfill for new CSSStyleSheet()
and adoptedStyleSheets
requires
use of URL.createObejctURL()
. As such, style-src
is suggested to allow blob:
URIs.
For the sake of the Sanitizer API polyfill, if you have trusted-types
as part
of your CSP, you must allow empty#html
and empty#script
(for trustedTypes.emptyHTML
and trustedTypes.emptyScript
).
- All commits are PGP/GPG signed, ensuring the authenticity of the author
- All releases use Package Provenance, proving that the generated & published package was created by a specific (and signed) commit
This library relies on the Sanitizer API proposal
and Constructable Stylesheets.
At minimum, there MUST be a polyfill for Element.prototype.setHTML
.
The html
function returns a DocumentFragment
which cannot be directly reused
in multiple places. Should you wish to reuse the fragment and only generate it once,
please use cloneNode(true)
:
import { html } from '@aegisjsproject/core';
const tmp = html`<!-- Your HTML here -->`
document.querySelector('.my-component').append(tmp.cloneNode(true));
On the other hand, css
relies on adoptedStyleSheets
for Document
/ShadowRoot
,
which does allow for easy reuse without cloning. You may (and probably should)
reuse where possible. You may use either rootOrShadow.adoptedStyleSheets = [sheet]
or the provided addStyles(target, ...sheets)
or replaceStyles(target, ...sheets)
functions.
Since import attributes
/ import attributes
/ CSS and HTML Modules
are not yet standardized or implemented anywhere, this library allows for easy
HTML templates and styles via plain old ES/JS Modules:
import { html, css } from '@aegisjsproject/core';
export const template = html`<!-- Your Markup Here -->`;
export const sheet = css`/* Your Styles Here */`;
Since this relies on Element.prototype.setHTML
,
which is a developing proposal, Aegis will remain in pre-1.0.0/stable release until
that proposal is stable. Until then, you may use @shgysk8zer0/polyfills
or any compatible polyfill of your choosing.
Outside of usage in web components/custom elements, it is important to understand
how Constructable Stylesheets and adoptedStyleSheets
work. When using css
, stylesheets will be adopted by the Document
and apply
the same as any other stylesheet (not scoped). Should you want to scope your styles
outside of the context of web components (using ShadowRoot
), you should create
a unique selector and utilize that when composing your stylesheets:
import { css, html, getUniqueSelector, addStyles } from '@aegisjsproject/core';
const scope = getUniqueSelector();
document.body.append(html`
<div id="${scope}">
<span class="my-class">Hello, World!</span>
</div>
`);
addStyles(document, css`#${scope} .my-class {color: red;}`);
import { html, css, registerComponent, registerCallback, attachListeners, EVENTS } from '@aegisjsproject/core';
const styles = css`:host {
display: block;
}
p.joke {
font-family: system-ui;
padding: 1.3rem;
border-radius: 8px;
border: 1px solid #cacaca;
}
button {
cursor: pointer;
}`;
const render = registerCallback('dad-joke:render', event => event.target.getRootNode().host.render());
const template = html`<div part="container">
<div id="joke-container"></div>
<button type="button" class="btn btn-primary" part="update-btn" ${EVENTS.onClick}="${render}">Update</button>
</div>`;
registerComponent('dad-joke', class HTMLDataJokeElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.adoptedStyleSheets = [styles];
this.shadowRoot.append(attachListeners(template.cloneNode(true)));
}
connectedCallback() {
this.render();
}
async render({ signal } = {}) {
this.shadowRoot.getElementById('joke-container').replaceChildren(
html`<p class="joke">${await HTMLDataJokeElement.getJoke({ signal })}</p>`
);
}
static async getJoke({ signal } = {}) {
const resp = await fetch('https://icanhazdadjoke.com', {
headers: { Accept: 'text/plain' },
referrerPolicy: 'no-referrer',
signal,
});
return await resp.text();
}
});
The provided html
uses the default white-list for elements and attributes,
which does not allow for <svg>
, <math>
(MathML), or custom elements by default.
Should you have need of such features, you may use createHTMLParser()
with the
desired config/options.
Note: Directions and documentation for config options are not provided in these early release, as the Sanitizer API is not stable and is subject to change.
There is also an additional createCSSParser({ media, disabled, baseURI })
function
which returns a custom css
parsing function.
import { createHTMLParser, createCSSParser } from '@aegisjsproject/core';
const html = createHTMLParser({
allowElements: ['h1', 'h2', 'span', 'a', 'img', 'blockquote'],
allowAttributes: ['href', 'class', 'id', 'src'],
});
const css = createCSSParser({ media: '(prefers-color-scheme: dark)', baseURI: 'https://cdn.example.com' });
// `html` & `css` then function as the regular / exported function, but with their
custom white/black lists / config options.
Although onclick
and other event attributes are stripped out by the sanitizer,
you can still (more securely) add event listeners when authoring HTML.
This works by registering a callback (in a private Map
object) which stores the
name as the key and the registered callback/function as the value. registerCallback()
adds the name and callback to the map and returns the key. attachListeners()
can
then be used to call addEventListener()
for the given value of the attribute by
retrieving the function from the map.
NOTE: The attribute name is subject to change, so you should only use EVENTS.onClick
,
etc, instead of setting the attribute manually.
import { registerCallback } from '@aegisjsproject/core/callbackRegistry.js';
import { EVENTS, attachListeners } from '@aegisjsproject/core/events.js';
const log = registerCallback('log', console.log);
const logBtn = attachListeners(html`<button ${EVENTS.onClick}="${log}">Click Me!</button>`);
const backBtn = attachListeners(html`<button ${EVENTS.onCLick}="${registerCallback('back', () => history.back())}">Back</button>`
There is a provided svg
function, but it should be noted that all SVGs MUST
include <svg xmlns="http://www.w3.org/2000/svg">
. This is due to the requirements
of new DOMParser().parseFromString(svgContent, 'image/svg+xml')
. You may also
want to set viewBox
as necessary.