rip-lang 3.16.0 → 3.16.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/README.md +1 -1
- package/bin/rip +162 -10
- package/docs/AGENTS.md +1 -1
- package/docs/RIP-APP.md +109 -17
- package/docs/RIP-LANG.md +4 -5
- package/docs/RIP-TYPES.md +74 -103
- package/docs/demo/README.md +4 -3
- package/docs/dist/rip.js +933 -338
- package/docs/dist/rip.min.js +209 -204
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/example/index.json +7 -7
- package/docs/example/index.json.br +0 -0
- package/docs/extensions/vscode/print/print-1.0.14.vsix +0 -0
- package/docs/extensions/vscode/print/print-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/rip-0.5.15.vsix +0 -0
- package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
- package/docs/ui/bundle.json +55 -55
- package/docs/ui/bundle.json.br +0 -0
- package/docs/ui/index.html +1 -1
- package/package.json +9 -4
- package/rip-loader.js +59 -2
- package/src/AGENTS.md +5 -5
- package/src/browser.js +52 -11
- package/src/compiler.js +318 -44
- package/src/components.js +178 -39
- package/src/dts.js +62 -47
- package/src/lexer.js +58 -15
- package/src/schema/schema.js +5 -5
- package/src/typecheck.js +1355 -100
- package/src/types.js +85 -5
- /package/docs/demo/{components → routes}/_layout.rip +0 -0
- /package/docs/demo/{components → routes}/about.rip +0 -0
- /package/docs/demo/{components → routes}/card.rip +0 -0
- /package/docs/demo/{components → routes}/counter.rip +0 -0
- /package/docs/demo/{components → routes}/index.rip +0 -0
- /package/docs/demo/{components → routes}/todos.rip +0 -0
package/docs/ui/bundle.json.br
CHANGED
|
Binary file
|
package/docs/ui/index.html
CHANGED
|
@@ -103,7 +103,7 @@ export WidgetGallery = component
|
|
|
103
103
|
return unless entry
|
|
104
104
|
sourceName = entry.name
|
|
105
105
|
sourceLines = entry.lines
|
|
106
|
-
src = window.__RIP__?.components?.read("
|
|
106
|
+
src = window.__RIP__?.components?.read("_pkg/ui/#{id}.rip")
|
|
107
107
|
return unless src
|
|
108
108
|
sourceCode = src
|
|
109
109
|
_closeSource: -> sourceCode = null
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rip-lang",
|
|
3
|
-
"version": "3.16.
|
|
3
|
+
"version": "3.16.1",
|
|
4
4
|
"description": "A modern language that compiles to JavaScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/compiler.js",
|
|
7
7
|
"workspaces": [
|
|
8
|
+
"examples/*",
|
|
8
9
|
"packages/*"
|
|
9
10
|
],
|
|
11
|
+
"catalog": {
|
|
12
|
+
"typescript": "5.9.3"
|
|
13
|
+
},
|
|
10
14
|
"browser": "docs/dist/rip.min.js",
|
|
11
15
|
"exports": {
|
|
12
16
|
".": {
|
|
@@ -35,8 +39,8 @@
|
|
|
35
39
|
"bump": "bun scripts/bump.js",
|
|
36
40
|
"gen:dom": "bun scripts/gen-dom.js",
|
|
37
41
|
"gallery": "bun scripts/gallery.js",
|
|
38
|
-
"bundle:demo": "bun scripts/bundle-app.js docs/demo -o docs/example/index.json -t 'Rip App Demo'",
|
|
39
|
-
"bundle:ui": "bun scripts/bundle-app.js packages/ui/browser -o docs/ui/bundle.json -t 'Rip UI'",
|
|
42
|
+
"bundle:demo": "bun scripts/bundle-app.js docs/demo/routes --prefix _route --css docs/demo/css -o docs/example/index.json -t 'Rip App Demo'",
|
|
43
|
+
"bundle:ui": "bun scripts/bundle-app.js packages/ui/browser/components --prefix _pkg/ui -o docs/ui/bundle.json -t 'Rip UI'",
|
|
40
44
|
"parser": "bun src/grammar/solar.rip -o src/parser.js src/grammar/grammar.rip",
|
|
41
45
|
"postinstall": "node scripts/postinstall.js --quiet",
|
|
42
46
|
"link-local": "bun scripts/link-local.js",
|
|
@@ -87,6 +91,7 @@
|
|
|
87
91
|
"author": "Steve Shreeve <steve.shreeve@gmail.com>",
|
|
88
92
|
"license": "MIT",
|
|
89
93
|
"devDependencies": {
|
|
90
|
-
"
|
|
94
|
+
"@types/bun": "1.3.14",
|
|
95
|
+
"typescript": "catalog:"
|
|
91
96
|
}
|
|
92
97
|
}
|
package/rip-loader.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { plugin } from "bun";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
|
+
import { dirname, resolve as resolvePath } from "path";
|
|
6
|
+
import { readFileSync, existsSync } from "fs";
|
|
5
7
|
import { compileToJS, formatError } from "./src/compiler.js";
|
|
6
8
|
// Register the full schema runtime provider so .rip files containing
|
|
7
9
|
// `schema :model` blocks compile correctly inside spawned workers.
|
|
@@ -9,11 +11,65 @@ import { compileToJS, formatError } from "./src/compiler.js";
|
|
|
9
11
|
// would call compileToJS without ever registering a provider.
|
|
10
12
|
import "./src/schema/loader-server.js";
|
|
11
13
|
|
|
14
|
+
// ── Undeclared-import diagnostic ────────────────────────────────────────
|
|
15
|
+
// Walk up from an importer to its nearest package.json, then verify that any
|
|
16
|
+
// `@rip-lang/<pkg>` specifier is declared in dependencies/devDependencies/
|
|
17
|
+
// peerDependencies/optionalDependencies (or is the package's own self-import).
|
|
18
|
+
//
|
|
19
|
+
// Throws a clear error before `import.meta.resolve` is even attempted, so
|
|
20
|
+
// "works on my machine" failures rooted in link-global rescue surface loudly
|
|
21
|
+
// instead of silently shipping.
|
|
22
|
+
const declarationCache = new Map(); // importerDir → { pkgName, declared } | null
|
|
23
|
+
|
|
24
|
+
function getDeclarationInfo(importerPath) {
|
|
25
|
+
const start = dirname(importerPath);
|
|
26
|
+
if (declarationCache.has(start)) return declarationCache.get(start);
|
|
27
|
+
let cur = start;
|
|
28
|
+
let info = null;
|
|
29
|
+
while (true) {
|
|
30
|
+
const pkgPath = resolvePath(cur, 'package.json');
|
|
31
|
+
if (existsSync(pkgPath)) {
|
|
32
|
+
try {
|
|
33
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
34
|
+
info = {
|
|
35
|
+
pkgName: pkg.name || null,
|
|
36
|
+
declared: new Set([
|
|
37
|
+
...Object.keys(pkg.dependencies || {}),
|
|
38
|
+
...Object.keys(pkg.devDependencies || {}),
|
|
39
|
+
...Object.keys(pkg.peerDependencies || {}),
|
|
40
|
+
...Object.keys(pkg.optionalDependencies || {}),
|
|
41
|
+
]),
|
|
42
|
+
};
|
|
43
|
+
} catch {
|
|
44
|
+
info = { pkgName: null, declared: new Set() };
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
const parent = dirname(cur);
|
|
49
|
+
if (parent === cur) break;
|
|
50
|
+
cur = parent;
|
|
51
|
+
}
|
|
52
|
+
declarationCache.set(start, info);
|
|
53
|
+
return info;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function assertDeclaredRipImport(importerPath, specifier) {
|
|
57
|
+
const m = specifier.match(/^(@rip-lang\/[^\/]+)/);
|
|
58
|
+
if (!m) return;
|
|
59
|
+
const pkgKey = m[1];
|
|
60
|
+
const info = getDeclarationInfo(importerPath);
|
|
61
|
+
if (!info) return; // ad-hoc script outside any package — don't block
|
|
62
|
+
if (info.pkgName === pkgKey) return; // self-import
|
|
63
|
+
if (info.declared.has(pkgKey)) return;
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Import of '${pkgKey}' is not declared in package.json. ` +
|
|
66
|
+
`Run \`bun add ${pkgKey}\` (or use \`workspace:*\` inside this monorepo).`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
12
70
|
await plugin({
|
|
13
71
|
name: "rip-loader",
|
|
14
72
|
async setup(build) {
|
|
15
|
-
const { readFileSync } = await import("fs");
|
|
16
|
-
|
|
17
73
|
// Handle .rip files
|
|
18
74
|
build.onLoad({ filter: /\.rip$/ }, async (args) => {
|
|
19
75
|
try {
|
|
@@ -25,6 +81,7 @@ await plugin({
|
|
|
25
81
|
// is broken in plugin handlers — so we use import.meta.resolve, which
|
|
26
82
|
// resolves from this file's location (inside the global node_modules tree).
|
|
27
83
|
js = js.replace(/(from\s+|import\s*\()(['"])(@rip-lang\/[^'"]+)\2/g, (match, prefix, quote, specifier) => {
|
|
84
|
+
assertDeclaredRipImport(args.path, specifier);
|
|
28
85
|
try {
|
|
29
86
|
return `${prefix}${quote}${fileURLToPath(import.meta.resolve(specifier))}${quote}`;
|
|
30
87
|
} catch {
|
package/src/AGENTS.md
CHANGED
|
@@ -241,15 +241,15 @@ Complete node reference:
|
|
|
241
241
|
Tokens are `[tag, val]` arrays with extra properties:
|
|
242
242
|
|
|
243
243
|
- `.pre` — whitespace count before token
|
|
244
|
-
- `.data` — metadata like `{ await,
|
|
244
|
+
- `.data` — metadata like `{ await, optional, quote, invert, parsedValue }`
|
|
245
245
|
- `.loc` — `{ r, c, n }`
|
|
246
246
|
- `.spaced` — sugar for `.pre > 0`
|
|
247
247
|
- `.newLine` — whether preceded by newline
|
|
248
248
|
|
|
249
249
|
Identifier suffixes:
|
|
250
250
|
|
|
251
|
-
- `!` sets `.data.
|
|
252
|
-
- `?` sets `.data.
|
|
251
|
+
- `!` sets `.data.bang = true` — a neutral "trailing `!`" flag resolved by context downstream: dammit/`await` at a call site (`fetch!` → `await fetch()`), or the void marker at a function definition (`foo! = ->`, `def foo!` → no implicit return). Void-ness is stamped onto the function node as `isVoid` by `applyVoidMarker` and read locally by the arrow emitters; `def` reads `meta(name, 'bang')` directly.
|
|
252
|
+
- `?` sets `.data.optional = true` (existence check on values; optional marker on prop/type-field names)
|
|
253
253
|
- `as!` in loops emits `FORASAWAIT` for `for await`
|
|
254
254
|
|
|
255
255
|
Tagged template bridge:
|
|
@@ -415,7 +415,7 @@ Block factories need locals and `ctx.member` references instead of `this._elN` a
|
|
|
415
415
|
- `_factoryVars` — variables that need local `let` declarations
|
|
416
416
|
- `_fragChildren` — fragment-to-children tracking for removals
|
|
417
417
|
- `_pushEffect(body)` — emits `__effect(...)` or `disposers.push(__effect(...))`
|
|
418
|
-
- `_loopVarStack` — threads loop variables through nested factories
|
|
418
|
+
- `_loopVarStack` — threads loop variables through nested factories. Each frame is `{ itemVar, indexVar, reactiveSource }`; `reactiveSource` is computed once when the loop is emitted (via `hasReactiveDeps(collection)`) and tells `hasReactiveDeps` to treat direct member access rooted at `itemVar`/`indexVar` (`item.foo`, `item[0]`, `item.a.b`) as reactive. Alias and destructuring forms are not tracked.
|
|
419
419
|
|
|
420
420
|
Factory mode is entered in `emitConditionBranch` and `emitTemplateLoop` via save/restore of `[_createLines, _setupLines, _factoryMode, _factoryVars]`.
|
|
421
421
|
|
|
@@ -653,7 +653,7 @@ enum Status
|
|
|
653
653
|
Type emission is split across two files by execution context:
|
|
654
654
|
|
|
655
655
|
- `types.js` (browser-side, ~21 KB) — `installTypeSupport(Lexer)` adds `rewriteTypes()` to strip type annotations from the token stream so user-typed Rip parses. This is the only thing the browser needs from type machinery.
|
|
656
|
-
- `dts.js` (CLI/LSP only, ~38 KB) — `emitTypes(tokens, sexpr, source)` generates `.d.ts`, plus `
|
|
656
|
+
- `dts.js` (CLI/LSP only, ~38 KB) — `emitTypes(tokens, sexpr, source)` generates `.d.ts`, plus `tsType`, `emitComponentTypes`, and the intrinsic declaration tables (`INTRINSIC_TYPE_DECLS`, `SIGNAL_*`, `COMPUTED_*`, `EFFECT_*`, etc.). Registers itself with the compiler at module load via `setTypesEmitter()`.
|
|
657
657
|
|
|
658
658
|
`emitEnum` (runtime JS for `enum` blocks) lives in `compiler.js` next to the rest of the codegen dispatch — it's not type machinery, it's real runtime emission.
|
|
659
659
|
|
package/src/browser.js
CHANGED
|
@@ -48,6 +48,40 @@ const dedent = s => {
|
|
|
48
48
|
const sanitizeSourceURL = (url) =>
|
|
49
49
|
String(url).replace(/[\r\n]/g, '').replace(/\s+$/g, '');
|
|
50
50
|
|
|
51
|
+
// Rewrite `import { … } from '@rip-lang/<pkg>'` into a `globalThis`
|
|
52
|
+
// destructure. The browser bundle copies every function export from
|
|
53
|
+
// `@rip-lang/app` (and friends) onto `globalThis` at startup (see
|
|
54
|
+
// `_entry.js` in scripts/build.js), so consumers can import them by name
|
|
55
|
+
// in source while the runtime form is a plain destructure. Named imports
|
|
56
|
+
// only — default / namespace forms warn and pass through.
|
|
57
|
+
function rewriteRipPkgImports(js) {
|
|
58
|
+
const re = /^(\s*)import\s+([^'"]+?)\s+from\s+['"](@rip-lang\/[^'"]+)['"];?\s*$/gm;
|
|
59
|
+
return js.replace(re, (full, indent, clause, spec) => {
|
|
60
|
+
const trimmed = clause.trim();
|
|
61
|
+
if (trimmed.startsWith('type ')) return `${indent}// type-only import erased: ${spec}`;
|
|
62
|
+
const open = trimmed.indexOf('{');
|
|
63
|
+
const close = trimmed.lastIndexOf('}');
|
|
64
|
+
if (open < 0 || close <= open) {
|
|
65
|
+
console.warn(`[Rip] Skipping non-named import from ${spec}; only \`import { … } from '@rip-lang/*'\` is supported in browser bundles.`);
|
|
66
|
+
return full;
|
|
67
|
+
}
|
|
68
|
+
const inside = trimmed.slice(open + 1, close);
|
|
69
|
+
const parts = [];
|
|
70
|
+
for (let raw of inside.split(',')) {
|
|
71
|
+
let name = raw.trim().replace(/^type\s+/, '');
|
|
72
|
+
if (!name) continue;
|
|
73
|
+
if (/\s+as\s+/.test(name)) {
|
|
74
|
+
const [orig, alias] = name.split(/\s+as\s+/).map(s => s.trim());
|
|
75
|
+
parts.push(`${orig}: ${alias}`);
|
|
76
|
+
} else {
|
|
77
|
+
parts.push(name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (parts.length === 0) return `${indent}// import erased: ${spec}`;
|
|
81
|
+
return `${indent}const { ${parts.join(', ')} } = globalThis;`;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
51
85
|
// Insert `//# sourceURL=<name>` BEFORE the existing `//# sourceMappingURL=...`
|
|
52
86
|
// comment (or append at end if none). NEVER prepend — that would shift every
|
|
53
87
|
// generated-line mapping by 1 line, breaking line-only source maps.
|
|
@@ -127,15 +161,17 @@ async function processRipScripts() {
|
|
|
127
161
|
let lastBundle;
|
|
128
162
|
|
|
129
163
|
// Step 1: Collect data-src URLs from the runtime script tag
|
|
130
|
-
// When data-src is omitted, default to '/app' (auto-scanned bundle from
|
|
164
|
+
// When data-src is omitted, default to '/app' (auto-scanned bundle from
|
|
165
|
+
// serve middleware). The default is silent on failure — only explicit
|
|
166
|
+
// data-src URLs warn — so static / file:// pages aren't noisy.
|
|
131
167
|
const runtimeTag = document.querySelector('script[src$="rip.min.js"], script[src$="rip.js"]');
|
|
132
168
|
const dataSrc = runtimeTag?.getAttribute('data-src');
|
|
133
169
|
if (dataSrc !== null && dataSrc !== undefined) {
|
|
134
170
|
for (const url of dataSrc.trim().split(/\s+/)) {
|
|
135
171
|
if (url) sources.push({ url });
|
|
136
172
|
}
|
|
137
|
-
} else if (runtimeTag) {
|
|
138
|
-
sources.push({ url: '/app' });
|
|
173
|
+
} else if (runtimeTag && /^https?:$/.test(location.protocol)) {
|
|
174
|
+
sources.push({ url: '/app', optional: true });
|
|
139
175
|
}
|
|
140
176
|
|
|
141
177
|
// Step 2: Collect all <script type="text/rip"> tags (inline and external)
|
|
@@ -161,8 +197,11 @@ async function processRipScripts() {
|
|
|
161
197
|
s.bundle = bundle;
|
|
162
198
|
}
|
|
163
199
|
}));
|
|
164
|
-
for (
|
|
165
|
-
|
|
200
|
+
for (let i = 0; i < results.length; i++) {
|
|
201
|
+
const r = results[i];
|
|
202
|
+
if (r.status === 'rejected' && !sources[i].optional) {
|
|
203
|
+
console.warn('Rip: fetch failed:', r.reason.message);
|
|
204
|
+
}
|
|
166
205
|
}
|
|
167
206
|
|
|
168
207
|
// Separate bundles from individual sources
|
|
@@ -209,7 +248,7 @@ async function processRipScripts() {
|
|
|
209
248
|
? { ...baseOpts, sourceMap: 'inline', filename: ripName }
|
|
210
249
|
: baseOpts;
|
|
211
250
|
let js;
|
|
212
|
-
try { js = compileToJS(s.code, opts); }
|
|
251
|
+
try { js = rewriteRipPkgImports(compileToJS(s.code, opts)); }
|
|
213
252
|
catch (e) { console.error(_formatError(e, { source: s.code, file: ripName, color: false })); continue; }
|
|
214
253
|
try { await (0, eval)(debug ? wrapForEval(js, ripName) : `(async()=>{\n${js}\n})()`); }
|
|
215
254
|
catch (e) { console.error(`Rip runtime error in ${ripName}:`, e); }
|
|
@@ -227,7 +266,8 @@ async function processRipScripts() {
|
|
|
227
266
|
// No routing — expand bundles into individual sources, compile everything
|
|
228
267
|
const expanded = [];
|
|
229
268
|
for (const b of bundles) {
|
|
230
|
-
|
|
269
|
+
const mods = b.modules || b.components || {};
|
|
270
|
+
for (const [name, code] of Object.entries(mods)) {
|
|
231
271
|
expanded.push({ code, url: name });
|
|
232
272
|
}
|
|
233
273
|
if (b.data) {
|
|
@@ -247,7 +287,8 @@ async function processRipScripts() {
|
|
|
247
287
|
if (bundles.length > 0 && typeof globalThis.createComponents === 'function') {
|
|
248
288
|
const sourceStore = globalThis.createComponents();
|
|
249
289
|
for (const b of bundles) {
|
|
250
|
-
|
|
290
|
+
const mods = b.modules || b.components;
|
|
291
|
+
if (mods) sourceStore.load(mods);
|
|
251
292
|
}
|
|
252
293
|
if (typeof window !== 'undefined') {
|
|
253
294
|
if (!window.__RIP__) window.__RIP__ = {};
|
|
@@ -280,7 +321,7 @@ async function processRipScripts() {
|
|
|
280
321
|
? { ...baseOpts, sourceMap: 'inline', filename: ripName }
|
|
281
322
|
: baseOpts;
|
|
282
323
|
try {
|
|
283
|
-
const js = compileToJS(s.code, opts);
|
|
324
|
+
const js = rewriteRipPkgImports(compileToJS(s.code, opts));
|
|
284
325
|
compiled.push({ js, url: ripName });
|
|
285
326
|
} catch (e) {
|
|
286
327
|
console.error(_formatError(e, { source: s.code, file: ripName, color: false }));
|
|
@@ -289,7 +330,7 @@ async function processRipScripts() {
|
|
|
289
330
|
|
|
290
331
|
// Create app stash
|
|
291
332
|
if (!globalThis.__ripApp && runtimeTag) {
|
|
292
|
-
const stashFn = globalThis.
|
|
333
|
+
const stashFn = globalThis.createStash;
|
|
293
334
|
if (stashFn) {
|
|
294
335
|
let initial = {};
|
|
295
336
|
const stateAttr = runtimeTag.getAttribute('data-state');
|
|
@@ -414,7 +455,7 @@ export async function importRip(url) {
|
|
|
414
455
|
if (!r.ok) throw new Error(`importRip: ${url} (${r.status})`);
|
|
415
456
|
return r.text();
|
|
416
457
|
});
|
|
417
|
-
const js = compileToJS(source);
|
|
458
|
+
const js = rewriteRipPkgImports(compileToJS(source));
|
|
418
459
|
const header = `// ${url}\n`;
|
|
419
460
|
const blob = new Blob([header + js], { type: 'application/javascript' });
|
|
420
461
|
const blobUrl = URL.createObjectURL(blob);
|