rooklang 0.3.4 → 0.4.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.
Files changed (135) hide show
  1. package/README.md +33 -2
  2. package/README.ru.md +33 -2
  3. package/dist/ast/declarations.d.ts +19 -3
  4. package/dist/ast/expressions.d.ts +4 -8
  5. package/dist/ast/statements.d.ts +3 -1
  6. package/dist/bun-register.d.ts +1 -0
  7. package/dist/bun-register.js +9 -0
  8. package/dist/bun.d.ts +34 -0
  9. package/dist/bun.js +99 -0
  10. package/dist/checker/assignments.d.ts +2 -0
  11. package/dist/checker/assignments.js +9 -3
  12. package/dist/checker/base.d.ts +8 -2
  13. package/dist/checker/base.js +3 -3
  14. package/dist/checker/builtins.js +7 -3
  15. package/dist/checker/channels.js +1 -2
  16. package/dist/checker/classes.js +7 -1
  17. package/dist/checker/closing.js +1 -1
  18. package/dist/checker/completion.d.ts +1 -1
  19. package/dist/checker/completion.js +2 -1
  20. package/dist/checker/component-bindings.d.ts +7 -0
  21. package/dist/checker/component-bindings.js +45 -0
  22. package/dist/checker/component-values.js +10 -7
  23. package/dist/checker/components.d.ts +2 -2
  24. package/dist/checker/components.js +8 -3
  25. package/dist/checker/context.d.ts +28 -5
  26. package/dist/checker/context.js +23 -0
  27. package/dist/checker/control-flow.d.ts +6 -5
  28. package/dist/checker/control-flow.js +48 -40
  29. package/dist/checker/declarations.d.ts +2 -2
  30. package/dist/checker/declarations.js +3 -2
  31. package/dist/checker/decorator-exports.js +4 -0
  32. package/dist/checker/decorator-includes.d.ts +9 -0
  33. package/dist/checker/decorator-includes.js +71 -0
  34. package/dist/checker/decorator-members.d.ts +8 -10
  35. package/dist/checker/decorator-members.js +28 -31
  36. package/dist/checker/decorator-wrappers.d.ts +1 -1
  37. package/dist/checker/decorator-wrappers.js +3 -2
  38. package/dist/checker/decorators.d.ts +14 -5
  39. package/dist/checker/decorators.js +52 -25
  40. package/dist/checker/expressions.d.ts +1 -1
  41. package/dist/checker/expressions.js +7 -6
  42. package/dist/checker/flow.d.ts +25 -3
  43. package/dist/checker/flow.js +48 -8
  44. package/dist/checker/functions.d.ts +2 -2
  45. package/dist/checker/functions.js +9 -5
  46. package/dist/checker/helpers.d.ts +4 -1
  47. package/dist/checker/helpers.js +7 -2
  48. package/dist/checker/imports.js +5 -2
  49. package/dist/checker/loops.d.ts +2 -0
  50. package/dist/checker/loops.js +27 -2
  51. package/dist/checker/markup.js +14 -2
  52. package/dist/checker/members.js +3 -1
  53. package/dist/checker/shared-state.d.ts +11 -0
  54. package/dist/checker/shared-state.js +37 -0
  55. package/dist/checker/statements.d.ts +5 -2
  56. package/dist/checker/statements.js +51 -22
  57. package/dist/checker/tagged-unions.d.ts +37 -0
  58. package/dist/checker/tagged-unions.js +132 -0
  59. package/dist/checker/untyped-globals.d.ts +1 -0
  60. package/dist/checker/untyped-globals.js +3 -1
  61. package/dist/codegen/attributes.d.ts +4 -0
  62. package/dist/codegen/attributes.js +24 -2
  63. package/dist/codegen/component-helpers.d.ts +6 -1
  64. package/dist/codegen/component-helpers.js +6 -1
  65. package/dist/codegen/component-props.d.ts +11 -2
  66. package/dist/codegen/component-props.js +50 -5
  67. package/dist/codegen/components.d.ts +6 -5
  68. package/dist/codegen/components.js +44 -21
  69. package/dist/codegen/control-flow.d.ts +2 -0
  70. package/dist/codegen/control-flow.js +23 -9
  71. package/dist/codegen/creation.js +5 -2
  72. package/dist/codegen/early-returns.d.ts +4 -3
  73. package/dist/codegen/early-returns.js +17 -5
  74. package/dist/codegen/emitter.d.ts +3 -0
  75. package/dist/codegen/emitter.js +18 -4
  76. package/dist/codegen/expressions.js +0 -2
  77. package/dist/codegen/functions.d.ts +11 -1
  78. package/dist/codegen/functions.js +31 -3
  79. package/dist/codegen/live.d.ts +60 -0
  80. package/dist/codegen/live.js +29 -0
  81. package/dist/codegen/mutations.d.ts +7 -0
  82. package/dist/codegen/mutations.js +43 -2
  83. package/dist/codegen/options.d.ts +2 -0
  84. package/dist/codegen/reactive.d.ts +4 -59
  85. package/dist/codegen/reactive.js +34 -72
  86. package/dist/codegen/runtime.js +24 -7
  87. package/dist/codegen/shared.d.ts +22 -0
  88. package/dist/codegen/shared.js +93 -0
  89. package/dist/codegen/simple-statements.d.ts +1 -1
  90. package/dist/codegen/statements.d.ts +5 -0
  91. package/dist/codegen/statements.js +35 -0
  92. package/dist/codegen/updates.d.ts +11 -3
  93. package/dist/codegen/updates.js +52 -4
  94. package/dist/codegen/web-components.d.ts +2 -2
  95. package/dist/codegen/web-components.js +2 -2
  96. package/dist/compile-file.d.ts +4 -1
  97. package/dist/compile.d.ts +1 -1
  98. package/dist/compile.js +6 -2
  99. package/dist/decorator-copy.d.ts +1 -1
  100. package/dist/decorator-copy.js +1 -0
  101. package/dist/decorator-names.d.ts +14 -0
  102. package/dist/decorator-names.js +56 -0
  103. package/dist/decorators.d.ts +2 -3
  104. package/dist/decorators.js +36 -14
  105. package/dist/index.d.ts +1 -1
  106. package/dist/inline-packages.d.ts +2 -1
  107. package/dist/inline-packages.js +9 -4
  108. package/dist/loader.js +6 -19
  109. package/dist/names.d.ts +4 -0
  110. package/dist/names.js +29 -0
  111. package/dist/parser/base.d.ts +1 -0
  112. package/dist/parser/base.js +1 -1
  113. package/dist/parser/control-flow.d.ts +5 -0
  114. package/dist/parser/control-flow.js +28 -2
  115. package/dist/parser/declarations.d.ts +5 -15
  116. package/dist/parser/declarations.js +15 -119
  117. package/dist/parser/decorators.d.ts +6 -2
  118. package/dist/parser/decorators.js +43 -19
  119. package/dist/parser/params.d.ts +21 -0
  120. package/dist/parser/params.js +143 -0
  121. package/dist/parser/simple-statements.d.ts +1 -0
  122. package/dist/parser/simple-statements.js +22 -6
  123. package/dist/parser/statements.d.ts +5 -2
  124. package/dist/parser/statements.js +18 -11
  125. package/dist/run-cache.d.ts +7 -0
  126. package/dist/run-cache.js +24 -0
  127. package/dist/run-file.js +6 -1
  128. package/dist/runtime/sources.js +4 -3
  129. package/dist/walk.js +3 -3
  130. package/package.json +10 -2
  131. package/src/runtime/decode.js +41 -10
  132. package/src/runtime/go.js +10 -4
  133. package/src/runtime/index.js +1 -0
  134. package/src/runtime/markup.js +27 -17
  135. package/src/runtime/shared.js +17 -0
@@ -1,6 +1,8 @@
1
1
  import { widenLiterals } from "../checker/types.js";
2
+ import { updatesOf } from "./reactive.js";
2
3
  import { StatementEmitter } from "./statements.js";
3
4
  import { startsWithObjectLiteral } from "./analysis.js";
5
+ import { sharedWrites } from "./shared.js";
4
6
  import { ARROW } from "./syntax.js";
5
7
  /**
6
8
  * Updates after changes of component state. Each state variable gets an update function; every
@@ -19,8 +21,37 @@ export class UpdateEmitter extends StatementEmitter {
19
21
  * the markup, which would update it again; code that runs later (handlers) is not included.
20
22
  */
21
23
  rendering = 0;
22
- /** After a statement that changes state, the update of what depends on it. */
24
+ /**
25
+ * After a statement that changes state of the module, the updates of the components that read
26
+ * it; code that renders markup changes none, as with the state of a component.
27
+ */
23
28
  statement(node) {
29
+ const shared = this.rendering > 0 ? [] : sharedWrites(node, this.shared, (each) => this.typeOf(each));
30
+ if (shared.length === 0) {
31
+ this.statementWithUpdates(node);
32
+ return;
33
+ }
34
+ const notify = () => {
35
+ for (const name of shared)
36
+ this.line(`$$changed(${this.shared.listeners.get(name)});`);
37
+ };
38
+ if (node.kind !== 'ReturnStatement' && node.kind !== 'ThrowStatement') {
39
+ this.statementWithUpdates(node);
40
+ notify();
41
+ return;
42
+ }
43
+ // The value is computed first, then the updates run, then the function leaves.
44
+ const values = node.kind === 'ThrowStatement' ? [node.argument] : node.values;
45
+ const temp = this.temp();
46
+ this.withHoisting(true, () => {
47
+ const rendered = values.map((value) => this.expression(value, ARROW));
48
+ this.line(`const ${temp} = ${rendered.length === 1 ? rendered[0] : `[${rendered.join(', ')}]`};`);
49
+ });
50
+ notify();
51
+ this.line(`${node.kind === 'ThrowStatement' ? 'throw' : 'return'} ${temp};`);
52
+ }
53
+ /** After a statement that changes state, the update of what depends on it. */
54
+ statementWithUpdates(node) {
24
55
  const { reactive } = this;
25
56
  if (reactive && this.fn === reactive.setup && reactive.declaresRenderFunction(node)) {
26
57
  this.withRendering(this.rendering + 1, () => super.statement(node));
@@ -33,6 +64,9 @@ export class UpdateEmitter extends StatementEmitter {
33
64
  for (const source of changed) {
34
65
  for (const computation of source.computations)
35
66
  this.line(computation);
67
+ // The parent of a bound property gets the value the body gave it.
68
+ if (source.bound)
69
+ this.line(source.bound);
36
70
  }
37
71
  return;
38
72
  }
@@ -137,7 +171,14 @@ export function resolveMarkers(block, reactive) {
137
171
  const sources = new Map();
138
172
  for (const source of reactive.sources.values())
139
173
  sources.set(source.id, source);
140
- return block.replace(/^([ \t]*)\uE000(\d+)(?::(\d+))?\uE001;\n/gm, (line, indent, id, skip) => {
174
+ // `$$watch(...)` of state of the module stays if something depends on it.
175
+ const listened = block.replace(/^([ \t]*)\uE004(\d+)\uE004(.*)\n/gm, (line, indent, id, text) => {
176
+ const source = sources.get(Number(id));
177
+ if (!source)
178
+ return line;
179
+ return updatesOf(source).length > 0 ? `${indent}${text}\n` : '';
180
+ });
181
+ return listened.replace(/^([ \t]*)\uE000(\d+)(?::(\d+))?\uE001;\n/gm, (line, indent, id, skip) => {
141
182
  const source = sources.get(Number(id));
142
183
  if (!source)
143
184
  return line;
@@ -148,9 +189,16 @@ export function resolveMarkers(block, reactive) {
148
189
  return `${indent}${text}\n`;
149
190
  });
150
191
  }
151
- /** The update statements that a place changing the source needs, the computations first. */
192
+ /**
193
+ * The update statements that a place changing the source needs: the computations, the call that
194
+ * gives a bound property to the parent, then the updates of the nodes.
195
+ */
152
196
  export function needed(source, write) {
153
- return [...source.computations, ...source.dependents.filter((_, i) => i !== write.skip)];
197
+ return [
198
+ ...source.computations,
199
+ ...(source.bound ? [source.bound] : []),
200
+ ...source.dependents.filter((_, i) => i !== write.skip),
201
+ ];
154
202
  }
155
203
  /** One place changes the source, after the markup exists, and needs one update statement. */
156
204
  export function inlinesUpdate(source) {
@@ -6,8 +6,8 @@ import { ComponentEmitter } from './components.ts';
6
6
  * have their values.
7
7
  *
8
8
  * The element renders the component into its shadow root when it is connected. When it is
9
- * disconnected and not put back in the same task, the component is removed: the functions
10
- * returned by `mount()` run. Moving the element keeps its markup.
9
+ * disconnected and not put back in the same task, the component is removed: what its
10
+ * `mount` deferred runs. Moving the element keeps its markup.
11
11
  *
12
12
  * Every property except `children` is a JS property of the element; its setter updates the markup.
13
13
  * Strings, numbers and bools also have attributes: `item-count="3"` sets `itemCount` to 3, and a
@@ -6,8 +6,8 @@ import { ComponentEmitter } from "./components.js";
6
6
  * have their values.
7
7
  *
8
8
  * The element renders the component into its shadow root when it is connected. When it is
9
- * disconnected and not put back in the same task, the component is removed: the functions
10
- * returned by `mount()` run. Moving the element keeps its markup.
9
+ * disconnected and not put back in the same task, the component is removed: what its
10
+ * `mount` deferred runs. Moving the element keeps its markup.
11
11
  *
12
12
  * Every property except `children` is a JS property of the element; its setter updates the markup.
13
13
  * Strings, numbers and bools also have attributes: `item-count="3"` sets `itemCount` to 3, and a
@@ -6,6 +6,9 @@ export declare class CompileError extends Error {
6
6
  export declare function compileFile(path: string, options: {
7
7
  rewriteImports: boolean;
8
8
  typeCheck: boolean;
9
- markup?: boolean;
9
+ markup?: boolean | undefined;
10
10
  quiet?: boolean;
11
+ /** Where the worker module of `go` is, and what its worker imports first (bun.ts). */
12
+ workerUrl?: string;
13
+ nodeLoader?: string;
11
14
  }): CompileResult;
package/dist/compile.d.ts CHANGED
@@ -23,7 +23,7 @@ export interface CoreCompileOptions {
23
23
  readStyleModule?: ((specifier: string) => string | null) | undefined;
24
24
  /** Where the worker module of `go` is, relative to the module; see workerUrl(). */
25
25
  workerUrl?: string | undefined;
26
- /** A module that a worker thread of Node imports first, to load `.rk` files (vite.ts). */
26
+ /** A module that a worker of Node or Bun imports first, to load `.rk` files (vite.ts, bun.ts). */
27
27
  nodeLoader?: string | undefined;
28
28
  /**
29
29
  * How the code gets the runtime of Rook: `import`, the default, imports it from
package/dist/compile.js CHANGED
@@ -102,11 +102,12 @@ export function prepareCheck(program, options) {
102
102
  }
103
103
  /**
104
104
  * What the module imports from other `.rk` modules for the generator: decorators, whose code is
105
- * inlined, and components, which are called or inlined.
105
+ * inlined, components, which are called or inlined, and state, whose changes it listens to.
106
106
  */
107
107
  function importedCode(program, importModule) {
108
108
  const found = new Map();
109
109
  const components = new Map();
110
+ const shared = new Map();
110
111
  for (const statement of program.body) {
111
112
  if (statement.kind !== 'ImportDeclaration' || !statement.source.value.endsWith('.rk')) {
112
113
  continue;
@@ -116,6 +117,9 @@ function importedCode(program, importModule) {
116
117
  const decorators = exports?.decorators;
117
118
  for (const specifier of statement.namedImports) {
118
119
  const component = exports?.components?.get(specifier.imported.name);
120
+ if (exports?.shared?.has(specifier.imported.name)) {
121
+ shared.set(specifier.local.name, specifier.imported.name);
122
+ }
119
123
  if (component) {
120
124
  components.set(specifier.local.name, {
121
125
  node: component.node,
@@ -135,7 +139,7 @@ function importedCode(program, importModule) {
135
139
  });
136
140
  }
137
141
  }
138
- return { decorators: found, components };
142
+ return { decorators: found, components, shared };
139
143
  }
140
144
  /** The CSS modules that the program imports, read and parsed, or what is wrong with them. */
141
145
  function readStyleModules(program, read) {
@@ -5,7 +5,7 @@ export type CopyListener = (copy: ast.Node, original: ast.Node) => void;
5
5
  export declare function originOf(node: ast.Node): ast.Node;
6
6
  /**
7
7
  * Renames of the names that are not values: tags of components, `<Icon />`, and decorators,
8
- * `get(@visible.shown)`. Code of another module refers to those of its own module.
8
+ * `@visible.shown`. Code of another module refers to those of its own module.
9
9
  */
10
10
  export interface OtherNames {
11
11
  tags?: ReadonlyMap<string, string>;
@@ -143,6 +143,7 @@ export function renamer(renames, copied, other = {}) {
143
143
  return done({
144
144
  ...node,
145
145
  iterable: copy(node.iterable, hidden),
146
+ ...(node.withKey ? { withKey: copy(node.withKey, scoped(names, hidden)) } : {}),
146
147
  body: copy(node.body, scoped(names, hidden)),
147
148
  }, node);
148
149
  }
@@ -0,0 +1,14 @@
1
+ import type * as ast from './ast.ts';
2
+ import type { DecoratorSource } from './decorators.ts';
3
+ /** The JS name of a member or a parameter of a decorator, in the components it is applied to. */
4
+ export declare function decoratedName(decorator: string, member: string): string;
5
+ /**
6
+ * The public members that a decorator gives the components it is applied to: its own and those of
7
+ * the decorators it includes, each with whether it changes (state and derived values do).
8
+ */
9
+ export declare function publicMembers(decorator: ast.DecoratorDeclaration, decorators: ReadonlyMap<string, DecoratorSource>, seen?: ReadonlySet<ast.DecoratorDeclaration>): Map<string, boolean>;
10
+ /**
11
+ * `@shop.count` of a member that `shop` gets from the `cart` it includes: a variable of `shop`
12
+ * that is that of `cart`, `derived` when it changes, so that what reads it is updated.
13
+ */
14
+ export declare function aliases(included: DecoratorSource, use: ast.DecoratorUse, prefix: string, decorators: ReadonlyMap<string, DecoratorSource>, prefixes: ReadonlyMap<string, string> | undefined): ast.VariableDeclaration[];
@@ -0,0 +1,56 @@
1
+ // The names of the members of decorators in the components they are applied to, and the members
2
+ // that a decorator gives with those of the decorators it includes.
3
+ /** The JS name of a member or a parameter of a decorator, in the components it is applied to. */
4
+ export function decoratedName(decorator, member) {
5
+ return `$$${decorator}$${member}`;
6
+ }
7
+ /**
8
+ * The public members that a decorator gives the components it is applied to: its own and those of
9
+ * the decorators it includes, each with whether it changes (state and derived values do).
10
+ */
11
+ export function publicMembers(decorator, decorators, seen = new Set()) {
12
+ const members = new Map();
13
+ const inner = new Set([...seen, decorator]);
14
+ for (const use of decorator.includes) {
15
+ const included = decorators.get(use.name.name);
16
+ if (!included || inner.has(included.node))
17
+ continue;
18
+ for (const [name, changes] of publicMembers(included.node, decorators, inner)) {
19
+ members.set(name, changes);
20
+ }
21
+ }
22
+ for (const statement of decorator.body.body) {
23
+ if (statement.kind === 'FuncDeclaration' && statement.isPublic) {
24
+ members.set(statement.name.name, false);
25
+ }
26
+ else if (statement.kind === 'VariableDeclaration' && statement.isPublic) {
27
+ const changes = statement.keyword === 'state' || statement.keyword === 'derived';
28
+ for (const name of statement.names)
29
+ members.set(name.name, changes);
30
+ }
31
+ }
32
+ return members;
33
+ }
34
+ /**
35
+ * `@shop.count` of a member that `shop` gets from the `cart` it includes: a variable of `shop`
36
+ * that is that of `cart`, `derived` when it changes, so that what reads it is updated.
37
+ */
38
+ export function aliases(included, use, prefix, decorators, prefixes) {
39
+ const from = prefixes?.get(included.node.name.name) ?? included.node.name.name;
40
+ return [...publicMembers(included.node, decorators)].map(([member, changes]) => ({
41
+ kind: 'VariableDeclaration',
42
+ exported: false,
43
+ isPublic: false,
44
+ keyword: changes ? 'derived' : 'const',
45
+ names: [
46
+ { kind: 'Identifier', name: decoratedName(prefix, member), start: use.start, end: use.end },
47
+ ],
48
+ type: null,
49
+ values: [
50
+ { kind: 'Identifier', name: decoratedName(from, member), start: use.start, end: use.end },
51
+ ],
52
+ body: null,
53
+ start: use.start,
54
+ end: use.end,
55
+ }));
56
+ }
@@ -1,7 +1,6 @@
1
1
  import type * as ast from './ast.ts';
2
2
  import { type CopyListener } from './decorator-copy.ts';
3
- /** The JS name of a member or a parameter of a decorator, in the components it is applied to. */
4
- export declare function decoratedName(decorator: string, member: string): string;
3
+ export { decoratedName, publicMembers } from './decorator-names.ts';
5
4
  /**
6
5
  * Whether a declaration of an applied component gets an argument of a decorator. When the
7
6
  * argument reads the component's properties, the declaration is a `state` that the properties
@@ -45,7 +44,7 @@ export declare function moduleNamesOf(decorator: ast.DecoratorDeclaration, progr
45
44
  * The component with its decorators applied. Its body first declares the arguments of each
46
45
  * decorator and runs its body, from the top decorator down, then runs the component's own body.
47
46
  * The names of a decorator get its prefix, `$$visible$shown`, so they clash neither with each other
48
- * nor with the component's names, and `get(@visible.shown)` reads that variable. The `mount()` of
47
+ * nor with the component's names, and `@visible.shown` reads that variable. The `mount` of
49
48
  * the decorators runs after the component's own, from the inside out, so it goes before every
50
49
  * `return` of the component's body.
51
50
  *
@@ -1,14 +1,12 @@
1
1
  import { renamer } from "./decorator-copy.js";
2
+ import { aliases, decoratedName } from "./decorator-names.js";
2
3
  import { declaredIn, freeNames, mentionsName, topLevelNames } from "./names.js";
3
4
  import { containsReturn } from "./recursion.js";
4
5
  import { forEachChild } from "./walk.js";
5
6
  // Applying decorators: a component with decorators becomes one component whose body runs the
6
7
  // bodies of its decorators first. The checker uses it to see what the inlined code uses, the
7
8
  // generator to write it.
8
- /** The JS name of a member or a parameter of a decorator, in the components it is applied to. */
9
- export function decoratedName(decorator, member) {
10
- return `$$${decorator}$${member}`;
11
- }
9
+ export { decoratedName, publicMembers } from "./decorator-names.js";
12
10
  const argumentDeclarations = new WeakSet();
13
11
  const wrappedContents = new WeakSet();
14
12
  /**
@@ -69,7 +67,7 @@ export function moduleNamesOf(decorator, program) {
69
67
  * The component with its decorators applied. Its body first declares the arguments of each
70
68
  * decorator and runs its body, from the top decorator down, then runs the component's own body.
71
69
  * The names of a decorator get its prefix, `$$visible$shown`, so they clash neither with each other
72
- * nor with the component's names, and `get(@visible.shown)` reads that variable. The `mount()` of
70
+ * nor with the component's names, and `@visible.shown` reads that variable. The `mount` of
73
71
  * the decorators runs after the component's own, from the inside out, so it goes before every
74
72
  * `return` of the component's body.
75
73
  *
@@ -83,37 +81,55 @@ export function moduleNamesOf(decorator, program) {
83
81
  export function decorate(component, decorators, copied, prefixes) {
84
82
  if (component.decorators.length === 0)
85
83
  return component;
86
- const props = new Set(component.params.map((param) => param.name.name));
84
+ // Arguments that read the properties, or such arguments of a decorator that includes another,
85
+ // are state: they change with them.
86
+ const reactive = new Set(component.params.map((param) => param.name.name));
87
87
  const setup = [];
88
88
  const mounts = [];
89
89
  const wrappers = [];
90
- for (const use of component.decorators) {
91
- const source = decorators.get(use.name.name);
92
- if (!source)
93
- continue;
90
+ // `argument` copies an argument into the component: one of the component stays as it is, one of
91
+ // a decorator it includes is that decorator's code.
92
+ const apply = (source, args, argument, applying) => {
94
93
  const decorator = source.node;
94
+ // A decorator that includes itself, through others too, is reported by the checker.
95
+ const inner = new Set([...applying, decorator]);
95
96
  const prefix = prefixes?.get(decorator.name.name) ?? decorator.name.name;
96
97
  const names = [...decoratorNames(decorator), ...source.moduleNames];
97
98
  const renames = membersOf(prefix, names);
98
99
  const copy = renamer(renames, copied, { decorators: prefixes, tags: source.tags });
99
100
  decorator.params.forEach((param, i) => {
100
- // A default value is the decorator's code, an argument the component's.
101
- const value = use.arguments[i] ?? (param.defaultValue && copy(param.defaultValue, new Set()));
101
+ const given = args[i];
102
+ // A default value is the decorator's code.
103
+ const value = given
104
+ ? argument(given)
105
+ : param.defaultValue && copy(param.defaultValue, new Set());
106
+ const name = renames.get(param.name.name);
107
+ const changes = value !== null && value !== undefined && mentionsName(value, reactive);
102
108
  const declaration = {
103
109
  kind: 'VariableDeclaration',
104
110
  exported: false,
105
111
  isPublic: false,
106
- keyword: value && mentionsName(value, props) ? 'state' : 'const',
107
- names: [{ ...param.name, name: renames.get(param.name.name) }],
112
+ keyword: changes ? 'state' : 'const',
113
+ names: [{ ...param.name, name }],
108
114
  type: param.type,
109
115
  values: value ? [value] : [],
116
+ body: null,
110
117
  start: param.start,
111
118
  end: param.end,
112
119
  };
120
+ if (changes)
121
+ reactive.add(name);
113
122
  argumentDeclarations.add(declaration);
114
123
  copied?.(declaration, param);
115
124
  setup.push(declaration);
116
125
  });
126
+ for (const use of decorator.includes) {
127
+ const included = decorators.get(use.name.name);
128
+ if (!included || inner.has(included.node))
129
+ continue;
130
+ apply(included, use.arguments, (value) => copy(value, new Set()), inner);
131
+ setup.push(...aliases(included, use, prefix, decorators, prefixes));
132
+ }
117
133
  const wrapper = wrapperOf(decorator);
118
134
  const own = [];
119
135
  for (const statement of decorator.body.body) {
@@ -128,6 +144,11 @@ export function decorate(component, decorators, copied, prefixes) {
128
144
  mounts.push(own);
129
145
  if (wrapper)
130
146
  wrappers.push(applyWrapper(source, wrapper, copied, prefix, prefixes));
147
+ };
148
+ for (const use of component.decorators) {
149
+ const source = decorators.get(use.name.name);
150
+ if (source)
151
+ apply(source, use.arguments, (value) => value, new Set());
131
152
  }
132
153
  const inside = mounts.reverse().flat();
133
154
  const chain = wrappers.reverse();
@@ -186,6 +207,7 @@ function wrapped(node, chain) {
186
207
  names: [{ ...wrapper.content }],
187
208
  type: wrapper.type,
188
209
  values: [value],
210
+ body: null,
189
211
  start: node.start,
190
212
  end: node.end,
191
213
  };
package/dist/index.d.ts CHANGED
@@ -20,7 +20,7 @@ export interface CompileOptions {
20
20
  declarations?: boolean;
21
21
  /** Where the worker module of `go` is, relative to the module; see workerUrl() in compile.ts. */
22
22
  workerUrl?: string;
23
- /** A module that a worker thread of Node imports first, to load `.rk` files (vite.ts). */
23
+ /** A module that a worker of Node or Bun imports first, to load `.rk` files (vite.ts, bun.ts). */
24
24
  nodeLoader?: string;
25
25
  /** Markup and components; by default as the package.json of the file says (src/config.ts). */
26
26
  markup?: boolean;
@@ -27,6 +27,7 @@ export interface ModulePackages {
27
27
  * recursive or a web component cannot be inlined, and neither can one that reaches components or
28
28
  * decorators the module imports: those would bring the names of a third module. Nor can code that
29
29
  * uses a component of the module as a value, which is the function in the module, or that assigns
30
- * a variable of the module: another module could only read it.
30
+ * a variable of the module: another module could only read it. Nor can code that reads state of
31
+ * the module, `state items` at its top level: it listens to the changes there.
31
32
  */
32
33
  export declare function inlinePackages(program: ast.Program): ModulePackages;
@@ -7,7 +7,8 @@ import { forEachChild } from "./walk.js";
7
7
  * recursive or a web component cannot be inlined, and neither can one that reaches components or
8
8
  * decorators the module imports: those would bring the names of a third module. Nor can code that
9
9
  * uses a component of the module as a value, which is the function in the module, or that assigns
10
- * a variable of the module: another module could only read it.
10
+ * a variable of the module: another module could only read it. Nor can code that reads state of
11
+ * the module, `state items` at its top level: it listens to the changes there.
11
12
  */
12
13
  export function inlinePackages(program) {
13
14
  const components = new Map();
@@ -30,9 +31,13 @@ export function inlinePackages(program) {
30
31
  };
31
32
  const topLevel = topLevelNames(program);
32
33
  const usesComponentValues = (node) => componentValues(node, (name) => components.has(name)).length > 0;
33
- const assignsModuleNames = (node) => {
34
+ const states = new Set(program.body.flatMap((statement) => statement.kind === 'VariableDeclaration' && statement.keyword === 'state'
35
+ ? statement.names.map((name) => name.name)
36
+ : []));
37
+ // It assigns a variable of the module or reads its state, which only the module can do.
38
+ const needsItsModule = (node) => {
34
39
  const assigned = assignedNames(node);
35
- return [...freeNames(node)].some((name) => topLevel.has(name) && assigned.has(name));
40
+ return [...freeNames(node)].some((name) => topLevel.has(name) && (assigned.has(name) || states.has(name)));
36
41
  };
37
42
  /** The package of a component, or of the tags in the code of a decorator. */
38
43
  const build = (root, tags) => {
@@ -69,7 +74,7 @@ export function inlinePackages(program) {
69
74
  found.components.set(current, component);
70
75
  if (usesComponentValues(expanded.get(current)))
71
76
  return false;
72
- if (assignsModuleNames(expanded.get(current)))
77
+ if (needsItsModule(expanded.get(current)))
73
78
  return false;
74
79
  for (const use of component.decorators) {
75
80
  const source = sources.get(use.name.name);
package/dist/loader.js CHANGED
@@ -1,11 +1,10 @@
1
- import { createHash } from 'node:crypto';
2
- import { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
2
  import { createRequire, registerHooks } from 'node:module';
4
- import { dirname, extname, join, resolve } from 'node:path';
3
+ import { dirname, extname, resolve } from 'node:path';
5
4
  import { fileURLToPath } from 'node:url';
6
5
  import { isMainThread } from 'node:worker_threads';
7
6
  import { RUNTIME_MODULE } from "./runtime-module.js";
8
- import { sourceMapComment } from "./source-map.js";
7
+ import { cachedFile, listFiles, withMap } from "./run-cache.js";
9
8
  // `rook run` imports this module first (`node --import`): Node then imports `.rk` files,
10
9
  // compiled when they load, and `app.rk?goroutines` is the worker module of `go` in app.rk.
11
10
  // The worker threads of `go` import it too, since they get the `--import` of the process. The main
@@ -24,7 +23,7 @@ const markup = markupSetting === undefined ? undefined : markupSetting === '1';
24
23
  const cache = process.env['ROOK_RUN_CACHE'];
25
24
  /** The file of the cache for the code of a URL, query included. */
26
25
  function cached(url) {
27
- return cache ? join(cache, `${createHash('sha1').update(url).digest('hex')}.js`) : null;
26
+ return cache ? cachedFile(cache, url) : null;
28
27
  }
29
28
  // The compiled code imports the runtime of this compiler, wherever the program is.
30
29
  const runtime = import.meta.resolve(RUNTIME_MODULE);
@@ -50,7 +49,7 @@ registerHooks({
50
49
  const { compileFile } = require(`./compile-file${extension}`);
51
50
  const path = displayPath(fileURLToPath(moduleUrl));
52
51
  if (isMainThread && cache)
53
- list([resolve(path)]);
52
+ listFiles(cache, [resolve(path)]);
54
53
  const result = compileFile(path, {
55
54
  rewriteImports: false,
56
55
  typeCheck,
@@ -62,7 +61,7 @@ registerHooks({
62
61
  if (isMainThread && cache) {
63
62
  // The CSS modules it imports; the .rk modules are listed when they are loaded.
64
63
  const read = result.dependencies.map((each) => resolve(dirname(path), each));
65
- list(read.filter((each) => existsSync(each)));
64
+ listFiles(cache, read.filter((each) => existsSync(each)));
66
65
  writeFileSync(cached(moduleUrl), code);
67
66
  if (worker !== undefined)
68
67
  writeFileSync(cached(`${moduleUrl}?goroutines`), worker);
@@ -71,15 +70,3 @@ registerHooks({
71
70
  return { format: 'module', source, shortCircuit: true };
72
71
  },
73
72
  });
74
- /** The code with its source map in it. */
75
- function withMap(code, map) {
76
- if (map === undefined || code === '')
77
- return code;
78
- const data = Buffer.from(JSON.stringify(map)).toString('base64');
79
- return `${code}${sourceMapComment(`data:application/json;base64,${data}`)}`;
80
- }
81
- /** Adds files to the list of the run: a line of the path and the time it was changed. */
82
- function list(paths) {
83
- const lines = paths.map((path) => `${path}\t${statSync(path).mtimeMs}\n`);
84
- appendFileSync(join(cache, 'files'), lines.join(''));
85
- }
package/dist/names.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import type * as ast from './ast.ts';
2
2
  /** Names assigned anywhere inside `node`: `x = ...`, `x += ...`, `x++`. */
3
3
  export declare function assignedNames(node: ast.Node, names?: Set<string>): Set<string>;
4
+ /** `order.delivery.kind`: the variable and the fields of a path, or `null` for other expressions. */
5
+ export declare function pathOf(node: ast.Expression): string[] | null;
6
+ /** Paths of fields assigned anywhere inside `node`, e.g. `order.delivery` of `order.delivery = d`. */
7
+ export declare function assignedPaths(node: ast.Node, paths?: Set<string>): Set<string>;
4
8
  /** Whether an identifier with one of `names` occurs in `node`, whatever it refers to. */
5
9
  export declare function mentionsName(node: ast.Node, names: ReadonlySet<string>): boolean;
6
10
  /** Every name declared inside `node`: parameters and all local declarations, nested ones too. */
package/dist/names.js CHANGED
@@ -19,6 +19,30 @@ export function assignedNames(node, names = new Set()) {
19
19
  forEachChild(node, (child) => assignedNames(child, names));
20
20
  return names;
21
21
  }
22
+ /** `order.delivery.kind`: the variable and the fields of a path, or `null` for other expressions. */
23
+ export function pathOf(node) {
24
+ if (node.kind === 'Identifier')
25
+ return [node.name];
26
+ if (node.kind !== 'MemberExpression' || node.optional)
27
+ return null;
28
+ const object = pathOf(node.object);
29
+ return object && [...object, node.property.name];
30
+ }
31
+ /** Paths of fields assigned anywhere inside `node`, e.g. `order.delivery` of `order.delivery = d`. */
32
+ export function assignedPaths(node, paths = new Set()) {
33
+ const targets = node.kind === 'AssignmentStatement'
34
+ ? node.targets
35
+ : node.kind === 'IncDecStatement'
36
+ ? [node.target]
37
+ : [];
38
+ for (const target of targets) {
39
+ const path = pathOf(target);
40
+ if (path && path.length > 1)
41
+ paths.add(path.join('.'));
42
+ }
43
+ forEachChild(node, (child) => assignedPaths(child, paths));
44
+ return paths;
45
+ }
22
46
  /** Whether an identifier with one of `names` occurs in `node`, whatever it refers to. */
23
47
  export function mentionsName(node, names) {
24
48
  if (node.kind === 'Identifier')
@@ -99,6 +123,7 @@ export function freeNames(component) {
99
123
  for (const name of node.names)
100
124
  declared.add(name.name);
101
125
  node.values.forEach(visit);
126
+ visit(node.body);
102
127
  return;
103
128
  case 'SelectCase':
104
129
  for (const name of node.names)
@@ -120,6 +145,8 @@ export function freeNames(component) {
120
145
  if (node.key)
121
146
  declared.add(node.key.name);
122
147
  visit(node.iterable);
148
+ if (node.withKey)
149
+ visit(node.withKey);
123
150
  visit(node.body);
124
151
  return;
125
152
  case 'CatchClause':
@@ -195,6 +222,7 @@ export function collectValueNames(node, names, isComponent = () => true) {
195
222
  return;
196
223
  case 'VariableDeclaration':
197
224
  node.values.forEach(visit);
225
+ visit(node.body);
198
226
  return;
199
227
  case 'FuncDeclaration':
200
228
  case 'MethodDeclaration':
@@ -237,6 +265,7 @@ export function collectValueNames(node, names, isComponent = () => true) {
237
265
  return;
238
266
  case 'ForInStatement':
239
267
  visit(node.iterable);
268
+ visit(node.withKey ?? null);
240
269
  visit(node.body);
241
270
  return;
242
271
  case 'CatchClause':
@@ -25,6 +25,7 @@ export declare abstract class ParserBase {
25
25
  protected abstract parseClass(start: number, exported: boolean): ast.ClassDeclaration;
26
26
  protected abstract parseInterface(start: number, exported: boolean): ast.InterfaceDeclaration;
27
27
  protected abstract parseTypeAlias(start: number, exported: boolean): ast.TypeAliasDeclaration;
28
+ protected abstract parseFunctionBody(async?: boolean): ast.BlockStatement;
28
29
  protected abstract canStartType(offset?: number): boolean;
29
30
  protected abstract parseType(): ast.TypeNode;
30
31
  protected abstract parseTypeArgs(): ast.TypeNode[];
@@ -1,6 +1,6 @@
1
1
  import { STATEMENT_KEYWORDS, BRACKETS, ParseError, describe } from "./syntax.js";
2
2
  // The parser is a chain of layers, one per area: base → simple statements → statements → control
3
- // flow → jumps → declarations → decorators → classes → types → expressions → literals → markup → Parser
3
+ // flow → jumps → params → declarations → decorators → classes → types → expressions → literals → markup → Parser
4
4
  // (parser.ts). Statements, expressions and markup contain each other, so methods of later layers
5
5
  // that earlier ones call are declared here as abstract.
6
6
  //
@@ -17,6 +17,11 @@ export declare abstract class ControlFlowParser extends StatementParser {
17
17
  ast.SimpleStatement | null
18
18
  ];
19
19
  protected parseForIn(start: number, go?: boolean): ast.ForInStatement;
20
+ /**
21
+ * `with key todo.id` after the items of a `{for}` in markup: its blocks are found again by the
22
+ * key instead of the item. `with` and `key` are words only here, not keywords.
23
+ */
24
+ private parseWithKey;
20
25
  /** `go for i, v in items { }`: the parts of the array run as goroutines, and the loop waits. */
21
26
  protected parseGoFor(): ast.ForStatement | ast.ForInStatement;
22
27
  protected parseLoopBody(): ast.BlockStatement;