Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: enjikaka/webact
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.2.15
Choose a base ref
...
head repository: enjikaka/webact
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref

Commits on Sep 11, 2020

  1. Copy the full SHA
    c7a44b0 View commit details

Commits on Nov 30, 2021

  1. Merge pull request #3 from enjikaka/dependabot/npm_and_yarn/http-prox…

    …y-1.18.1
    
    Bump http-proxy from 1.18.0 to 1.18.1
    enjikaka authored Nov 30, 2021
    Copy the full SHA
    0bd2c41 View commit details
  2. Create codeql-analysis.yml

    enjikaka authored Nov 30, 2021
    Copy the full SHA
    575eab1 View commit details

Commits on Dec 14, 2021

  1. Add platform and target.

    enjikaka committed Dec 14, 2021
    Copy the full SHA
    7b0bd24 View commit details
  2. Copy the full SHA
    3a9b612 View commit details

Commits on Jan 5, 2022

  1. Copy the full SHA
    bcd3b4b View commit details
  2. also build doccs

    enjikaka committed Jan 5, 2022
    Copy the full SHA
    4d8a86b View commit details
  3. Fixaloo

    enjikaka committed Jan 5, 2022
    Copy the full SHA
    f85ca99 View commit details

Commits on Feb 3, 2022

  1. Copy the full SHA
    509787c View commit details

Commits on Feb 9, 2022

  1. Fix for #4

    enjikaka committed Feb 9, 2022
    Copy the full SHA
    6ac1fa6 View commit details
  2. Eslint fix

    enjikaka committed Feb 9, 2022
    Copy the full SHA
    0cbcb1a View commit details
  3. Fix redudant propChange

    enjikaka committed Feb 9, 2022
    Copy the full SHA
    383f945 View commit details

Commits on May 24, 2022

  1. Simplify html method

    enjikaka authored May 24, 2022
    Copy the full SHA
    3d792cd View commit details
  2. Fix cSS

    enjikaka authored May 24, 2022
    Copy the full SHA
    e7a9368 View commit details

Commits on Dec 5, 2022

  1. Copy the full SHA
    bfb946a View commit details
  2. build

    enjikaka committed Dec 5, 2022
    Copy the full SHA
    b92f530 View commit details
  3. 0.2.11

    enjikaka committed Dec 5, 2022
    Copy the full SHA
    7eb7940 View commit details
  4. 0.2.12

    enjikaka committed Dec 5, 2022
    Copy the full SHA
    791b541 View commit details
  5. build

    enjikaka committed Dec 5, 2022
    Copy the full SHA
    f15d412 View commit details
  6. 0.2.21

    enjikaka committed Dec 5, 2022
    Copy the full SHA
    a8d7df8 View commit details
  7. shadowRootMode

    enjikaka committed Dec 5, 2022
    Copy the full SHA
    48058b2 View commit details
  8. 0.2.22

    enjikaka committed Dec 5, 2022
    Copy the full SHA
    18564dc View commit details

Commits on Jan 20, 2023

  1. Fix useCSS and useHTML

    enjikaka committed Jan 20, 2023
    Copy the full SHA
    67baeb9 View commit details
  2. 0.2.23

    enjikaka committed Jan 20, 2023
    Copy the full SHA
    8d98477 View commit details

Commits on Feb 27, 2024

  1. Create LICENSE

    enjikaka authored Feb 27, 2024
    Copy the full SHA
    36d5715 View commit details

Commits on May 8, 2024

  1. Add jsr jsonc

    enjikaka committed May 8, 2024
    Copy the full SHA
    6f0ee75 View commit details

Commits on Feb 17, 2025

  1. Conditonally split version string

    enjikaka authored Feb 17, 2025
    Copy the full SHA
    e079e22 View commit details
70 changes: 70 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"

on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '27 7 * * 0'

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write

strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support

steps:
- name: Checkout repository
uses: actions/checkout@v2

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main

# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl

# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language

#- run: |
# make bootstrap
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"eslint.format.enable": true,
"eslint.lintTask.enable": true,
"eslint.run": "onSave",
"editor.formatOnSave": true,
"javascript.format.enable": false
}
24 changes: 24 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <https://unlicense.org>
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
build:
npx esbuild src/index.js --bundle --minify --sourcemap --format=esm --outfile=pkg/index.js
npx esbuild src/index.js --bundle --minify --sourcemap --format=esm --platform=browser --target=chrome58 --outfile=pkg/index.js
npx tsc
deno run --allow-run --allow-read --allow-write scripts/compile-package-json.ts
cp README.md pkg/README.md
cp pkg/index.js docs/webact.js

release:
npm version patch
make build
cd pkg && npm publish
git push --follow-tags

test:
test: build
cp pkg/index.js docs/webact.js
http-server docs -p 1444
Original file line number Diff line number Diff line change
@@ -1,51 +1,58 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Function component w/ advanced prop updates - Webact</title>
<title>Function component w/ prop updates - Webact</title>
</head>
<body>
<h1>Function component w/ prop updates</h1>
<pre>
<code>
import { registerFunctionComponent } from './webact.js';
import { registerFunctionComponent } from './webact.js';

async function NiceButton (({ value }) {
const { html, css, $, postRender } = this;
async function NiceButton (initialProps) {
const { html, css, propsChanged, postRender, $ } = this;

html`
&lt;button&gt;&lt;/button&gt;
`;
html`
&lt;button&gt;
&lt;slot&gt;&lt;/slot&gt;
&lt;/button&gt;
`;

css`
button {
background-color: blue;
color: white;
padding: 1rem;
border: 1px solid yellow;
border-radius: 4px;
}
`;
css`
button {
background-color: blue;
color: white;
padding: 1rem;
border: 1px solid yellow;
border-radius: 4px;
}
`;

postRender(() => {
$('button').textContent = value;
});
}
const updateContent = ({ value }) => {
// This will be the unnamed &lt;slot&gt;.
host.textContent = value;
};

registerFunctionComponent(NiceButton, {
observedAttributes: ['value']
});
postRender(() => updateContent(initialProps));
propsChanged(updateContent);
}

registerFunctionComponent(NiceButton, {
observedAttributes: ['value']
});
</code>
</pre>
<nice-button></nice-button>
<nice-button value="Time to come..."></nice-button>
<script type="module">
import { registerFunctionComponent } from './webact.js';

async function NiceButton ({ value }) {
const { html, css } = this;
async function NiceButton (initialProps) {
const { html, css, propsChanged, postRender, $ } = this;
const host = $(':host');

html`
<button>
${value}
<slot></slot>
</button>
`;

@@ -58,6 +65,16 @@ <h1>Function component w/ prop updates</h1>
border-radius: 4px;
}
`;

const updateContent = ({ value }) => {
// This will be the unnamed <slot>.
host.textContent = value;

console.log(initialProps, this.props, this);
};

postRender(() => updateContent(initialProps));
propsChanged(updateContent);
}

registerFunctionComponent(NiceButton, {
@@ -67,7 +84,7 @@ <h1>Function component w/ prop updates</h1>
<script>
const niceButton = document.querySelector('nice-button');;
setInterval(() => {
const val = Date.now();
const val = 'same value all the time';

niceButton.setAttribute('value', val);
}, 1000);
68 changes: 40 additions & 28 deletions docs/function-component-update.html
Original file line number Diff line number Diff line change
@@ -7,39 +7,48 @@
<h1>Function component w/ prop updates</h1>
<pre>
<code>
import { registerFunctionComponent } from './webact.js';
import { registerFunctionComponent } from './webact.js';

async function NiceButton (({ value }) {
const { html, css } = this;
async function NiceButton (initialProps) {
const { html, css, propsChanged, postRender, $ } = this;

html`
&lt;button&gt;
${value}
&lt;/button&gt;
`;
html`
&lt;button&gt;
&lt;slot&gt;&lt;/slot&gt;
&lt;/button&gt;
`;

css`
button {
background-color: blue;
color: white;
padding: 1rem;
border: 1px solid yellow;
border-radius: 4px;
}
`;
}
css`
button {
background-color: blue;
color: white;
padding: 1rem;
border: 1px solid yellow;
border-radius: 4px;
}
`;

registerFunctionComponent(NiceButton, {
observedAttributes: ['value']
});
const updateContent = ({ value }) => {
// This will be the unnamed &lt;slot&gt;.
host.textContent = value;
};

postRender(() => updateContent(initialProps));
propsChanged(updateContent);
}

registerFunctionComponent(NiceButton, {
observedAttributes: ['value']
});
</code>
</pre>
<nice-button></nice-button>
<nice-button value="Time to come..."></nice-button>
<script type="module">
import { registerFunctionComponent } from './webact.js';

async function NiceButton ({ value }) {
const { html, css, propsChanged, $ } = this;
async function NiceButton (initialProps) {
const { html, css, propsChanged, postRender, $ } = this;
const host = $(':host');

html`
<button>
@@ -57,12 +66,15 @@ <h1>Function component w/ prop updates</h1>
}
`;

propsChanged(({ value }) => {
const host = $();

const updateContent = ({ value }) => {
// This will be the unnamed <slot>.
host.textContent = value;
});

console.log(initialProps, this.props, this);
};

postRender(() => updateContent(initialProps));
propsChanged(updateContent);
}

registerFunctionComponent(NiceButton, {
8 changes: 4 additions & 4 deletions docs/webact.js

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions jsr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@enjikaka/webact",
"version": "0.2.23",
"exports": "./src/index.js"
}
578 changes: 568 additions & 10 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "webact",
"version": "0.2.10",
"version": "0.2.23",
"description": "A framework for efficiently handling web components",
"type": "module",
"module": "index.js",
@@ -20,7 +20,7 @@
"author": "Jeremy Karlsson <karlsson@jeremy.se>",
"license": "MIT",
"devDependencies": {
"esbuild": "^0.11.12",
"esbuild": "^0.15.18",
"eslint": "^6.8.0",
"eslint-config-tidal": "^1.2.0",
"http-server": "^0.12.1",
8 changes: 4 additions & 4 deletions scripts/compile-package-json.ts
Original file line number Diff line number Diff line change
@@ -4,15 +4,15 @@ const json = JSON.parse(data);
const entry = './index.js';

const process = Deno.run({
cmd: ["git", "describe", "--tags", "--abbrev=0"],
cmd: ["git", "describe", "--tags", "--abbrev=0"],
stdout: "piped"
});

const output = await process.output();
const version = new TextDecoder().decode(output);

json.version = version.split('v')[1].trim();
json.exports = {};
json.version = version.includes('v') ? version.split('v')[1].trim() : version;
json.exports = {};
json.exports['.'] = entry;
json.browser = entry;

@@ -24,4 +24,4 @@ if ('scripts' in json) {
delete json.scripts;
}

await Deno.writeTextFile('pkg/package.json', JSON.stringify(json, null , 2));
await Deno.writeTextFile('pkg/package.json', JSON.stringify(json, null, 2));
150 changes: 98 additions & 52 deletions src/functionComponent.js
Original file line number Diff line number Diff line change
@@ -9,15 +9,26 @@ const styleSheets = new Map();

const cssFetches = new Map();
const htmlFetches = new Map();
const lastPropChange = new Map();

/** @typedef FunctionComponentOptions
* @prop {string} [metaUrl]
* @prop {string[]} [observedAttributes]
* @prop {string} [name]
* @prop {ShadowRootMode} [shadowRootMode]
*/

/**
* @param {Function} functionalComponent
* @param {{ metaUrl: ?string, observedAttributes: string[] }} options
* @param {FunctionComponentOptions} options
* @returns {CustomElementConstructor}
*/
function generateFunctionComponent(functionalComponent, { metaUrl, observedAttributes, kebabName }) {
function generateFunctionComponent (
functionalComponent,
{ metaUrl, observedAttributes, name: kebabName, shadowRootMode }
) {
return class extends HTMLElement {
constructor() {
constructor () {
super();

this._postRender = undefined;
@@ -29,12 +40,14 @@ function generateFunctionComponent(functionalComponent, { metaUrl, observedAttri

document.addEventListener('esm-hmr:webact-function-component', () => {
this._hmrUpdate = true;
functionalComponent = HMROverride.has(kebabName) ? HMROverride.get(kebabName) : functionalComponent;
functionalComponent = HMROverride.has(kebabName) ?
HMROverride.get(kebabName) :
functionalComponent;
this._render();
});
}

set _html(documentFragment) {
set _html (documentFragment) {
if (!templates.has(kebabName) || this._hmrUpdate) {
const templateElement = document.createElement('template');

@@ -44,60 +57,71 @@ function generateFunctionComponent(functionalComponent, { metaUrl, observedAttri
}
}

get _html() {
return templates.has(kebabName) ? templates.get(kebabName).content.cloneNode(true) : null;
get _html () {
return templates.has(kebabName) ?
templates.get(kebabName).content.cloneNode(true) :
null;
}

set _css(cssStyleSheet) {
set _css (cssStyleSheet) {
if (!styleSheets.has(kebabName) || this._hmrUpdate) {
styleSheets.set(kebabName, cssStyleSheet);
}
}

get _css() {
get _css () {
return styleSheets.has(kebabName) ? styleSheets.get(kebabName) : null;
}

get cssPath() {
get cssPath () {
return (
this._componentPath &&
this._componentPath.replace(/\.(html|js)/gi, '.css')
);
}

get htmlPath() {
get htmlPath () {
return (
this._componentPath &&
this._componentPath.replace(/\.(css|js)/gi, '.html')
);
}

static get observedAttributes() {
static get observedAttributes () {
return observedAttributes;
}

/**
* @param {Record<string, string>} props
* @param {{ force: boolean }} options
*/
async _render(props) {
async _render (props) {
this._rendering = functionalComponent.apply(this.customThis, [props]);

if (this._rendering instanceof Promise) {
await this._rendering;
}

if (this._css) {
requestAnimationFrame(() => {
this._sDOM.adoptedStyleSheets = [this._css];
});
} else {
if (
'adoptedStyleSheets' in this._sDOM &&
this._css instanceof CSSStyleSheet
) {
requestAnimationFrame(() => {
this._sDOM.adoptedStyleSheets = [this._css];
});
}

if (this._css instanceof HTMLStyleElement) {
this._sDOM.appendChild(this._css);
}
} else if (document.location.href.includes('localhost')) {
console.warn(`<${kebabName}>: Missing CSS. Will render without it.`);
}

if (this._html) {
requestAnimationFrame(() => this._sDOM.appendChild(this._html));
} else {
} else if (document.location.href.includes('localhost')) {
console.warn(`<${kebabName}>: Missing HTML. Will render without it.`);
}

@@ -112,7 +136,11 @@ function generateFunctionComponent(functionalComponent, { metaUrl, observedAttri
}
}

get customThis() {
get _props () {
return attributesToObject(this.attributes);
}

get customThis () {
return {
/**
* @param {string[]} strings
@@ -174,7 +202,8 @@ function generateFunctionComponent(functionalComponent, { metaUrl, observedAttri
const response = await fetch(path);
const text = await response.text();

this.customThis.html([text]);
// eslint-disable-next-line no-unused-expressions
this.customThis.html`${text}`;
};

const promise = htmlFetching();
@@ -206,7 +235,8 @@ function generateFunctionComponent(functionalComponent, { metaUrl, observedAttri
const response = await fetch(path);
const text = await response.text();

this.customThis.css([text]);
// eslint-disable-next-line no-unused-expressions
this.customThis.css`${text}`;

cssFetches.delete(kebabName);
};
@@ -227,16 +257,11 @@ function generateFunctionComponent(functionalComponent, { metaUrl, observedAttri
this._propsChanged = method;
},
$: selector => {
if (
selector === undefined ||
selector === ':host'
) {
if (selector === undefined || selector === ':host') {
return this;
}

if (
selector === ':root'
) {
if (selector === ':root') {
return this._sDOM;
}

@@ -246,34 +271,40 @@ function generateFunctionComponent(functionalComponent, { metaUrl, observedAttri
};
}

async attributeChangedCallback() {
if (this._hasRendered) {
requestAnimationFrame(() => {
if (this._propsChanged instanceof Function) {
this._propsChanged(attributesToObject(this.attributes));
} else {
console.error(`
<${kebabName}>: Attribute has changed and you are observing attributes, but not handling them in a propsChanged handler.
Remove observedAttributes or or actually use them.
`);
}
});
} else {
console.warn(`
<${kebabName}>: Attribute has changed and you are observing attributes, but the attributes changed before render.
`);
async attributeChangedCallback () {
if (this._rendering instanceof Promise) {
await this._rendering;
}

requestAnimationFrame(() => {
if (this._propsChanged instanceof Function) {
const serializedChange = JSON.stringify(attributesToObject(this.attributes));

// Avoid emitting propChanges when no props change, even if the attributeChangedCallback is run.
if (lastPropChange.get(kebabName) === serializedChange) {
return;
}

this._propsChanged(attributesToObject(this.attributes));
lastPropChange.set(kebabName, serializedChange);
} else if (document.location.href.includes('localhost')) {
console.error(`
<${kebabName}>: Attribute has changed and you are observing attributes, but not handling them in a propsChanged handler.
Remove observedAttributes or or actually use them.
`);
}
});
}

async connectedCallback() {
async connectedCallback () {
this._sDOM = this.attachShadow({
mode: 'closed'
mode: shadowRootMode || 'closed'
});

this._render(attributesToObject(this.attributes));
this._render(this._props);
}

disconnectedCallback() {
disconnectedCallback () {
if (this._deRender) {
this._deRender();
}
@@ -283,23 +314,38 @@ function generateFunctionComponent(functionalComponent, { metaUrl, observedAttri

/**
* @param {Function} functionComponent
* @param {{ metaUrl: ?string, observedAttributes: string[], name: ?string }} options
* @param {FunctionComponentOptions} options
* @returns {string} Custom element tag name.
*/
export default function registerFunctionComponent(functionComponent, { metaUrl, observedAttributes, name } = { metaUrl: undefined, observedAttributes: [], name: undefined }) {
export default function registerFunctionComponent (
functionComponent,
{ metaUrl, observedAttributes, name, shadowRootMode } = {
metaUrl: undefined,
observedAttributes: [],
name: undefined,
shadowRootMode: 'closed'
}
) {
const kebabName = name || camelToKebabCase(functionComponent.name);

if (customElements.get(kebabName)) {
if (componentsByUs.includes(kebabName)) {
HMROverride.set(kebabName, functionComponent);
document.dispatchEvent(new CustomEvent('esm-hmr:webact-function-component'));
document.dispatchEvent(
new CustomEvent('esm-hmr:webact-function-component')
);
} else {
throw new Error(`
Some else has already registered <${kebabName}> in the custom element registry.
`);
}
} else {
const customElementClass = generateFunctionComponent(functionComponent, { metaUrl, observedAttributes, kebabName });
const customElementClass = generateFunctionComponent(functionComponent, {
metaUrl,
observedAttributes,
name: kebabName,
shadowRootMode
});

customElements.define(kebabName, customElementClass);
componentsByUs.push(kebabName);
51 changes: 36 additions & 15 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -52,32 +52,53 @@ export function stringToElements (string) {
* @param {any[]} rest
* @returns {CSSStyleSheet}
*/
export function css (strings, ...rest) {
const text = Array.isArray(strings) ?
strings.reduce((acc, curr, i) => {
return acc + (rest[i] ? curr + rest[i] : curr);
}, '') :
strings;
export function modernCSS () {
const sheet = new CSSStyleSheet();

// @ts-ignore
sheet.replace(text);
sheet.replace(String.raw(...arguments));

return sheet;
}

/**
* @param {string[] | string} strings
* @param {any[]} rest
* @returns {HTMLStyleElement}
*/
export function oldCSS () {
const style = document.createElement('style');

style.innerText = String.raw(...arguments);

return style;
}

/**
* @param {string[] | string} strings
* @param {any[]} rest
* @returns {CSSStyleSheet}
*/
export function css (strings, ...rest) {
let modern = false;

try {
// eslint-disable-next-line no-new
new CSSStyleSheet();
modern = true;
} catch (e) {
modern = false;
}

return modern ? modernCSS(strings, ...rest) : oldCSS(strings, ...rest);
}

/**
* @export
* @param {string[] | string} strings
* @param {any[]} rest
* @returns {DocumentFragment}
*/
export function html (strings, ...rest) {
const text = Array.isArray(strings) ?
strings.reduce((acc, curr, i) => {
return acc + (rest[i] ? curr + rest[i] : curr);
}, '') :
strings;

return stringToElements(text);
export function html () {
return stringToElements(String.raw(...arguments));
}