marko 5.39.34 → 5.39.35

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.
@@ -1 +1,3 @@
1
- "use strict";require("./runtime-dom.js").p(require("@marko/runtime-tags/debug/dom").compat);
1
+ "use strict";const runtimeDom = require("./runtime-dom.js");
2
+ runtimeDom.p(require("@marko/runtime-tags/debug/dom").compat);
3
+ exports.f = runtimeDom.f;
@@ -1,4 +1,5 @@
1
1
  import { compat } from "@marko/runtime-tags/debug/dom";
2
2
 
3
- import { p } from "./runtime-dom.js";
3
+ import { f, p } from "./runtime-dom.js";
4
4
  p(compat);
5
+ export { f };
@@ -1 +1,3 @@
1
- "use strict";require("./runtime-dom.js").p(require("@marko/runtime-tags/dom").compat);
1
+ "use strict";const runtimeDom = require("./runtime-dom.js");
2
+ runtimeDom.p(require("@marko/runtime-tags/dom").compat);
3
+ exports.f = runtimeDom.f;
@@ -1,4 +1,5 @@
1
1
  import { compat } from "@marko/runtime-tags/dom";
2
2
 
3
- import { p } from "./runtime-dom.js";
3
+ import { f, p } from "./runtime-dom.js";
4
4
  p(compat);
5
+ export { f };
@@ -1,3 +1,3 @@
1
- "use strict";exports.s = require("./runtime-html.js").p(
2
- require("@marko/runtime-tags/debug/html").compat
3
- );
1
+ "use strict";const runtimeHtml = require("./runtime-html.js");
2
+ exports.s = runtimeHtml.p(require("@marko/runtime-tags/debug/html").compat);
3
+ exports.f = runtimeHtml.f;
@@ -1,4 +1,5 @@
1
1
  import { compat } from "@marko/runtime-tags/debug/html";
2
2
 
3
- import { p } from "./runtime-html.js";
3
+ import { f, p } from "./runtime-html.js";
4
4
  export const s = p(compat);
5
+ export { f };
@@ -1,3 +1,3 @@
1
- "use strict";exports.s = require("./runtime-html.js").p(
2
- require("@marko/runtime-tags/html").compat
3
- );
1
+ "use strict";const runtimeHtml = require("./runtime-html.js");
2
+ exports.s = runtimeHtml.p(require("@marko/runtime-tags/html").compat);
3
+ exports.f = runtimeHtml.f;
@@ -1,4 +1,5 @@
1
1
  import { compat } from "@marko/runtime-tags/html";
2
2
 
3
- import { p } from "./runtime-html.js";
3
+ import { f, p } from "./runtime-html.js";
4
4
  export const s = p(compat);
5
+ export { f };
@@ -13,7 +13,29 @@ const dynamicTag = require("../dynamic-tag");
13
13
  const Component = require("../../components/Component");
14
14
  const noopRenderer = require("../serialize-noop").aM_;
15
15
 
16
+ // Bound in `p` so entry files (dom / dom-debug, cjs / esm) just re-export `f`.
17
+ let resumeClassFunction;
18
+
19
+ // Rebuilds a hoisted class handler against whichever component the resumed
20
+ // scope names, so it fires without the parent having to rerender.
21
+ exports.f = (id, factory) => {
22
+ resumeClassFunction(
23
+ id,
24
+ (scope) =>
25
+ function () {
26
+ // Resolved on call: the tags resume runs before `$MC` creates the class
27
+ // component this handler belongs to.
28
+ return factory(
29
+ _n_[scope.m5h] ||
30
+ _n_[scope.m5c] ||
31
+ scope.bs_
32
+ ).apply(this, arguments);
33
+ }
34
+ );
35
+ };
36
+
16
37
  exports.p = function (domCompat) {
38
+ resumeClassFunction = domCompat.resumeClassFunction;
17
39
  dynamicTag.bq_ = function tagsToVdom(
18
40
  renderer,
19
41
  renderBody,
@@ -9,7 +9,14 @@ const createRenderer = require("../../components/renderer");
9
9
  const defaultCreateOut = require("../../createOut");
10
10
  const dynamicTag5 = require("../dynamic-tag");
11
11
 
12
+ // Bound in `p` so entry files (html / html-debug, cjs / esm) just re-export `f`.
13
+ let registerClassFunction;
14
+
15
+ exports.f = (id, fn, component, out) =>
16
+ registerClassFunction(out.global, id, fn, component.id);
17
+
12
18
  exports.p = function (htmlCompat) {
19
+ registerClassFunction = htmlCompat.registerClassFunction;
13
20
  const writersByGlobal = new WeakMap();
14
21
  const boundaryModeByRenderer = new WeakMap();
15
22
  const isMarko6 = (fn) => typeof fn !== "function" || htmlCompat.isTagsAPI(fn);
@@ -144,7 +151,9 @@ exports.p = function (htmlCompat) {
144
151
  component,
145
152
  input,
146
153
  writers.tagsAPI,
147
- !willRerender && hasBridgedClassEvent(input)
154
+ !willRerender && (
155
+ hasBridgedClassEvent(input) ||
156
+ htmlCompat.hasPendingClassFunctions(out.global))
148
157
  );
149
158
  out.ef();
150
159
  },
@@ -7,6 +7,9 @@ var _babelUtils = require("@marko/compiler/babel-utils");
7
7
 
8
8
 
9
9
 
10
+
11
+
12
+
10
13
  var _loadImport = require("../util/load-import");
11
14
  var _withPreviousLocation = _interopRequireDefault(require("../util/with-previous-location"));
12
15
  var _dynamicTag = _interopRequireDefault(require("./dynamic-tag"));
@@ -26,6 +29,7 @@ function _default(path, isNullable) {
26
29
  let tagIdentifier;
27
30
 
28
31
  if (node.extra?.featureType === "tags") {
32
+ registerClassHandlerFunctions(path);
29
33
  // A dynamic `<${tagName}>` already references the imported template
30
34
  // binding directly; only a static tag name needs to be resolved to one.
31
35
  if (_compiler.types.isStringLiteral(name)) {
@@ -131,4 +135,125 @@ function _default(path, isNullable) {
131
135
  } else {
132
136
  path.replaceWith(customTagRenderCall);
133
137
  }
138
+ }
139
+
140
+ // Hoist direct attribute handlers to a component factory for class→tags resume.
141
+ // Nested functions in object/array attr values are not supported across the boundary.
142
+ function registerClassHandlerFunctions(path) {
143
+ for (const attr of path.get("attributes")) {
144
+ if (!attr.isMarkoAttribute()) continue;
145
+ const valuePath = attr.get("value");
146
+ if (
147
+ !valuePath.isFunctionExpression() &&
148
+ !valuePath.isArrowFunctionExpression())
149
+ {
150
+ continue;
151
+ }
152
+ const factory = hoistToFactory(valuePath);
153
+ if (factory) {
154
+ replaceWithResumable(valuePath, factory);
155
+ }
156
+ }
157
+ }
158
+
159
+ // `input`/`out`/`state`/`$global` only become renderer parameters in `Program.exit`,
160
+ // so at this point they can still look module scoped.
161
+ const RENDER_LOCALS = new Set(["input", "out", "state", "$global"]);
162
+
163
+ // Only the component survives into the browser; anything else the body closes
164
+ // over belongs to a render frame that resume cannot rebuild.
165
+ function hoistToFactory(fnPath) {
166
+ const file = (0, _babelUtils.getFile)();
167
+ const componentId = file._componentInstanceIdentifier;
168
+ if (!componentId) return null;
169
+
170
+ const program = (0, _babelUtils.getProgram)();
171
+ const outer = new Set();
172
+
173
+ for (let scope = fnPath.scope.parent; scope; scope = scope.parent) {
174
+ for (const name in scope.bindings) {
175
+ const binding = scope.bindings[name];
176
+ if (!binding.referencePaths.some((ref) => ref.isDescendant(fnPath))) {
177
+ continue;
178
+ }
179
+
180
+ if (
181
+ name === "component" ||
182
+ name === componentId.name ||
183
+ binding.identifier === componentId)
184
+ {
185
+ outer.add("component");
186
+ } else if (RENDER_LOCALS.has(name) || binding.scope !== program.scope) {
187
+ outer.add(name);
188
+ }
189
+ }
190
+ if (scope === program.scope) break;
191
+ }
192
+
193
+ for (const name of outer) {
194
+ if (name !== "component") return null;
195
+ }
196
+
197
+ const factoryId = program.scope.generateUidIdentifier("marko_class_fn");
198
+
199
+ program.pushContainer("body", [
200
+ _compiler.types.variableDeclaration("const", [
201
+ _compiler.types.variableDeclarator(
202
+ factoryId,
203
+ _compiler.types.arrowFunctionExpression([_compiler.types.cloneNode(componentId)], fnPath.node)
204
+ )]
205
+ )]
206
+ );
207
+
208
+ return factoryId;
209
+ }
210
+
211
+ function replaceWithResumable(fnPath, factoryId) {
212
+ const file = (0, _babelUtils.getFile)();
213
+ const { markoOpts } = file;
214
+ const isHTML = markoOpts.output === "html";
215
+ const id = _compiler.types.stringLiteral(nextClassFnId());
216
+ const built = _compiler.types.callExpression(_compiler.types.cloneNode(factoryId), [
217
+ _compiler.types.cloneNode(file._componentInstanceIdentifier)]
218
+ );
219
+ let replacement;
220
+
221
+ if (isHTML) {
222
+ replacement = _compiler.types.callExpression(compatHelper(), [
223
+ id,
224
+ built,
225
+ _compiler.types.cloneNode(file._componentInstanceIdentifier),
226
+ _compiler.types.identifier("out")]
227
+ );
228
+ } else {
229
+ // Registered once per module so a resumed reference can rebuild the
230
+ // handler against whichever component instance the scope names.
231
+ (0, _babelUtils.getProgram)().pushContainer("body", [
232
+ _compiler.types.expressionStatement(
233
+ _compiler.types.callExpression(compatHelper(), [id, _compiler.types.cloneNode(factoryId)])
234
+ )]
235
+ );
236
+ replacement = built;
237
+ }
238
+
239
+ fnPath.replaceWith(replacement);
240
+ }
241
+
242
+ function nextClassFnId() {
243
+ const file = (0, _babelUtils.getFile)();
244
+ file._markoClassFnCount = (file._markoClassFnCount || 0) + 1;
245
+ return `${file.metadata.marko.id}/h${file._markoClassFnCount - 1}`;
246
+ }
247
+
248
+ function compatHelper() {
249
+ const file = (0, _babelUtils.getFile)();
250
+ const { optimize, modules, output } = file.markoOpts;
251
+ return (0, _babelUtils.importNamed)(
252
+ file,
253
+ `marko/${optimize ? "dist" : "src"}/runtime/helpers/tags-compat/${
254
+ output === "html" ? "html" : "dom"}${
255
+ optimize ? "" : "-debug"}.${modules === "esm" ? "mjs" : "js"}`,
256
+ "f",
257
+ "marko_class_fn"
258
+ );
134
259
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "marko",
3
- "version": "5.39.34",
3
+ "version": "5.39.35",
4
4
  "private": false,
5
5
  "description": "UI Components + streaming, async, high performance, HTML templating for Node.js and the browser.",
6
6
  "keywords": [
@@ -68,7 +68,7 @@
68
68
  "types": "index.d.ts",
69
69
  "dependencies": {
70
70
  "@marko/compiler": "^5.42.1",
71
- "@marko/runtime-tags": "^6.3.36",
71
+ "@marko/runtime-tags": "^6.3.41",
72
72
  "app-module-path": "^2.2.0",
73
73
  "argly": "^1.2.0",
74
74
  "browser-refresh-client": "1.1.4",
@@ -85,7 +85,7 @@
85
85
  "warp10": "^2.1.0"
86
86
  },
87
87
  "devDependencies": {
88
- "marko": "5.39.34"
88
+ "marko": "5.39.35"
89
89
  },
90
90
  "engines": {
91
91
  "node": ">=22"
@@ -1 +1,3 @@
1
- require("./runtime-dom.js").p(require("@marko/runtime-tags/debug/dom").compat);
1
+ const runtimeDom = require("./runtime-dom.js");
2
+ runtimeDom.p(require("@marko/runtime-tags/debug/dom").compat);
3
+ exports.f = runtimeDom.f;
@@ -1,4 +1,5 @@
1
1
  import { compat } from "@marko/runtime-tags/debug/dom";
2
2
 
3
- import { p } from "./runtime-dom.js";
3
+ import { f, p } from "./runtime-dom.js";
4
4
  p(compat);
5
+ export { f };
@@ -1 +1,3 @@
1
- require("./runtime-dom.js").p(require("@marko/runtime-tags/dom").compat);
1
+ const runtimeDom = require("./runtime-dom.js");
2
+ runtimeDom.p(require("@marko/runtime-tags/dom").compat);
3
+ exports.f = runtimeDom.f;
@@ -1,4 +1,5 @@
1
1
  import { compat } from "@marko/runtime-tags/dom";
2
2
 
3
- import { p } from "./runtime-dom.js";
3
+ import { f, p } from "./runtime-dom.js";
4
4
  p(compat);
5
+ export { f };
@@ -1,3 +1,3 @@
1
- exports.s = require("./runtime-html.js").p(
2
- require("@marko/runtime-tags/debug/html").compat,
3
- );
1
+ const runtimeHtml = require("./runtime-html.js");
2
+ exports.s = runtimeHtml.p(require("@marko/runtime-tags/debug/html").compat);
3
+ exports.f = runtimeHtml.f;
@@ -1,4 +1,5 @@
1
1
  import { compat } from "@marko/runtime-tags/debug/html";
2
2
 
3
- import { p } from "./runtime-html.js";
3
+ import { f, p } from "./runtime-html.js";
4
4
  export const s = p(compat);
5
+ export { f };
@@ -1,3 +1,3 @@
1
- exports.s = require("./runtime-html.js").p(
2
- require("@marko/runtime-tags/html").compat,
3
- );
1
+ const runtimeHtml = require("./runtime-html.js");
2
+ exports.s = runtimeHtml.p(require("@marko/runtime-tags/html").compat);
3
+ exports.f = runtimeHtml.f;
@@ -1,4 +1,5 @@
1
1
  import { compat } from "@marko/runtime-tags/html";
2
2
 
3
- import { p } from "./runtime-html.js";
3
+ import { f, p } from "./runtime-html.js";
4
4
  export const s = p(compat);
5
+ export { f };
@@ -13,7 +13,29 @@ const dynamicTag = require("../dynamic-tag");
13
13
  const Component = require("../../components/Component");
14
14
  const noopRenderer = require("../serialize-noop").___noop;
15
15
 
16
+ // Bound in `p` so entry files (dom / dom-debug, cjs / esm) just re-export `f`.
17
+ let resumeClassFunction;
18
+
19
+ // Rebuilds a hoisted class handler against whichever component the resumed
20
+ // scope names, so it fires without the parent having to rerender.
21
+ exports.f = (id, factory) => {
22
+ resumeClassFunction(
23
+ id,
24
+ (scope) =>
25
+ function () {
26
+ // Resolved on call: the tags resume runs before `$MC` creates the class
27
+ // component this handler belongs to.
28
+ return factory(
29
+ ___componentLookup[scope.m5h] ||
30
+ ___componentLookup[scope.m5c] ||
31
+ scope.___marko5Component,
32
+ ).apply(this, arguments);
33
+ },
34
+ );
35
+ };
36
+
16
37
  exports.p = function (domCompat) {
38
+ resumeClassFunction = domCompat.resumeClassFunction;
17
39
  dynamicTag.___runtimeCompat = function tagsToVdom(
18
40
  renderer,
19
41
  renderBody,
@@ -9,7 +9,14 @@ const createRenderer = require("../../components/renderer");
9
9
  const defaultCreateOut = require("../../createOut");
10
10
  const dynamicTag5 = require("../dynamic-tag");
11
11
 
12
+ // Bound in `p` so entry files (html / html-debug, cjs / esm) just re-export `f`.
13
+ let registerClassFunction;
14
+
15
+ exports.f = (id, fn, component, out) =>
16
+ registerClassFunction(out.global, id, fn, component.id);
17
+
12
18
  exports.p = function (htmlCompat) {
19
+ registerClassFunction = htmlCompat.registerClassFunction;
13
20
  const writersByGlobal = new WeakMap();
14
21
  const boundaryModeByRenderer = new WeakMap();
15
22
  const isMarko6 = (fn) => typeof fn !== "function" || htmlCompat.isTagsAPI(fn);
@@ -144,7 +151,9 @@ exports.p = function (htmlCompat) {
144
151
  component,
145
152
  input,
146
153
  writers.tagsAPI,
147
- !willRerender && hasBridgedClassEvent(input),
154
+ !willRerender &&
155
+ (hasBridgedClassEvent(input) ||
156
+ htmlCompat.hasPendingClassFunctions(out.global)),
148
157
  );
149
158
  out.ef();
150
159
  },
@@ -1,8 +1,11 @@
1
1
  import { types as t } from "@marko/compiler";
2
2
  import {
3
3
  assertNoArgs,
4
+ getFile,
5
+ getProgram,
4
6
  getTagDef,
5
7
  importDefault,
8
+ importNamed,
6
9
  resolveRelativePath,
7
10
  resolveTagImport,
8
11
  } from "@marko/compiler/babel-utils";
@@ -26,6 +29,7 @@ export default function (path, isNullable) {
26
29
  let tagIdentifier;
27
30
 
28
31
  if (node.extra?.featureType === "tags") {
32
+ registerClassHandlerFunctions(path);
29
33
  // A dynamic `<${tagName}>` already references the imported template
30
34
  // binding directly; only a static tag name needs to be resolved to one.
31
35
  if (t.isStringLiteral(name)) {
@@ -132,3 +136,124 @@ export default function (path, isNullable) {
132
136
  path.replaceWith(customTagRenderCall);
133
137
  }
134
138
  }
139
+
140
+ // Hoist direct attribute handlers to a component factory for class→tags resume.
141
+ // Nested functions in object/array attr values are not supported across the boundary.
142
+ function registerClassHandlerFunctions(path) {
143
+ for (const attr of path.get("attributes")) {
144
+ if (!attr.isMarkoAttribute()) continue;
145
+ const valuePath = attr.get("value");
146
+ if (
147
+ !valuePath.isFunctionExpression() &&
148
+ !valuePath.isArrowFunctionExpression()
149
+ ) {
150
+ continue;
151
+ }
152
+ const factory = hoistToFactory(valuePath);
153
+ if (factory) {
154
+ replaceWithResumable(valuePath, factory);
155
+ }
156
+ }
157
+ }
158
+
159
+ // `input`/`out`/`state`/`$global` only become renderer parameters in `Program.exit`,
160
+ // so at this point they can still look module scoped.
161
+ const RENDER_LOCALS = new Set(["input", "out", "state", "$global"]);
162
+
163
+ // Only the component survives into the browser; anything else the body closes
164
+ // over belongs to a render frame that resume cannot rebuild.
165
+ function hoistToFactory(fnPath) {
166
+ const file = getFile();
167
+ const componentId = file._componentInstanceIdentifier;
168
+ if (!componentId) return null;
169
+
170
+ const program = getProgram();
171
+ const outer = new Set();
172
+
173
+ for (let scope = fnPath.scope.parent; scope; scope = scope.parent) {
174
+ for (const name in scope.bindings) {
175
+ const binding = scope.bindings[name];
176
+ if (!binding.referencePaths.some((ref) => ref.isDescendant(fnPath))) {
177
+ continue;
178
+ }
179
+
180
+ if (
181
+ name === "component" ||
182
+ name === componentId.name ||
183
+ binding.identifier === componentId
184
+ ) {
185
+ outer.add("component");
186
+ } else if (RENDER_LOCALS.has(name) || binding.scope !== program.scope) {
187
+ outer.add(name);
188
+ }
189
+ }
190
+ if (scope === program.scope) break;
191
+ }
192
+
193
+ for (const name of outer) {
194
+ if (name !== "component") return null;
195
+ }
196
+
197
+ const factoryId = program.scope.generateUidIdentifier("marko_class_fn");
198
+
199
+ program.pushContainer("body", [
200
+ t.variableDeclaration("const", [
201
+ t.variableDeclarator(
202
+ factoryId,
203
+ t.arrowFunctionExpression([t.cloneNode(componentId)], fnPath.node),
204
+ ),
205
+ ]),
206
+ ]);
207
+
208
+ return factoryId;
209
+ }
210
+
211
+ function replaceWithResumable(fnPath, factoryId) {
212
+ const file = getFile();
213
+ const { markoOpts } = file;
214
+ const isHTML = markoOpts.output === "html";
215
+ const id = t.stringLiteral(nextClassFnId());
216
+ const built = t.callExpression(t.cloneNode(factoryId), [
217
+ t.cloneNode(file._componentInstanceIdentifier),
218
+ ]);
219
+ let replacement;
220
+
221
+ if (isHTML) {
222
+ replacement = t.callExpression(compatHelper(), [
223
+ id,
224
+ built,
225
+ t.cloneNode(file._componentInstanceIdentifier),
226
+ t.identifier("out"),
227
+ ]);
228
+ } else {
229
+ // Registered once per module so a resumed reference can rebuild the
230
+ // handler against whichever component instance the scope names.
231
+ getProgram().pushContainer("body", [
232
+ t.expressionStatement(
233
+ t.callExpression(compatHelper(), [id, t.cloneNode(factoryId)]),
234
+ ),
235
+ ]);
236
+ replacement = built;
237
+ }
238
+
239
+ fnPath.replaceWith(replacement);
240
+ }
241
+
242
+ function nextClassFnId() {
243
+ const file = getFile();
244
+ file._markoClassFnCount = (file._markoClassFnCount || 0) + 1;
245
+ return `${file.metadata.marko.id}/h${file._markoClassFnCount - 1}`;
246
+ }
247
+
248
+ function compatHelper() {
249
+ const file = getFile();
250
+ const { optimize, modules, output } = file.markoOpts;
251
+ return importNamed(
252
+ file,
253
+ `marko/${optimize ? "dist" : "src"}/runtime/helpers/tags-compat/${
254
+ output === "html" ? "html" : "dom"
255
+ }${optimize ? "" : "-debug"}.${modules === "esm" ? "mjs" : "js"}`,
256
+ "f",
257
+ "marko_class_fn",
258
+ );
259
+ }