rip-lang 3.9.0 → 3.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +14 -14
- package/docs/RIP-LANG.md +10 -1
- package/docs/dist/rip-ui.min.js +119 -119
- package/docs/dist/rip-ui.min.js.br +0 -0
- package/docs/dist/rip.browser.min.js +143 -143
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/example/index.html +34 -0
- package/docs/example/index.json +14 -0
- package/docs/index.html +1475 -3
- package/docs/sierpinski.html +114 -0
- package/package.json +1 -1
- package/src/browser.js +18 -9
- package/src/compiler.js +3 -0
- package/src/components.js +63 -16
- package/src/grammar/grammar.rip +2 -2
- package/src/grammar/parser.js +360 -0
- package/src/lexer.js +10 -0
- package/src/parser.js +5 -6
- package/docs/demo.html +0 -342
- package/docs/playground-app.html +0 -1022
- package/docs/playground-js.html +0 -1645
- package/docs/playground-rip-ui.html +0 -1419
- package/docs/playground-rip.html +0 -1450
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Sierpinski Triangle — Rip UI</title>
|
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
10
|
+
body { background: #0a0a0a; color: white; font-family: 'JetBrains Mono', monospace; overflow: hidden; }
|
|
11
|
+
.scene { position: relative; width: 100vw; height: 100vh; }
|
|
12
|
+
.stats { position: fixed; top: 20px; left: 24px; z-index: 10; }
|
|
13
|
+
.stats-title { font-size: 18px; font-weight: bold; }
|
|
14
|
+
.stats-line { font-size: 13px; color: rgba(255,255,255,0.45); margin-top: 3px; }
|
|
15
|
+
.stats-note { font-size: 11px; color: rgba(255,255,255,0.25); margin-top: 8px; max-width: 280px; line-height: 1.5; }
|
|
16
|
+
.source-toggle { display: inline-block; margin-top: 10px; padding: 4px 10px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.15); border-radius: 4px; color: rgba(255,255,255,0.5); font-family: inherit; font-size: 12px; cursor: pointer; transition: all 0.2s; }
|
|
17
|
+
.source-toggle:hover { color: white; border-color: rgba(255,255,255,0.4); background: rgba(255,255,255,0.12); }
|
|
18
|
+
.source-panel { position: fixed; top: 0; right: 0; bottom: 0; width: min(520px, 90vw); z-index: 100; background: rgba(10,10,10,0.92); backdrop-filter: blur(12px); border-left: 1px solid rgba(255,255,255,0.1); transform: translateX(100%); transition: transform 0.3s ease; overflow-y: auto; }
|
|
19
|
+
.source-panel.open { transform: translateX(0); }
|
|
20
|
+
.source-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid rgba(255,255,255,0.08); }
|
|
21
|
+
.source-label { font-size: 13px; font-weight: bold; color: rgba(255,255,255,0.6); letter-spacing: 0.05em; }
|
|
22
|
+
.source-close { background: none; border: none; color: rgba(255,255,255,0.4); font-size: 20px; cursor: pointer; padding: 4px 8px; border-radius: 4px; font-family: inherit; transition: all 0.2s; }
|
|
23
|
+
.source-close:hover { color: white; background: rgba(255,255,255,0.1); }
|
|
24
|
+
.source-code { padding: 16px 20px; font-size: 13px; line-height: 1.7; color: rgba(255,255,255,0.85); white-space: pre; overflow-x: auto; }
|
|
25
|
+
.container { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(var(--scale, 1)); width: 1000px; height: 870px; transform-origin: center center; }
|
|
26
|
+
.dot {
|
|
27
|
+
position: absolute; width: 24px; height: 24px; border-radius: 50%;
|
|
28
|
+
display: flex; align-items: center; justify-content: center;
|
|
29
|
+
font-size: 10px; font-weight: bold; color: rgba(255,255,255,0.9);
|
|
30
|
+
user-select: none; cursor: default; transition: transform 0.1s;
|
|
31
|
+
}
|
|
32
|
+
.dot:hover { transform: scale(2.5); z-index: 1; }
|
|
33
|
+
</style>
|
|
34
|
+
<script type="module" src="https://shreeve.github.io/rip-lang/dist/rip-ui.min.js"></script>
|
|
35
|
+
<script>
|
|
36
|
+
function updateScale() {
|
|
37
|
+
var s = Math.min(window.innerWidth / 1350, window.innerHeight / 1250, 1);
|
|
38
|
+
document.documentElement.style.setProperty('--scale', s);
|
|
39
|
+
}
|
|
40
|
+
updateScale();
|
|
41
|
+
window.addEventListener('resize', updateScale);
|
|
42
|
+
</script>
|
|
43
|
+
</head>
|
|
44
|
+
<body>
|
|
45
|
+
<div class="source-panel" id="source-panel">
|
|
46
|
+
<div class="source-header">
|
|
47
|
+
<span class="source-label">SOURCE — 48 LINES OF RIP</span>
|
|
48
|
+
<button class="source-close" onclick="document.getElementById('source-panel').classList.remove('open')">×</button>
|
|
49
|
+
</div>
|
|
50
|
+
<pre class="source-code" id="source-code"></pre>
|
|
51
|
+
</div>
|
|
52
|
+
<script type="text/rip">
|
|
53
|
+
# Sierpinski Triangle — 729 reactive dots at 60fps
|
|
54
|
+
# No virtual DOM. No tree diffing. Just fine-grained reactivity.
|
|
55
|
+
|
|
56
|
+
App = component
|
|
57
|
+
elapsed := 0
|
|
58
|
+
fps := 0
|
|
59
|
+
|
|
60
|
+
dots := do ->
|
|
61
|
+
result = []
|
|
62
|
+
build = (x, y, s) ->
|
|
63
|
+
if s <= 25
|
|
64
|
+
result.push { x, y, id: result.length }
|
|
65
|
+
else
|
|
66
|
+
build x , y - s / 4, s / 2
|
|
67
|
+
build x - s / 2, y + s / 4, s / 2
|
|
68
|
+
build x + s / 2, y + s / 4, s / 2
|
|
69
|
+
build 500, 435, 1000
|
|
70
|
+
result
|
|
71
|
+
|
|
72
|
+
mounted: ->
|
|
73
|
+
start = performance.now()
|
|
74
|
+
frames = 0
|
|
75
|
+
last = start
|
|
76
|
+
tick = ->
|
|
77
|
+
now = performance.now()
|
|
78
|
+
elapsed = (now - start) / 1000
|
|
79
|
+
frames++
|
|
80
|
+
if now - last >= 1000
|
|
81
|
+
fps = frames
|
|
82
|
+
frames = 0
|
|
83
|
+
last = now
|
|
84
|
+
requestAnimationFrame tick
|
|
85
|
+
requestAnimationFrame tick
|
|
86
|
+
|
|
87
|
+
render
|
|
88
|
+
.scene
|
|
89
|
+
.stats
|
|
90
|
+
.stats-title "Sierpinski Triangle"
|
|
91
|
+
.stats-line dots.length + " dots · " + dots.length * 2 + " reactive bindings"
|
|
92
|
+
.stats-line fps + " fps"
|
|
93
|
+
.stats-note "Each dot updates independently via fine-grained reactivity"
|
|
94
|
+
button.source-toggle @click: (-> document.getElementById('source-panel').classList.toggle('open')), "View Source"
|
|
95
|
+
|
|
96
|
+
.container
|
|
97
|
+
for dot in dots
|
|
98
|
+
.dot style: "left:" + (dot.x - 12) + "px;top:" + (dot.y - 12) + "px;background:hsl(" + ((dot.id / 729 * 360 + elapsed * 40) % 360) + " 70% 50%)"
|
|
99
|
+
Math.floor(elapsed % 10) + 1
|
|
100
|
+
|
|
101
|
+
App.new().mount document.body
|
|
102
|
+
|
|
103
|
+
# Populate source panel with this page's own Rip source
|
|
104
|
+
ripScript = document.querySelector('script[type="text/rip"]')
|
|
105
|
+
lines = ripScript.textContent.split("\n")
|
|
106
|
+
while lines.length and lines[0].trim() is ''
|
|
107
|
+
lines.shift()
|
|
108
|
+
cutoff = lines.findIndex((l) -> l.includes('Populate source panel'))
|
|
109
|
+
lines = lines.slice(0, cutoff).filter((l, i, a) -> i < a.length - 1 or l.trim() isnt '')
|
|
110
|
+
sourceEl = document.getElementById('source-code')
|
|
111
|
+
sourceEl.textContent = lines.join("\n")
|
|
112
|
+
</script>
|
|
113
|
+
</body>
|
|
114
|
+
</html>
|
package/package.json
CHANGED
package/src/browser.js
CHANGED
|
@@ -71,8 +71,14 @@ export { processRipScripts };
|
|
|
71
71
|
* Import a .rip file as an ES module
|
|
72
72
|
* Fetches the URL, compiles Rip→JS, dynamically imports via Blob URL
|
|
73
73
|
* Usage: const { launch } = await importRip('/ui.rip')
|
|
74
|
+
*
|
|
75
|
+
* Pre-compiled modules can be registered on importRip.modules to skip fetching.
|
|
76
|
+
* The rip-ui bundle uses this to embed ui.rip without a server round-trip.
|
|
74
77
|
*/
|
|
75
78
|
export async function importRip(url) {
|
|
79
|
+
for (const [key, mod] of Object.entries(importRip.modules)) {
|
|
80
|
+
if (url.includes(key)) return mod;
|
|
81
|
+
}
|
|
76
82
|
const source = await fetch(url).then(r => {
|
|
77
83
|
if (!r.ok) throw new Error(`importRip: ${url} (${r.status})`);
|
|
78
84
|
return r.text();
|
|
@@ -86,6 +92,7 @@ export async function importRip(url) {
|
|
|
86
92
|
URL.revokeObjectURL(blobUrl);
|
|
87
93
|
}
|
|
88
94
|
}
|
|
95
|
+
importRip.modules = {};
|
|
89
96
|
|
|
90
97
|
/**
|
|
91
98
|
* Browser Console REPL
|
|
@@ -128,14 +135,16 @@ if (typeof globalThis !== 'undefined') {
|
|
|
128
135
|
globalThis.__ripExports = { compile, compileToJS, formatSExpr, VERSION, BUILD_DATE, getReactiveRuntime, getComponentRuntime };
|
|
129
136
|
}
|
|
130
137
|
|
|
131
|
-
// Auto-process
|
|
132
|
-
//
|
|
138
|
+
// Auto-process <script type="text/rip"> blocks.
|
|
139
|
+
// Deferred via queueMicrotask so bundled entry code (e.g. rip-ui.min.js registering
|
|
140
|
+
// importRip.modules) runs before script processing begins.
|
|
133
141
|
if (typeof document !== 'undefined') {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
142
|
+
globalThis.__ripScriptsReady = new Promise(resolve => {
|
|
143
|
+
const run = () => processRipScripts().then(resolve);
|
|
144
|
+
if (document.readyState === 'loading') {
|
|
145
|
+
document.addEventListener('DOMContentLoaded', () => queueMicrotask(run));
|
|
146
|
+
} else {
|
|
147
|
+
queueMicrotask(run);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
141
150
|
}
|
package/src/compiler.js
CHANGED
|
@@ -1094,6 +1094,9 @@ export class CodeGenerator {
|
|
|
1094
1094
|
if (!this.reactiveVars) this.reactiveVars = new Set();
|
|
1095
1095
|
let varName = str(name) ?? name;
|
|
1096
1096
|
this.reactiveVars.add(varName);
|
|
1097
|
+
if (this.is(expr, 'block') && expr.length > 2) {
|
|
1098
|
+
return `const ${varName} = __computed(() => ${this.generateFunctionBody(expr)})`;
|
|
1099
|
+
}
|
|
1097
1100
|
return `const ${varName} = __computed(() => ${this.generate(expr, 'value')})`;
|
|
1098
1101
|
}
|
|
1099
1102
|
|
package/src/components.js
CHANGED
|
@@ -51,6 +51,23 @@ function getMemberName(target) {
|
|
|
51
51
|
return null;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Detect fragment root and collect direct child variables for proper removal.
|
|
56
|
+
* After insertBefore, a DocumentFragment is empty — .remove() is a no-op.
|
|
57
|
+
* Callers must remove each child element individually.
|
|
58
|
+
*/
|
|
59
|
+
function getFragChildren(rootVar, createLines, localizeVar) {
|
|
60
|
+
const root = localizeVar(rootVar);
|
|
61
|
+
if (!/_frag\d+$/.test(root)) return null;
|
|
62
|
+
const children = [];
|
|
63
|
+
const re = new RegExp(`^${root.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\.appendChild\\(([^)]+)\\);`);
|
|
64
|
+
for (const line of createLines) {
|
|
65
|
+
const m = localizeVar(line).match(re);
|
|
66
|
+
if (m) children.push(m[1]);
|
|
67
|
+
}
|
|
68
|
+
return children.length > 0 ? children : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
// ============================================================================
|
|
55
72
|
// Prototype Installation
|
|
56
73
|
// ============================================================================
|
|
@@ -211,8 +228,8 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
211
228
|
}
|
|
212
229
|
|
|
213
230
|
// ─────────────────────────────────────────────────────────────────────
|
|
214
|
-
// Implicit div for class-only selectors
|
|
215
|
-
// .card → div.card
|
|
231
|
+
// Implicit div for class-only or bare dot selectors
|
|
232
|
+
// .card → div.card | . (with children) → div
|
|
216
233
|
// ─────────────────────────────────────────────────────────────────────
|
|
217
234
|
if (tag === '.') {
|
|
218
235
|
let prevToken = i > 0 ? tokens[i - 1] : null;
|
|
@@ -223,6 +240,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
223
240
|
tokens.splice(i, 0, divToken);
|
|
224
241
|
return 2;
|
|
225
242
|
}
|
|
243
|
+
// Skip .('classes') — handled by dynamic classes handler below
|
|
244
|
+
if (!nextToken || nextToken[0] !== '(') {
|
|
245
|
+
token[0] = 'IDENTIFIER';
|
|
246
|
+
token[1] = 'div';
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
226
249
|
}
|
|
227
250
|
}
|
|
228
251
|
|
|
@@ -343,7 +366,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
343
366
|
isTemplateElement = true;
|
|
344
367
|
} else if (tag === 'IDENTIFIER' && isTemplateTag(token[1]) && !isAfterControlFlow) {
|
|
345
368
|
isTemplateElement = true;
|
|
346
|
-
} else if (tag === 'PROPERTY' || tag === 'STRING' || tag === 'CALL_END' || tag === ')') {
|
|
369
|
+
} else if (tag === 'PROPERTY' || tag === 'STRING' || tag === 'STRING_END' || tag === 'CALL_END' || tag === ')') {
|
|
347
370
|
isTemplateElement = startsWithTag(tokens, i);
|
|
348
371
|
}
|
|
349
372
|
else if (tag === 'IDENTIFIER' && i > 1 && tokens[i - 1][0] === '...') {
|
|
@@ -614,8 +637,14 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
614
637
|
|
|
615
638
|
// Computed (derived)
|
|
616
639
|
for (const { name, expr } of derivedVars) {
|
|
617
|
-
|
|
618
|
-
|
|
640
|
+
if (this.is(expr, 'block') && expr.length > 2) {
|
|
641
|
+
const transformed = this.transformComponentMembers(expr);
|
|
642
|
+
const body = this.generateFunctionBody(transformed);
|
|
643
|
+
lines.push(` this.${name} = __computed(() => ${body});`);
|
|
644
|
+
} else {
|
|
645
|
+
const val = this.generateInComponent(expr, 'value');
|
|
646
|
+
lines.push(` this.${name} = __computed(() => ${val});`);
|
|
647
|
+
}
|
|
619
648
|
}
|
|
620
649
|
|
|
621
650
|
// Effects
|
|
@@ -991,10 +1020,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
991
1020
|
const eventName = key[2];
|
|
992
1021
|
// Bind method references to this
|
|
993
1022
|
if (typeof value === 'string' && this.componentMembers?.has(value)) {
|
|
994
|
-
this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => this.${value}(e));`);
|
|
1023
|
+
this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => __batch(() => this.${value}(e)));`);
|
|
995
1024
|
} else {
|
|
996
1025
|
const handlerCode = this.generateInComponent(value, 'value');
|
|
997
|
-
this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => (${handlerCode})(e));`);
|
|
1026
|
+
this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => __batch(() => (${handlerCode})(e)));`);
|
|
998
1027
|
}
|
|
999
1028
|
continue;
|
|
1000
1029
|
}
|
|
@@ -1221,7 +1250,14 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1221
1250
|
if (hasEffects) {
|
|
1222
1251
|
factoryLines.push(` disposers.forEach(d => d());`);
|
|
1223
1252
|
}
|
|
1224
|
-
|
|
1253
|
+
const condFragChildren = getFragChildren(rootVar, createLines, localizeVar);
|
|
1254
|
+
if (condFragChildren) {
|
|
1255
|
+
for (const child of condFragChildren) {
|
|
1256
|
+
factoryLines.push(` if (detaching) ${child}.remove();`);
|
|
1257
|
+
}
|
|
1258
|
+
} else {
|
|
1259
|
+
factoryLines.push(` if (detaching) ${localizeVar(rootVar)}.remove();`);
|
|
1260
|
+
}
|
|
1225
1261
|
factoryLines.push(` }`);
|
|
1226
1262
|
|
|
1227
1263
|
factoryLines.push(` };`);
|
|
@@ -1311,9 +1347,16 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1311
1347
|
}
|
|
1312
1348
|
factoryLines.push(` },`);
|
|
1313
1349
|
|
|
1314
|
-
// m() - mount
|
|
1350
|
+
// m() - mount (also repositions already-mounted blocks)
|
|
1351
|
+
const loopFragChildren = getFragChildren(itemNode, itemCreateLines, localizeVar);
|
|
1315
1352
|
factoryLines.push(` m(target, anchor) {`);
|
|
1316
|
-
|
|
1353
|
+
if (loopFragChildren) {
|
|
1354
|
+
for (const child of loopFragChildren) {
|
|
1355
|
+
factoryLines.push(` target.insertBefore(${child}, anchor);`);
|
|
1356
|
+
}
|
|
1357
|
+
} else {
|
|
1358
|
+
factoryLines.push(` target.insertBefore(${localizeVar(itemNode)}, anchor);`);
|
|
1359
|
+
}
|
|
1317
1360
|
factoryLines.push(` },`);
|
|
1318
1361
|
|
|
1319
1362
|
// p() - update
|
|
@@ -1340,7 +1383,13 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1340
1383
|
if (hasEffects) {
|
|
1341
1384
|
factoryLines.push(` disposers.forEach(d => d());`);
|
|
1342
1385
|
}
|
|
1343
|
-
|
|
1386
|
+
if (loopFragChildren) {
|
|
1387
|
+
for (const child of loopFragChildren) {
|
|
1388
|
+
factoryLines.push(` if (detaching) ${child}.remove();`);
|
|
1389
|
+
}
|
|
1390
|
+
} else {
|
|
1391
|
+
factoryLines.push(` if (detaching) ${localizeVar(itemNode)}.remove();`);
|
|
1392
|
+
}
|
|
1344
1393
|
factoryLines.push(` }`);
|
|
1345
1394
|
|
|
1346
1395
|
factoryLines.push(` };`);
|
|
@@ -1363,14 +1412,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1363
1412
|
setupLines.push(` const ${itemVar} = items[${indexVar}];`);
|
|
1364
1413
|
setupLines.push(` const key = ${keyExpr};`);
|
|
1365
1414
|
setupLines.push(` let block = map.get(key);`);
|
|
1366
|
-
setupLines.push(` if (block) {`);
|
|
1367
|
-
setupLines.push(` block.p(this, ${itemVar}, ${indexVar});`);
|
|
1368
|
-
setupLines.push(` } else {`);
|
|
1415
|
+
setupLines.push(` if (!block) {`);
|
|
1369
1416
|
setupLines.push(` block = ${blockName}(this, ${itemVar}, ${indexVar});`);
|
|
1370
1417
|
setupLines.push(` block.c();`);
|
|
1371
|
-
setupLines.push(` block.m(parent, anchor);`);
|
|
1372
|
-
setupLines.push(` block.p(this, ${itemVar}, ${indexVar});`);
|
|
1373
1418
|
setupLines.push(` }`);
|
|
1419
|
+
setupLines.push(` block.m(parent, anchor);`);
|
|
1420
|
+
setupLines.push(` block.p(this, ${itemVar}, ${indexVar});`);
|
|
1374
1421
|
setupLines.push(` newMap.set(key, block);`);
|
|
1375
1422
|
setupLines.push(` }`);
|
|
1376
1423
|
setupLines.push(``);
|
package/src/grammar/grammar.rip
CHANGED
|
@@ -197,7 +197,7 @@ grammar =
|
|
|
197
197
|
ComputedAssign: [
|
|
198
198
|
o 'Assignable COMPUTED_ASSIGN Expression' , '["computed", 1, 3]'
|
|
199
199
|
o 'Assignable COMPUTED_ASSIGN TERMINATOR Expression' , '["computed", 1, 4]'
|
|
200
|
-
o 'Assignable COMPUTED_ASSIGN
|
|
200
|
+
o 'Assignable COMPUTED_ASSIGN Block' , '["computed", 1, 3]'
|
|
201
201
|
]
|
|
202
202
|
|
|
203
203
|
# Reactive readonly (=!) — constants that cannot be reassigned
|
|
@@ -443,7 +443,7 @@ grammar =
|
|
|
443
443
|
Invocation: [
|
|
444
444
|
o 'Value String' , '["tagged-template", 1, 2]' # Tagged template
|
|
445
445
|
o 'Value Arguments' , '[1, ...2]' # Regular call
|
|
446
|
-
o 'Value ES6_OPTIONAL_CALL Arguments' , '["optcall", 1, ...3]'
|
|
446
|
+
o 'Value ES6_OPTIONAL_CALL Arguments' , '["optcall", 1, ...3]' # Optional call: x?.(args)
|
|
447
447
|
o 'SUPER Arguments' , '["super", ...2]' # Super call
|
|
448
448
|
o 'DYNAMIC_IMPORT Arguments' , '[1, ...2]' # Dynamic import()
|
|
449
449
|
]
|