chocola 2.0.0-next.7 → 2.0.0-next.9
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 +42 -35
- package/compiler/component-processor.js +218 -37
- package/compiler/config.js +2 -2
- package/compiler/dom-processor.js +6 -4
- package/compiler/index.js +76 -55
- package/compiler/utils.js +78 -0
- package/dev/index.js +3 -3
- package/package.json +2 -2
- package/parser/component.js +140 -1
- package/parser/context.js +11 -10
- package/parser/css.js +2 -2
- package/parser/index.js +3 -3
- package/parser/template.js +15 -15
- package/parser/utils.js +20 -1
- package/runtime/index.js +34 -31
- package/utils.js +28 -3
package/compiler/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { performance } from "perf_hooks";
|
|
3
4
|
import chalk from "./chalk.js";
|
|
4
5
|
import { loadConfig, resolvePaths } from "./config.js";
|
|
5
6
|
import {
|
|
@@ -16,8 +17,8 @@ import {
|
|
|
16
17
|
} from "./dom-processor.js";
|
|
17
18
|
import { processAllComponents } from "./component-processor.js";
|
|
18
19
|
import { generateRuntimeScript } from "./runtime-generator.js";
|
|
19
|
-
import { genRandomId, throwError } from "./utils.js";
|
|
20
|
-
import {
|
|
20
|
+
import { genRandomId, throwError, warnConstantCondition, findElementLine } from "./utils.js";
|
|
21
|
+
import { compileExpr, evaluateConstant, hasMountIf, getMountIf, removeMountIf } from "../parser/index.js";
|
|
21
22
|
import {
|
|
22
23
|
copyStaticDir,
|
|
23
24
|
getComponents,
|
|
@@ -26,48 +27,47 @@ import {
|
|
|
26
27
|
processScript,
|
|
27
28
|
processStylesheet,
|
|
28
29
|
} from "./pipeline.js";
|
|
30
|
+
import { text } from "stream/consumers";
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
========================================================================
|
|
34
|
-
`);
|
|
32
|
+
const GOLD_COLOR = "#D87416";
|
|
33
|
+
const WHITE_COLOR = "#FAFAF8";
|
|
34
|
+
const TEXT_FAINT = "#E7EBE1";
|
|
35
35
|
|
|
36
36
|
function logBanner() {
|
|
37
|
-
console.log(chalk.bold.hex("#945e33")(`\n RUNNING CHOCOLA BUNDLER`));
|
|
38
|
-
console.log(logSeparation);
|
|
39
37
|
console.log(
|
|
40
|
-
chalk.hex(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
38
|
+
chalk.hex(GOLD_COLOR)(`
|
|
39
|
+
┌─────────────────────────────────────────────┐
|
|
40
|
+
│┌-------------------------------------------┐│
|
|
41
|
+
││ ││`)
|
|
42
|
+
);
|
|
43
|
+
console.log(
|
|
44
|
+
chalk.hex(GOLD_COLOR)(` ││ `) +
|
|
45
|
+
chalk.bold.hex(WHITE_COLOR)(`{`) +
|
|
46
|
+
chalk.bold.hex(GOLD_COLOR)(` C H O C O L A `) +
|
|
47
|
+
chalk.bold.hex(WHITE_COLOR)(`}`) +
|
|
48
|
+
chalk.hex(GOLD_COLOR)(` ││\n`) +
|
|
49
|
+
chalk.hex(GOLD_COLOR)(` ││ ││
|
|
50
|
+
││ `) +
|
|
51
|
+
chalk.hex(TEXT_FAINT)(`THE SWEETEST WAY TO BUILD THE WEB`) +
|
|
52
|
+
chalk.hex(GOLD_COLOR)(` ││
|
|
53
|
+
││ ││
|
|
54
|
+
│└-------------------------------------------┘│
|
|
55
|
+
└─────────────────────────────────────────────┘
|
|
56
|
+
`)
|
|
56
57
|
);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
function logSuccess(outDirPath) {
|
|
60
|
-
console.log(`
|
|
61
|
-
▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄ ▄▄ ▄▄▄▄▄ ██
|
|
62
|
-
██ ██▀██ ██▄██ ██▀██ ██▀██ ███▄██ ██▄▄ ██
|
|
63
|
-
▄▄█▀ ▀███▀ ██▄█▀ ████▀ ▀███▀ ██ ▀██ ██▄▄▄ ▄▄
|
|
64
|
-
|
|
65
|
-
`);
|
|
60
|
+
function logSuccess(outDirPath, durationMs) {
|
|
66
61
|
console.log(
|
|
67
62
|
chalk.bold.green(">"),
|
|
68
63
|
"Project bundled succesfully at",
|
|
69
|
-
chalk.green.underline(outDirPath)
|
|
70
|
-
|
|
64
|
+
chalk.green.underline(outDirPath));
|
|
65
|
+
console.log(chalk.bold.green(`\nJOB DONE!`) + chalk.hex(TEXT_FAINT)(` (${formatDuration(durationMs)})\n`));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatDuration(ms) {
|
|
69
|
+
if (ms >= 1000) return (ms / 1000).toFixed(2) + "s";
|
|
70
|
+
return Math.round(ms) + "ms";
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
async function setupOutputDirectory(outDirPath, emptyOutDir) {
|
|
@@ -81,9 +81,8 @@ async function loadAndDisplayComponents(srcComponentsPath) {
|
|
|
81
81
|
const foundComponents = await getComponents(srcComponentsPath);
|
|
82
82
|
const { loadedComponents, notDefComps: emptyComps, componentsLib } = foundComponents;
|
|
83
83
|
|
|
84
|
-
console.log(` LOADING COMPONENTS`);
|
|
85
84
|
console.log(chalk.bold.green(">"), "Components found in", chalk.green.underline(srcComponentsPath) + ":");
|
|
86
|
-
console.log(" ", componentsLib, "\n");
|
|
85
|
+
console.log(" ", componentsLib, "\n\n");
|
|
87
86
|
|
|
88
87
|
if (emptyComps?.length > 0) {
|
|
89
88
|
console.warn(chalk.bold.yellow("WARNING!"), "The following component files are empty:");
|
|
@@ -114,39 +113,62 @@ async function processAssets(doc, rootDir, srcDir, outDirPath) {
|
|
|
114
113
|
return cssContents
|
|
115
114
|
}
|
|
116
115
|
|
|
117
|
-
function processPageConditionals(parent) {
|
|
116
|
+
function processPageConditionals(parent, sourceFile, sourceContent) {
|
|
118
117
|
const children = [...parent.children];
|
|
119
118
|
let chainActive = false;
|
|
120
119
|
let chainRendered = false;
|
|
121
120
|
|
|
121
|
+
const getLocation = (child) => {
|
|
122
|
+
if (!sourceContent) return sourceFile;
|
|
123
|
+
const lineNum = findElementLine(sourceContent, child.outerHTML);
|
|
124
|
+
return lineNum !== null ? `${sourceFile}:${lineNum}` : sourceFile;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const warnIfConstant = (expr, child, attr) => {
|
|
128
|
+
const { constant, value } = evaluateConstant(expr);
|
|
129
|
+
if (!constant) return;
|
|
130
|
+
warnConstantCondition(getLocation(child), child.tagName.toLowerCase(), attr, Boolean(value));
|
|
131
|
+
};
|
|
132
|
+
|
|
122
133
|
for (const child of children) {
|
|
123
134
|
const hasIf = child.hasAttribute("if");
|
|
124
|
-
const hasDelIf =
|
|
135
|
+
const hasDelIf = hasMountIf(child);
|
|
125
136
|
const hasElif = child.hasAttribute("elif");
|
|
126
137
|
const hasElse = child.hasAttribute("else");
|
|
127
138
|
|
|
139
|
+
if (hasIf || hasDelIf || hasElif) {
|
|
140
|
+
const stripBraces = (raw) => raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
141
|
+
if (hasIf) warnIfConstant(stripBraces(child.getAttribute("if")), child, "if");
|
|
142
|
+
if (hasDelIf) warnIfConstant(stripBraces(getMountIf(child)), child, "mount:if");
|
|
143
|
+
if (hasElif) warnIfConstant(stripBraces(child.getAttribute("elif")), child, "elif");
|
|
144
|
+
}
|
|
145
|
+
|
|
128
146
|
if (hasElif || hasElse) {
|
|
129
|
-
if (!chainActive)
|
|
147
|
+
if (!chainActive) {
|
|
148
|
+
const tag = child.tagName.toLowerCase();
|
|
149
|
+
const attr = hasElif ? "elif" : "else";
|
|
150
|
+
throwError(`${getLocation(child)}\n <${tag}> has ${attr} without a preceding if/mount:if sibling`);
|
|
151
|
+
}
|
|
130
152
|
if (chainRendered) { child.remove(); continue; }
|
|
131
153
|
}
|
|
132
154
|
|
|
133
155
|
if (hasIf) {
|
|
134
156
|
const raw = child.getAttribute("if");
|
|
135
157
|
const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
136
|
-
const fn =
|
|
158
|
+
const fn = compileExpr(expr, false);
|
|
137
159
|
const result = fn();
|
|
138
160
|
chainActive = true;
|
|
139
161
|
if (result) {
|
|
140
162
|
chainRendered = true;
|
|
141
163
|
} else {
|
|
142
|
-
child.
|
|
164
|
+
child.style.display = "none";
|
|
143
165
|
chainRendered = false;
|
|
144
166
|
}
|
|
145
167
|
child.removeAttribute("if");
|
|
146
168
|
} else if (hasDelIf) {
|
|
147
|
-
const raw =
|
|
169
|
+
const raw = getMountIf(child);
|
|
148
170
|
const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
149
|
-
const fn =
|
|
171
|
+
const fn = compileExpr(expr, false);
|
|
150
172
|
const result = fn();
|
|
151
173
|
chainActive = true;
|
|
152
174
|
if (result) {
|
|
@@ -155,11 +177,11 @@ function processPageConditionals(parent) {
|
|
|
155
177
|
child.remove();
|
|
156
178
|
chainRendered = false;
|
|
157
179
|
}
|
|
158
|
-
|
|
180
|
+
removeMountIf(child);
|
|
159
181
|
} else if (hasElif) {
|
|
160
182
|
const raw = child.getAttribute("elif");
|
|
161
183
|
const expr = raw.startsWith("{") ? raw.slice(1, -1) : raw;
|
|
162
|
-
const fn =
|
|
184
|
+
const fn = compileExpr(expr, false);
|
|
163
185
|
const result = fn();
|
|
164
186
|
if (result) {
|
|
165
187
|
chainRendered = true;
|
|
@@ -177,18 +199,18 @@ function processPageConditionals(parent) {
|
|
|
177
199
|
}
|
|
178
200
|
|
|
179
201
|
if (child.parentNode) {
|
|
180
|
-
processPageConditionals(child);
|
|
202
|
+
processPageConditionals(child, sourceFile, sourceContent);
|
|
181
203
|
}
|
|
182
204
|
}
|
|
183
205
|
}
|
|
184
206
|
|
|
185
207
|
export default async function compile(rootDir, buildConfig) {
|
|
186
208
|
const isHotReload = buildConfig?.isHotReload || null;
|
|
209
|
+
const startTime = performance.now();
|
|
187
210
|
!isHotReload && logBanner();
|
|
188
211
|
|
|
189
212
|
const config = await loadConfig(rootDir);
|
|
190
213
|
const paths = resolvePaths(rootDir, config);
|
|
191
|
-
!isHotReload && console.log(logSeparation);
|
|
192
214
|
|
|
193
215
|
await setupOutputDirectory(paths.outDir, config.emptyOutDir);
|
|
194
216
|
|
|
@@ -197,16 +219,13 @@ export default async function compile(rootDir, buildConfig) {
|
|
|
197
219
|
const pageSourcePath = indexFiles.srcPath;
|
|
198
220
|
|
|
199
221
|
const loadedComponents = await loadAndDisplayComponents(paths.components);
|
|
200
|
-
!isHotReload && console.log(
|
|
201
|
-
!isHotReload && console.log(` BUNDLING STATIC BUILD`);
|
|
202
|
-
!isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(paths.outDir) + "\n");
|
|
203
|
-
!isHotReload && console.log(logSeparation);
|
|
222
|
+
!isHotReload && console.log(chalk.bold.green(">"), "Creating Chocola static build in directory", chalk.green.underline(paths.outDir));
|
|
204
223
|
|
|
205
224
|
const dom = createDOM(srcIndexContent);
|
|
206
|
-
const doc = dom.
|
|
225
|
+
const doc = dom.document;
|
|
207
226
|
const appContainer = validateAppContainer(doc);
|
|
208
227
|
|
|
209
|
-
processPageConditionals(appContainer);
|
|
228
|
+
processPageConditionals(appContainer, pageSourcePath, dom.protectedContent);
|
|
210
229
|
|
|
211
230
|
const appElements = getAppElements(appContainer);
|
|
212
231
|
const { runtimeScript, scopesCss, hashMap, csrClasses } = processAllComponents(appElements, loadedComponents, pageSourcePath, srcIndexContent);
|
|
@@ -236,8 +255,10 @@ export default async function compile(rootDir, buildConfig) {
|
|
|
236
255
|
await fs.mkdir(chocolaDir, { recursive: true });
|
|
237
256
|
await fs.writeFile(path.join(chocolaDir, "hashes.json"), JSON.stringify(hashMap, null, 2) + "\n");
|
|
238
257
|
|
|
239
|
-
|
|
240
|
-
|
|
258
|
+
const durationMs = performance.now() - startTime;
|
|
259
|
+
|
|
260
|
+
!isHotReload && logSuccess(paths.outDir, durationMs);
|
|
261
|
+
isHotReload && console.log("Dev server updated " + chalk.hex(TEXT_FAINT)(`(${formatDuration(durationMs)})`));
|
|
241
262
|
}
|
|
242
263
|
|
|
243
264
|
/**
|
package/compiler/utils.js
CHANGED
|
@@ -6,6 +6,84 @@ export function throwError(err) {
|
|
|
6
6
|
throw new Error(err);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
export function warnConstantCondition(location, tag, attr, truthy) {
|
|
10
|
+
const article = /^[aeiou]/.test(attr) ? "an" : "a";
|
|
11
|
+
console.warn(
|
|
12
|
+
chalk.bold.yellow("WARNING!"),
|
|
13
|
+
`${location}: <${tag}> has ${article} \`${attr}\` condition that is always ${truthy ? "truthy" : "falsy"}`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function warnUnusedDeclaration(location, kind, name) {
|
|
18
|
+
console.warn(
|
|
19
|
+
chalk.bold.yellow("WARNING!"),
|
|
20
|
+
`${location}: ${kind} \`${name}\` is never used`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeAttributeQuotes(html) {
|
|
25
|
+
return html
|
|
26
|
+
.replace(/(\s[\w:.-]+)=(['"])([\s\S]*?)\2/g, '$1="$3"')
|
|
27
|
+
.replace(/(\s[\w:.-]+)=([^\s"'<>=]+)/g, '$1="$2"');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getStartTag(outerHTML) {
|
|
31
|
+
let inQuote = null;
|
|
32
|
+
for (let i = 0; i < outerHTML.length; i++) {
|
|
33
|
+
const c = outerHTML[i];
|
|
34
|
+
if (inQuote) {
|
|
35
|
+
if (c === inQuote) inQuote = null;
|
|
36
|
+
} else if (c === '"' || c === "'") {
|
|
37
|
+
inQuote = c;
|
|
38
|
+
} else if (c === ">") {
|
|
39
|
+
return outerHTML.slice(0, i + 1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return outerHTML;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function escapeRegex(str) {
|
|
46
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function flexibleStartTagRegex(startTag) {
|
|
50
|
+
return new RegExp(
|
|
51
|
+
startTag
|
|
52
|
+
.split(/\s+/)
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.map(escapeRegex)
|
|
55
|
+
.join("\\s+")
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function findElementLine(sourceContent, outerHTML) {
|
|
60
|
+
const source = normalizeAttributeQuotes(sourceContent).toLowerCase().replace(/\/\s*>/g, ">");
|
|
61
|
+
const startTags = [
|
|
62
|
+
normalizeAttributeQuotes(getStartTag(outerHTML)),
|
|
63
|
+
normalizeAttributeQuotes(
|
|
64
|
+
getStartTag(
|
|
65
|
+
outerHTML
|
|
66
|
+
.replace(/"/g, '"')
|
|
67
|
+
.replace(/&/g, "&")
|
|
68
|
+
.replace(/</g, "<")
|
|
69
|
+
.replace(/>/g, ">")
|
|
70
|
+
)
|
|
71
|
+
),
|
|
72
|
+
].map(tag => tag.toLowerCase());
|
|
73
|
+
const candidates = [
|
|
74
|
+
flexibleStartTagRegex(startTags[0]),
|
|
75
|
+
flexibleStartTagRegex(startTags[0].replace(/=""/g, "")),
|
|
76
|
+
flexibleStartTagRegex(startTags[1]),
|
|
77
|
+
];
|
|
78
|
+
for (const re of candidates) {
|
|
79
|
+
const match = re.exec(source);
|
|
80
|
+
if (match) {
|
|
81
|
+
return source.substring(0, match.index).split("\n").length;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
9
87
|
export function genRandomId(collection = null, length = 10, lettersOnly = false) {
|
|
10
88
|
let id;
|
|
11
89
|
if (lettersOnly) {
|
package/dev/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from "path";
|
|
|
4
4
|
import chalk from "../compiler/chalk.js";
|
|
5
5
|
import compile from "../compiler/index.js";
|
|
6
6
|
import { loadConfig, resolvePaths } from "../compiler/config.js";
|
|
7
|
-
import {
|
|
7
|
+
import { getConfig } from "../utils.js";
|
|
8
8
|
|
|
9
9
|
export async function serve(__rootdir) {
|
|
10
10
|
await compile(__rootdir);
|
|
@@ -17,7 +17,7 @@ export async function serve(__rootdir) {
|
|
|
17
17
|
|
|
18
18
|
let lastBuildTime = Date.now();
|
|
19
19
|
|
|
20
|
-
const fullConfig = await
|
|
20
|
+
const fullConfig = await getConfig(__rootdir);
|
|
21
21
|
const config = await loadConfig(__rootdir);
|
|
22
22
|
const paths = resolvePaths(__rootdir, config);
|
|
23
23
|
const devConfig = fullConfig.dev;
|
|
@@ -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.9",
|
|
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
|
@@ -44,7 +44,146 @@ export function extractRuntime(script, compName) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
throw new Error(`${compName} $runtime function has unclosed curly braces
|
|
47
|
+
throw new Error(`${compName} $runtime function has unclosed curly braces`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function extractTopLevelVariables(script) {
|
|
51
|
+
if (!script) return [];
|
|
52
|
+
const vars = [];
|
|
53
|
+
let i = 0;
|
|
54
|
+
let depth = 0;
|
|
55
|
+
let parenDepth = 0;
|
|
56
|
+
let bracketDepth = 0;
|
|
57
|
+
let inStr = false;
|
|
58
|
+
let strChar = null;
|
|
59
|
+
|
|
60
|
+
while (i < script.length) {
|
|
61
|
+
const ch = script[i];
|
|
62
|
+
|
|
63
|
+
if (inStr) {
|
|
64
|
+
if (ch === "\\") i++;
|
|
65
|
+
else if (ch === strChar) inStr = false;
|
|
66
|
+
i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
71
|
+
inStr = true;
|
|
72
|
+
strChar = ch;
|
|
73
|
+
i++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (ch === "/" && script[i + 1] === "/") {
|
|
78
|
+
while (i < script.length && script[i] !== "\n") i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (ch === "/" && script[i + 1] === "*") {
|
|
82
|
+
i += 2;
|
|
83
|
+
while (i < script.length && !(script[i] === "*" && script[i + 1] === "/")) i++;
|
|
84
|
+
i += 2;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (ch === "{") { depth++; i++; continue; }
|
|
89
|
+
if (ch === "}") { depth--; i++; continue; }
|
|
90
|
+
if (ch === "(") { parenDepth++; i++; continue; }
|
|
91
|
+
if (ch === ")") { parenDepth--; i++; continue; }
|
|
92
|
+
if (ch === "[") { bracketDepth++; i++; continue; }
|
|
93
|
+
if (ch === "]") { bracketDepth--; i++; continue; }
|
|
94
|
+
|
|
95
|
+
if (depth === 0 && parenDepth === 0 && bracketDepth === 0) {
|
|
96
|
+
const rest = script.slice(i);
|
|
97
|
+
const m = rest.match(/^(let|const)\s+([a-zA-Z_$][0-9a-zA-Z_$]*)\s*/);
|
|
98
|
+
if (m) {
|
|
99
|
+
const keyword = m[1];
|
|
100
|
+
const before = script.slice(0, i).trimEnd();
|
|
101
|
+
if (!before.endsWith("export")) {
|
|
102
|
+
let name = m[2];
|
|
103
|
+
let p = i + m[0].length;
|
|
104
|
+
while (p < script.length && /\s/.test(script[p])) p++;
|
|
105
|
+
i = p;
|
|
106
|
+
|
|
107
|
+
while (p < script.length) {
|
|
108
|
+
i = p;
|
|
109
|
+
let q = p;
|
|
110
|
+
|
|
111
|
+
if (script[q] === "=") {
|
|
112
|
+
q++;
|
|
113
|
+
while (q < script.length && /\s/.test(script[q])) q++;
|
|
114
|
+
const delim = findDeclaratorEnd(script, q);
|
|
115
|
+
if (name !== "self" && name !== "ctx") {
|
|
116
|
+
vars.push({ keyword, name, value: script.slice(q, delim).trim() });
|
|
117
|
+
}
|
|
118
|
+
if (script[delim] === ";") { i = delim + 1; break; }
|
|
119
|
+
p = delim + 1;
|
|
120
|
+
while (p < script.length && /\s/.test(script[p])) p++;
|
|
121
|
+
} else {
|
|
122
|
+
if (name !== "self" && name !== "ctx") {
|
|
123
|
+
vars.push({ keyword, name, value: undefined });
|
|
124
|
+
}
|
|
125
|
+
if (script[q] === ";") { i = q + 1; break; }
|
|
126
|
+
if (script[q] !== ",") { i = q; break; }
|
|
127
|
+
p = q + 1;
|
|
128
|
+
while (p < script.length && /\s/.test(script[p])) p++;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const nameMatch = script.slice(p).match(/^([a-zA-Z_$][0-9a-zA-Z_$]*)\s*/);
|
|
132
|
+
if (!nameMatch) { i = p; break; }
|
|
133
|
+
name = nameMatch[1];
|
|
134
|
+
p += nameMatch[0].length;
|
|
135
|
+
}
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
i++;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return vars;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function findDeclaratorEnd(script, start) {
|
|
147
|
+
let i = start;
|
|
148
|
+
let parens = 0;
|
|
149
|
+
let brackets = 0;
|
|
150
|
+
let braces = 0;
|
|
151
|
+
let inStr = false;
|
|
152
|
+
let strChar = null;
|
|
153
|
+
|
|
154
|
+
while (i < script.length) {
|
|
155
|
+
const c = script[i];
|
|
156
|
+
|
|
157
|
+
if (inStr) {
|
|
158
|
+
if (c === "\\") i++;
|
|
159
|
+
else if (c === strChar) inStr = false;
|
|
160
|
+
i++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (c === "/" && script[i + 1] === "/") {
|
|
165
|
+
while (i < script.length && script[i] !== "\n") i++;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (c === "/" && script[i + 1] === "*") {
|
|
169
|
+
i += 2;
|
|
170
|
+
while (i < script.length && !(script[i] === "*" && script[i + 1] === "/")) i++;
|
|
171
|
+
i += 2;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (c === '"' || c === "'" || c === "`") { inStr = true; strChar = c; i++; continue; }
|
|
176
|
+
if (c === "(") { parens++; i++; continue; }
|
|
177
|
+
if (c === ")") { parens--; i++; continue; }
|
|
178
|
+
if (c === "[") { brackets++; i++; continue; }
|
|
179
|
+
if (c === "]") { brackets--; i++; continue; }
|
|
180
|
+
if (c === "{") { braces++; i++; continue; }
|
|
181
|
+
if (c === "}") { braces--; i++; continue; }
|
|
182
|
+
if ((c === ";" || c === ",") && parens === 0 && brackets === 0 && braces === 0) return i;
|
|
183
|
+
i++;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return i;
|
|
48
187
|
}
|
|
49
188
|
|
|
50
189
|
export function extractTopLevelFunctions(script, excludeName) {
|
package/parser/context.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { compileExpr } from "./utils.js";
|
|
2
|
+
import { restoreTemplateChars } from "../utils.js";
|
|
2
3
|
|
|
3
|
-
export function
|
|
4
|
+
export function extractCtxFromEl(element) {
|
|
4
5
|
const ctx = {};
|
|
5
6
|
for (const attr of element.attributes) {
|
|
6
7
|
const key = attr.name;
|
|
7
|
-
const val = attr.value;
|
|
8
|
+
const val = restoreTemplateChars(attr.value);
|
|
8
9
|
if (!val.includes("{")) { ctx[key] = val; continue; }
|
|
9
10
|
const matches = [...val.matchAll(/\{([^}]+)\}/g)];
|
|
10
11
|
if (matches.length === 1 && matches[0][0] === val) {
|
|
11
12
|
try {
|
|
12
|
-
ctx[key] =
|
|
13
|
+
ctx[key] = compileExpr(matches[0][1], false)();
|
|
13
14
|
continue;
|
|
14
15
|
} catch {}
|
|
15
16
|
}
|
|
@@ -18,12 +19,12 @@ export function extractContextFromElement(element) {
|
|
|
18
19
|
return ctx;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export function
|
|
22
|
-
return el.hasAttribute("
|
|
22
|
+
export function hasMountIf(el) {
|
|
23
|
+
return el.hasAttribute("mount:if");
|
|
23
24
|
}
|
|
24
|
-
export function
|
|
25
|
-
return el.getAttribute("
|
|
25
|
+
export function getMountIf(el) {
|
|
26
|
+
return el.getAttribute("mount:if");
|
|
26
27
|
}
|
|
27
|
-
export function
|
|
28
|
-
el.removeAttribute("
|
|
28
|
+
export function removeMountIf(el) {
|
|
29
|
+
el.removeAttribute("mount:if");
|
|
29
30
|
}
|
package/parser/css.js
CHANGED
|
@@ -38,7 +38,7 @@ export function scopeCss(cssString, cssId) {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function
|
|
41
|
+
function findClosure(s, openIndex) {
|
|
42
42
|
let depth = 0;
|
|
43
43
|
for (let i = openIndex; i < s.length; i++) {
|
|
44
44
|
const ch = s[i];
|
|
@@ -65,7 +65,7 @@ export function scopeCss(cssString, cssId) {
|
|
|
65
65
|
break;
|
|
66
66
|
}
|
|
67
67
|
const header = str.substring(i, braceIndex);
|
|
68
|
-
const endBrace =
|
|
68
|
+
const endBrace = findClosure(str, braceIndex);
|
|
69
69
|
const inner = str.substring(braceIndex + 1, endBrace);
|
|
70
70
|
|
|
71
71
|
if (header.trim().startsWith("@")) {
|
package/parser/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { extractPropsDefaults, extractRuntime, extractTopLevelFunctions } from "./component.js";
|
|
2
|
-
export {
|
|
1
|
+
export { extractPropsDefaults, extractRuntime, extractTopLevelFunctions, extractTopLevelVariables } from "./component.js";
|
|
2
|
+
export { extractCtxFromEl, hasMountIf, getMountIf, removeMountIf } from "./context.js";
|
|
3
3
|
export { reservedAttrs, getLineNumber, validateChainStructure, applyConditionalToElement, interpolateNode } from "./template.js";
|
|
4
4
|
export { scopeCss } from "./css.js";
|
|
5
|
-
export {
|
|
5
|
+
export { compileExpr, evaluateConstant } from "./utils.js";
|
package/parser/template.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { compileExpr } from "./utils.js";
|
|
2
|
+
import { hasMountIf, getMountIf, removeMountIf } from "./context.js";
|
|
3
3
|
|
|
4
|
-
export const reservedAttrs = ["if", "
|
|
4
|
+
export const reservedAttrs = ["if", "mount:if", "elif", "else"];
|
|
5
5
|
|
|
6
|
-
export function getLineNumber(
|
|
7
|
-
if (
|
|
6
|
+
export function getLineNumber(srcContent, parentContent, idxInParent) {
|
|
7
|
+
if (srcContent === parentContent) {
|
|
8
8
|
return parentContent.substring(0, idxInParent).split("\n").length;
|
|
9
9
|
}
|
|
10
|
-
const templateStartMatch =
|
|
10
|
+
const templateStartMatch = srcContent.match(/<template[^>]*>/);
|
|
11
11
|
if (templateStartMatch) {
|
|
12
12
|
const contentStart = templateStartMatch.index + templateStartMatch[0].length;
|
|
13
|
-
const beforeContent =
|
|
13
|
+
const beforeContent = srcContent.substring(0, contentStart);
|
|
14
14
|
const linesInBefore = beforeContent.split("\n").length;
|
|
15
15
|
const linesInTemplate = parentContent.substring(0, idxInParent).split("\n").length;
|
|
16
16
|
return linesInBefore + linesInTemplate - 1;
|
|
@@ -24,7 +24,7 @@ export function validateChainStructure(parent, sourceFile, sourceContent, parent
|
|
|
24
24
|
|
|
25
25
|
for (const child of children) {
|
|
26
26
|
const hasIf = child.hasAttribute("if");
|
|
27
|
-
const hasDelIf =
|
|
27
|
+
const hasDelIf = hasMountIf(child);
|
|
28
28
|
const hasElif = child.hasAttribute("elif");
|
|
29
29
|
const hasElse = child.hasAttribute("else");
|
|
30
30
|
|
|
@@ -40,7 +40,7 @@ export function validateChainStructure(parent, sourceFile, sourceContent, parent
|
|
|
40
40
|
if (lineNum !== null) loc = `${sourceFile}:${lineNum}`;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
throw new Error(`${loc}\n <${tag}> has ${attr} without a preceding if/
|
|
43
|
+
throw new Error(`${loc}\n <${tag}> has ${attr} without a preceding if/mount:if sibling`);
|
|
44
44
|
}
|
|
45
45
|
if (hasElse) {
|
|
46
46
|
chainActive = false;
|
|
@@ -60,7 +60,7 @@ export function validateChainStructure(parent, sourceFile, sourceContent, parent
|
|
|
60
60
|
export function applyConditionalToElement(child, ctxProxy, condChain, hasIf, hasDelIf, hasElif, hasElse) {
|
|
61
61
|
if (hasIf) {
|
|
62
62
|
const expr = child.getAttribute("if").slice(1, -1);
|
|
63
|
-
const fn =
|
|
63
|
+
const fn = compileExpr(expr, true);
|
|
64
64
|
condChain.active = true;
|
|
65
65
|
if (fn(ctxProxy)) {
|
|
66
66
|
condChain.rendered = true;
|
|
@@ -70,8 +70,8 @@ export function applyConditionalToElement(child, ctxProxy, condChain, hasIf, has
|
|
|
70
70
|
}
|
|
71
71
|
child.removeAttribute("if");
|
|
72
72
|
} else if (hasDelIf) {
|
|
73
|
-
const expr =
|
|
74
|
-
const fn =
|
|
73
|
+
const expr = getMountIf(child).slice(1, -1);
|
|
74
|
+
const fn = compileExpr(expr, true);
|
|
75
75
|
condChain.active = true;
|
|
76
76
|
if (fn(ctxProxy)) {
|
|
77
77
|
condChain.rendered = true;
|
|
@@ -79,10 +79,10 @@ export function applyConditionalToElement(child, ctxProxy, condChain, hasIf, has
|
|
|
79
79
|
child.remove();
|
|
80
80
|
condChain.rendered = false;
|
|
81
81
|
}
|
|
82
|
-
|
|
82
|
+
removeMountIf(child);
|
|
83
83
|
} else if (hasElif) {
|
|
84
84
|
const expr = child.getAttribute("elif").slice(1, -1);
|
|
85
|
-
const fn =
|
|
85
|
+
const fn = compileExpr(expr, true);
|
|
86
86
|
if (fn(ctxProxy)) {
|
|
87
87
|
condChain.rendered = true;
|
|
88
88
|
} else {
|
|
@@ -106,7 +106,7 @@ export function interpolateNode(root, ctxProxy) {
|
|
|
106
106
|
if (node.nodeType === 3) {
|
|
107
107
|
node.textContent = node.textContent.replace(/\{([^}]+)\}/g, (_, expr) => {
|
|
108
108
|
try {
|
|
109
|
-
return
|
|
109
|
+
return compileExpr(expr, true)(ctxProxy);
|
|
110
110
|
} catch {
|
|
111
111
|
return "";
|
|
112
112
|
}
|