tkeron 5.1.1 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +31 -0
- package/examples/with_style_dedup/websrc/index.html +29 -0
- package/examples/with_style_dedup/websrc/tag-chip.com.ts +19 -0
- package/package.json +2 -11
- package/src/build.ts +6 -0
- package/src/deduplicateComStyles.ts +41 -0
- package/src/processCom.ts +12 -0
- package/src/processComTs.ts +12 -0
- package/src/processPost.ts +80 -0
- package/tkeron.d.ts +1 -0
package/changelog.md
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
# v5.3.0
|
|
2
|
+
|
|
3
|
+
## Feature: Automatic component style deduplication
|
|
4
|
+
|
|
5
|
+
- Component `<style data-tk-com>` elements are now automatically deduplicated at build time
|
|
6
|
+
- When a component is used multiple times, only one copy of its `<style>` is kept — moved to `<head>`
|
|
7
|
+
- Runs as a new build step (`deduplicateComStyles`) between component resolution and `.post.ts` execution
|
|
8
|
+
- The `.post.ts` script now receives an already-clean DOM with no duplicate styles
|
|
9
|
+
- Added example `with_style_dedup` demonstrating the feature
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# v5.2.0
|
|
14
|
+
|
|
15
|
+
## Feature: `.post.ts` post-processing support
|
|
16
|
+
|
|
17
|
+
- New file type `.post.ts`: runs after the build to post-process the corresponding `.html` file
|
|
18
|
+
- The `.post.ts` script receives a `document` object (parsed from the HTML) and writes the result back to the HTML file after execution
|
|
19
|
+
- Executes with `bun run` in the project root context
|
|
20
|
+
- Useful for final DOM manipulation, injecting metadata, or any transformation after components are resolved
|
|
21
|
+
- `processPost` returns `false` when no `.post.ts` files are found (no-op)
|
|
22
|
+
|
|
23
|
+
## Feature: `data-tk-com` attribute on component `<style>` elements
|
|
24
|
+
|
|
25
|
+
- Root-level `<style>` elements in component output now receive a `data-tk-com="component-name"` attribute
|
|
26
|
+
- Nested `<style>` elements (inside wrappers) also receive `data-tk-com`
|
|
27
|
+
- Applies to both `.com.html` and `.com.ts` components
|
|
28
|
+
- Enables per-component style scoping and identification in the final HTML
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
1
32
|
# v5.1.0
|
|
2
33
|
|
|
3
34
|
## Feature: .com.html template injection into .com.ts components
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Style Deduplication Example</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<h1>Auto Style Deduplication</h1>
|
|
10
|
+
<p>
|
|
11
|
+
Each <code><tag-chip></code> component has its own
|
|
12
|
+
<code><style></code>. tkeron automatically keeps only one copy in
|
|
13
|
+
<code><head></code> — no matter how many times the component is
|
|
14
|
+
used.
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<tag-chip label="TypeScript"></tag-chip>
|
|
18
|
+
<tag-chip label="HTML"></tag-chip>
|
|
19
|
+
<tag-chip label="CSS"></tag-chip>
|
|
20
|
+
<tag-chip label="Bun"></tag-chip>
|
|
21
|
+
<tag-chip label="tkeron"></tag-chip>
|
|
22
|
+
<tag-chip label="zero deps"></tag-chip>
|
|
23
|
+
|
|
24
|
+
<p>
|
|
25
|
+
6 chips above → 1 <code><style data-tk-com="tag-chip"></code> in
|
|
26
|
+
<code><head></code>.
|
|
27
|
+
</p>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const label = com.getAttribute("label") || "tag";
|
|
2
|
+
|
|
3
|
+
com.innerHTML = `
|
|
4
|
+
<style>
|
|
5
|
+
.tag-chip {
|
|
6
|
+
display: inline-block;
|
|
7
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
8
|
+
color: white;
|
|
9
|
+
padding: 0.2rem 0.65rem;
|
|
10
|
+
border-radius: 999px;
|
|
11
|
+
font-size: 0.75rem;
|
|
12
|
+
font-weight: 600;
|
|
13
|
+
margin: 0.2rem;
|
|
14
|
+
font-family: monospace;
|
|
15
|
+
letter-spacing: 0.02em;
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
18
|
+
<span class="tag-chip">${label}</span>
|
|
19
|
+
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tkeron",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "CLI build tool for vanilla web development with TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -18,16 +18,7 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"tk": "bun index.ts ",
|
|
20
20
|
"test": "bun test --concurrent",
|
|
21
|
-
"lint": "prettier --write ."
|
|
22
|
-
"sample1": "bun . build examples/basic_build/src",
|
|
23
|
-
"sample2": "bun . build examples/with_assets/src",
|
|
24
|
-
"sample3": "bun . build examples/with_pre/src",
|
|
25
|
-
"sample4": "bun . build examples/with_com_html_priority/src",
|
|
26
|
-
"sample5": "bun . build examples/with_com_ts/src",
|
|
27
|
-
"sample6": "bun . build examples/with_com_ts_priority/src",
|
|
28
|
-
"sample7": "bun . build examples/with_com_mixed_priority/src",
|
|
29
|
-
"sample8": "bun . build examples/with_com_html_in_ts/src",
|
|
30
|
-
"build_examples": "bun run sample1 && bun run sample2 && bun run sample3 && bun run sample4 && bun run sample5 && bun run sample6 && bun run sample7 && bun run sample8"
|
|
21
|
+
"lint": "prettier --write ."
|
|
31
22
|
},
|
|
32
23
|
"author": "tkeron",
|
|
33
24
|
"license": "MIT",
|
package/src/build.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { processPre } from "./processPre";
|
|
|
4
4
|
import { processCom } from "./processCom";
|
|
5
5
|
import { processComTs } from "./processComTs";
|
|
6
6
|
import { processComMd } from "./processComMd";
|
|
7
|
+
import { processPost } from "./processPost";
|
|
8
|
+
import { deduplicateComStyles } from "./deduplicateComStyles";
|
|
7
9
|
import { rm, exists, mkdir, cp } from "fs/promises";
|
|
8
10
|
import type { Logger } from "@tkeron/tools";
|
|
9
11
|
import { silentLogger } from "@tkeron/tools";
|
|
@@ -56,6 +58,10 @@ export const build = async (options: BuildOptions) => {
|
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
await deduplicateComStyles(tempDir);
|
|
62
|
+
|
|
63
|
+
await processPost(tempDir, { logger: log, projectRoot: sourceParent });
|
|
64
|
+
|
|
59
65
|
if (await exists(target)) {
|
|
60
66
|
await rm(target, { recursive: true, force: true });
|
|
61
67
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { parseHTML } from "@tkeron/html-parser";
|
|
2
|
+
import { getFilePaths } from "@tkeron/tools";
|
|
3
|
+
|
|
4
|
+
const DOCTYPE = "<!doctype html>\n";
|
|
5
|
+
|
|
6
|
+
export const deduplicateComStyles = async (tempDir: string): Promise<void> => {
|
|
7
|
+
if (!tempDir || typeof tempDir !== "string") return;
|
|
8
|
+
|
|
9
|
+
const htmlFiles = getFilePaths(tempDir, "**/*.html", true).filter(
|
|
10
|
+
(p) => !p.endsWith(".com.html"),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
await Promise.all(
|
|
14
|
+
htmlFiles.map(async (htmlFile) => {
|
|
15
|
+
const htmlContent = await Bun.file(htmlFile).text();
|
|
16
|
+
const document = parseHTML(htmlContent);
|
|
17
|
+
|
|
18
|
+
const allStyles = document.querySelectorAll("style[data-tk-com]");
|
|
19
|
+
if (!allStyles || allStyles.length === 0) return;
|
|
20
|
+
|
|
21
|
+
const seen = new Set<string>();
|
|
22
|
+
const head = document.querySelector("head");
|
|
23
|
+
|
|
24
|
+
for (const style of allStyles) {
|
|
25
|
+
const tkId = style.getAttribute("data-tk-com");
|
|
26
|
+
if (!tkId) continue;
|
|
27
|
+
|
|
28
|
+
if (seen.has(tkId)) {
|
|
29
|
+
style.remove();
|
|
30
|
+
} else {
|
|
31
|
+
seen.add(tkId);
|
|
32
|
+
if (head) head.appendChild(style);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const htmlElement = document.documentElement;
|
|
37
|
+
const output = DOCTYPE + (htmlElement?.outerHTML || "");
|
|
38
|
+
await Bun.write(htmlFile, output);
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
};
|
package/src/processCom.ts
CHANGED
|
@@ -189,6 +189,18 @@ const processComponents = async (
|
|
|
189
189
|
(tempContainer as any).childNodes || [],
|
|
190
190
|
).map((node: any) => (node?.cloneNode ? node.cloneNode(true) : node));
|
|
191
191
|
|
|
192
|
+
for (const node of nodesToInsert) {
|
|
193
|
+
if ((node as any).nodeType === 1) {
|
|
194
|
+
if ((node as any).tagName?.toLowerCase() === "style") {
|
|
195
|
+
(node as any).setAttribute("data-tk-com", tagName);
|
|
196
|
+
}
|
|
197
|
+
const nested = (node as any).querySelectorAll?.("style") || [];
|
|
198
|
+
for (const styleEl of nested) {
|
|
199
|
+
(styleEl as any).setAttribute("data-tk-com", tagName);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
192
204
|
const parent = (child as any).parentNode;
|
|
193
205
|
|
|
194
206
|
if (parent) {
|
package/src/processComTs.ts
CHANGED
|
@@ -335,6 +335,18 @@ ${codeWithoutImports}
|
|
|
335
335
|
(node: any) => (node?.cloneNode ? node.cloneNode(true) : node),
|
|
336
336
|
);
|
|
337
337
|
|
|
338
|
+
for (const node of nodesToInsert) {
|
|
339
|
+
if ((node as any).nodeType === 1) {
|
|
340
|
+
if ((node as any).tagName?.toLowerCase() === "style") {
|
|
341
|
+
(node as any).setAttribute("data-tk-com", tagName);
|
|
342
|
+
}
|
|
343
|
+
const nested = (node as any).querySelectorAll?.("style") || [];
|
|
344
|
+
for (const styleEl of nested) {
|
|
345
|
+
(styleEl as any).setAttribute("data-tk-com", tagName);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
338
350
|
const parent = (child as any).parentNode;
|
|
339
351
|
|
|
340
352
|
if (parent) {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { join, dirname } from "path";
|
|
2
|
+
import { getFilePaths } from "@tkeron/tools";
|
|
3
|
+
import type { Logger } from "@tkeron/tools";
|
|
4
|
+
import { silentLogger } from "@tkeron/tools";
|
|
5
|
+
|
|
6
|
+
const HTML_PARSER_PATH = import.meta.resolve("@tkeron/html-parser");
|
|
7
|
+
|
|
8
|
+
export interface ProcessPostOptions {
|
|
9
|
+
logger?: Logger;
|
|
10
|
+
projectRoot?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const processPost = async (
|
|
14
|
+
tempDir: string,
|
|
15
|
+
options: ProcessPostOptions = {},
|
|
16
|
+
): Promise<boolean> => {
|
|
17
|
+
const log = options.logger || silentLogger;
|
|
18
|
+
|
|
19
|
+
if (!tempDir || typeof tempDir !== "string") {
|
|
20
|
+
log.error(`\n❌ Error: Invalid tempDir provided for processPost.`);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const postFiles = getFilePaths(tempDir, "**/*.post.ts", true);
|
|
25
|
+
|
|
26
|
+
if (postFiles.length === 0) return false;
|
|
27
|
+
|
|
28
|
+
for (const postFile of postFiles) {
|
|
29
|
+
const htmlFile = postFile.replace(/\.post\.ts$/, ".html");
|
|
30
|
+
|
|
31
|
+
const originalCode = await Bun.file(postFile).text();
|
|
32
|
+
|
|
33
|
+
const htmlContent = await Bun.file(htmlFile).text();
|
|
34
|
+
|
|
35
|
+
const modifiedCode = `
|
|
36
|
+
import { parseHTML } from ${JSON.stringify(HTML_PARSER_PATH)};
|
|
37
|
+
|
|
38
|
+
const htmlPath = ${JSON.stringify(htmlFile)};
|
|
39
|
+
const htmlContent = ${JSON.stringify(htmlContent)};
|
|
40
|
+
const document = parseHTML(htmlContent);
|
|
41
|
+
|
|
42
|
+
${originalCode}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
const doctype = '<!DOCTYPE html>\\n';
|
|
46
|
+
const htmlOutput = doctype + document.documentElement.outerHTML;
|
|
47
|
+
await Bun.write(htmlPath, htmlOutput);
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
await Bun.write(postFile, modifiedCode);
|
|
51
|
+
|
|
52
|
+
const proc = Bun.spawn(["bun", "run", postFile], {
|
|
53
|
+
cwd: options.projectRoot || dirname(postFile),
|
|
54
|
+
stdout: "pipe",
|
|
55
|
+
stderr: "pipe",
|
|
56
|
+
env: { ...process.env },
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await proc.exited;
|
|
60
|
+
|
|
61
|
+
if (proc.exitCode !== 0) {
|
|
62
|
+
const stderr = await new Response(proc.stderr).text();
|
|
63
|
+
const stdout = await new Response(proc.stdout).text();
|
|
64
|
+
log.error(
|
|
65
|
+
`\n❌ Error: Post-processing failed for ${postFile.split("/").pop()}`,
|
|
66
|
+
);
|
|
67
|
+
log.error(`\n💡 File: ${postFile}`);
|
|
68
|
+
if (stderr) {
|
|
69
|
+
log.error(`\nError details:\n${stderr}`);
|
|
70
|
+
}
|
|
71
|
+
if (stdout) {
|
|
72
|
+
log.error(`\nOutput:\n${stdout}`);
|
|
73
|
+
}
|
|
74
|
+
log.error("");
|
|
75
|
+
throw new Error(`Post-processing failed for ${postFile}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return true;
|
|
80
|
+
};
|
package/tkeron.d.ts
CHANGED