From f412dfbd8f818d35c54fdbf2ccc188529c4b910f Mon Sep 17 00:00:00 2001
From: Michael Rawlings
Date: Fri, 15 Sep 2023 15:49:33 -0400
Subject: [PATCH] feat: class to tags ssr/hydration interop (wip)
---
.../src/runtime/helpers/tags-compat-html.js | 50 +++-
packages/runtime/src/html/dynamic-tag.ts | 16 +-
packages/runtime/src/html/index.ts | 2 +-
packages/runtime/src/html/serializer.ts | 21 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/tags-counter.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/resume.expected.md | 246 ++++++++++++++++++
.../__snapshots__/ssr.expected.md | 28 +-
.../interop-basic-tags-to-class/test.ts | 3 -
.../html.expected/components/tags-layout.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../html.expected/components/tags-layout.js | 6 +-
.../__snapshots__/html.expected/template.js | 10 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../src/__tests__/main.test.ts | 2 +-
.../components/custom-tag/index.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../html.expected/components/hello/index.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../html.expected/components/hello/index.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../html.expected/components/hello/index.js | 6 +-
.../__snapshots__/html.expected/template.js | 10 +-
.../html.expected/components/hello/index.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/custom-tag.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/custom-tag.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/my-button.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/my-button.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/counter.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/comments.js | 8 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/layout.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 10 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../components/display-intersection.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/child.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/other.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/other.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../html.expected/components/child.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/child.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/hello/index.js | 6 +-
.../html.expected/components/message.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/child.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/custom-tag.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/child/index.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/hello.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/child.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../html.expected/components/child.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../html.expected/components/child.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/child.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/child1.js | 6 +-
.../html.expected/components/child2.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/tag-a/index.js | 6 +-
.../html.expected/components/tag-b/index.js | 6 +-
.../__snapshots__/html.expected/template.js | 14 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../html.expected/components/counter.js | 6 +-
.../__snapshots__/html.expected/template.js | 10 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../__snapshots__/html.expected/template.js | 16 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/baz.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/hello-setter.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../html.expected/components/child.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 10 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../__snapshots__/html.expected/template.js | 8 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
.../__snapshots__/html.expected/template.js | 6 +-
packages/translator/src/core/condition/if.ts | 5 +-
packages/translator/src/core/tag.ts | 10 +-
.../translator/src/visitors/program/html.ts | 17 +-
.../translator/src/visitors/tag/custom-tag.ts | 40 +--
.../src/visitors/tag/dynamic-tag.ts | 31 ++-
182 files changed, 958 insertions(+), 607 deletions(-)
create mode 100644 packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/__snapshots__/resume.expected.md
diff --git a/packages/marko/src/runtime/helpers/tags-compat-html.js b/packages/marko/src/runtime/helpers/tags-compat-html.js
index ab062833a..acc1c31c7 100644
--- a/packages/marko/src/runtime/helpers/tags-compat-html.js
+++ b/packages/marko/src/runtime/helpers/tags-compat-html.js
@@ -1,9 +1,17 @@
-const { createRenderFn } = require("@marko/runtime-fluurt/dist/debug/html"); // TODO: use the non-debug version when built for production
+import initComponentsTag from "../../core-tags/components/init-components-tag";
+
+const {
+ patchDynamicTag,
+ createRenderFn,
+ fork,
+ write,
+} = require("@marko/runtime-fluurt/dist/debug/html"); // TODO: use the non-debug version when built for production
const createRenderer = require("../components/renderer");
const dynamicTag5 = require("./dynamic-tag");
+const defaultCreateOut = require("../createOut");
-// TODO: this is really bad
-const isMarko5 = (fn) => /^(function[^(]*)?\(out/.test(fn.toString());
+const isMarko6 = (fn) => !!fn.___isTagsAPI;
+const isMarko5 = (fn) => !fn.___isTagsAPI;
export default dynamicTag5.___runtimeCompat = function tagsToVdom(
tagsRenderer,
@@ -42,3 +50,39 @@ const TagsCompat = createRenderer(
},
{}
);
+
+patchDynamicTag(
+ function getRenderer(tag) {
+ const renderer = tag._ || tag.renderBody || tag;
+ if (isMarko6(renderer)) return renderer;
+
+ const renderer5 =
+ tag._ ||
+ tag.render ||
+ (tag.renderer && tag.renderer.renderer) ||
+ tag.renderer;
+ const renderBody5 = tag.renderBody || tag;
+
+ return (input) => {
+ const out = defaultCreateOut();
+
+ fork(out, (result) => {
+ write(result.toString());
+ });
+
+ if (renderer5) {
+ renderer5(input, out);
+ } else {
+ renderBody5(out);
+ }
+
+ initComponentsTag({}, out);
+
+ out.end();
+ };
+ },
+ function createRenderer(renderFn) {
+ renderFn.___isTagsAPI = true;
+ return renderFn;
+ }
+);
diff --git a/packages/runtime/src/html/dynamic-tag.ts b/packages/runtime/src/html/dynamic-tag.ts
index b61673500..02ec9052b 100644
--- a/packages/runtime/src/html/dynamic-tag.ts
+++ b/packages/runtime/src/html/dynamic-tag.ts
@@ -68,8 +68,7 @@ export function dynamicTag(
return internalScope;
}
- const renderer =
- (tag as Template)._ || (tag as RenderBodyObject).renderBody || tag;
+ const renderer = getDynamicRenderer(tag);
if (typeof renderer === "function") {
renderer(
@@ -82,3 +81,16 @@ export function dynamicTag(
throw new Error(`Invalid renderer passed for dynamic tag: ${tag}`);
}
}
+
+let getDynamicRenderer = (
+ tag: unknown | string | Renderer | RenderBodyObject | Template
+) => (tag as Template)._ || (tag as RenderBodyObject).renderBody || tag;
+export let createRenderer = (fn: Renderer) => fn;
+
+export function patchDynamicTag(
+ newGetDynamicRenderer: typeof getDynamicRenderer,
+ newCreateRenderer: typeof createRenderer
+) {
+ getDynamicRenderer = newGetDynamicRenderer;
+ createRenderer = newCreateRenderer;
+}
diff --git a/packages/runtime/src/html/index.ts b/packages/runtime/src/html/index.ts
index 1bafaf7df..9b1e3b5c2 100644
--- a/packages/runtime/src/html/index.ts
+++ b/packages/runtime/src/html/index.ts
@@ -8,7 +8,7 @@ export {
export { attr, attrs, classAttr, styleAttr } from "./attrs";
-export { dynamicTag } from "./dynamic-tag";
+export { dynamicTag, createRenderer, patchDynamicTag } from "./dynamic-tag";
export {
write,
diff --git a/packages/runtime/src/html/serializer.ts b/packages/runtime/src/html/serializer.ts
index af9f424e7..4448772aa 100644
--- a/packages/runtime/src/html/serializer.ts
+++ b/packages/runtime/src/html/serializer.ts
@@ -281,7 +281,7 @@ export class Serializer {
if (knownParent === undefined) {
PARENTS.set(cur, parent!);
- KEYS.set(cur, accessor);
+ KEYS.set(cur, toObjectKey(accessor));
return false;
} else {
let ref = "";
@@ -339,10 +339,10 @@ export class Serializer {
}
}
-function toObjectKey(name: string) {
- const invalidPropertyPos = getInvalidPropertyPos(name);
- return invalidPropertyPos === -1 ? name : quote(name, invalidPropertyPos);
-}
+// function toObjectKey(name: string) {
+// const invalidPropertyPos = getInvalidPropertyPos(name);
+// return invalidPropertyPos === -1 ? name : quote(name, invalidPropertyPos);
+// }
function toAssignment(parent: string, key: string | number) {
return parent + toPropertyAccess(key);
@@ -354,16 +354,19 @@ function toPropertyAccess(key: string | number) {
: "." + key;
}
-function getInvalidPropertyPos(name: string) {
+function toObjectKey(name: string | number) {
+ if (typeof name !== "string") return name;
+
let char = name[0];
if (char >= "0" && char <= "9") {
// numeric
for (let i = 1, len = name.length; i < len; i++) {
char = name[i];
if (!(char >= "0" && char <= "9")) {
- return i;
+ return quote(name, i);
}
}
+ return parseInt(name, 10);
} else {
// or valid identifier
for (let i = 0, len = name.length; i < len; i++) {
@@ -377,12 +380,12 @@ function getInvalidPropertyPos(name: string) {
char === "_"
)
) {
- return i;
+ return quote(name, i);
}
}
}
- return -1;
+ return name;
}
// Creates a JavaScript double quoted string and escapes all characters not listed as DoubleStringCharacters on
diff --git a/packages/translator-interop/src/__tests__/fixtures/explicit/__snapshots__/html.expected/template.js b/packages/translator-interop/src/__tests__/fixtures/explicit/__snapshots__/html.expected/template.js
index 9a4707515..2f01980e4 100644
--- a/packages/translator-interop/src/__tests__/fixtures/explicit/__snapshots__/html.expected/template.js
+++ b/packages/translator-interop/src/__tests__/fixtures/explicit/__snapshots__/html.expected/template.js
@@ -1,6 +1,6 @@
-import { write as _write, nextScopeId as _nextScopeId, createTemplate as _createTemplate } from "@marko/runtime-fluurt/dist/debug/html";
-const _renderer = (input, _tagVar, _scope0_) => {
+import { write as _write, nextScopeId as _nextScopeId, createRenderer as _createRenderer, createTemplate as _createTemplate } from "@marko/runtime-fluurt/dist/debug/html";
+const _renderer = /* @__PURE__ */_createRenderer((input, _tagVar, _scope0_) => {
const _scope0_id = _nextScopeId();
_write("Hello world
");
-};
+});
export default /* @__PURE__ */_createTemplate(_renderer, "packages/translator-interop/src/__tests__/fixtures/explicit/template.marko");
\ No newline at end of file
diff --git a/packages/translator-interop/src/__tests__/fixtures/interop-basic-class-to-tags/__snapshots__/html.expected/components/tags-counter.js b/packages/translator-interop/src/__tests__/fixtures/interop-basic-class-to-tags/__snapshots__/html.expected/components/tags-counter.js
index cb5bccdba..d80322e5b 100644
--- a/packages/translator-interop/src/__tests__/fixtures/interop-basic-class-to-tags/__snapshots__/html.expected/components/tags-counter.js
+++ b/packages/translator-interop/src/__tests__/fixtures/interop-basic-class-to-tags/__snapshots__/html.expected/components/tags-counter.js
@@ -1,5 +1,5 @@
-import { attr as _attr, escapeXML as _escapeXML, markResumeNode as _markResumeNode, write as _write, nextScopeId as _nextScopeId, writeEffect as _writeEffect, writeScope as _writeScope, createTemplate as _createTemplate } from "@marko/runtime-fluurt/dist/debug/html";
-const _renderer = (input, _tagVar, _scope0_) => {
+import { attr as _attr, escapeXML as _escapeXML, markResumeNode as _markResumeNode, write as _write, nextScopeId as _nextScopeId, writeEffect as _writeEffect, writeScope as _writeScope, createRenderer as _createRenderer, createTemplate as _createTemplate } from "@marko/runtime-fluurt/dist/debug/html";
+const _renderer = /* @__PURE__ */_createRenderer((input, _tagVar, _scope0_) => {
const _scope0_id = _nextScopeId();
const count = 0;
_write(`${_markResumeNode(_scope0_id, "#button/0")}`);
@@ -7,5 +7,5 @@ const _renderer = (input, _tagVar, _scope0_) => {
_writeScope(_scope0_id, {
"count": count
}, _scope0_);
-};
+});
export default /* @__PURE__ */_createTemplate(_renderer, "packages/translator-interop/src/__tests__/fixtures/interop-basic-class-to-tags/components/tags-counter.marko");
\ No newline at end of file
diff --git a/packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/__snapshots__/html.expected/template.js b/packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/__snapshots__/html.expected/template.js
index 884d3a608..042eba246 100644
--- a/packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/__snapshots__/html.expected/template.js
+++ b/packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/__snapshots__/html.expected/template.js
@@ -1,7 +1,7 @@
-import { escapeXML as _escapeXML, markResumeNode as _markResumeNode, write as _write, dynamicTag as _dynamicTag, markResumeControlEnd as _markResumeControlEnd, nextScopeId as _nextScopeId, writeEffect as _writeEffect, writeScope as _writeScope, createTemplate as _createTemplate } from "@marko/runtime-fluurt/dist/debug/html";
+import { escapeXML as _escapeXML, markResumeNode as _markResumeNode, write as _write, dynamicTag as _dynamicTag, markResumeControlEnd as _markResumeControlEnd, nextScopeId as _nextScopeId, writeEffect as _writeEffect, writeScope as _writeScope, createRenderer as _createRenderer, createTemplate as _createTemplate } from "@marko/runtime-fluurt/dist/debug/html";
import _classCounter from "./components/class-counter.marko";
import _marko_tags_compat from "marko/src/runtime/helpers/tags-compat-html.js";
-const _renderer = (input, _tagVar, _scope0_) => {
+const _renderer = /* @__PURE__ */_createRenderer((input, _tagVar, _scope0_) => {
const _scope0_id = _nextScopeId();
const count = 0;
_write(`${_markResumeNode(_scope0_id, "#button/0")}`);
@@ -15,5 +15,5 @@ const _renderer = (input, _tagVar, _scope0_) => {
"#text/2!": _dynamicScope,
"#text/2(": _classCounter
}, _scope0_);
-};
+});
export default /* @__PURE__ */_createTemplate(_renderer, "packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/template.marko");
\ No newline at end of file
diff --git a/packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/__snapshots__/resume.expected.md b/packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/__snapshots__/resume.expected.md
new file mode 100644
index 000000000..a8c4acecb
--- /dev/null
+++ b/packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/__snapshots__/resume.expected.md
@@ -0,0 +1,246 @@
+# Render {}
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+# Mutations
+```
+inserted #document/html0/body1/#text4
+inserted #document/html0/body1/#text6
+removed #comment after #document/html0/body1/script3
+removed #comment after #document/html0/body1/#text6
+#document/html0/body1/button5: attr(data-parent) "0" => "0"
+```
+
+
+# Render
+container.querySelector("#tags").click()
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+# Mutations
+```
+#document/html0/body1/button0/#text0: "0" => "1"
+```
+
+
+# Render
+container.querySelector("#class").click()
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+# Mutations
+```
+#document/html0/body1/button5/#text0: "0" => "1"
+```
+
+
+# Render
+container.querySelector("#tags").click()
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+# Mutations
+```
+#document/html0/body1/button0/#text0: "1" => "2"
+```
+
+
+# Render
+container.querySelector("#class").click()
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+# Mutations
+```
+#document/html0/body1/button5/#text0: "1" => "2"
+```
+
+
+# Render
+container.querySelector("#tags").click()
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+# Mutations
+```
+#document/html0/body1/button0/#text0: "2" => "3"
+```
\ No newline at end of file
diff --git a/packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/__snapshots__/ssr.expected.md b/packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/__snapshots__/ssr.expected.md
index 19b442745..fdf246315 100644
--- a/packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/__snapshots__/ssr.expected.md
+++ b/packages/translator-interop/src/__tests__/fixtures/interop-basic-tags-to-class/__snapshots__/ssr.expected.md
@@ -2,8 +2,8 @@
-# Emit error
- TypeError: Cannot read properties of null (reading 'isSync')
+# Write
+
# Render "End"
@@ -22,6 +22,21 @@
+
+
+
+
+
+