chocola 2.0.0-next.6 → 2.0.0-next.8
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 +39 -36
- package/compiler/chalk.js +32 -32
- package/compiler/component-processor.js +180 -22
- package/compiler/dom-processor.js +3 -4
- package/compiler/index.js +35 -43
- package/compiler/runtime-generator.js +22 -5
- package/compiler/utils.js +52 -52
- package/dev/index.js +1 -1
- package/package.json +2 -2
- package/parser/component.js +129 -129
- package/parser/context.js +30 -29
- package/parser/css.js +96 -96
- package/parser/index.js +5 -5
- package/parser/template.js +120 -121
- package/parser/utils.js +16 -13
- package/runtime/index.js +312 -0
- package/utils.js +27 -2
package/compiler/utils.js
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import { createHash } from "crypto";
|
|
2
|
-
import chalk from "./chalk.js";
|
|
3
|
-
|
|
4
|
-
export function throwError(err) {
|
|
5
|
-
console.log(chalk.red.bold("Error!"), "A fatal error has occurred:\n");
|
|
6
|
-
throw new Error(err);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function genRandomId(collection = null, length = 10, lettersOnly = false) {
|
|
10
|
-
let id;
|
|
11
|
-
if (lettersOnly) {
|
|
12
|
-
id = Array.from({ length }, () => ID_LETTERS[Math.floor(Math.random() * ID_LETTERS.length)]).join("");
|
|
13
|
-
} else {
|
|
14
|
-
id = Math.random().toString(36).substring(2, length + 2);
|
|
15
|
-
}
|
|
16
|
-
if (!collection) return id;
|
|
17
|
-
if (collection.includes(id)) {
|
|
18
|
-
return genRandomId(collection, length, lettersOnly);
|
|
19
|
-
} else {
|
|
20
|
-
collection.push(id);
|
|
21
|
-
return id;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function incrementAlfabet(letters) {
|
|
26
|
-
let arr = letters.split("");
|
|
27
|
-
let i = arr.length - 1;
|
|
28
|
-
|
|
29
|
-
while (i >= 0) {
|
|
30
|
-
let pos = arr[i].charCodeAt(0) - 97;
|
|
31
|
-
if (pos < 25) {
|
|
32
|
-
arr[i] = String.fromCharCode(pos + 97 + 1);
|
|
33
|
-
return arr.join("");
|
|
34
|
-
} else {
|
|
35
|
-
arr[i] = "a";
|
|
36
|
-
i--;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return "a" + arr.join("");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function deterministicHash(content, length = 8) {
|
|
44
|
-
const hash = createHash("sha256").update(content).digest();
|
|
45
|
-
let result = "";
|
|
46
|
-
for (let i = 0; i < length; i++) {
|
|
47
|
-
result += String.fromCharCode(97 + (hash[i % hash.length] % 26));
|
|
48
|
-
}
|
|
49
|
-
return result;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const ID_LETTERS = "abcdefghijklmnopqrstuvwxyz";
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import chalk from "./chalk.js";
|
|
3
|
+
|
|
4
|
+
export function throwError(err) {
|
|
5
|
+
console.log(chalk.red.bold("Error!"), "A fatal error has occurred:\n");
|
|
6
|
+
throw new Error(err);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function genRandomId(collection = null, length = 10, lettersOnly = false) {
|
|
10
|
+
let id;
|
|
11
|
+
if (lettersOnly) {
|
|
12
|
+
id = Array.from({ length }, () => ID_LETTERS[Math.floor(Math.random() * ID_LETTERS.length)]).join("");
|
|
13
|
+
} else {
|
|
14
|
+
id = Math.random().toString(36).substring(2, length + 2);
|
|
15
|
+
}
|
|
16
|
+
if (!collection) return id;
|
|
17
|
+
if (collection.includes(id)) {
|
|
18
|
+
return genRandomId(collection, length, lettersOnly);
|
|
19
|
+
} else {
|
|
20
|
+
collection.push(id);
|
|
21
|
+
return id;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function incrementAlfabet(letters) {
|
|
26
|
+
let arr = letters.split("");
|
|
27
|
+
let i = arr.length - 1;
|
|
28
|
+
|
|
29
|
+
while (i >= 0) {
|
|
30
|
+
let pos = arr[i].charCodeAt(0) - 97;
|
|
31
|
+
if (pos < 25) {
|
|
32
|
+
arr[i] = String.fromCharCode(pos + 97 + 1);
|
|
33
|
+
return arr.join("");
|
|
34
|
+
} else {
|
|
35
|
+
arr[i] = "a";
|
|
36
|
+
i--;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return "a" + arr.join("");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function deterministicHash(content, length = 8) {
|
|
44
|
+
const hash = createHash("sha256").update(content).digest();
|
|
45
|
+
let result = "";
|
|
46
|
+
for (let i = 0; i < length; i++) {
|
|
47
|
+
result += String.fromCharCode(97 + (hash[i % hash.length] % 26));
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ID_LETTERS = "abcdefghijklmnopqrstuvwxyz";
|
package/dev/index.js
CHANGED
|
@@ -144,7 +144,7 @@ export async function serve(__rootdir) {
|
|
|
144
144
|
});
|
|
145
145
|
|
|
146
146
|
server.listen(__config.port, __config.hostname, () => {
|
|
147
|
-
console.log(
|
|
147
|
+
console.log('Live server running at', chalk.hex("#68C4EE").underline(`http://${__config.hostname}:${__config.port}/`));
|
|
148
148
|
});
|
|
149
149
|
}
|
|
150
150
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chocola",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.8",
|
|
4
4
|
"description": "The sweetest way to build the web",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web",
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
"./dev": "./dev/index.js"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"
|
|
38
|
+
"linkedom": "^0.18.13"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/parser/component.js
CHANGED
|
@@ -1,129 +1,129 @@
|
|
|
1
|
-
export function extractPropsDefaults(script) {
|
|
2
|
-
if (!script) return [];
|
|
3
|
-
const propsRegex = /export\s+let\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*(?:=\s*([^;]+))?;/g;
|
|
4
|
-
let props = [];
|
|
5
|
-
let match;
|
|
6
|
-
while ((match = propsRegex.exec(script)) !== null) {
|
|
7
|
-
props.push({ name: match[1].trim(), defaultValue: match[2]?.trim() });
|
|
8
|
-
}
|
|
9
|
-
return props;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function extractRuntime(script, compName) {
|
|
13
|
-
const startRegex = /(?:async\s+)?function\s+\$runtime\(([^)]*)\)\s*\{/;
|
|
14
|
-
const match = script.match(startRegex);
|
|
15
|
-
if (!match) return null;
|
|
16
|
-
|
|
17
|
-
const startIndexBrace = match.index + match[0].length - 1;
|
|
18
|
-
let bracesCount = 0;
|
|
19
|
-
let inString = false;
|
|
20
|
-
let stringChar = null;
|
|
21
|
-
|
|
22
|
-
for (let i = startIndexBrace; i < script.length; i++) {
|
|
23
|
-
const ch = script[i];
|
|
24
|
-
|
|
25
|
-
if (inString) {
|
|
26
|
-
if (ch === '\\') { i++; continue; }
|
|
27
|
-
if (ch === stringChar) inString = false;
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (ch === "'" || ch === '"' || ch === '`') {
|
|
32
|
-
inString = true;
|
|
33
|
-
stringChar = ch;
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (ch === '{') {
|
|
38
|
-
bracesCount++;
|
|
39
|
-
} else if (ch === '}') {
|
|
40
|
-
bracesCount--;
|
|
41
|
-
if (bracesCount === 0) {
|
|
42
|
-
return script.substring(match.index, i + 1);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
throw new Error(`${compName} $runtime function has unclosed curly braces.`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function extractTopLevelFunctions(script, excludeName) {
|
|
51
|
-
const funcs = [];
|
|
52
|
-
let i = 0;
|
|
53
|
-
let depth = 0;
|
|
54
|
-
let inStr = false;
|
|
55
|
-
let strChar = null;
|
|
56
|
-
|
|
57
|
-
while (i < script.length) {
|
|
58
|
-
const ch = script[i];
|
|
59
|
-
|
|
60
|
-
if (inStr) {
|
|
61
|
-
if (ch === '\\') i++;
|
|
62
|
-
else if (ch === strChar) inStr = false;
|
|
63
|
-
i++;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
68
|
-
inStr = true;
|
|
69
|
-
strChar = ch;
|
|
70
|
-
i++;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (ch === '{') { depth++; i++; continue; }
|
|
75
|
-
if (ch === '}') { depth--; i++; continue; }
|
|
76
|
-
|
|
77
|
-
if (depth === 0) {
|
|
78
|
-
const rest = script.slice(i);
|
|
79
|
-
const m = rest.match(/^(?:async\s+)?function\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*\(/);
|
|
80
|
-
if (m) {
|
|
81
|
-
const name = m[1];
|
|
82
|
-
const pastParen = i + m[0].length;
|
|
83
|
-
|
|
84
|
-
let parenDepth = 1;
|
|
85
|
-
let p = pastParen;
|
|
86
|
-
while (p < script.length && parenDepth > 0) {
|
|
87
|
-
if (script[p] === '(') parenDepth++;
|
|
88
|
-
if (script[p] === ')') parenDepth--;
|
|
89
|
-
p++;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
while (p < script.length && script[p] !== '{') p++;
|
|
93
|
-
if (p >= script.length) { i++; continue; }
|
|
94
|
-
|
|
95
|
-
let bDepth = 1;
|
|
96
|
-
let bodyStr = false;
|
|
97
|
-
let bodyStrChar = null;
|
|
98
|
-
let end = p + 1;
|
|
99
|
-
while (end < script.length && bDepth > 0) {
|
|
100
|
-
const c = script[end];
|
|
101
|
-
if (bodyStr) {
|
|
102
|
-
if (c === '\\') end++;
|
|
103
|
-
else if (c === bodyStrChar) bodyStr = false;
|
|
104
|
-
end++;
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
if (c === '"' || c === "'" || c === '`') {
|
|
108
|
-
bodyStr = true;
|
|
109
|
-
bodyStrChar = c;
|
|
110
|
-
end++;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (c === '{') bDepth++;
|
|
114
|
-
if (c === '}') bDepth--;
|
|
115
|
-
end++;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (name !== excludeName) {
|
|
119
|
-
funcs.push(script.slice(i, end));
|
|
120
|
-
}
|
|
121
|
-
i = end;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
i++;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return funcs;
|
|
129
|
-
}
|
|
1
|
+
export function extractPropsDefaults(script) {
|
|
2
|
+
if (!script) return [];
|
|
3
|
+
const propsRegex = /export\s+let\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*(?:=\s*([^;]+))?;/g;
|
|
4
|
+
let props = [];
|
|
5
|
+
let match;
|
|
6
|
+
while ((match = propsRegex.exec(script)) !== null) {
|
|
7
|
+
props.push({ name: match[1].trim(), defaultValue: match[2]?.trim() });
|
|
8
|
+
}
|
|
9
|
+
return props;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function extractRuntime(script, compName) {
|
|
13
|
+
const startRegex = /(?:async\s+)?function\s+\$runtime\(([^)]*)\)\s*\{/;
|
|
14
|
+
const match = script.match(startRegex);
|
|
15
|
+
if (!match) return null;
|
|
16
|
+
|
|
17
|
+
const startIndexBrace = match.index + match[0].length - 1;
|
|
18
|
+
let bracesCount = 0;
|
|
19
|
+
let inString = false;
|
|
20
|
+
let stringChar = null;
|
|
21
|
+
|
|
22
|
+
for (let i = startIndexBrace; i < script.length; i++) {
|
|
23
|
+
const ch = script[i];
|
|
24
|
+
|
|
25
|
+
if (inString) {
|
|
26
|
+
if (ch === '\\') { i++; continue; }
|
|
27
|
+
if (ch === stringChar) inString = false;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (ch === "'" || ch === '"' || ch === '`') {
|
|
32
|
+
inString = true;
|
|
33
|
+
stringChar = ch;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (ch === '{') {
|
|
38
|
+
bracesCount++;
|
|
39
|
+
} else if (ch === '}') {
|
|
40
|
+
bracesCount--;
|
|
41
|
+
if (bracesCount === 0) {
|
|
42
|
+
return script.substring(match.index, i + 1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error(`${compName} $runtime function has unclosed curly braces.`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function extractTopLevelFunctions(script, excludeName) {
|
|
51
|
+
const funcs = [];
|
|
52
|
+
let i = 0;
|
|
53
|
+
let depth = 0;
|
|
54
|
+
let inStr = false;
|
|
55
|
+
let strChar = null;
|
|
56
|
+
|
|
57
|
+
while (i < script.length) {
|
|
58
|
+
const ch = script[i];
|
|
59
|
+
|
|
60
|
+
if (inStr) {
|
|
61
|
+
if (ch === '\\') i++;
|
|
62
|
+
else if (ch === strChar) inStr = false;
|
|
63
|
+
i++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
68
|
+
inStr = true;
|
|
69
|
+
strChar = ch;
|
|
70
|
+
i++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (ch === '{') { depth++; i++; continue; }
|
|
75
|
+
if (ch === '}') { depth--; i++; continue; }
|
|
76
|
+
|
|
77
|
+
if (depth === 0) {
|
|
78
|
+
const rest = script.slice(i);
|
|
79
|
+
const m = rest.match(/^(?:async\s+)?function\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*\(/);
|
|
80
|
+
if (m) {
|
|
81
|
+
const name = m[1];
|
|
82
|
+
const pastParen = i + m[0].length;
|
|
83
|
+
|
|
84
|
+
let parenDepth = 1;
|
|
85
|
+
let p = pastParen;
|
|
86
|
+
while (p < script.length && parenDepth > 0) {
|
|
87
|
+
if (script[p] === '(') parenDepth++;
|
|
88
|
+
if (script[p] === ')') parenDepth--;
|
|
89
|
+
p++;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
while (p < script.length && script[p] !== '{') p++;
|
|
93
|
+
if (p >= script.length) { i++; continue; }
|
|
94
|
+
|
|
95
|
+
let bDepth = 1;
|
|
96
|
+
let bodyStr = false;
|
|
97
|
+
let bodyStrChar = null;
|
|
98
|
+
let end = p + 1;
|
|
99
|
+
while (end < script.length && bDepth > 0) {
|
|
100
|
+
const c = script[end];
|
|
101
|
+
if (bodyStr) {
|
|
102
|
+
if (c === '\\') end++;
|
|
103
|
+
else if (c === bodyStrChar) bodyStr = false;
|
|
104
|
+
end++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
108
|
+
bodyStr = true;
|
|
109
|
+
bodyStrChar = c;
|
|
110
|
+
end++;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (c === '{') bDepth++;
|
|
114
|
+
if (c === '}') bDepth--;
|
|
115
|
+
end++;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (name !== excludeName) {
|
|
119
|
+
funcs.push(script.slice(i, end));
|
|
120
|
+
}
|
|
121
|
+
i = end;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
i++;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return funcs;
|
|
129
|
+
}
|
package/parser/context.js
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import { compileExpression } from "./utils.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
import { compileExpression } from "./utils.js";
|
|
2
|
+
import { restoreTemplateChars } from "../utils.js";
|
|
3
|
+
|
|
4
|
+
export function extractContextFromElement(element) {
|
|
5
|
+
const ctx = {};
|
|
6
|
+
for (const attr of element.attributes) {
|
|
7
|
+
const key = attr.name;
|
|
8
|
+
const val = restoreTemplateChars(attr.value);
|
|
9
|
+
if (!val.includes("{")) { ctx[key] = val; continue; }
|
|
10
|
+
const matches = [...val.matchAll(/\{([^}]+)\}/g)];
|
|
11
|
+
if (matches.length === 1 && matches[0][0] === val) {
|
|
12
|
+
try {
|
|
13
|
+
ctx[key] = compileExpression(matches[0][1], false)();
|
|
14
|
+
continue;
|
|
15
|
+
} catch {}
|
|
16
|
+
}
|
|
17
|
+
ctx[key] = val;
|
|
18
|
+
}
|
|
19
|
+
return ctx;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function hasDelIfAttr(el) {
|
|
23
|
+
return el.hasAttribute("mount:if");
|
|
24
|
+
}
|
|
25
|
+
export function getDelIfAttr(el) {
|
|
26
|
+
return el.getAttribute("mount:if");
|
|
27
|
+
}
|
|
28
|
+
export function removeDelIfAttr(el) {
|
|
29
|
+
el.removeAttribute("mount:if");
|
|
30
|
+
}
|
package/parser/css.js
CHANGED
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
function hasCombinator(sel) {
|
|
2
|
-
let depth = 0;
|
|
3
|
-
for (const ch of sel) {
|
|
4
|
-
if (ch === '[') depth++;
|
|
5
|
-
else if (ch === ']') depth--;
|
|
6
|
-
else if (depth === 0 && (ch === ' ' || ch === '>' || ch === '+' || ch === '~')) return true;
|
|
7
|
-
}
|
|
8
|
-
return false;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function scopeSelector(sel, cssId) {
|
|
12
|
-
const s = sel.trim();
|
|
13
|
-
if (s.startsWith("." + cssId)) return s;
|
|
14
|
-
if (hasCombinator(s)) return `.${cssId} ${s}`;
|
|
15
|
-
return `.${cssId}${s}, .${cssId} ${s}`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function scopeCss(cssString, cssId) {
|
|
19
|
-
const keyframeRegex = /@(-webkit-)?keyframes\s+(\S+)/gi;
|
|
20
|
-
const animNames = new Set();
|
|
21
|
-
let match;
|
|
22
|
-
while ((match = keyframeRegex.exec(cssString))) {
|
|
23
|
-
animNames.add(match[2]);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (animNames.size > 0) {
|
|
27
|
-
const sorted = [...animNames].sort((a, b) => b.length - a.length);
|
|
28
|
-
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
29
|
-
for (const name of sorted) {
|
|
30
|
-
cssString = cssString.replace(
|
|
31
|
-
new RegExp(`(@(?:-webkit-)?keyframes\\s+)${esc(name)}`, "gi"),
|
|
32
|
-
`$1${name}-${cssId}`
|
|
33
|
-
);
|
|
34
|
-
cssString = cssString.replace(
|
|
35
|
-
new RegExp(`((?:animation|animation-name)\\s*:\\s*[^;{]*?)\\b${esc(name)}\\b`, "gi"),
|
|
36
|
-
`$1${name}-${cssId}`
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function findMatchingBrace(s, openIndex) {
|
|
42
|
-
let depth = 0;
|
|
43
|
-
for (let i = openIndex; i < s.length; i++) {
|
|
44
|
-
const ch = s[i];
|
|
45
|
-
if (ch === '{') depth++;
|
|
46
|
-
else if (ch === '}') {
|
|
47
|
-
depth--;
|
|
48
|
-
if (depth === 0) return i;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return s.length - 1;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const scopeInnerAtRules = new Set(["@media", "@supports", "@container", "@layer"]);
|
|
55
|
-
|
|
56
|
-
function processBlock(str) {
|
|
57
|
-
str = str.replaceAll(":root", `.${cssId}`);
|
|
58
|
-
|
|
59
|
-
let out = "";
|
|
60
|
-
let i = 0;
|
|
61
|
-
while (i < str.length) {
|
|
62
|
-
const braceIndex = str.indexOf('{', i);
|
|
63
|
-
if (braceIndex === -1) {
|
|
64
|
-
out += str.slice(i);
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
const header = str.substring(i, braceIndex);
|
|
68
|
-
const endBrace = findMatchingBrace(str, braceIndex);
|
|
69
|
-
const inner = str.substring(braceIndex + 1, endBrace);
|
|
70
|
-
|
|
71
|
-
if (header.trim().startsWith("@")) {
|
|
72
|
-
const atKeyword = header.trim().split(/\s+/)[0].toLowerCase();
|
|
73
|
-
if (scopeInnerAtRules.has(atKeyword)) {
|
|
74
|
-
out += header + "{" + processBlock(inner) + "}";
|
|
75
|
-
} else {
|
|
76
|
-
out += header + "{" + inner + "}";
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
const innerScoped = processBlock(inner);
|
|
80
|
-
const selectors = header
|
|
81
|
-
.split(",")
|
|
82
|
-
.map(s => s.trim())
|
|
83
|
-
.filter(Boolean);
|
|
84
|
-
const scopedHeader = selectors.length > 0
|
|
85
|
-
? selectors.map(sel => scopeSelector(sel, cssId)).join(", ")
|
|
86
|
-
: header;
|
|
87
|
-
out += scopedHeader + "{" + innerScoped + "}";
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
i = endBrace + 1;
|
|
91
|
-
}
|
|
92
|
-
return out;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return processBlock(cssString);
|
|
96
|
-
}
|
|
1
|
+
function hasCombinator(sel) {
|
|
2
|
+
let depth = 0;
|
|
3
|
+
for (const ch of sel) {
|
|
4
|
+
if (ch === '[') depth++;
|
|
5
|
+
else if (ch === ']') depth--;
|
|
6
|
+
else if (depth === 0 && (ch === ' ' || ch === '>' || ch === '+' || ch === '~')) return true;
|
|
7
|
+
}
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function scopeSelector(sel, cssId) {
|
|
12
|
+
const s = sel.trim();
|
|
13
|
+
if (s.startsWith("." + cssId)) return s;
|
|
14
|
+
if (hasCombinator(s)) return `.${cssId} ${s}`;
|
|
15
|
+
return `.${cssId}${s}, .${cssId} ${s}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function scopeCss(cssString, cssId) {
|
|
19
|
+
const keyframeRegex = /@(-webkit-)?keyframes\s+(\S+)/gi;
|
|
20
|
+
const animNames = new Set();
|
|
21
|
+
let match;
|
|
22
|
+
while ((match = keyframeRegex.exec(cssString))) {
|
|
23
|
+
animNames.add(match[2]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (animNames.size > 0) {
|
|
27
|
+
const sorted = [...animNames].sort((a, b) => b.length - a.length);
|
|
28
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
29
|
+
for (const name of sorted) {
|
|
30
|
+
cssString = cssString.replace(
|
|
31
|
+
new RegExp(`(@(?:-webkit-)?keyframes\\s+)${esc(name)}`, "gi"),
|
|
32
|
+
`$1${name}-${cssId}`
|
|
33
|
+
);
|
|
34
|
+
cssString = cssString.replace(
|
|
35
|
+
new RegExp(`((?:animation|animation-name)\\s*:\\s*[^;{]*?)\\b${esc(name)}\\b`, "gi"),
|
|
36
|
+
`$1${name}-${cssId}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function findMatchingBrace(s, openIndex) {
|
|
42
|
+
let depth = 0;
|
|
43
|
+
for (let i = openIndex; i < s.length; i++) {
|
|
44
|
+
const ch = s[i];
|
|
45
|
+
if (ch === '{') depth++;
|
|
46
|
+
else if (ch === '}') {
|
|
47
|
+
depth--;
|
|
48
|
+
if (depth === 0) return i;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return s.length - 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const scopeInnerAtRules = new Set(["@media", "@supports", "@container", "@layer"]);
|
|
55
|
+
|
|
56
|
+
function processBlock(str) {
|
|
57
|
+
str = str.replaceAll(":root", `.${cssId}`);
|
|
58
|
+
|
|
59
|
+
let out = "";
|
|
60
|
+
let i = 0;
|
|
61
|
+
while (i < str.length) {
|
|
62
|
+
const braceIndex = str.indexOf('{', i);
|
|
63
|
+
if (braceIndex === -1) {
|
|
64
|
+
out += str.slice(i);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
const header = str.substring(i, braceIndex);
|
|
68
|
+
const endBrace = findMatchingBrace(str, braceIndex);
|
|
69
|
+
const inner = str.substring(braceIndex + 1, endBrace);
|
|
70
|
+
|
|
71
|
+
if (header.trim().startsWith("@")) {
|
|
72
|
+
const atKeyword = header.trim().split(/\s+/)[0].toLowerCase();
|
|
73
|
+
if (scopeInnerAtRules.has(atKeyword)) {
|
|
74
|
+
out += header + "{" + processBlock(inner) + "}";
|
|
75
|
+
} else {
|
|
76
|
+
out += header + "{" + inner + "}";
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
const innerScoped = processBlock(inner);
|
|
80
|
+
const selectors = header
|
|
81
|
+
.split(",")
|
|
82
|
+
.map(s => s.trim())
|
|
83
|
+
.filter(Boolean);
|
|
84
|
+
const scopedHeader = selectors.length > 0
|
|
85
|
+
? selectors.map(sel => scopeSelector(sel, cssId)).join(", ")
|
|
86
|
+
: header;
|
|
87
|
+
out += scopedHeader + "{" + innerScoped + "}";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
i = endBrace + 1;
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return processBlock(cssString);
|
|
96
|
+
}
|
package/parser/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { extractPropsDefaults, extractRuntime, extractTopLevelFunctions } from "./component.js";
|
|
2
|
-
export { extractContextFromElement, hasDelIfAttr, getDelIfAttr, removeDelIfAttr } from "./context.js";
|
|
3
|
-
export { reservedAttrs, getLineNumber, validateChainStructure, applyConditionalToElement, interpolateNode } from "./template.js";
|
|
4
|
-
export { scopeCss } from "./css.js";
|
|
5
|
-
export { compileExpression } from "./utils.js";
|
|
1
|
+
export { extractPropsDefaults, extractRuntime, extractTopLevelFunctions } from "./component.js";
|
|
2
|
+
export { extractContextFromElement, hasDelIfAttr, getDelIfAttr, removeDelIfAttr } from "./context.js";
|
|
3
|
+
export { reservedAttrs, getLineNumber, validateChainStructure, applyConditionalToElement, interpolateNode } from "./template.js";
|
|
4
|
+
export { scopeCss } from "./css.js";
|
|
5
|
+
export { compileExpression } from "./utils.js";
|