chocola 1.5.1 → 1.6.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/README.md +10 -6
- package/compiler/component-processor.js +55 -9
- package/compiler/dom-processor.js +9 -1
- package/compiler/index.js +254 -168
- package/compiler/pipeline.js +2 -15
- package/package.json +7 -3
- package/.gitattributes +0 -2
package/README.md
CHANGED
|
@@ -12,25 +12,29 @@ Chocola is a JavaScript library for creating web user interfaces.
|
|
|
12
12
|
<button title={title}>{text}</button>
|
|
13
13
|
|
|
14
14
|
<div class="main">
|
|
15
|
-
<div class="number"
|
|
15
|
+
<div class="number">{count}</div>
|
|
16
16
|
</div>
|
|
17
17
|
</div>
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
```js
|
|
21
|
-
import
|
|
22
|
-
import
|
|
21
|
+
import body from "./html/counter.html";
|
|
22
|
+
import styles from "./css/counter.css";
|
|
23
23
|
|
|
24
24
|
function RUNTIME(self, ctx) {
|
|
25
|
-
self.querySelector("button")
|
|
25
|
+
const btn = self.querySelector("button");
|
|
26
|
+
const number = self.querySelector(".number");
|
|
27
|
+
|
|
28
|
+
btn.addEventListener("click", () => {
|
|
26
29
|
ctx.count++;
|
|
30
|
+
number.textContent = ctx.count;
|
|
27
31
|
})
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
export default function Counter() {
|
|
31
35
|
return {
|
|
32
|
-
body
|
|
33
|
-
styles
|
|
36
|
+
body,
|
|
37
|
+
styles,
|
|
34
38
|
script: RUNTIME
|
|
35
39
|
}
|
|
36
40
|
}
|
|
@@ -22,6 +22,28 @@ function scopeSelector(sel, cssId) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function scopeCss(cssString, cssId) {
|
|
25
|
+
const keyframeRegex = /@(-webkit-)?keyframes\s+(\S+)/gi;
|
|
26
|
+
const animNames = new Set();
|
|
27
|
+
let match;
|
|
28
|
+
while ((match = keyframeRegex.exec(cssString))) {
|
|
29
|
+
animNames.add(match[2]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (animNames.size > 0) {
|
|
33
|
+
const sorted = [...animNames].sort((a, b) => b.length - a.length);
|
|
34
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
35
|
+
for (const name of sorted) {
|
|
36
|
+
cssString = cssString.replace(
|
|
37
|
+
new RegExp(`(@(?:-webkit-)?keyframes\\s+)${esc(name)}`, "gi"),
|
|
38
|
+
`$1${name}-${cssId}`
|
|
39
|
+
);
|
|
40
|
+
cssString = cssString.replace(
|
|
41
|
+
new RegExp(`((?:animation|animation-name)\\s*:\\s*[^;{]*?)\\b${esc(name)}\\b`, "gi"),
|
|
42
|
+
`$1${name}-${cssId}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
25
47
|
function findMatchingBrace(s, openIndex) {
|
|
26
48
|
let depth = 0;
|
|
27
49
|
for (let i = openIndex; i < s.length; i++) {
|
|
@@ -35,6 +57,8 @@ function scopeCss(cssString, cssId) {
|
|
|
35
57
|
return s.length - 1;
|
|
36
58
|
}
|
|
37
59
|
|
|
60
|
+
const scopeInnerAtRules = new Set(["@media", "@supports", "@container", "@layer"]);
|
|
61
|
+
|
|
38
62
|
function processBlock(str) {
|
|
39
63
|
str = str.replaceAll(":root", `.${cssId}`);
|
|
40
64
|
|
|
@@ -49,11 +73,16 @@ function scopeCss(cssString, cssId) {
|
|
|
49
73
|
const header = str.substring(i, braceIndex);
|
|
50
74
|
const endBrace = findMatchingBrace(str, braceIndex);
|
|
51
75
|
const inner = str.substring(braceIndex + 1, endBrace);
|
|
52
|
-
const innerScoped = processBlock(inner);
|
|
53
76
|
|
|
54
77
|
if (header.trim().startsWith("@")) {
|
|
55
|
-
|
|
78
|
+
const atKeyword = header.trim().split(/\s+/)[0].toLowerCase();
|
|
79
|
+
if (scopeInnerAtRules.has(atKeyword)) {
|
|
80
|
+
out += header + "{" + processBlock(inner) + "}";
|
|
81
|
+
} else {
|
|
82
|
+
out += header + "{" + inner + "}";
|
|
83
|
+
}
|
|
56
84
|
} else {
|
|
85
|
+
const innerScoped = processBlock(inner);
|
|
57
86
|
const selectors = header
|
|
58
87
|
.split(",")
|
|
59
88
|
.map(s => s.trim())
|
|
@@ -72,6 +101,23 @@ function scopeCss(cssString, cssId) {
|
|
|
72
101
|
return processBlock(cssString);
|
|
73
102
|
}
|
|
74
103
|
|
|
104
|
+
const warnedDeprecated = new Set();
|
|
105
|
+
function hasDelIfAttr(el, sourceLoc) {
|
|
106
|
+
if (el.hasAttribute("del-if") && !el.hasAttribute("del:if") && !warnedDeprecated.has(sourceLoc)) {
|
|
107
|
+
warnedDeprecated.add(sourceLoc);
|
|
108
|
+
const name = sourceLoc ? sourceLoc.split(/[\\/]/).pop() : "unknown";
|
|
109
|
+
console.warn(chalk.yellow(`[deprecation] "del-if" is deprecated in ${chalk.cyan(name)} and will be removed in Chocola 2. Use "del:if" instead.`));
|
|
110
|
+
}
|
|
111
|
+
return el.hasAttribute("del-if") || el.hasAttribute("del:if");
|
|
112
|
+
}
|
|
113
|
+
function getDelIfAttr(el) {
|
|
114
|
+
return el.getAttribute("del:if") ?? el.getAttribute("del-if");
|
|
115
|
+
}
|
|
116
|
+
function removeDelIfAttr(el) {
|
|
117
|
+
el.removeAttribute("del-if");
|
|
118
|
+
el.removeAttribute("del:if");
|
|
119
|
+
}
|
|
120
|
+
|
|
75
121
|
function interpolateNode(node, ctxProxy) {
|
|
76
122
|
if (node.nodeType === 3) {
|
|
77
123
|
node.textContent = node.textContent.replace(/\{([^}]+)\}/g, (_, expr) => {
|
|
@@ -93,7 +139,7 @@ function validateChainStructure(parent, sourceFile, sourceContent, parentContent
|
|
|
93
139
|
|
|
94
140
|
for (const child of children) {
|
|
95
141
|
const hasIf = child.hasAttribute("if");
|
|
96
|
-
const hasDelIf = child
|
|
142
|
+
const hasDelIf = hasDelIfAttr(child, sourceFile);
|
|
97
143
|
const hasElif = child.hasAttribute("elif");
|
|
98
144
|
const hasElse = child.hasAttribute("else");
|
|
99
145
|
|
|
@@ -224,7 +270,7 @@ export function processComponentElement(
|
|
|
224
270
|
const condChain = condChains.get(parent);
|
|
225
271
|
|
|
226
272
|
const hasIf = child.hasAttribute("if");
|
|
227
|
-
const hasDelIf = child.
|
|
273
|
+
const hasDelIf = hasDelIfAttr(child, instance.__sourceFile || compName);
|
|
228
274
|
const hasElif = child.hasAttribute("elif");
|
|
229
275
|
const hasElse = child.hasAttribute("else");
|
|
230
276
|
|
|
@@ -257,8 +303,8 @@ export function processComponentElement(
|
|
|
257
303
|
condChain.active = false;
|
|
258
304
|
}
|
|
259
305
|
} else if (hasIf || hasDelIf) {
|
|
260
|
-
const
|
|
261
|
-
const expr =
|
|
306
|
+
const raw = hasIf ? child.getAttribute("if") : getDelIfAttr(child);
|
|
307
|
+
const expr = raw.slice(1, -1);
|
|
262
308
|
const fn = new Function("ctx", `with(ctx) { return (${expr}); }`);
|
|
263
309
|
condChain.active = true;
|
|
264
310
|
if (fn(ctxProxy)) {
|
|
@@ -276,7 +322,7 @@ export function processComponentElement(
|
|
|
276
322
|
return;
|
|
277
323
|
}
|
|
278
324
|
|
|
279
|
-
const reservedAttrs = ["if", "del-if", "elif", "else"];
|
|
325
|
+
const reservedAttrs = ["if", "del-if", "del:if", "elif", "else"];
|
|
280
326
|
|
|
281
327
|
Array.from(child.attributes).forEach(attribute => {
|
|
282
328
|
if (!attribute || attribute === undefined) return;
|
|
@@ -321,7 +367,7 @@ export function processComponentElement(
|
|
|
321
367
|
}
|
|
322
368
|
child.removeAttribute("if");
|
|
323
369
|
} else if (hasDelIf) {
|
|
324
|
-
const expr = child
|
|
370
|
+
const expr = getDelIfAttr(child).slice(1, -1);
|
|
325
371
|
const fn = new Function("ctx", `with(ctx) { return (${expr}); }`);
|
|
326
372
|
condChain.active = true;
|
|
327
373
|
if (fn(ctxProxy)) {
|
|
@@ -330,7 +376,7 @@ export function processComponentElement(
|
|
|
330
376
|
child.remove();
|
|
331
377
|
condChain.rendered = false;
|
|
332
378
|
}
|
|
333
|
-
child
|
|
379
|
+
removeDelIfAttr(child);
|
|
334
380
|
} else if (hasElif) {
|
|
335
381
|
const expr = child.getAttribute("elif").slice(1, -1);
|
|
336
382
|
const fn = new Function("ctx", `with(ctx) { return (${expr}); }`);
|
|
@@ -44,7 +44,15 @@ export function extractContextFromElement(element) {
|
|
|
44
44
|
const ctx = {};
|
|
45
45
|
for (const attr of element.attributes) {
|
|
46
46
|
const key = attr.name;
|
|
47
|
-
|
|
47
|
+
const val = attr.value;
|
|
48
|
+
const matches = [...val.matchAll(/\{([^}]+)\}/g)];
|
|
49
|
+
if (matches.length === 1 && matches[0][0] === val) {
|
|
50
|
+
try {
|
|
51
|
+
ctx[key] = Function(`"use strict"; return (${matches[0][1]})`)();
|
|
52
|
+
continue;
|
|
53
|
+
} catch {}
|
|
54
|
+
}
|
|
55
|
+
ctx[key] = val;
|
|
48
56
|
}
|
|
49
57
|
return ctx;
|
|
50
58
|
}
|
package/compiler/index.js
CHANGED
|
@@ -1,168 +1,254 @@
|
|
|
1
|
-
import { promises as fs } from "fs";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import { loadConfig, resolvePaths } from "./config.js";
|
|
4
|
-
import {
|
|
5
|
-
createDOM,
|
|
6
|
-
validateAppContainer,
|
|
7
|
-
getAppElements,
|
|
8
|
-
getAssetLinks,
|
|
9
|
-
appendRuntimeScript,
|
|
10
|
-
appendStylesheetLink,
|
|
11
|
-
serializeDOM,
|
|
12
|
-
writeHTMLOutput,
|
|
13
|
-
writeCSSOutput,
|
|
14
|
-
} from "./dom-processor.js";
|
|
15
|
-
import { processAllComponents } from "./component-processor.js";
|
|
16
|
-
import { generateRuntimeScript } from "./runtime-generator.js";
|
|
17
|
-
import { genRandomId, throwError } from "./utils.js";
|
|
18
|
-
import {
|
|
19
|
-
copyResources,
|
|
20
|
-
copyStaticDir,
|
|
21
|
-
getComponents,
|
|
22
|
-
getSrcIndex,
|
|
23
|
-
processIcons,
|
|
24
|
-
processStylesheet,
|
|
25
|
-
} from "./pipeline.js";
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const logSeparation = chalk.yellow(`
|
|
29
|
-
________________________________________________________________________
|
|
30
|
-
========================================================================
|
|
31
|
-
`);
|
|
32
|
-
|
|
33
|
-
function logBanner() {
|
|
34
|
-
console.log(chalk.bold.hex("#945e33")(`\n RUNNING CHOCOLA BUNDLER`));
|
|
35
|
-
console.log(logSeparation);
|
|
36
|
-
console.log(
|
|
37
|
-
chalk.hex("#945e33")(`
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
▄████▄ ██░ ██ ▒█████ ▄████▄ ▒█████ ██▓ ▄▄▄
|
|
41
|
-
▒██▀ ▀█ ▓██░ ██▒▒██▒ ██▒▒██▀ ▀█ ▒██▒ ██▒▓██▒ ▒████▄
|
|
42
|
-
▒▓█ ▄ ▒██▀▀██░▒██░ ██▒▒▓█ ▄ ▒██░ ██▒▒██░ ▒██ ▀█▄
|
|
43
|
-
▒▓▓▄ ▄██▒░▓█ ░██ ▒██ ██░▒▓▓▄ ▄██▒▒██ ██░▒██░ ░██▄▄▄▄██
|
|
44
|
-
▒ ▓███▀ ░░▓█▒░██▓░ ████▓▒░▒ ▓███▀ ░░ ████▓▒░░██████▒▓█ ▓██▒
|
|
45
|
-
░ ░▒ ▒ ░ ▒ ░░▒░▒░ ▒░▒░▒░ ░ ░▒ ▒ ░░ ▒░▒░▒░ ░ ▒░▓ ░▒▒ ▓▒█░
|
|
46
|
-
░ ▒ ▒ ░▒░ ░ ░ ░ ▒ ▒░ ░ ▒ ░ ░ ▒░ ░ ░ ▒ ░ ░ ▒▒ ░
|
|
47
|
-
░ ░ ░░ ░░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ▒
|
|
48
|
-
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`)
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function logSuccess(outDirPath) {
|
|
57
|
-
console.log(`
|
|
58
|
-
▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄▄▄ ██
|
|
59
|
-
██ ██▀██ ██▄██ ██▀██ ██▀██ ███▄██ ██▄▄ ██
|
|
60
|
-
▄▄█▀ ▀███▀ ██▄█▀ ████▀ ▀███▀ ██ ▀██ ██▄▄▄ ▄▄
|
|
61
|
-
|
|
62
|
-
`);
|
|
63
|
-
console.log(
|
|
64
|
-
chalk.bold.green(">"),
|
|
65
|
-
"Project bundled succesfully at",
|
|
66
|
-
chalk.green.underline(outDirPath) + "\n\n"
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function setupOutputDirectory(outDirPath, emptyOutDir) {
|
|
71
|
-
if (emptyOutDir) {
|
|
72
|
-
await fs.rm(outDirPath, { recursive: true, force: true });
|
|
73
|
-
await fs.mkdir(outDirPath);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function loadAndDisplayComponents(srcComponentsPath) {
|
|
78
|
-
const foundComponents = await getComponents(srcComponentsPath);
|
|
79
|
-
const { loadedComponents, notDefComps, componentsLib } = foundComponents;
|
|
80
|
-
|
|
81
|
-
console.log(` LOADING COMPONENTS`);
|
|
82
|
-
console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(srcComponentsPath) + ":");
|
|
83
|
-
console.log(" ", componentsLib, "\n");
|
|
84
|
-
|
|
85
|
-
if (notDefComps.length > 0) {
|
|
86
|
-
console.warn(chalk.bold.yellow("WARNING!"), "The following components don't include a default export:");
|
|
87
|
-
console.log(" ", notDefComps);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return loadedComponents;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function processAssets(doc, rootDir, srcDir, outDirPath) {
|
|
94
|
-
const { stylesheets, icons } = getAssetLinks(doc);
|
|
95
|
-
const fileIds = [];
|
|
96
|
-
let cssContents = [];
|
|
97
|
-
|
|
98
|
-
for (const link of stylesheets) {
|
|
99
|
-
const css = await processStylesheet(link, rootDir, srcDir, outDirPath, fileIds);
|
|
100
|
-
cssContents.push(css);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
for (const link of icons) {
|
|
104
|
-
await processIcons(link, rootDir, srcDir, outDirPath);
|
|
105
|
-
}
|
|
106
|
-
return cssContents
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Compiles a static build of your Chocola project from the directory provided.
|
|
111
|
-
* @param {import("fs").PathLike} rootDir
|
|
112
|
-
* @param {Object<{
|
|
113
|
-
* isHotReload?: boolean
|
|
114
|
-
* }
|
|
115
|
-
* >} config
|
|
116
|
-
*/
|
|
117
|
-
export default async function runtime(rootDir, buildConfig) {
|
|
118
|
-
const isHotReload = buildConfig?.isHotReload || null;
|
|
119
|
-
!isHotReload && logBanner();
|
|
120
|
-
|
|
121
|
-
const config = await loadConfig(rootDir);
|
|
122
|
-
const paths = resolvePaths(rootDir, config);
|
|
123
|
-
!isHotReload && console.log(logSeparation);
|
|
124
|
-
|
|
125
|
-
await setupOutputDirectory(paths.outDir, config.emptyOutDir);
|
|
126
|
-
|
|
127
|
-
const indexFiles = await getSrcIndex(paths.src);
|
|
128
|
-
const srcIndexContent = indexFiles.srcHtmlFile
|
|
129
|
-
const pageSourcePath = indexFiles.srcPath;
|
|
130
|
-
|
|
131
|
-
const loadedComponents = await loadAndDisplayComponents(paths.components);
|
|
132
|
-
!isHotReload && console.log(logSeparation);
|
|
133
|
-
!isHotReload && console.log(` BUNDLING STATIC BUILD`);
|
|
134
|
-
!isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(paths.outDir) + "\n");
|
|
135
|
-
!isHotReload && console.log(logSeparation);
|
|
136
|
-
|
|
137
|
-
const dom = createDOM(srcIndexContent);
|
|
138
|
-
const doc = dom.window.document;
|
|
139
|
-
const appContainer = validateAppContainer(doc);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { loadConfig, resolvePaths } from "./config.js";
|
|
4
|
+
import {
|
|
5
|
+
createDOM,
|
|
6
|
+
validateAppContainer,
|
|
7
|
+
getAppElements,
|
|
8
|
+
getAssetLinks,
|
|
9
|
+
appendRuntimeScript,
|
|
10
|
+
appendStylesheetLink,
|
|
11
|
+
serializeDOM,
|
|
12
|
+
writeHTMLOutput,
|
|
13
|
+
writeCSSOutput,
|
|
14
|
+
} from "./dom-processor.js";
|
|
15
|
+
import { processAllComponents } from "./component-processor.js";
|
|
16
|
+
import { generateRuntimeScript } from "./runtime-generator.js";
|
|
17
|
+
import { genRandomId, throwError } from "./utils.js";
|
|
18
|
+
import {
|
|
19
|
+
copyResources,
|
|
20
|
+
copyStaticDir,
|
|
21
|
+
getComponents,
|
|
22
|
+
getSrcIndex,
|
|
23
|
+
processIcons,
|
|
24
|
+
processStylesheet,
|
|
25
|
+
} from "./pipeline.js";
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
const logSeparation = chalk.yellow(`
|
|
29
|
+
________________________________________________________________________
|
|
30
|
+
========================================================================
|
|
31
|
+
`);
|
|
32
|
+
|
|
33
|
+
function logBanner() {
|
|
34
|
+
console.log(chalk.bold.hex("#945e33")(`\n RUNNING CHOCOLA BUNDLER`));
|
|
35
|
+
console.log(logSeparation);
|
|
36
|
+
console.log(
|
|
37
|
+
chalk.hex("#945e33")(`
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
▄████▄ ██░ ██ ▒█████ ▄████▄ ▒█████ ██▓ ▄▄▄
|
|
41
|
+
▒██▀ ▀█ ▓██░ ██▒▒██▒ ██▒▒██▀ ▀█ ▒██▒ ██▒▓██▒ ▒████▄
|
|
42
|
+
▒▓█ ▄ ▒██▀▀██░▒██░ ██▒▒▓█ ▄ ▒██░ ██▒▒██░ ▒██ ▀█▄
|
|
43
|
+
▒▓▓▄ ▄██▒░▓█ ░██ ▒██ ██░▒▓▓▄ ▄██▒▒██ ██░▒██░ ░██▄▄▄▄██
|
|
44
|
+
▒ ▓███▀ ░░▓█▒░██▓░ ████▓▒░▒ ▓███▀ ░░ ████▓▒░░██████▒▓█ ▓██▒
|
|
45
|
+
░ ░▒ ▒ ░ ▒ ░░▒░▒░ ▒░▒░▒░ ░ ░▒ ▒ ░░ ▒░▒░▒░ ░ ▒░▓ ░▒▒ ▓▒█░
|
|
46
|
+
░ ▒ ▒ ░▒░ ░ ░ ░ ▒ ▒░ ░ ▒ ░ ░ ▒░ ░ ░ ▒ ░ ░ ▒▒ ░
|
|
47
|
+
░ ░ ░░ ░░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ▒
|
|
48
|
+
░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░
|
|
49
|
+
░ ░
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
`)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function logSuccess(outDirPath) {
|
|
57
|
+
console.log(`
|
|
58
|
+
▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄▄▄ ██
|
|
59
|
+
██ ██▀██ ██▄██ ██▀██ ██▀██ ███▄██ ██▄▄ ██
|
|
60
|
+
▄▄█▀ ▀███▀ ██▄█▀ ████▀ ▀███▀ ██ ▀██ ██▄▄▄ ▄▄
|
|
61
|
+
|
|
62
|
+
`);
|
|
63
|
+
console.log(
|
|
64
|
+
chalk.bold.green(">"),
|
|
65
|
+
"Project bundled succesfully at",
|
|
66
|
+
chalk.green.underline(outDirPath) + "\n\n"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function setupOutputDirectory(outDirPath, emptyOutDir) {
|
|
71
|
+
if (emptyOutDir) {
|
|
72
|
+
await fs.rm(outDirPath, { recursive: true, force: true });
|
|
73
|
+
await fs.mkdir(outDirPath);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function loadAndDisplayComponents(srcComponentsPath) {
|
|
78
|
+
const foundComponents = await getComponents(srcComponentsPath);
|
|
79
|
+
const { loadedComponents, notDefComps, componentsLib } = foundComponents;
|
|
80
|
+
|
|
81
|
+
console.log(` LOADING COMPONENTS`);
|
|
82
|
+
console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(srcComponentsPath) + ":");
|
|
83
|
+
console.log(" ", componentsLib, "\n");
|
|
84
|
+
|
|
85
|
+
if (notDefComps.length > 0) {
|
|
86
|
+
console.warn(chalk.bold.yellow("WARNING!"), "The following components don't include a default export:");
|
|
87
|
+
console.log(" ", notDefComps);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return loadedComponents;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function processAssets(doc, rootDir, srcDir, outDirPath) {
|
|
94
|
+
const { stylesheets, icons } = getAssetLinks(doc);
|
|
95
|
+
const fileIds = [];
|
|
96
|
+
let cssContents = [];
|
|
97
|
+
|
|
98
|
+
for (const link of stylesheets) {
|
|
99
|
+
const css = await processStylesheet(link, rootDir, srcDir, outDirPath, fileIds);
|
|
100
|
+
cssContents.push(css);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const link of icons) {
|
|
104
|
+
await processIcons(link, rootDir, srcDir, outDirPath);
|
|
105
|
+
}
|
|
106
|
+
return cssContents
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Compiles a static build of your Chocola project from the directory provided.
|
|
111
|
+
* @param {import("fs").PathLike} rootDir
|
|
112
|
+
* @param {Object<{
|
|
113
|
+
* isHotReload?: boolean
|
|
114
|
+
* }
|
|
115
|
+
* >} config
|
|
116
|
+
*/
|
|
117
|
+
export default async function runtime(rootDir, buildConfig) {
|
|
118
|
+
const isHotReload = buildConfig?.isHotReload || null;
|
|
119
|
+
!isHotReload && logBanner();
|
|
120
|
+
|
|
121
|
+
const config = await loadConfig(rootDir);
|
|
122
|
+
const paths = resolvePaths(rootDir, config);
|
|
123
|
+
!isHotReload && console.log(logSeparation);
|
|
124
|
+
|
|
125
|
+
await setupOutputDirectory(paths.outDir, config.emptyOutDir);
|
|
126
|
+
|
|
127
|
+
const indexFiles = await getSrcIndex(paths.src);
|
|
128
|
+
const srcIndexContent = indexFiles.srcHtmlFile;
|
|
129
|
+
const pageSourcePath = indexFiles.srcPath;
|
|
130
|
+
|
|
131
|
+
const loadedComponents = await loadAndDisplayComponents(paths.components);
|
|
132
|
+
!isHotReload && console.log(logSeparation);
|
|
133
|
+
!isHotReload && console.log(` BUNDLING STATIC BUILD`);
|
|
134
|
+
!isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(paths.outDir) + "\n");
|
|
135
|
+
!isHotReload && console.log(logSeparation);
|
|
136
|
+
|
|
137
|
+
const dom = createDOM(srcIndexContent);
|
|
138
|
+
const doc = dom.window.document;
|
|
139
|
+
const appContainer = validateAppContainer(doc);
|
|
140
|
+
|
|
141
|
+
const warnedDeprecated = new Set();
|
|
142
|
+
function hasDelIfAttr(el, sourceLoc) {
|
|
143
|
+
if (el.hasAttribute("del-if") && !el.hasAttribute("del:if") && !warnedDeprecated.has(sourceLoc)) {
|
|
144
|
+
warnedDeprecated.add(sourceLoc);
|
|
145
|
+
const name = sourceLoc ? sourceLoc.split(/[\\/]/).pop() : "unknown";
|
|
146
|
+
console.warn(chalk.yellow(`[deprecation] "del-if" is deprecated in ${chalk.cyan(name)} and will be removed in Chocola 2. Use "del:if" instead.`));
|
|
147
|
+
}
|
|
148
|
+
return el.hasAttribute("del-if") || el.hasAttribute("del:if");
|
|
149
|
+
}
|
|
150
|
+
function getDelIfAttr(el) {
|
|
151
|
+
return el.getAttribute("del:if") ?? el.getAttribute("del-if");
|
|
152
|
+
}
|
|
153
|
+
function removeDelIfAttr(el) {
|
|
154
|
+
el.removeAttribute("del-if");
|
|
155
|
+
el.removeAttribute("del:if");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function processPageConditionals(parent) {
|
|
159
|
+
const children = [...parent.children];
|
|
160
|
+
let chainActive = false;
|
|
161
|
+
let chainRendered = false;
|
|
162
|
+
|
|
163
|
+
for (const child of children) {
|
|
164
|
+
const hasIf = child.hasAttribute("if");
|
|
165
|
+
const hasDelIf = hasDelIfAttr(child, pageSourcePath);
|
|
166
|
+
const hasElif = child.hasAttribute("elif");
|
|
167
|
+
const hasElse = child.hasAttribute("else");
|
|
168
|
+
|
|
169
|
+
if (hasElif || hasElse) {
|
|
170
|
+
if (!chainActive) continue;
|
|
171
|
+
if (chainRendered) { child.remove(); continue; }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (hasIf) {
|
|
175
|
+
const raw = child.getAttribute("if");
|
|
176
|
+
const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
177
|
+
const fn = new Function(`"use strict"; return (${expr})`);
|
|
178
|
+
const result = fn();
|
|
179
|
+
chainActive = true;
|
|
180
|
+
if (result) {
|
|
181
|
+
chainRendered = true;
|
|
182
|
+
} else {
|
|
183
|
+
child.remove();
|
|
184
|
+
chainRendered = false;
|
|
185
|
+
}
|
|
186
|
+
child.removeAttribute("if");
|
|
187
|
+
} else if (hasDelIf) {
|
|
188
|
+
const raw = getDelIfAttr(child);
|
|
189
|
+
const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
190
|
+
const fn = new Function(`"use strict"; return (${expr})`);
|
|
191
|
+
const result = fn();
|
|
192
|
+
chainActive = true;
|
|
193
|
+
if (result) {
|
|
194
|
+
chainRendered = true;
|
|
195
|
+
} else {
|
|
196
|
+
child.remove();
|
|
197
|
+
chainRendered = false;
|
|
198
|
+
}
|
|
199
|
+
removeDelIfAttr(child);
|
|
200
|
+
} else if (hasElif) {
|
|
201
|
+
const raw = child.getAttribute("elif");
|
|
202
|
+
const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
203
|
+
const fn = new Function(`"use strict"; return (${expr})`);
|
|
204
|
+
const result = fn();
|
|
205
|
+
if (result) {
|
|
206
|
+
chainRendered = true;
|
|
207
|
+
} else {
|
|
208
|
+
child.remove();
|
|
209
|
+
}
|
|
210
|
+
child.removeAttribute("elif");
|
|
211
|
+
} else if (hasElse) {
|
|
212
|
+
chainRendered = true;
|
|
213
|
+
chainActive = false;
|
|
214
|
+
child.removeAttribute("else");
|
|
215
|
+
} else {
|
|
216
|
+
chainActive = false;
|
|
217
|
+
chainRendered = false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (child.parentNode) {
|
|
221
|
+
processPageConditionals(child);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
processPageConditionals(appContainer);
|
|
226
|
+
|
|
227
|
+
const appElements = getAppElements(appContainer);
|
|
228
|
+
const { runtimeScript, scopesCss } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
|
|
229
|
+
const runtimeFilename = await generateRuntimeScript(runtimeScript, paths.outDir);
|
|
230
|
+
const globalCss = (await processAssets(doc, rootDir, config.srcDir, paths.outDir)).join("\n");
|
|
231
|
+
|
|
232
|
+
if (scopesCss) {
|
|
233
|
+
const fileName = "sc-" + genRandomId(null, 6) + ".css";
|
|
234
|
+
await writeCSSOutput(scopesCss, paths.outDir, fileName);
|
|
235
|
+
appendStylesheetLink(doc, fileName);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
appendRuntimeScript(doc, runtimeFilename);
|
|
239
|
+
const html = await serializeDOM(dom);
|
|
240
|
+
await writeHTMLOutput(html, paths.outDir);
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
if (config.assetImport === "static") {
|
|
244
|
+
await copyStaticDir(paths.src, paths.outDir);
|
|
245
|
+
} else {
|
|
246
|
+
await copyResources(rootDir, scopesCss, globalCss, config.srcDir, paths.outDir);
|
|
247
|
+
}
|
|
248
|
+
} catch (err) {
|
|
249
|
+
throwError(err.message || err);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
!isHotReload && logSuccess(paths.outDir);
|
|
253
|
+
isHotReload && console.log("Dev server updated");
|
|
254
|
+
}
|
package/compiler/pipeline.js
CHANGED
|
@@ -70,32 +70,19 @@ export async function getComponents(libDir) {
|
|
|
70
70
|
*/
|
|
71
71
|
export async function getSrcIndex(srcPath) {
|
|
72
72
|
const srcHtmlPath = path.join(srcPath, "index.html");
|
|
73
|
-
const srcChocoPath = path.join(srcPath, "index.choco");
|
|
74
73
|
|
|
75
74
|
const htmlExists = await checkFile(srcHtmlPath);
|
|
76
|
-
const chocoExists = await checkFile(srcChocoPath);
|
|
77
75
|
|
|
78
76
|
let srcHtmlFile = null;
|
|
79
|
-
let srcChocoFile = null;
|
|
80
|
-
|
|
81
|
-
if (htmlExists && chocoExists) {
|
|
82
|
-
throwError(
|
|
83
|
-
"Can't have both .choco and .html source index files at a time: please remove one of the two"
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
77
|
|
|
87
78
|
if (htmlExists) {
|
|
88
79
|
try {
|
|
89
80
|
srcHtmlFile = await readMyFile(srcHtmlPath);
|
|
90
|
-
return { srcHtmlFile,
|
|
81
|
+
return { srcHtmlFile, srcPath: srcHtmlPath };
|
|
91
82
|
} catch (err) {
|
|
92
83
|
throwError(err);
|
|
93
84
|
}
|
|
94
85
|
}
|
|
95
|
-
|
|
96
|
-
if (chocoExists) {
|
|
97
|
-
throwError(".choco files are not supported yet");
|
|
98
|
-
}
|
|
99
86
|
}
|
|
100
87
|
|
|
101
88
|
/**
|
|
@@ -244,7 +231,7 @@ export async function copyResources(rootDir, scopesCss, globalCss, srcDir, outDi
|
|
|
244
231
|
`Failed to copy resource from inline style on <${el.tagName.toLowerCase()}> url("${filePath}")` +
|
|
245
232
|
(getComponentSource(el) ? ` in component "${getComponentSource(el)}"` : "") +
|
|
246
233
|
`: ${err.message || err}`
|
|
247
|
-
);
|
|
234
|
+
);
|
|
248
235
|
}
|
|
249
236
|
}
|
|
250
237
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chocola",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "The sweetest way to build
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "The sweetest way to build the web",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web",
|
|
7
7
|
"app",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"components",
|
|
12
12
|
"ui"
|
|
13
13
|
],
|
|
14
|
-
"homepage": "https://
|
|
14
|
+
"homepage": "https://chocolaframework.web.app/",
|
|
15
15
|
"bugs": {
|
|
16
16
|
"url": "https://github.com/sad-gabi/chocola/issues"
|
|
17
17
|
},
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"author": "SadGabi",
|
|
24
24
|
"type": "module",
|
|
25
25
|
"main": "index.js",
|
|
26
|
+
"funding": {
|
|
27
|
+
"type": "github",
|
|
28
|
+
"url": "https://github.com/sponsors/sad-gabi"
|
|
29
|
+
},
|
|
26
30
|
"scripts": {
|
|
27
31
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
28
32
|
},
|
package/.gitattributes
DELETED