Skip to content

Commit

Permalink
Next (#13)
Browse files Browse the repository at this point in the history
* bump next version

* fix format

* deprecate executables for dynamic value

- deprecate executables for dynamic values
- improve logic of handling template
- fixe bugs related to state order update

* optimize and simplify
  • Loading branch information
ECorreia45 authored Apr 20, 2024
1 parent 7c08de7 commit 4e18b87
Show file tree
Hide file tree
Showing 51 changed files with 2,229 additions and 1,509 deletions.
31 changes: 31 additions & 0 deletions build-scripts/build-browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import esbuild, { Plugin } from 'esbuild'

const emptyParserDoc = {
name: 'empty-parser-Doc-plugin',
setup(build) {
build.onResolve({ filter: /Doc$/ }, (args) => {
if (args.importer.includes('@beforesemicolon/html-parser')) {
return {
path: args.path,
namespace: 'empty-Doc',
}
}
})
build.onLoad({ filter: /.*/, namespace: 'empty-Doc' }, () => {
return {
contents: '',
}
})
},
} as Plugin

await esbuild.build({
entryPoints: ['src/client.ts'],
outfile: 'dist/client.js',
bundle: true,
keepNames: true,
sourcemap: true,
target: 'esnext',
minify: true,
plugins: [emptyParserDoc],
})
6 changes: 3 additions & 3 deletions docs-src/data/reasons.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
"img": "./assets/small.svg",
"imgAlt": "small templating system",
"breakDown": [
"<strong>< 8kb </strong> CDN compressed",
"<strong>< 18kb </strong>minified",
"<strong>< 230kb </strong> raw"
"<strong>~ 8kb </strong> CDN compressed",
"<strong>~ 16kb </strong>minified",
"<strong>~ 175kb </strong> raw"
]
},
{
Expand Down
20 changes: 19 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@beforesemicolon/markup",
"version": "0.17.0",
"version": "0.18.6-next",
"description": "Reactive HTML Templating System",
"engines": {
"node": ">=18.16.0"
Expand All @@ -14,17 +14,17 @@
"types": "./dist/types/index.d.ts"
},
"scripts": {
"test": "jest",
"test": "jest --coverage",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"docs:css-watch": "nodemon --watch docs-src -e scss --exec 'npm run docs:css'",
"docs:css": "node-sass docs-src/stylesheets -o docs/stylesheets --output-style compressed",
"docs:watch": "rm -rf docs && nodemon --watch docs-src -e ts,json --exec 'NODE_ENV=development npm run docs:build'",
"docs:build": "tsx docs-src/build.ts",
"local": "nodemon --watch src -e ts --exec 'npm run build:browser'",
"build:docs": "rm -rf docs && npm-run-all docs:build docs:css",
"build:cjs-min": "esbuild `find src \\( -name '*.ts' ! -name '*.spec.ts' ! -name 'client.ts' \\)` --minify --outdir=dist/cjs --platform=node --format=cjs --keep-names --target=esnext",
"build:esm-min": "esbuild `find src \\( -name '*.ts' ! -name '*.spec.ts' ! -name 'client.ts' \\)` --minify --outdir=dist/esm --platform=node --format=esm --keep-names --target=esnext",
"build:browser": "esbuild src/client.ts --bundle --minify --keep-names --sourcemap --target=esnext --outfile=dist/client.js",
"build:browser": "tsx build-scripts/build-browser.ts",
"build": "rm -rf dist && npm-run-all lint test && tsc --emitDeclarationOnly && npm-run-all build:cjs-min build:esm-min build:browser",
"lint": "eslint ./src && prettier --check .",
"format": "eslint ./src --fix && prettier --write ."
Expand Down Expand Up @@ -55,6 +55,7 @@
"@typescript-eslint/parser": "^6.15.0",
"core-js": "^3.34.0",
"esbuild": "^0.19.10",
"esbuild-plugin-text-replace": "^1.3.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
Expand All @@ -76,6 +77,6 @@
"typescript": "^5.3.3"
},
"dependencies": {
"@beforesemicolon/html-parser": "*"
"@beforesemicolon/html-parser": "^0.4.0"
}
}
242 changes: 242 additions & 0 deletions src/Doc.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import { DocumentLike, parse } from "@beforesemicolon/html-parser";
import { Doc } from "./Doc";

describe("Doc", () => {
const dynamicValueCollector = jest.fn();
let refs: Record<string, Set<Element>> = {};

beforeEach(() => {
jest.clearAllMocks();
refs = {};
});

it("should parse plain html", () => {
const res = parse(
"<p>simple</p>",
Doc([], refs, dynamicValueCollector) as unknown as DocumentLike
);

expect(res.childNodes.length).toBe(1);
expect(res.childNodes[0]).toBeInstanceOf(HTMLParagraphElement);
expect((res.childNodes[0] as HTMLParagraphElement).outerHTML).toBe("<p>simple</p>");
expect(dynamicValueCollector).not.toHaveBeenCalled();
});

it("should parse comment", () => {
const res = parse(
"<!-- <p>simple</p> -->",
Doc([], refs, dynamicValueCollector) as unknown as DocumentLike
);

expect(res.childNodes.length).toBe(1);
expect(res.childNodes[0]).toBeInstanceOf(Comment);
expect((res.childNodes[0] as Comment).nodeValue).toBe(" <p>simple</p> ");
expect(dynamicValueCollector).not.toHaveBeenCalled();
});

it("should parse plain html with static injected value", () => {
const res = parse(
"<p>$val0</p>",
Doc(["simple"], refs, dynamicValueCollector) as unknown as DocumentLike
);

expect(res.childNodes.length).toBe(1);
expect(res.childNodes[0]).toBeInstanceOf(HTMLParagraphElement);
expect((res.childNodes[0] as HTMLParagraphElement).outerHTML).toBe("<p>simple</p>");
expect(dynamicValueCollector).not.toHaveBeenCalled();
});

it("should parse plain html with dynamic injected value", () => {
const fn = () => "simple";
const res = parse(
"<p>$val0</p>",
Doc([fn], refs, dynamicValueCollector) as unknown as DocumentLike
);

expect(res.childNodes.length).toBe(1);
expect(res.childNodes[0]).toBeInstanceOf(HTMLParagraphElement);
expect((res.childNodes[0] as HTMLParagraphElement).outerHTML).toBe("<p>$val0</p>");
expect(dynamicValueCollector).toHaveBeenCalledWith({
data: null,
name: "nodeValue",
value: fn,
rawValue: "$val0",
prop: null,
});
});

it("should parse event attribute", () => {
const handler = () => {
};
const res = parse(
"<button type=\"button\" onclick=\"$val0\">click me</button>",
Doc([handler], {}, dynamicValueCollector) as unknown as DocumentLike
);

expect(res.childNodes.length).toBe(1);
expect(res.childNodes[0]).toBeInstanceOf(HTMLButtonElement);
expect((res.childNodes[0] as HTMLButtonElement).outerHTML).toBe("<button type=\"button\">click me</button>");
expect(dynamicValueCollector).toHaveBeenCalledTimes(1);
expect(dynamicValueCollector).toHaveBeenCalledWith({
"data": null,
"name": "onclick",
"prop": "click",
"rawValue": "$val0",
"value": [handler]
});
});

it("should parse event attribute with option", () => {
const handler = () => {
};
const res = parse(
'<button type="button" onclick="$val0, $val1">click me</button>',
Doc([handler, {once: true}], {}, dynamicValueCollector) as unknown as DocumentLike
);

expect(res.childNodes.length).toBe(1);
expect(res.childNodes[0]).toBeInstanceOf(HTMLButtonElement);
expect((res.childNodes[0] as HTMLButtonElement).outerHTML).toBe("<button type=\"button\">click me</button>");
expect(dynamicValueCollector).toHaveBeenCalledTimes(1);
expect(dynamicValueCollector).toHaveBeenCalledWith({
"data": null,
"name": "onclick",
"prop": "click",
"rawValue": "$val0, $val1",
"value": [handler, ", ", {once: true}]
});
});

it("should parse and ignore unknown event attribute", () => {
const handler = () => {
};
const res = parse(
"<button type=\"button\" onval=\"$val0\">click me</button>",
Doc([handler], {}, dynamicValueCollector) as unknown as DocumentLike
);

expect(res.childNodes.length).toBe(1);
expect(res.childNodes[0]).toBeInstanceOf(HTMLButtonElement);
expect((res.childNodes[0] as HTMLButtonElement).outerHTML).toBe("<button type=\"button\">click me</button>");
expect(dynamicValueCollector).toHaveBeenCalledTimes(1);
expect(dynamicValueCollector).toHaveBeenCalledWith({
"data": null,
"name": "onval",
"prop": null,
"rawValue": "$val0",
"value": [handler]
});
});

it("should parse event attribute for webComponent", () => {
class MyButton extends HTMLElement {
}

customElements.define("my-button", MyButton);

const handler = () => {
};
const res = parse(
"<my-button type=\"button\" onval=\"$val0\">click me</my-button>",
Doc([handler], {}, dynamicValueCollector) as unknown as DocumentLike
);

expect(res.childNodes.length).toBe(1);
expect(res.childNodes[0]).toBeInstanceOf(MyButton);
expect((res.childNodes[0] as MyButton).outerHTML).toBe("<my-button type=\"button\">click me</my-button>");
expect(dynamicValueCollector).toHaveBeenCalledTimes(1);
expect(dynamicValueCollector).toHaveBeenCalledWith({
"data": null,
"name": "onval",
"prop": "val",
"rawValue": "$val0",
"value": [handler]
});
});

it("should parse ref attribute", () => {
const res = parse(
"<button type=\"button\" ref=\"btn\">click me</button>",
Doc([], refs, dynamicValueCollector) as unknown as DocumentLike
);

expect(res.childNodes.length).toBe(1);
expect(res.childNodes[0]).toBeInstanceOf(HTMLButtonElement);
expect((res.childNodes[0] as HTMLButtonElement).outerHTML).toBe("<button type=\"button\">click me</button>");
expect(dynamicValueCollector).not.toHaveBeenCalled();
expect(refs["btn"].size).toBe(1);
expect(refs["btn"].has(res.childNodes[0] as HTMLButtonElement)).toBeTruthy();
});

it("should handle boolean, class, style and data attributes", () => {
const active = () => true;
const loading = () => false;
const disabled = () => true;
const sample = () => false;


const res = parse(
"<button type=\"button\" class=\"btn\" class.active=\"$val0\" style.loading=\"color: blue; | $val1\" disabled=\"$val2\" data.sample=\"$val3\">click me</button>",
Doc([active, loading, disabled, sample], refs, dynamicValueCollector) as unknown as DocumentLike
);

expect(res.childNodes.length).toBe(1);
expect(res.childNodes[0]).toBeInstanceOf(HTMLButtonElement);
expect((res.childNodes[0] as HTMLButtonElement).outerHTML).toBe('<button type="button" class="btn">click me</button>');
expect(dynamicValueCollector).toHaveBeenCalledTimes(4);


expect(dynamicValueCollector.mock.calls[0][0]).toEqual({
"data": null,
"name": "class",
"prop": "active",
"rawValue": "$val0",
"value": [active]
});
expect(dynamicValueCollector.mock.calls[1][0]).toEqual({
"data": null,
"name": "style",
"prop": "loading",
"rawValue": "color: blue; | $val1",
"value": ["color: blue; | ", loading]
});
expect(dynamicValueCollector.mock.calls[2][0]).toEqual({
"data": null,
"name": "disabled",
"prop": "",
"rawValue": "$val2",
"value": [disabled]
});
expect(dynamicValueCollector.mock.calls[3][0]).toEqual({
"data": null,
"name": "data",
"prop": "sample",
"rawValue": "$val3",
"value": [sample]
});
});

it("should parse and ignore boolean attributes with false value", () => {
const res = parse(
"<button type=\"button\" disabled=\"false\">click me</button>",
Doc([], refs, dynamicValueCollector) as unknown as DocumentLike
);

expect(res.childNodes.length).toBe(1);
expect(res.childNodes[0]).toBeInstanceOf(HTMLButtonElement);
expect((res.childNodes[0] as HTMLButtonElement).outerHTML).toBe("<button type=\"button\">click me</button>");
expect(dynamicValueCollector).not.toHaveBeenCalled();
});

it("should parse and ignore injected value names", () => {
const res = parse(
'<button type="button" $val0="true">click me</button>',
Doc(['disabled'], refs, dynamicValueCollector) as unknown as DocumentLike
);

expect(res.childNodes.length).toBe(1);
expect(res.childNodes[0]).toBeInstanceOf(HTMLButtonElement);
expect((res.childNodes[0] as HTMLButtonElement).outerHTML).toBe("<button type=\"button\">click me</button>");
expect(dynamicValueCollector).not.toHaveBeenCalled();
});
});
Loading

0 comments on commit 4e18b87

Please sign in to comment.