round-core 0.0.2 → 0.0.4
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/.github/workflows/benchmarks.yml +44 -0
- package/LICENSE +21 -0
- package/README.md +78 -48
- package/Round.png +0 -0
- package/benchmarks/apps/react/index.html +9 -0
- package/benchmarks/apps/react/main.jsx +25 -0
- package/benchmarks/apps/react/vite.config.js +12 -0
- package/benchmarks/apps/round/index.html +11 -0
- package/benchmarks/apps/round/main.jsx +22 -0
- package/benchmarks/apps/round/vite.config.js +15 -0
- package/benchmarks/bun.lock +497 -0
- package/benchmarks/dist-bench/react/assets/index-9KGqIPOU.js +8 -0
- package/benchmarks/dist-bench/react/index.html +10 -0
- package/benchmarks/dist-bench/round/assets/index-CBBIRhox.js +52 -0
- package/benchmarks/dist-bench/round/index.html +8 -0
- package/benchmarks/package.json +22 -0
- package/benchmarks/scripts/measure-build.js +64 -0
- package/benchmarks/tests/runtime.bench.js +51 -0
- package/benchmarks/vitest.config.js +8 -0
- package/dist/cli.js +582 -0
- package/dist/index.js +1975 -0
- package/dist/vite-plugin.js +736 -0
- package/logo.svg +9 -72
- package/package.json +14 -6
- package/src/cli.js +10 -3
- package/src/compiler/transformer.js +61 -2
- package/src/compiler/vite-plugin.js +5 -1
- package/src/runtime/context.js +17 -1
- package/src/runtime/dom.js +90 -42
- package/src/runtime/lifecycle.js +1 -1
- package/src/runtime/suspense.js +39 -17
- package/vite.config.build.js +47 -0
package/logo.svg
CHANGED
|
@@ -1,73 +1,10 @@
|
|
|
1
|
-
<svg width="
|
|
2
|
-
<defs>
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<stop offset="
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<!-- Efecto de sombra para las órbitas -->
|
|
11
|
-
<filter id="dropShadow" x="-20%" y="-20%" width="140%" height="140%">
|
|
12
|
-
<feDropShadow dx="2" dy="2" stdDeviation="3" flood-color="#000000" flood-opacity="0.2"/>
|
|
13
|
-
</filter>
|
|
14
|
-
|
|
15
|
-
<!-- Gradiente con brillo para azul -->
|
|
16
|
-
<linearGradient id="blueGlow" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
17
|
-
<stop offset="0%" stop-color="#1a4fd8"/>
|
|
18
|
-
<stop offset="30%" stop-color="#4a7dff"/>
|
|
19
|
-
<stop offset="70%" stop-color="#2962FF"/>
|
|
20
|
-
<stop offset="100%" stop-color="#1a4fd8"/>
|
|
21
|
-
</linearGradient>
|
|
22
|
-
|
|
23
|
-
<!-- Gradiente con brillo para verde -->
|
|
24
|
-
<linearGradient id="greenGlow" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
25
|
-
<stop offset="0%" stop-color="#00b894"/>
|
|
26
|
-
<stop offset="30%" stop-color="#00ffc3"/>
|
|
27
|
-
<stop offset="70%" stop-color="#00D8A7"/>
|
|
28
|
-
<stop offset="100%" stop-color="#00b894"/>
|
|
29
|
-
</linearGradient>
|
|
30
|
-
</defs>
|
|
31
|
-
|
|
32
|
-
<!-- Fondo sutil circular -->
|
|
33
|
-
<circle cx="100" cy="100" r="90" fill="url(#blueGlow)" opacity="0.05"/>
|
|
34
|
-
|
|
35
|
-
<!-- Órbita exterior con efecto de neón -->
|
|
36
|
-
<path d="M40,100 Q100,30 160,100 Q100,170 40,100"
|
|
37
|
-
fill="none"
|
|
38
|
-
stroke="url(#blueGlow)"
|
|
39
|
-
stroke-width="10"
|
|
40
|
-
stroke-linecap="round"
|
|
41
|
-
filter="url(#dropShadow)"
|
|
42
|
-
style="mix-blend-mode: screen"/>
|
|
43
|
-
|
|
44
|
-
<!-- Órbita media con efecto brillante -->
|
|
45
|
-
<path d="M60,100 Q100,50 140,100 Q100,150 60,100"
|
|
46
|
-
fill="none"
|
|
47
|
-
stroke="url(#greenGlow)"
|
|
48
|
-
stroke-width="10"
|
|
49
|
-
stroke-linecap="round"
|
|
50
|
-
filter="url(#dropShadow)"
|
|
51
|
-
style="mix-blend-mode: screen"/>
|
|
52
|
-
|
|
53
|
-
<!-- Núcleo con efecto 3D marcado -->
|
|
54
|
-
<circle cx="100" cy="100" r="22" fill="url(#nucleusGradient)">
|
|
55
|
-
<feDropShadow dx="0" dy="0" stdDeviation="8" flood-color="#00ffaa" flood-opacity="0.4"/>
|
|
56
|
-
</circle>
|
|
57
|
-
|
|
58
|
-
<!-- Reflejo superior en el núcleo -->
|
|
59
|
-
<ellipse cx="95" cy="90" rx="8" ry="5" fill="#ffffff" opacity="0.4"/>
|
|
60
|
-
|
|
61
|
-
<!-- Puntos de energía sutilmente distribuidos -->
|
|
62
|
-
<g opacity="0.6">
|
|
63
|
-
<circle cx="170" cy="100" r="3" fill="#4a7dff"/>
|
|
64
|
-
<circle cx="100" cy="170" r="3" fill="#4a7dff"/>
|
|
65
|
-
<circle cx="30" cy="100" r="3" fill="#4a7dff"/>
|
|
66
|
-
<circle cx="100" cy="30" r="3" fill="#4a7dff"/>
|
|
67
|
-
|
|
68
|
-
<circle cx="140" cy="140" r="2.5" fill="#00ffc3"/>
|
|
69
|
-
<circle cx="60" cy="140" r="2.5" fill="#00ffc3"/>
|
|
70
|
-
<circle cx="140" cy="60" r="2.5" fill="#00ffc3"/>
|
|
71
|
-
<circle cx="60" cy="60" r="2.5" fill="#00ffc3"/>
|
|
72
|
-
</g>
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 16 16">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#6d28d9"/>
|
|
5
|
+
<stop offset="1" stop-color="#16a34a"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect x="1" y="1" width="14" height="14" rx="4" fill="#ffffff" stroke="url(#g)" stroke-width="1.25"/>
|
|
9
|
+
<path d="M5.2 11.3V4.7h2.65c.9 0 1.62.2 2.16.62.55.41.82 1.02.82 1.82 0 .56-.14 1.02-.43 1.37-.28.35-.68.6-1.18.73l1.6 2.06H9.58L8.15 9.45H6.45v1.85H5.2zm1.25-2.9h1.3c.55 0 .96-.1 1.22-.32.26-.22.4-.55.4-.99 0-.44-.13-.76-.39-.96-.26-.2-.67-.3-1.23-.3H6.45v2.57z" fill="#111827"/>
|
|
73
10
|
</svg>
|
package/package.json
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "round-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "A lightweight frontend framework for SPA with signals and fine grained reactivity",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js",
|
|
8
|
+
"./vite-plugin": "./dist/vite-plugin.js"
|
|
9
|
+
},
|
|
6
10
|
"type": "module",
|
|
7
11
|
"icon": "round.png",
|
|
8
12
|
"repository": {
|
|
9
13
|
"url": "https://github.com/ZtaMDev/RoundJS.git"
|
|
10
14
|
},
|
|
11
15
|
"bin": {
|
|
12
|
-
"round": "./
|
|
16
|
+
"round": "./dist/cli.js"
|
|
13
17
|
},
|
|
14
18
|
"scripts": {
|
|
15
|
-
"dev": "node ./cli.js dev --config ./test/main/round.config.json",
|
|
16
|
-
"build": "node ./cli.js build --config ./test/main/round.config.json",
|
|
17
|
-
"
|
|
19
|
+
"dev": "node ./src/cli.js dev --config ./test/main/round.config.json",
|
|
20
|
+
"build": "node ./src/cli.js build --config ./test/main/round.config.json",
|
|
21
|
+
"build:core": "vite build -c vite.config.build.js",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"bench": "bun run --cwd benchmarks bench",
|
|
24
|
+
"bench:build": "bun run --cwd benchmarks bench:build",
|
|
25
|
+
"bench:runtime": "bun run --cwd benchmarks bench:runtime"
|
|
18
26
|
},
|
|
19
27
|
"keywords": [
|
|
20
28
|
"framework",
|
package/src/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import process from 'node:process';
|
|
@@ -6,6 +6,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { createServer, build as viteBuild, preview as vitePreview } from 'vite';
|
|
7
7
|
import RoundPlugin from './compiler/vite-plugin.js';
|
|
8
8
|
|
|
9
|
+
// Handle graceful shutdown
|
|
10
|
+
function onSignal() {
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
process.on('SIGINT', onSignal);
|
|
14
|
+
process.on('SIGTERM', onSignal);
|
|
15
|
+
|
|
9
16
|
function normalizePath(p) {
|
|
10
17
|
return p.replaceAll('\\', '/');
|
|
11
18
|
}
|
|
@@ -239,7 +246,7 @@ async function runInit({ name }) {
|
|
|
239
246
|
preview: 'round preview'
|
|
240
247
|
},
|
|
241
248
|
dependencies: {
|
|
242
|
-
'round-core': '^0.0.
|
|
249
|
+
'round-core': '^0.0.4'
|
|
243
250
|
},
|
|
244
251
|
devDependencies: {
|
|
245
252
|
vite: '^5.0.0'
|
|
@@ -272,7 +279,7 @@ async function runInit({ name }) {
|
|
|
272
279
|
|
|
273
280
|
writeFileIfMissing(viteConfigPath, [
|
|
274
281
|
"import { defineConfig } from 'vite';",
|
|
275
|
-
"import RoundPlugin from 'round-core/
|
|
282
|
+
"import RoundPlugin from 'round-core/vite-plugin';",
|
|
276
283
|
'',
|
|
277
284
|
'export default defineConfig({',
|
|
278
285
|
" plugins: [RoundPlugin({ configPath: './round.config.json' })],",
|
|
@@ -12,11 +12,70 @@ export function transform(code) {
|
|
|
12
12
|
let startBlockIndex = -1;
|
|
13
13
|
let endBlockIndex = -1;
|
|
14
14
|
|
|
15
|
+
let inSingle = false;
|
|
16
|
+
let inDouble = false;
|
|
17
|
+
let inTemplate = false;
|
|
18
|
+
let inCommentLine = false;
|
|
19
|
+
let inCommentMulti = false;
|
|
20
|
+
|
|
15
21
|
for (let i = startIndex; i < str.length; i++) {
|
|
16
|
-
|
|
22
|
+
const ch = str[i];
|
|
23
|
+
const prev = i > 0 ? str[i - 1] : '';
|
|
24
|
+
const next = i < str.length - 1 ? str[i + 1] : '';
|
|
25
|
+
|
|
26
|
+
// Handle strings and comments
|
|
27
|
+
if (inCommentLine) {
|
|
28
|
+
if (ch === '\n' || ch === '\r') inCommentLine = false;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (inCommentMulti) {
|
|
32
|
+
if (ch === '*' && next === '/') {
|
|
33
|
+
inCommentMulti = false;
|
|
34
|
+
i++;
|
|
35
|
+
}
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (inTemplate) {
|
|
39
|
+
if (ch === '`' && prev !== '\\') inTemplate = false;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (inSingle) {
|
|
43
|
+
if (ch === '\'' && prev !== '\\') inSingle = false;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (inDouble) {
|
|
47
|
+
if (ch === '"' && prev !== '\\') inDouble = false;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check for start of strings/comments
|
|
52
|
+
if (ch === '/' && next === '/') {
|
|
53
|
+
inCommentLine = true;
|
|
54
|
+
i++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (ch === '/' && next === '*') {
|
|
58
|
+
inCommentMulti = true;
|
|
59
|
+
i++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (ch === '`') {
|
|
63
|
+
inTemplate = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (ch === '\'') {
|
|
67
|
+
inSingle = true;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (ch === '"') {
|
|
71
|
+
inDouble = true;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (ch === '{') {
|
|
17
76
|
if (open === 0) startBlockIndex = i;
|
|
18
77
|
open++;
|
|
19
|
-
} else if (
|
|
78
|
+
} else if (ch === '}') {
|
|
20
79
|
open--;
|
|
21
80
|
if (open === 0) {
|
|
22
81
|
endBlockIndex = i;
|
|
@@ -131,6 +131,9 @@ export default function RoundPlugin(pluginOptions = {}) {
|
|
|
131
131
|
const trailingSlash = config?.routing?.trailingSlash;
|
|
132
132
|
state.routingTrailingSlash = trailingSlash !== undefined ? Boolean(trailingSlash) : true;
|
|
133
133
|
|
|
134
|
+
const customTags = config?.htmlTags;
|
|
135
|
+
state.customTags = Array.isArray(customTags) ? customTags : [];
|
|
136
|
+
|
|
134
137
|
const entryRel = config?.entry;
|
|
135
138
|
state.entryAbs = entryRel ? resolveMaybeRelative(configDir, entryRel) : null;
|
|
136
139
|
|
|
@@ -303,7 +306,8 @@ export default function RoundPlugin(pluginOptions = {}) {
|
|
|
303
306
|
|
|
304
307
|
return {
|
|
305
308
|
define: {
|
|
306
|
-
__ROUND_ROUTING_TRAILING_SLASH__: JSON.stringify(state.routingTrailingSlash)
|
|
309
|
+
__ROUND_ROUTING_TRAILING_SLASH__: JSON.stringify(state.routingTrailingSlash),
|
|
310
|
+
__ROUND_CUSTOM_TAGS__: JSON.stringify(state.customTags ?? [])
|
|
307
311
|
},
|
|
308
312
|
esbuild: {
|
|
309
313
|
include: /\.(round|js|jsx|ts|tsx)$/,
|
package/src/runtime/context.js
CHANGED
|
@@ -11,7 +11,7 @@ function popContext() {
|
|
|
11
11
|
contextStack.pop();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function readContext(ctx) {
|
|
14
|
+
export function readContext(ctx) {
|
|
15
15
|
for (let i = contextStack.length - 1; i >= 0; i--) {
|
|
16
16
|
const layer = contextStack[i];
|
|
17
17
|
if (layer && Object.prototype.hasOwnProperty.call(layer, ctx.id)) {
|
|
@@ -60,3 +60,19 @@ export function bindContext(ctx) {
|
|
|
60
60
|
return provided;
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
export function captureContext() {
|
|
65
|
+
return contextStack.slice();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function runInContext(snapshot, fn) {
|
|
69
|
+
const prev = contextStack.slice();
|
|
70
|
+
contextStack.length = 0;
|
|
71
|
+
contextStack.push(...snapshot);
|
|
72
|
+
try {
|
|
73
|
+
return fn();
|
|
74
|
+
} finally {
|
|
75
|
+
contextStack.length = 0;
|
|
76
|
+
contextStack.push(...prev);
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/runtime/dom.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { effect, untrack } from './signals.js';
|
|
2
|
-
import {
|
|
2
|
+
import { runInLifecycle, createComponentInstance, mountComponent, initLifecycleRoot } from './lifecycle.js';
|
|
3
3
|
import { reportErrorSafe } from './error-reporter.js';
|
|
4
|
+
import { captureContext, runInContext, readContext } from './context.js';
|
|
5
|
+
import { SuspenseContext } from './suspense.js';
|
|
6
|
+
|
|
4
7
|
|
|
5
8
|
let isObserverInitialized = false;
|
|
6
9
|
|
|
@@ -32,14 +35,20 @@ export function createElement(tag, props = {}, ...children) {
|
|
|
32
35
|
const componentName = tag?.name ?? 'Anonymous';
|
|
33
36
|
componentInstance.name = componentName;
|
|
34
37
|
|
|
35
|
-
let node =
|
|
38
|
+
let node = runInLifecycle(componentInstance, () => {
|
|
36
39
|
const componentProps = { ...props, children };
|
|
37
40
|
try {
|
|
38
41
|
const res = untrack(() => tag(componentProps));
|
|
39
42
|
if (isPromiseLike(res)) throw res;
|
|
40
43
|
return res;
|
|
41
44
|
} catch (e) {
|
|
42
|
-
if (isPromiseLike(e))
|
|
45
|
+
if (isPromiseLike(e)) {
|
|
46
|
+
const suspense = readContext(SuspenseContext);
|
|
47
|
+
if (!suspense) {
|
|
48
|
+
throw new Error("cannot instance a lazy component outside a suspense");
|
|
49
|
+
}
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
43
52
|
reportErrorSafe(e, { phase: 'component.render', component: componentName });
|
|
44
53
|
return createElement('div', { style: { padding: '16px' } }, `Error in ${componentName}`);
|
|
45
54
|
}
|
|
@@ -65,6 +74,19 @@ export function createElement(tag, props = {}, ...children) {
|
|
|
65
74
|
return node;
|
|
66
75
|
}
|
|
67
76
|
|
|
77
|
+
if (typeof tag === 'string') {
|
|
78
|
+
const isCustomElement = tag.includes('-');
|
|
79
|
+
|
|
80
|
+
const isStandard = /^(a|abbr|address|area|article|aside|audio|b|base|bdi|bdo|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|data|datalist|dd|del|details|dfn|dialog|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|label|legend|li|link|main|map|mark|meta|meter|nav|noscript|object|ol|optgroup|option|output|p|param|picture|pre|progress|q|rp|rt|ruby|s|samp|script|search|section|select|slot|small|source|span|strong|style|sub|summary|sup|svg|table|tbody|td|template|textarea|tfoot|th|thead|time|title|tr|track|u|ul|var|video|wbr|menu|animate|animateMotion|animateTransform|circle|clipPath|defs|desc|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|foreignObject|g|image|line|linearGradient|marker|mask|metadata|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|stop|switch|symbol|text|textPath|tspan|use|view)$/.test(tag);
|
|
81
|
+
|
|
82
|
+
// __ROUND_CUSTOM_TAGS__ is injected by the vite plugin from round.config.json
|
|
83
|
+
const isCustomConfigured = typeof __ROUND_CUSTOM_TAGS__ !== 'undefined' && __ROUND_CUSTOM_TAGS__.includes(tag);
|
|
84
|
+
|
|
85
|
+
if (!isCustomElement && !isStandard && !isCustomConfigured && /^[a-z]/.test(tag)) {
|
|
86
|
+
throw new Error(`Component names must start with an uppercase letter: <${tag} />`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
68
90
|
const element = document.createElement(tag);
|
|
69
91
|
|
|
70
92
|
if (props) {
|
|
@@ -250,6 +272,21 @@ export function createElement(tag, props = {}, ...children) {
|
|
|
250
272
|
return;
|
|
251
273
|
}
|
|
252
274
|
|
|
275
|
+
if (key === 'classList') {
|
|
276
|
+
if (value && typeof value === 'object') {
|
|
277
|
+
Object.entries(value).forEach(([className, condition]) => {
|
|
278
|
+
if (typeof condition === 'function') {
|
|
279
|
+
effect(() => {
|
|
280
|
+
element.classList.toggle(className, !!condition());
|
|
281
|
+
}, { onLoad: false });
|
|
282
|
+
} else {
|
|
283
|
+
element.classList.toggle(className, !!condition);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
253
290
|
if (key === 'className') element.className = value;
|
|
254
291
|
else if (key === 'value') element.value = value;
|
|
255
292
|
else if (key === 'checked') element.checked = Boolean(value);
|
|
@@ -282,54 +319,65 @@ function appendChild(parent, child) {
|
|
|
282
319
|
|
|
283
320
|
let currentNode = placeholder;
|
|
284
321
|
|
|
285
|
-
|
|
286
|
-
let val;
|
|
287
|
-
try {
|
|
288
|
-
val = child();
|
|
289
|
-
if (isPromiseLike(val)) throw val;
|
|
290
|
-
} catch (e) {
|
|
291
|
-
if (isPromiseLike(e)) throw e;
|
|
292
|
-
reportErrorSafe(e, { phase: 'child.dynamic' });
|
|
293
|
-
val = createElement('div', { style: { padding: '16px' } }, 'Error');
|
|
294
|
-
}
|
|
322
|
+
const ctxSnapshot = captureContext();
|
|
295
323
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
|
|
303
|
-
|
|
324
|
+
effect(() => {
|
|
325
|
+
runInContext(ctxSnapshot, () => {
|
|
326
|
+
let val;
|
|
327
|
+
try {
|
|
328
|
+
val = child();
|
|
329
|
+
if (isPromiseLike(val)) throw val;
|
|
330
|
+
} catch (e) {
|
|
331
|
+
if (isPromiseLike(e)) {
|
|
332
|
+
const suspense = readContext(SuspenseContext);
|
|
333
|
+
if (suspense && typeof suspense.register === 'function') {
|
|
334
|
+
suspense.register(e);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
throw new Error("cannot instance a lazy component outside a suspense");
|
|
304
338
|
}
|
|
339
|
+
reportErrorSafe(e, { phase: 'child.dynamic' });
|
|
340
|
+
val = createElement('div', { style: { padding: '16px' } }, 'Error');
|
|
305
341
|
}
|
|
306
342
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
343
|
+
if (Array.isArray(val)) {
|
|
344
|
+
if (!(currentNode instanceof Element) || !currentNode._roundArrayWrapper) {
|
|
345
|
+
const wrapper = document.createElement('span');
|
|
346
|
+
wrapper.style.display = 'contents';
|
|
347
|
+
wrapper._roundArrayWrapper = true;
|
|
348
|
+
if (currentNode.parentNode) {
|
|
349
|
+
currentNode.parentNode.replaceChild(wrapper, currentNode);
|
|
350
|
+
currentNode = wrapper;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
while (currentNode.firstChild) currentNode.removeChild(currentNode.firstChild);
|
|
355
|
+
val.forEach(v => appendChild(currentNode, v));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
311
358
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
359
|
+
if (val instanceof Node) {
|
|
360
|
+
if (currentNode !== val) {
|
|
361
|
+
if (currentNode.parentNode) {
|
|
362
|
+
currentNode.parentNode.replaceChild(val, currentNode);
|
|
363
|
+
currentNode = val;
|
|
364
|
+
}
|
|
317
365
|
}
|
|
318
366
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
367
|
+
else {
|
|
368
|
+
const textContent = (val === null || val === undefined) ? '' : val;
|
|
369
|
+
|
|
370
|
+
if (currentNode instanceof Element) {
|
|
371
|
+
const newText = document.createTextNode(textContent);
|
|
372
|
+
if (currentNode.parentNode) {
|
|
373
|
+
currentNode.parentNode.replaceChild(newText, currentNode);
|
|
374
|
+
currentNode = newText;
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
currentNode.textContent = textContent;
|
|
328
378
|
}
|
|
329
|
-
} else {
|
|
330
|
-
currentNode.textContent = textContent;
|
|
331
379
|
}
|
|
332
|
-
}
|
|
380
|
+
});
|
|
333
381
|
}, { onLoad: false });
|
|
334
382
|
return;
|
|
335
383
|
}
|
package/src/runtime/lifecycle.js
CHANGED
|
@@ -6,7 +6,7 @@ export function getCurrentComponent() {
|
|
|
6
6
|
return componentStack[componentStack.length - 1];
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export function
|
|
9
|
+
export function runInLifecycle(componentInstance, fn) {
|
|
10
10
|
componentStack.push(componentInstance);
|
|
11
11
|
try {
|
|
12
12
|
return fn();
|
package/src/runtime/suspense.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { signal } from './signals.js';
|
|
2
2
|
import { createElement, Fragment } from './dom.js';
|
|
3
|
+
import { createContext } from './context.js';
|
|
3
4
|
|
|
4
5
|
function isPromiseLike(v) {
|
|
5
6
|
return v && (typeof v === 'object' || typeof v === 'function') && typeof v.then === 'function';
|
|
6
7
|
}
|
|
7
8
|
|
|
9
|
+
const SuspenseContext = createContext(null);
|
|
10
|
+
export { SuspenseContext };
|
|
11
|
+
|
|
8
12
|
export function lazy(loader) {
|
|
9
13
|
if (typeof loader !== 'function') {
|
|
10
14
|
throw new Error('lazy(loader) expects a function that returns a Promise');
|
|
@@ -67,37 +71,55 @@ export function lazy(loader) {
|
|
|
67
71
|
|
|
68
72
|
export function Suspense(props = {}) {
|
|
69
73
|
const tick = signal(0);
|
|
70
|
-
|
|
74
|
+
const pending = new Set();
|
|
75
|
+
|
|
76
|
+
// Track promises we are currently waiting for to avoid re-adding them or flickering
|
|
77
|
+
const waiting = new Set();
|
|
71
78
|
|
|
72
79
|
const child = Array.isArray(props.children) ? props.children[0] : props.children;
|
|
73
80
|
const childFn = typeof child === 'function' ? child : () => child;
|
|
74
81
|
|
|
75
|
-
|
|
82
|
+
const register = (promise) => {
|
|
83
|
+
if (!waiting.has(promise)) {
|
|
84
|
+
waiting.add(promise);
|
|
85
|
+
pending.add(promise);
|
|
86
|
+
promise.then(
|
|
87
|
+
() => {
|
|
88
|
+
waiting.delete(promise);
|
|
89
|
+
pending.delete(promise);
|
|
90
|
+
tick(tick.peek() + 1);
|
|
91
|
+
},
|
|
92
|
+
() => {
|
|
93
|
+
waiting.delete(promise);
|
|
94
|
+
pending.delete(promise);
|
|
95
|
+
tick(tick.peek() + 1);
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return createElement(SuspenseContext.Provider, {
|
|
102
|
+
value: { register }
|
|
103
|
+
}, () => {
|
|
104
|
+
// Read tick to re-render when promises resolve
|
|
76
105
|
tick();
|
|
77
106
|
|
|
107
|
+
// If pending promises, show fallback depending on strategy.
|
|
108
|
+
|
|
109
|
+
if (pending.size > 0) {
|
|
110
|
+
return props.fallback ?? null;
|
|
111
|
+
}
|
|
112
|
+
|
|
78
113
|
try {
|
|
79
114
|
const res = childFn();
|
|
80
115
|
if (isPromiseLike(res)) {
|
|
81
|
-
|
|
82
|
-
lastPromise = res;
|
|
83
|
-
res.then(
|
|
84
|
-
() => tick(tick.peek() + 1),
|
|
85
|
-
() => tick(tick.peek() + 1)
|
|
86
|
-
);
|
|
87
|
-
}
|
|
116
|
+
register(res);
|
|
88
117
|
return props.fallback ?? null;
|
|
89
118
|
}
|
|
90
|
-
lastPromise = null;
|
|
91
119
|
return res ?? null;
|
|
92
120
|
} catch (e) {
|
|
93
121
|
if (isPromiseLike(e)) {
|
|
94
|
-
|
|
95
|
-
lastPromise = e;
|
|
96
|
-
e.then(
|
|
97
|
-
() => tick(tick.peek() + 1),
|
|
98
|
-
() => tick(tick.peek() + 1)
|
|
99
|
-
);
|
|
100
|
-
}
|
|
122
|
+
register(e);
|
|
101
123
|
return props.fallback ?? null;
|
|
102
124
|
}
|
|
103
125
|
throw e;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
|
|
5
|
+
// Custom plugin to move .d.ts files or raw assets if needed,
|
|
6
|
+
// for now we just handle JS bundling.
|
|
7
|
+
|
|
8
|
+
// Custom plugin to move .d.ts files or raw assets if needed,
|
|
9
|
+
// for now we just handle JS bundling.
|
|
10
|
+
|
|
11
|
+
export default defineConfig({
|
|
12
|
+
build: {
|
|
13
|
+
// Target modern environments
|
|
14
|
+
target: 'es2022',
|
|
15
|
+
outDir: 'dist',
|
|
16
|
+
emptyOutDir: true,
|
|
17
|
+
minify: false, // User can enable if they want extreme minification, but for a lib readable code is nice.
|
|
18
|
+
// Wait, user asked for "extremo rapido y liviano" (extremely fast and light).
|
|
19
|
+
// So I SHOULD minify.
|
|
20
|
+
lib: {
|
|
21
|
+
entry: {
|
|
22
|
+
index: path.resolve(__dirname, 'src/index.js'),
|
|
23
|
+
cli: path.resolve(__dirname, 'src/cli.js'),
|
|
24
|
+
// We expose the plugin separately so users can import it in their vite.config.js
|
|
25
|
+
'vite-plugin': path.resolve(__dirname, 'src/compiler/vite-plugin.js')
|
|
26
|
+
},
|
|
27
|
+
formats: ['es'] // ESM only is fine for modern "type": "module" package
|
|
28
|
+
},
|
|
29
|
+
rollupOptions: {
|
|
30
|
+
// Externalize dependencies so they aren't bundled into the library
|
|
31
|
+
external: [
|
|
32
|
+
'vite',
|
|
33
|
+
'marked',
|
|
34
|
+
'node:fs', 'node:path', 'node:process', 'node:url', 'node:vm', 'node:util',
|
|
35
|
+
'fs', 'path', 'process', 'url', 'vm', 'util'
|
|
36
|
+
],
|
|
37
|
+
output: {
|
|
38
|
+
banner: (chunk) => {
|
|
39
|
+
if (chunk.name === 'cli' || chunk.fileName === 'cli.js') {
|
|
40
|
+
return '#!/usr/bin/env node';
|
|
41
|
+
}
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
});
|