dom-expressions 0.33.2 → 0.33.3

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 (2) hide show
  1. package/package.json +2 -2
  2. package/src/server.js +92 -55
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dom-expressions",
3
3
  "description": "A Fine-Grained Runtime for Performant DOM Rendering",
4
- "version": "0.33.2",
4
+ "version": "0.33.3",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -22,5 +22,5 @@
22
22
  "devDependencies": {
23
23
  "babel-plugin-jsx-dom-expressions": "^0.33.0"
24
24
  },
25
- "gitHead": "05fffe9d325238d9be374efa0cccd0ab77d03a31"
25
+ "gitHead": "ecf8a9328c17b9dead125ebc9ecce0bdc2bca5a4"
26
26
  }
package/src/server.js CHANGED
@@ -39,8 +39,8 @@ export function renderToStringAsync(code, options = {}) {
39
39
  writeResource(id, p, error) {
40
40
  if (error) return (scripts += `_$HY.set("${id}", ${serializeError(p)});`);
41
41
  if (!p || typeof p !== "object" || !("then" in p))
42
- return (scripts += serializeSet(dedupe, id, p));
43
- p.then(d => (scripts += serializeSet(dedupe, id, d))).catch(
42
+ return (scripts += serializeSet(dedupe, id, p)) + ";";
43
+ p.then(d => (scripts += serializeSet(dedupe, id, d) + ";")).catch(
44
44
  () => (scripts += `_$HY.set("${id}", {});`)
45
45
  );
46
46
  }
@@ -91,6 +91,7 @@ export function renderToStream(code, options = {}) {
91
91
  const { nonce, onCompleteShell, onCompleteAll, renderId } = options;
92
92
  const tmp = [];
93
93
  const tasks = [];
94
+ const blockingResources = [];
94
95
  const registry = new Map();
95
96
  const dedupe = new WeakMap();
96
97
  const checkEnd = () => {
@@ -106,6 +107,13 @@ export function renderToStream(code, options = {}) {
106
107
  completed = true;
107
108
  }
108
109
  };
110
+ const pushTask = task => {
111
+ tasks.push(task);
112
+ if (!scheduled) {
113
+ Promise.resolve().then(writeTasks);
114
+ scheduled = true;
115
+ }
116
+ };
109
117
  const writeTasks = () => {
110
118
  if (tasks.length && !completed) {
111
119
  buffer.write(`<script${nonce ? ` nonce="${nonce}"` : ""}>${tasks.join(";")}</script>`);
@@ -115,6 +123,7 @@ export function renderToStream(code, options = {}) {
115
123
  };
116
124
 
117
125
  let writable;
126
+ let firstFlushed = false;
118
127
  let completed = false;
119
128
  let scriptFlushed = false;
120
129
  let scheduled = true;
@@ -132,57 +141,58 @@ export function renderToStream(code, options = {}) {
132
141
  suspense: {},
133
142
  assets: [],
134
143
  nonce,
135
- writeResource(id, p, error) {
136
- if (!scheduled) {
137
- Promise.resolve().then(writeTasks);
138
- scheduled = true;
139
- }
140
- if (error) return tasks.push(`_$HY.set("${id}", ${serializeError(p)})`);
144
+ writeResource(id, p, error, wait) {
145
+ if (error) return pushTask(`_$HY.set("${id}", ${serializeError(p)})`);
141
146
  if (!p || typeof p !== "object" || !("then" in p))
142
- return tasks.push(serializeSet(dedupe, id, p));
143
- tasks.push(`_$HY.init("${id}")`);
147
+ return pushTask(serializeSet(dedupe, id, p));
148
+ if (wait && !firstFlushed) blockingResources.push(p);
149
+ else pushTask(`_$HY.init("${id}")`);
144
150
  p.then(d => {
145
- !completed &&
146
- buffer.write(
147
- `<script${nonce ? ` nonce="${nonce}"` : ""}>${serializeSet(dedupe, id, d)}</script>`
148
- );
151
+ !completed && pushTask(serializeSet(dedupe, id, d));
149
152
  }).catch(() => {
150
- !completed &&
151
- buffer.write(`<script${nonce ? ` nonce="${nonce}"` : ""}>_$HY.set("${id}", {})</script>`);
153
+ !completed && pushTask(`_$HY.set("${id}", {})`);
152
154
  });
153
155
  },
154
156
  registerFragment(key) {
155
- registry.set(key, []);
156
- if (!scheduled) {
157
- Promise.resolve().then(writeTasks);
158
- scheduled = true;
157
+ if (!registry.has(key)) {
158
+ registry.set(key, []);
159
+ pushTask(`_$HY.init("${key}")`);
159
160
  }
160
- tasks.push(`_$HY.init("${key}")`);
161
161
  return (value, error) => {
162
162
  if (registry.has(key)) {
163
163
  const keys = registry.get(key);
164
164
  registry.delete(key);
165
165
  if (waitForFragments(registry, key)) return;
166
166
  if ((value !== undefined || error) && !completed) {
167
- buffer.write(
168
- `<div hidden id="${key}">${value !== undefined ? value : " "}</div><script${
169
- nonce ? ` nonce="${nonce}"` : ""
170
- }>${!scriptFlushed ? REPLACE_SCRIPT : ""}${
171
- keys.length ? keys.map(k => `_$HY.unset("${k}");`) : ""
172
- }$df("${key}"${error ? "," + serializeError(error) : ""})</script>`
173
- );
174
- scriptFlushed = true;
167
+ if (!firstFlushed) {
168
+ Promise.resolve().then(
169
+ () => (html = replacePlaceholder(html, key, value !== undefined ? value : ""))
170
+ );
171
+ pushTask(
172
+ `${keys.length ? keys.map(k => `_$HY.unset("${k}");`) : ""}_$HY.set("${key}",${
173
+ error ? serializeError(error) : "null"
174
+ })`
175
+ );
176
+ } else {
177
+ buffer.write(`<div hidden id="${key}">${value !== undefined ? value : " "}</div>`);
178
+ pushTask(
179
+ `${keys.length ? keys.map(k => `_$HY.unset("${k}")`).join(";") : ""}$df("${key}"${
180
+ error ? "," + serializeError(error) : ""
181
+ })${!scriptFlushed ? ";" + REPLACE_SCRIPT : ""}`
182
+ );
183
+ scriptFlushed = true;
184
+ }
175
185
  }
176
186
  }
177
- checkEnd();
187
+ if (firstFlushed) checkEnd();
178
188
  return true;
179
189
  };
180
190
  }
181
191
  };
182
192
 
183
193
  let html = resolveSSRNode(escape(code()));
184
- html = injectAssets(sharedConfig.context.assets, html);
185
- Promise.resolve().then(() => {
194
+ function doShell() {
195
+ html = injectAssets(sharedConfig.context.assets, html);
186
196
  if (tasks.length) html = injectScripts(html, tasks.join(";"), nonce);
187
197
  buffer.write(html);
188
198
  tasks.length = 0;
@@ -193,32 +203,40 @@ export function renderToStream(code, options = {}) {
193
203
  !completed && buffer.write(v);
194
204
  }
195
205
  });
196
- });
206
+ }
197
207
 
198
208
  return {
199
209
  pipe(w) {
200
- buffer = writable = w;
201
- tmp.forEach(chunk => buffer.write(chunk));
202
- if (completed) writable.end();
203
- else setTimeout(checkEnd);
210
+ Promise.allSettled(blockingResources).then(() => {
211
+ doShell();
212
+ buffer = writable = w;
213
+ tmp.forEach(chunk => buffer.write(chunk));
214
+ firstFlushed = true;
215
+ if (completed) writable.end();
216
+ else setTimeout(checkEnd);
217
+ });
204
218
  },
205
219
  pipeTo(w) {
206
- const encoder = new TextEncoder();
207
- const writer = w.getWriter();
208
- writable = {
209
- end() {
210
- writer.releaseLock();
211
- w.close();
212
- }
213
- };
214
- buffer = {
215
- write(payload) {
216
- writer.write(encoder.encode(payload));
217
- }
218
- };
219
- tmp.forEach(chunk => buffer.write(chunk));
220
- if (completed) writable.end();
221
- else setTimeout(checkEnd);
220
+ Promise.allSettled(blockingResources).then(() => {
221
+ doShell();
222
+ const encoder = new TextEncoder();
223
+ const writer = w.getWriter();
224
+ writable = {
225
+ end() {
226
+ writer.releaseLock();
227
+ w.close();
228
+ }
229
+ };
230
+ buffer = {
231
+ write(payload) {
232
+ writer.write(encoder.encode(payload));
233
+ }
234
+ };
235
+ tmp.forEach(chunk => buffer.write(chunk));
236
+ firstFlushed = true;
237
+ if (completed) writable.end();
238
+ else setTimeout(checkEnd);
239
+ });
222
240
  }
223
241
  };
224
242
  }
@@ -465,9 +483,28 @@ function waitForFragments(registry, key) {
465
483
 
466
484
  function serializeSet(registry, key, value) {
467
485
  const exist = registry.get(value);
468
- if (exist) return `_$HY.set("${key}", _$HY.r["${exist}"][0]);`;
486
+ if (exist) return `_$HY.set("${key}", _$HY.r["${exist}"][0])`;
469
487
  value !== null && typeof value === "object" && registry.set(value, key);
470
- return `_$HY.set("${key}", ${devalue(value)});`;
488
+ return `_$HY.set("${key}", ${devalue(value)})`;
489
+ }
490
+
491
+ function replacePlaceholder(html, key, value) {
492
+ const nextRegex = /(<[/]?span[^>]*>)/g;
493
+ const marker = `<span id="pl-${key}">`;
494
+
495
+ const first = html.indexOf(marker);
496
+ if (first === -1) return html;
497
+ nextRegex.lastIndex = first + marker.length;
498
+ let match;
499
+ let open = 0,
500
+ close = 0;
501
+ while ((match = nextRegex.exec(html))) {
502
+ if (match[0][1] === "/") {
503
+ close++;
504
+ if (close > open) break;
505
+ } else open++;
506
+ }
507
+ return html.slice(0, first) + value + html.slice(nextRegex.lastIndex);
471
508
  }
472
509
 
473
510
  /* istanbul ignore next */