rip-lang 3.13.11 → 3.13.13
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 +43 -7
- package/docs/charts.html +6 -6
- package/docs/demo.html +8 -8
- package/docs/dist/rip.js +8512 -8445
- package/docs/dist/rip.min.js +172 -165
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/example/index.html +1 -1
- package/docs/index.html +1 -1
- package/docs/results/index.html +8 -8
- package/docs/sierpinski.html +1 -1
- package/package.json +3 -4
- package/scripts/serve.js +6 -1
- package/src/app.rip +4 -24
- package/src/browser.js +81 -52
- package/src/compiler.js +27 -5
- package/src/components.js +5 -2
- package/docs/WEB-ROADMAP.md +0 -126
- package/src/grammar/parser.js +0 -360
package/docs/dist/rip.min.js.br
CHANGED
|
Binary file
|
package/docs/example/index.html
CHANGED
package/docs/index.html
CHANGED
|
@@ -495,7 +495,7 @@
|
|
|
495
495
|
body.light .repl-prompt-text { color: #007acc; }
|
|
496
496
|
</style>
|
|
497
497
|
<link rel="preload" href="https://cdn.jsdelivr.net/npm/monaco-editor@0.52.0/min/vs/loader.js" as="script">
|
|
498
|
-
<script
|
|
498
|
+
<script defer src="dist/rip.min.js"></script>
|
|
499
499
|
</head>
|
|
500
500
|
<body>
|
|
501
501
|
|
package/docs/results/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,500;0,700;1,400&family=Nothing+You+Could+Do&display=swap" rel="stylesheet">
|
|
10
|
-
<script
|
|
10
|
+
<script defer src="../dist/rip.min.js" data-mount="Home"></script>
|
|
11
11
|
<style>
|
|
12
12
|
/* ==========================================================================
|
|
13
13
|
Lab Results — Styles
|
|
@@ -596,7 +596,7 @@ body {
|
|
|
596
596
|
|
|
597
597
|
<!-- ===== Components ===== -->
|
|
598
598
|
|
|
599
|
-
<script type="text/rip"
|
|
599
|
+
<script type="text/rip">
|
|
600
600
|
# Lab Results - Main Page
|
|
601
601
|
|
|
602
602
|
export Home = component
|
|
@@ -758,7 +758,7 @@ export Home = component
|
|
|
758
758
|
acceptable: (k, v) -> @acceptable(k, v)
|
|
759
759
|
</script>
|
|
760
760
|
|
|
761
|
-
<script type="text/rip"
|
|
761
|
+
<script type="text/rip">
|
|
762
762
|
# Settings - Form panel with two-way bound inputs
|
|
763
763
|
|
|
764
764
|
export Settings = component
|
|
@@ -831,7 +831,7 @@ export Settings = component
|
|
|
831
831
|
@click: -> window.print()
|
|
832
832
|
</script>
|
|
833
833
|
|
|
834
|
-
<script type="text/rip"
|
|
834
|
+
<script type="text/rip">
|
|
835
835
|
# Brochure — Container composing all pages
|
|
836
836
|
|
|
837
837
|
export Brochure = component
|
|
@@ -859,7 +859,7 @@ export Brochure = component
|
|
|
859
859
|
DetailPage topic: topic, history: history, ranges: ranges, acceptable: acceptable
|
|
860
860
|
</script>
|
|
861
861
|
|
|
862
|
-
<script type="text/rip"
|
|
862
|
+
<script type="text/rip">
|
|
863
863
|
# Cover — Brochure cover page
|
|
864
864
|
|
|
865
865
|
export Cover = component
|
|
@@ -900,7 +900,7 @@ export Cover = component
|
|
|
900
900
|
div poweredBy
|
|
901
901
|
</script>
|
|
902
902
|
|
|
903
|
-
<script type="text/rip"
|
|
903
|
+
<script type="text/rip">
|
|
904
904
|
# Summary - Medical Summary table (Page 1)
|
|
905
905
|
|
|
906
906
|
export Summary = component
|
|
@@ -996,7 +996,7 @@ export Summary = component
|
|
|
996
996
|
td.value row.value
|
|
997
997
|
</script>
|
|
998
998
|
|
|
999
|
-
<script type="text/rip"
|
|
999
|
+
<script type="text/rip">
|
|
1000
1000
|
# DetailPage - Reusable health detail page with gradient header and gauges
|
|
1001
1001
|
|
|
1002
1002
|
export DetailPage = component
|
|
@@ -1068,7 +1068,7 @@ export DetailPage = component
|
|
|
1068
1068
|
.detail__footer-image style: "background-image: url(#{topic.image})"
|
|
1069
1069
|
</script>
|
|
1070
1070
|
|
|
1071
|
-
<script type="text/rip"
|
|
1071
|
+
<script type="text/rip">
|
|
1072
1072
|
# Gauge — SVG semi-circular gauge with rotating needle
|
|
1073
1073
|
|
|
1074
1074
|
export Gauge = component
|
package/docs/sierpinski.html
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
}
|
|
33
33
|
.dot:hover { transform: scale(2.5); z-index: 1; }
|
|
34
34
|
</style>
|
|
35
|
-
<script
|
|
35
|
+
<script defer src="https://shreeve.github.io/rip-lang/dist/rip.min.js"></script>
|
|
36
36
|
<script>
|
|
37
37
|
function updateScale() {
|
|
38
38
|
var s = Math.min(window.innerWidth / 1350, window.innerHeight / 1250, 1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rip-lang",
|
|
3
|
-
"version": "3.13.
|
|
3
|
+
"version": "3.13.13",
|
|
4
4
|
"description": "A modern language that compiles to JavaScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/compiler.js",
|
|
@@ -31,9 +31,8 @@
|
|
|
31
31
|
"CHANGELOG.md"
|
|
32
32
|
],
|
|
33
33
|
"scripts": {
|
|
34
|
-
"build": "bun scripts/build
|
|
35
|
-
"
|
|
36
|
-
"bump": "bun scripts/bump-version.js",
|
|
34
|
+
"build": "bun scripts/build.js",
|
|
35
|
+
"bump": "bun scripts/bump.js",
|
|
37
36
|
"parser": "bun src/grammar/solar.rip -o src/parser.js src/grammar/grammar.rip",
|
|
38
37
|
"serve": "bun scripts/serve.js",
|
|
39
38
|
"test": "bun test/runner.js",
|
package/scripts/serve.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// Simple static file server with brotli support
|
|
3
|
-
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { readFileSync, existsSync, statSync } from 'fs';
|
|
4
4
|
import { join, extname, dirname } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
|
|
@@ -37,6 +37,11 @@ function handleRequest(req) {
|
|
|
37
37
|
pathname = pathname.slice('/rip-lang'.length);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// Redirect /dir to /dir/ if it's a directory
|
|
41
|
+
if (!pathname.endsWith('/') && !extname(pathname)) {
|
|
42
|
+
try { if (statSync(join(ROOT, pathname)).isDirectory()) return Response.redirect(pathname + '/', 301); } catch {}
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
// Default to index.html for directory requests
|
|
41
46
|
if (pathname.endsWith('/')) {
|
|
42
47
|
pathname += 'index.html';
|
package/src/app.rip
CHANGED
|
@@ -876,11 +876,9 @@ connectWatch = (url) ->
|
|
|
876
876
|
# ==============================================================================
|
|
877
877
|
# Launch — fetch bundle, create stash, wire router + renderer, start app
|
|
878
878
|
#
|
|
879
|
-
# Entry point for Rip applications. Handles
|
|
879
|
+
# Entry point for Rip applications. Handles two bundle sources:
|
|
880
880
|
# 1. Explicit bundle object (opts.bundle)
|
|
881
|
-
# 2.
|
|
882
|
-
# 3. Inline <script type="text/rip" data-name="..."> tags
|
|
883
|
-
# 4. Server fetch from {appBase}/bundle
|
|
881
|
+
# 2. Fetch from URL (opts.bundleUrl) — used by serve middleware's data-launch
|
|
884
882
|
#
|
|
885
883
|
# Creates the app stash, sets up persistence if configured, builds the
|
|
886
884
|
# component resolver, initializes the router and renderer, and optionally
|
|
@@ -908,33 +906,15 @@ export launch = (appBase = '', opts = {}) ->
|
|
|
908
906
|
el.id = target.replace(/^#/, '')
|
|
909
907
|
document.body.prepend el
|
|
910
908
|
|
|
911
|
-
# Get the app bundle — explicit object
|
|
909
|
+
# Get the app bundle — explicit object or fetch from URL
|
|
912
910
|
if opts.bundle
|
|
913
911
|
bundle = opts.bundle
|
|
914
912
|
else if opts.bundleUrl
|
|
915
913
|
res = await fetch(opts.bundleUrl, cache: 'no-cache')
|
|
916
914
|
throw new Error "launch: #{opts.bundleUrl} (#{res.status})" unless res.ok
|
|
917
915
|
bundle = res.json!
|
|
918
|
-
else if opts.components and Array.isArray(opts.components)
|
|
919
|
-
components = {}
|
|
920
|
-
for url in opts.components
|
|
921
|
-
res = await fetch(url)
|
|
922
|
-
if res.ok
|
|
923
|
-
name = url.split('/').pop()
|
|
924
|
-
components["components/#{name}"] = await res.text()
|
|
925
|
-
bundle = { components, data: {} }
|
|
926
|
-
else if typeof document isnt 'undefined' and document.querySelectorAll('script[type="text/rip"][data-name]').length > 0
|
|
927
|
-
components = {}
|
|
928
|
-
for script in document.querySelectorAll('script[type="text/rip"][data-name]')
|
|
929
|
-
name = script.getAttribute('data-name')
|
|
930
|
-
name += '.rip' unless name.endsWith('.rip')
|
|
931
|
-
components["components/#{name}"] = script.textContent
|
|
932
|
-
bundle = { components, data: {} }
|
|
933
916
|
else
|
|
934
|
-
|
|
935
|
-
res = await fetch(bundleUrl, cache: 'no-cache')
|
|
936
|
-
throw new Error "launch: #{bundleUrl} (#{res.status})" unless res.ok
|
|
937
|
-
bundle = res.json!
|
|
917
|
+
throw new Error "launch: no bundle or bundleUrl provided"
|
|
938
918
|
|
|
939
919
|
# Create the unified stash
|
|
940
920
|
app = stash { components: {}, routes: {}, data: {} }
|
package/src/browser.js
CHANGED
|
@@ -13,11 +13,11 @@ export const BUILD_DATE = "0000-00-00@00:00:00GMT";
|
|
|
13
13
|
// Import compiler functions for use in rip() function and globalThis registration
|
|
14
14
|
import { compile, compileToJS, formatSExpr, getReactiveRuntime, getComponentRuntime } from './compiler.js';
|
|
15
15
|
|
|
16
|
-
// Eagerly register Rip's reactive
|
|
17
|
-
// framework code (app.rip) can use them directly
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
new Function(
|
|
16
|
+
// Eagerly register Rip's reactive and component runtimes on globalThis so that
|
|
17
|
+
// framework code (app.rip) and browser-compiled scripts can use them directly
|
|
18
|
+
if (typeof globalThis !== 'undefined') {
|
|
19
|
+
if (!globalThis.__rip) new Function(getReactiveRuntime())();
|
|
20
|
+
if (!globalThis.__ripComponent) new Function(getComponentRuntime())();
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const dedent = s => {
|
|
@@ -26,43 +26,88 @@ const dedent = s => {
|
|
|
26
26
|
return s.replace(RegExp(`^[ \t]{${i}}`, 'gm'), '').trim();
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// Browser runtime
|
|
30
|
-
//
|
|
29
|
+
// Browser runtime: collect all <script type="text/rip"> sources (inline + src)
|
|
30
|
+
// plus any data-src URLs on the runtime tag, compile them all with shared-scope
|
|
31
|
+
// options, and execute as one async IIFE. Then handle data-launch for server mode.
|
|
31
32
|
async function processRipScripts() {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
const sources = [];
|
|
34
|
+
|
|
35
|
+
// Step 1: Collect data-src URLs from the runtime script tag
|
|
36
|
+
const runtimeTag = document.querySelector('script[src$="rip.min.js"], script[src$="rip.js"]');
|
|
37
|
+
const dataSrc = runtimeTag?.getAttribute('data-src');
|
|
38
|
+
if (dataSrc) {
|
|
39
|
+
for (const url of dataSrc.trim().split(/\s+/)) {
|
|
40
|
+
if (url) sources.push({ url });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Step 2: Collect all <script type="text/rip"> tags (inline and external)
|
|
45
|
+
for (const script of document.querySelectorAll('script[type="text/rip"]')) {
|
|
46
|
+
if (script.src) {
|
|
47
|
+
sources.push({ url: script.src });
|
|
48
|
+
} else {
|
|
49
|
+
const code = dedent(script.textContent);
|
|
50
|
+
if (code) sources.push({ code });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Step 3: Fetch externals, compile all, execute in shared scope
|
|
55
|
+
if (sources.length > 0) {
|
|
56
|
+
await Promise.all(sources.map(async (s) => {
|
|
57
|
+
if (!s.url) return;
|
|
58
|
+
try {
|
|
59
|
+
const res = await fetch(s.url);
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
console.error(`Rip: failed to fetch ${s.url} (${res.status})`);
|
|
62
|
+
return;
|
|
45
63
|
}
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
64
|
+
s.code = await res.text();
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.error(`Rip: failed to fetch ${s.url}:`, e.message);
|
|
49
67
|
}
|
|
68
|
+
}));
|
|
50
69
|
|
|
51
|
-
|
|
70
|
+
const opts = { skipRuntimes: true, skipExports: true };
|
|
71
|
+
const compiled = [];
|
|
72
|
+
for (const s of sources) {
|
|
73
|
+
if (!s.code) continue;
|
|
52
74
|
try {
|
|
53
|
-
|
|
54
|
-
} catch (
|
|
55
|
-
console.error('Rip compile error:',
|
|
56
|
-
console.error('Source:', ripCode);
|
|
57
|
-
continue;
|
|
75
|
+
compiled.push(compileToJS(s.code, opts));
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error('Rip compile error:', e.message);
|
|
58
78
|
}
|
|
79
|
+
}
|
|
59
80
|
|
|
60
|
-
|
|
61
|
-
|
|
81
|
+
if (compiled.length > 0) {
|
|
82
|
+
let js = compiled.join('\n');
|
|
62
83
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
84
|
+
// Step 4: Append data-mount call inside the shared IIFE
|
|
85
|
+
const mount = runtimeTag?.getAttribute('data-mount');
|
|
86
|
+
if (mount) {
|
|
87
|
+
const target = runtimeTag.getAttribute('data-target') || 'body';
|
|
88
|
+
js += `\n${mount}.mount(${JSON.stringify(target)});`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
await (0, eval)(`(async()=>{\n${js}\n})()`);
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error('Rip runtime error:', e);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Step 5: data-launch triggers launch() for server mode
|
|
100
|
+
const cfg = document.querySelector('script[data-launch]');
|
|
101
|
+
if (cfg && !globalThis.__ripLaunched) {
|
|
102
|
+
const ui = importRip.modules?.['app.rip'];
|
|
103
|
+
if (ui?.launch) {
|
|
104
|
+
const url = cfg.getAttribute('data-launch') || '';
|
|
105
|
+
const hash = cfg.getAttribute('data-hash');
|
|
106
|
+
const persist = cfg.getAttribute('data-persist');
|
|
107
|
+
const opts = { hash: hash !== 'false' };
|
|
108
|
+
if (url) opts.bundleUrl = url;
|
|
109
|
+
if (persist != null) opts.persist = persist === 'local' ? 'local' : true;
|
|
110
|
+
await ui.launch('', opts);
|
|
66
111
|
}
|
|
67
112
|
}
|
|
68
113
|
}
|
|
@@ -134,28 +179,12 @@ if (typeof globalThis !== 'undefined') {
|
|
|
134
179
|
globalThis.__ripExports = { compile, compileToJS, formatSExpr, getStdlibCode, VERSION, BUILD_DATE, getReactiveRuntime, getComponentRuntime };
|
|
135
180
|
}
|
|
136
181
|
|
|
137
|
-
// Auto-
|
|
138
|
-
// data-url is the literal fetch URL for the component bundle.
|
|
139
|
-
async function autoLaunch() {
|
|
140
|
-
if (globalThis.__ripLaunched) return;
|
|
141
|
-
const ui = importRip.modules?.['app.rip'];
|
|
142
|
-
if (!ui?.launch) return;
|
|
143
|
-
const cfg = document.querySelector('script[data-url], script[data-hash]');
|
|
144
|
-
const tag = document.querySelectorAll('script[type="text/rip"][data-name]').length > 0;
|
|
145
|
-
if (!cfg && !tag) return;
|
|
146
|
-
const url = cfg?.getAttribute('data-url') || '';
|
|
147
|
-
const hash = cfg?.getAttribute('data-hash');
|
|
148
|
-
const opts = { hash: hash !== 'false' };
|
|
149
|
-
if (url) opts.bundleUrl = url;
|
|
150
|
-
await ui.launch('', opts);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Auto-process <script type="text/rip"> blocks, then auto-launch if applicable.
|
|
182
|
+
// Auto-process <script type="text/rip"> blocks and handle data-launch.
|
|
154
183
|
// Deferred via queueMicrotask so bundled entry code (e.g. rip.min.js registering
|
|
155
184
|
// importRip.modules) runs before script processing begins.
|
|
156
185
|
if (typeof document !== 'undefined') {
|
|
157
186
|
globalThis.__ripScriptsReady = new Promise(resolve => {
|
|
158
|
-
const run = () => processRipScripts().then(
|
|
187
|
+
const run = () => processRipScripts().then(resolve);
|
|
159
188
|
if (document.readyState === 'loading') {
|
|
160
189
|
document.addEventListener('DOMContentLoaded', () => queueMicrotask(run));
|
|
161
190
|
} else {
|
package/src/compiler.js
CHANGED
|
@@ -636,6 +636,7 @@ export class CodeGenerator {
|
|
|
636
636
|
}
|
|
637
637
|
|
|
638
638
|
let skip = this.options.skipPreamble;
|
|
639
|
+
let skipRT = this.options.skipRuntimes;
|
|
639
640
|
|
|
640
641
|
if (!skip) {
|
|
641
642
|
|
|
@@ -645,10 +646,12 @@ export class CodeGenerator {
|
|
|
645
646
|
needsBlank = true;
|
|
646
647
|
|
|
647
648
|
// On-demand helpers — only emitted when referenced
|
|
648
|
-
|
|
649
|
-
|
|
649
|
+
// Use var when skipRuntimes is set so helpers can be safely re-emitted across concatenated files
|
|
650
|
+
let helperDecl = skipRT ? 'var' : 'const';
|
|
651
|
+
if (this.helpers.has('slice' )) { code += `${helperDecl} slice = [].slice;\n`; needsBlank = true; }
|
|
652
|
+
if (this.helpers.has('modulo' )) { code += `${helperDecl} modulo = (n, d) => { n = +n; d = +d; return (n % d + d) % d; };\n`; needsBlank = true; }
|
|
650
653
|
if (this.helpers.has('toMatchable')) {
|
|
651
|
-
code +=
|
|
654
|
+
code += `${helperDecl} toMatchable = (v, allowNewlines) => {\n`;
|
|
652
655
|
code += ' if (typeof v === "string") return !allowNewlines && /[\\n\\r]/.test(v) ? null : v;\n';
|
|
653
656
|
code += ' if (v == null) return "";\n';
|
|
654
657
|
code += ' if (typeof v === "number" || typeof v === "bigint" || typeof v === "boolean") return String(v);\n';
|
|
@@ -673,7 +676,9 @@ export class CodeGenerator {
|
|
|
673
676
|
}
|
|
674
677
|
|
|
675
678
|
if (this.usesReactivity && !skip) {
|
|
676
|
-
if (
|
|
679
|
+
if (skipRT) {
|
|
680
|
+
code += 'var { __state, __computed, __effect, __batch, __readonly, __setErrorHandler, __handleError, __catchErrors } = globalThis.__rip;\n';
|
|
681
|
+
} else if (typeof globalThis !== 'undefined' && globalThis.__rip) {
|
|
677
682
|
code += 'const { __state, __computed, __effect, __batch, __readonly, __setErrorHandler, __handleError, __catchErrors } = globalThis.__rip;\n';
|
|
678
683
|
} else {
|
|
679
684
|
code += this.getReactiveRuntime();
|
|
@@ -682,7 +687,9 @@ export class CodeGenerator {
|
|
|
682
687
|
}
|
|
683
688
|
|
|
684
689
|
if (this.usesTemplates && !skip) {
|
|
685
|
-
if (
|
|
690
|
+
if (skipRT) {
|
|
691
|
+
code += 'var { __pushComponent, __popComponent, setContext, getContext, hasContext, __clsx, __Component } = globalThis.__ripComponent;\n';
|
|
692
|
+
} else if (typeof globalThis !== 'undefined' && globalThis.__ripComponent) {
|
|
686
693
|
code += 'const { __pushComponent, __popComponent, setContext, getContext, hasContext, __clsx, __Component } = globalThis.__ripComponent;\n';
|
|
687
694
|
} else {
|
|
688
695
|
code += this.getComponentRuntime();
|
|
@@ -1601,6 +1608,8 @@ export class CodeGenerator {
|
|
|
1601
1608
|
|
|
1602
1609
|
if (rest.length === 3) tryCode += ' finally ' + this.generate(rest[2], 'statement');
|
|
1603
1610
|
|
|
1611
|
+
if (rest.length === 1) tryCode += ' catch {}';
|
|
1612
|
+
|
|
1604
1613
|
if (needsReturns) {
|
|
1605
1614
|
let isAsync = this.containsAwait(rest[0]) || (rest[1] && this.containsAwait(rest[1]));
|
|
1606
1615
|
return `(${isAsync ? 'async ' : ''}() => { ${tryCode} })()`;
|
|
@@ -2024,6 +2033,11 @@ export class CodeGenerator {
|
|
|
2024
2033
|
|
|
2025
2034
|
generateExport(head, rest) {
|
|
2026
2035
|
let [decl] = rest;
|
|
2036
|
+
if (this.options.skipExports) {
|
|
2037
|
+
if (Array.isArray(decl) && decl.every(i => typeof i === 'string')) return '';
|
|
2038
|
+
if (this.is(decl, '=')) return `const ${decl[1]} = ${this.generate(decl[2], 'value')}`;
|
|
2039
|
+
return this.generate(decl, 'statement');
|
|
2040
|
+
}
|
|
2027
2041
|
if (Array.isArray(decl) && decl.every(i => typeof i === 'string')) return `export { ${decl.join(', ')} }`;
|
|
2028
2042
|
if (this.is(decl, '=')) return `export const ${decl[1]} = ${this.generate(decl[2], 'value')}`;
|
|
2029
2043
|
return `export ${this.generate(decl, 'statement')}`;
|
|
@@ -2031,6 +2045,10 @@ export class CodeGenerator {
|
|
|
2031
2045
|
|
|
2032
2046
|
generateExportDefault(head, rest) {
|
|
2033
2047
|
let [expr] = rest;
|
|
2048
|
+
if (this.options.skipExports) {
|
|
2049
|
+
if (this.is(expr, '=')) return `const ${expr[1]} = ${this.generate(expr[2], 'value')}`;
|
|
2050
|
+
return this.generate(expr, 'statement');
|
|
2051
|
+
}
|
|
2034
2052
|
if (this.is(expr, '=')) {
|
|
2035
2053
|
return `const ${expr[1]} = ${this.generate(expr[2], 'value')};\nexport default ${expr[1]}`;
|
|
2036
2054
|
}
|
|
@@ -2038,10 +2056,12 @@ export class CodeGenerator {
|
|
|
2038
2056
|
}
|
|
2039
2057
|
|
|
2040
2058
|
generateExportAll(head, rest) {
|
|
2059
|
+
if (this.options.skipExports) return '';
|
|
2041
2060
|
return `export * from ${this.addJsExtensionAndAssertions(rest[0])}`;
|
|
2042
2061
|
}
|
|
2043
2062
|
|
|
2044
2063
|
generateExportFrom(head, rest) {
|
|
2064
|
+
if (this.options.skipExports) return '';
|
|
2045
2065
|
let [specifiers, source] = rest;
|
|
2046
2066
|
let fixedSource = this.addJsExtensionAndAssertions(source);
|
|
2047
2067
|
if (Array.isArray(specifiers)) {
|
|
@@ -3226,6 +3246,8 @@ export class Compiler {
|
|
|
3226
3246
|
let generator = new CodeGenerator({
|
|
3227
3247
|
dataSection,
|
|
3228
3248
|
skipPreamble: this.options.skipPreamble,
|
|
3249
|
+
skipRuntimes: this.options.skipRuntimes,
|
|
3250
|
+
skipExports: this.options.skipExports,
|
|
3229
3251
|
reactiveVars: this.options.reactiveVars,
|
|
3230
3252
|
sourceMap,
|
|
3231
3253
|
});
|
package/src/components.js
CHANGED
|
@@ -570,8 +570,8 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
570
570
|
}
|
|
571
571
|
|
|
572
572
|
// Dot access: transform the object but not the property name
|
|
573
|
-
if (sexpr[0] === '.') {
|
|
574
|
-
return [
|
|
573
|
+
if (sexpr[0] === '.' || sexpr[0] === '?.') {
|
|
574
|
+
return [sexpr[0], this.transformComponentMembers(sexpr[1]), sexpr[2]];
|
|
575
575
|
}
|
|
576
576
|
|
|
577
577
|
// Force thin arrows to fat arrows inside components to preserve this binding
|
|
@@ -1914,6 +1914,9 @@ class __Component {
|
|
|
1914
1914
|
this._root.parentNode.removeChild(this._root);
|
|
1915
1915
|
}
|
|
1916
1916
|
}
|
|
1917
|
+
static mount(target = 'body') {
|
|
1918
|
+
return new this().mount(target);
|
|
1919
|
+
}
|
|
1917
1920
|
}
|
|
1918
1921
|
|
|
1919
1922
|
// Register on globalThis for runtime deduplication
|
package/docs/WEB-ROADMAP.md
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
# Rip UI — Roadmap
|
|
2
|
-
|
|
3
|
-
## What's There
|
|
4
|
-
|
|
5
|
-
### Core Reactive System
|
|
6
|
-
- **Reactive primitives** — `:=` (state), `~=` (computed), `~>` (effects) as
|
|
7
|
-
language syntax. Fine-grained dependency tracking, batching, readonly (`=!`).
|
|
8
|
-
- **`__Component` base class** — mount, unmount, context push/pop, constructor
|
|
9
|
-
lifecycle all handled in the runtime. Components just override `_init`.
|
|
10
|
-
- **`__state` signal passthrough** — if a value is already a signal, `__state`
|
|
11
|
-
returns it as-is. No separate `isSignal` check needed.
|
|
12
|
-
- **Fine-grained DOM** — `_create` builds real DOM nodes, `_setup` wires
|
|
13
|
-
reactive effects that update individual text nodes and attributes.
|
|
14
|
-
- **Timing primitives** — `delay`, `debounce`, `throttle`, `hold` as small
|
|
15
|
-
user-space functions composing `:=` + `~>` + cleanup. No framework API.
|
|
16
|
-
|
|
17
|
-
### Component Model
|
|
18
|
-
- **Component composition** — PascalCase identifiers in render blocks
|
|
19
|
-
instantiate child components. `card.rip` → `Card`. App-scoped, lazy-compiled,
|
|
20
|
-
cached after first use. No imports needed.
|
|
21
|
-
- **Reactive props** — parent passes `:=` signals directly to children.
|
|
22
|
-
Child's `__state` passthrough returns the signal as-is. Two-way binding.
|
|
23
|
-
- **Readonly props** — `=!` for props that children can read but not write.
|
|
24
|
-
- **Children blocks** — `Card title: "Hello" -> p "content"` passes children
|
|
25
|
-
as a DOM node via the `@children` slot. `#content` for layout slots.
|
|
26
|
-
- **Unmount cascade** — parent tracks child instances in `_children`.
|
|
27
|
-
`unmount()` cascades depth-first.
|
|
28
|
-
- **Conditional rendering** — `if`/`else` in render blocks with anchor-based
|
|
29
|
-
conditional DOM.
|
|
30
|
-
- **List rendering** — `for item in items` with keyed reconciliation.
|
|
31
|
-
- **Event handling** — `@click: method` binds to `this.method` correctly.
|
|
32
|
-
- **CSS classes** — `div.counter.active` compiles to static className.
|
|
33
|
-
Dynamic classes via `__clsx(...)`.
|
|
34
|
-
- **Context** — `setContext`/`getContext`/`hasContext` for sharing data
|
|
35
|
-
between ancestor and descendant components without prop drilling.
|
|
36
|
-
- **Lifecycle hooks** — `mounted`, `unmounted` work. `beforeMount`,
|
|
37
|
-
`beforeUnmount` are recognized.
|
|
38
|
-
|
|
39
|
-
### State & Routing
|
|
40
|
-
- **Reactive stash** — shared reactive store with proxy-based access.
|
|
41
|
-
- **File-based router** — URL-to-component mapping with params, guards,
|
|
42
|
-
layouts, `_navigating` signal, keep-alive component cache.
|
|
43
|
-
- **Hash routing** — `launch '/app', hash: true` for static single-file
|
|
44
|
-
deployment. Uses `readUrl()`/`writeUrl()` helpers. Back/forward and
|
|
45
|
-
direct URL loading work correctly.
|
|
46
|
-
- **State persistence** — `persist: true` enables debounced auto-save of
|
|
47
|
-
`app.data` to sessionStorage. `_writeVersion` signal + `beforeunload` safety.
|
|
48
|
-
- **Error boundaries** — catch mount-time errors.
|
|
49
|
-
|
|
50
|
-
### Infrastructure
|
|
51
|
-
- **Component store** — in-memory `.rip` file storage with compilation cache
|
|
52
|
-
and file watchers for hot reload via SSE.
|
|
53
|
-
- **`launch bundle:`** — inline all components as heredoc strings in a single
|
|
54
|
-
HTML file. Zero-server deployment. `docs/demo.html` is a 337-line example.
|
|
55
|
-
- **Combined bundle** — `rip-ui.min.js` (~52KB Brotli) packages the compiler
|
|
56
|
-
and pre-compiled UI framework in one file. Eliminates the `ui.rip` fetch
|
|
57
|
-
and its runtime compilation. `importRip('ui.rip')` is intercepted and returns
|
|
58
|
-
the pre-compiled module instantly.
|
|
59
|
-
- **Parallel loading** — Monaco Editor preloaded via `<link rel="preload">`,
|
|
60
|
-
compiler exports available instantly via `globalThis.__ripExports`. All
|
|
61
|
-
synchronous setup runs in parallel with the Monaco CDN fetch.
|
|
62
|
-
- **FOUC prevention** — playground pages use `body { opacity: 0 }` with a
|
|
63
|
-
`body.ready` fade-in transition after full initialization.
|
|
64
|
-
- **Runtime deduplication** — both runtimes register on `globalThis.__rip`
|
|
65
|
-
and `globalThis.__ripComponent`. Multiple compilations share one runtime.
|
|
66
|
-
- **Smooth app launch** — container fades in after first mount + font load.
|
|
67
|
-
- **Navigation anti-flicker** — `_navigating` uses `delay 100` to suppress
|
|
68
|
-
brief loading indicators.
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## Where Rip UI Wins
|
|
73
|
-
|
|
74
|
-
1. **Simplicity of the reactive model.** Three operators, minimal complete set. No hooks rules, no dependency arrays, no `.value` papercuts.
|
|
75
|
-
2. **Zero-build development.** No other framework runs entirely in the browser with zero build tooling.
|
|
76
|
-
3. **Timing primitives from composition.** `delay`, `debounce`, `throttle`, `hold` prove architectural correctness — hard problems dissolve into small functions.
|
|
77
|
-
4. **Effect cleanup design.** Returning a function from an effect for cleanup is the cleanest pattern across all frameworks.
|
|
78
|
-
5. **Syntax over API.** `:=` beats `ref()`. `~=` beats `computed()`. `~>` beats `watchEffect()`. Less ceremony, same power.
|
|
79
|
-
6. **Static deployment.** Hash routing + `launch bundle:` = full SPA in a single HTML file on any static host.
|
|
80
|
-
|
|
81
|
-
## Where Others Win
|
|
82
|
-
|
|
83
|
-
1. **Ecosystem.** Zero component libraries, zero third-party integrations, zero community packages.
|
|
84
|
-
2. **SSR.** No server-side rendering means no SEO, no progressive enhancement.
|
|
85
|
-
3. **Performance proof.** No benchmarks, no published numbers.
|
|
86
|
-
4. **TypeScript depth.** Framework exports have no `.d.ts` files.
|
|
87
|
-
5. **Tooling.** No DevTools extension, no CLI scaffolding.
|
|
88
|
-
6. **Battle-testing.** React serves billions. We serve a demo app.
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## The Path Forward
|
|
93
|
-
|
|
94
|
-
### Must-have (blocks adoption)
|
|
95
|
-
1. Named slots — multiple content projection points (header, body, footer)
|
|
96
|
-
2. Framework `.d.ts` files — TypeScript definitions for all exports
|
|
97
|
-
|
|
98
|
-
### Should-have (competitive parity)
|
|
99
|
-
3. AOT compilation path — component-level ahead-of-time for production (framework AOT done in v0.3.2)
|
|
100
|
-
4. SSR — server rendering for SEO
|
|
101
|
-
5. js-framework-benchmark — published performance numbers
|
|
102
|
-
6. Keyed list reconciliation — optimized array diffing for large datasets
|
|
103
|
-
|
|
104
|
-
### Nice-to-have (ecosystem growth)
|
|
105
|
-
7. DevTools extension — visual component/state inspector
|
|
106
|
-
8. `create-rip-app` CLI — project scaffolding
|
|
107
|
-
9. Headless UI primitives — accessible dropdown, modal, dialog
|
|
108
|
-
10. State-preserving HMR — keep reactive state during hot reload
|
|
109
|
-
11. Scoped slots and teleport — advanced composition patterns
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## Known Caveats
|
|
114
|
-
|
|
115
|
-
- **`getCompiled`/`setCompiled` cache modules, not JS strings.** Same source
|
|
116
|
-
at two paths = two cached modules. No deduplication across paths.
|
|
117
|
-
- **Stash proxy missing `getOwnPropertyDescriptor` trap.** Works in practice
|
|
118
|
-
but could cause issues with `Object.keys`/spread in strict environments.
|
|
119
|
-
- **Router regex recreated on every `buildRoutes` call.** Not a problem with
|
|
120
|
-
small route tables. Could optimize with fingerprint comparison.
|
|
121
|
-
- **`router.current` creates a new object on every read.** A cached object
|
|
122
|
-
that updates inside batch would reduce garbage.
|
|
123
|
-
- **REPL reactive state doesn't persist across `rip()` calls.** Each call is
|
|
124
|
-
a separate compilation. All reactive code must be in a single call.
|
|
125
|
-
- **Hash routing and path routing are mutually exclusive.** Set once at
|
|
126
|
-
`launch` time. No runtime switching between modes.
|