Skip to content

Commit

Permalink
feat: no more data-global attribute
Browse files Browse the repository at this point in the history
BREAKING CHANGE: instead of data-global just paste your styles and script into the page
  • Loading branch information
infodusha committed Jul 28, 2024
1 parent 1ca4834 commit b99d785
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 71 deletions.
2 changes: 0 additions & 2 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ You can have multiple style tags in one file.

Inside you can use [:host](https://developer.mozilla.org/en-US/docs/Web/CSS/:host) pseudo-class to target the component itself.

You can use `data-global` attribute to make style global.

## Attributes

Inside your template you can use attributes to read component values itself (value would be set as child text):
Expand Down
10 changes: 10 additions & 0 deletions app-root.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<template>
<h1>Hello world</h1>
<app-test></app-test>
</template>

<style>
h1 {
color: red;
}
</style>
9 changes: 9 additions & 0 deletions app-test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<template>
<h1>Hello world test</h1>
</template>

<style>
h1 {
color: green;
}
</style>
6 changes: 0 additions & 6 deletions example/components/app-root.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@
}
</style>

<style data-global>
body {
margin: 4%;
}
</style>

<script type="module">
import { log } from "../scripts/utils.js";

Expand Down
6 changes: 6 additions & 0 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,11 @@
</head>
<body>
<app-root></app-root>

<style>
body {
margin: 4%;
}
</style>
</body>
</html>
45 changes: 15 additions & 30 deletions src/create-component.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import {
appendCssLink,
applyGlobalStyles,
getEncapsulatedCss,
} from "./css-helpers.js";
import { appendCssLink, getEncapsulatedCss } from "./css-helpers.js";
import { type CleanupFn, executeScript } from "./execute-script.js";
import { cloneNode, returnIfDefined, throwIfNotDefined } from "./helpers.js";

interface AttributeChanged {
attributeChangedCallback(
name: string,
oldValue: string | null,
newValue: string | null,
newValue: string | null
): void;
}

Expand All @@ -20,34 +16,23 @@ interface Disconnected {

export function createComponent(
definedElement: Document,
relativeTo: string,
relativeTo: string
): [string, typeof HTMLElement] {
const template = returnIfDefined(
definedElement.querySelector("template"),
"Template is required",
"Template is required"
);
const filename = returnIfDefined(relativeTo.split("/").pop()).replace(
/\.html$/,
"",
""
);
const selector = template.getAttribute("data-selector") ?? filename;
const useShadow = template.hasAttribute("data-shadow");

const styles: NodeListOf<HTMLStyleElement> = definedElement.querySelectorAll(
"style:not([data-global])",
);
const styles: NodeListOf<HTMLStyleElement> =
definedElement.querySelectorAll("style");
const scripts: NodeListOf<HTMLScriptElement> =
definedElement.querySelectorAll("script:not([data-global])");

const globalStyles: NodeListOf<HTMLStyleElement> =
definedElement.querySelectorAll("style[data-global]");
const globalScripts: NodeListOf<HTMLScriptElement> =
definedElement.querySelectorAll("script[data-global]");

applyGlobalStyles(Array.from(globalStyles));
for (const globalScript of globalScripts) {
executeScript(globalScript, relativeTo).catch(console.error);
}
definedElement.querySelectorAll("script");

if (!useShadow) {
for (const style of styles) {
Expand Down Expand Up @@ -79,7 +64,7 @@ export function createComponent(
const content = cloneNode(template.content);
this.#attrElements = Array.from(content.querySelectorAll("[data-attr]"));
this.#optionalElements = Array.from(
content.querySelectorAll("[data-if]"),
content.querySelectorAll("[data-if]")
);
this.#initOptionality();
this.#attach(content);
Expand All @@ -98,7 +83,7 @@ export function createComponent(
attributeChangedCallback(
name: string,
_oldValue: unknown,
newValue: string | null,
newValue: string | null
): void {
this.#applyAttr(name, newValue);
this.#applyOptionality(name);
Expand Down Expand Up @@ -166,7 +151,7 @@ export function createComponent(
const slot = content.querySelector(`slot[name=${slotName}]`);
if (!slot) {
console.warn(
`No slot with name "${slotName}" found for ${selector}`,
`No slot with name "${slotName}" found for ${selector}`
);
continue;
}
Expand All @@ -193,7 +178,7 @@ export function createComponent(

#applyAttr(name: string, value: string | null): void {
const attrElements = this.#attrElements.filter(
(element) => element.getAttribute("data-attr") === name,
(element) => element.getAttribute("data-attr") === name
);
for (const element of attrElements) {
if (value !== null) {
Expand All @@ -208,7 +193,7 @@ export function createComponent(

#applyOptionality(name: string): void {
const optionalForElements = this.#optionalElements.filter(
(element) => element.getAttribute("data-if") === name,
(element) => element.getAttribute("data-if") === name
);
for (const element of optionalForElements) {
if (this.#isElementVisible(element)) {
Expand Down Expand Up @@ -275,12 +260,12 @@ export function createComponent(

function getUsedAttributes(
template: HTMLTemplateElement,
attributeNames: string[],
attributeNames: string[]
): string[] {
return attributeNames
.flatMap((attributeName) => {
return Array.from(
template.content.querySelectorAll(`[${attributeName}]`),
template.content.querySelectorAll(`[${attributeName}]`)
).map((element) => returnIfDefined(element.getAttribute(attributeName)));
})
.filter((v, i, arr) => arr.indexOf(v) === i);
Expand Down
8 changes: 0 additions & 8 deletions src/css-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,3 @@ export function appendCssLink(cssText: string): void {
document.head.appendChild(element);
URL.revokeObjectURL(url);
}

export function applyGlobalStyles(styles: HTMLStyleElement[]): void {
for (const style of styles) {
const element = cloneNode(style);
element.removeAttribute("data-global");
document.head.appendChild(element);
}
}
37 changes: 12 additions & 25 deletions src/execute-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ window[scriptContextSymbol] = new Map<string, Element>();

async function getCode(
element: HTMLScriptElement,
relativeTo: string,
relativeTo: string
): Promise<string> {
const src = element.getAttribute("src");
if (src) {
Expand Down Expand Up @@ -52,40 +52,36 @@ function changeImport(match: RegExpMatchArray, relativeTo: string): string {
function setContextForModuleScript(
code: string,
uuid: string,
relativeTo: string,
relativeTo: string
): string {
const imports = [...code.matchAll(importRe)]
.map((match) => changeImport(match, relativeTo))
.join("\n");
const component = `window[Symbol.for('${scriptContextSymbolKey}')].get('${uuid}')`;
return `${imports}\n(function () {\n${code.replaceAll(
importRe,
"",
""
)}\n}).call(${component});`;
}

async function executeModule(
code: string,
relativeTo: string,
context?: Element,
): Promise<CleanupFn | undefined> {
let cleanup: CleanupFn | undefined = undefined;
let jsCode = code;
if (context) {
const uuid = crypto.randomUUID();
window[scriptContextSymbol].set(uuid, context);
jsCode = setContextForModuleScript(code, uuid, relativeTo);
cleanup = () => window[scriptContextSymbol].delete(uuid);
}
context: Element
): Promise<CleanupFn> {
const uuid = crypto.randomUUID();
window[scriptContextSymbol].set(uuid, context);
const jsCode = setContextForModuleScript(code, uuid, relativeTo);
const cleanup = () => window[scriptContextSymbol].delete(uuid);
const url = URL.createObjectURL(
new Blob([jsCode], { type: "text/javascript" }),
new Blob([jsCode], { type: "text/javascript" })
);
await import(url);
URL.revokeObjectURL(url);
return cleanup;
}

function execute(code: string, context?: Element): CleanupFn | undefined {
function execute(code: string, context: Element): CleanupFn | undefined {
const fn = Function(code);
try {
const result = fn.call(context);
Expand All @@ -111,16 +107,7 @@ function execute(code: string, context?: Element): CleanupFn | undefined {
export async function executeScript(
element: HTMLScriptElement,
relativeTo: string,
): Promise<undefined>;
export async function executeScript(
element: HTMLScriptElement,
relativeTo: string,
context: Element,
): Promise<CleanupFn>;
export async function executeScript(
element: HTMLScriptElement,
relativeTo: string,
context?: Element,
context: Element
): Promise<CleanupFn | undefined> {
const code = await getCode(element, relativeTo);
const isModule = element.getAttribute("type") === "module";
Expand Down
18 changes: 18 additions & 0 deletions test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>define-html</title>
<link rel="preload" href="./app-root.html" as="fetch" crossorigin />
<link rel="preload" href="./app-test.html" as="fetch" crossorigin />
<script src="dist/index.js" type="module"></script>
</head>
<body>
<app-root></app-root>
<style>
h1 {
background-color: blue;
}
</style>
</body>
</html>

0 comments on commit b99d785

Please sign in to comment.