@modern-js/runtime 3.9.1 → 3.9.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.
- package/dist/cjs/cli/template.server.js +1 -2
- package/dist/cjs/core/browser/hydrate.js +13 -11
- package/dist/cjs/core/server/requestHandler.js +2 -0
- package/dist/cjs/core/server/stream/beforeTemplate.js +3 -3
- package/dist/cjs/core/server/stream/createReadableStream.js +51 -8
- package/dist/cjs/core/server/stream/createReadableStream.worker.js +37 -15
- package/dist/cjs/core/server/stream/deferredScript.js +12 -6
- package/dist/cjs/core/server/stream/deferredScriptOutputCoordinator.js +370 -0
- package/dist/cjs/core/server/string/loadable.js +4 -4
- package/dist/cjs/router/cli/code/nestedRoutes.js +1 -2
- package/dist/cjs/router/cli/code/templates.js +1 -1
- package/dist/cjs/router/cli/code/utils.js +2 -2
- package/dist/cjs/router/runtime/DeferredDataScripts.node.js +13 -10
- package/dist/cjs/router/runtime/plugin.node.js +1 -1
- package/dist/esm/cli/template.server.mjs +1 -2
- package/dist/esm/core/browser/hydrate.mjs +13 -11
- package/dist/esm/core/server/requestHandler.mjs +2 -0
- package/dist/esm/core/server/stream/beforeTemplate.mjs +3 -3
- package/dist/esm/core/server/stream/createReadableStream.mjs +51 -8
- package/dist/esm/core/server/stream/createReadableStream.worker.mjs +37 -15
- package/dist/esm/core/server/stream/deferredScript.mjs +12 -6
- package/dist/esm/core/server/stream/deferredScriptOutputCoordinator.mjs +332 -0
- package/dist/esm/core/server/string/loadable.mjs +4 -4
- package/dist/esm/router/cli/code/nestedRoutes.mjs +1 -2
- package/dist/esm/router/cli/code/templates.mjs +1 -1
- package/dist/esm/router/cli/code/utils.mjs +2 -2
- package/dist/esm/router/runtime/DeferredDataScripts.node.mjs +13 -10
- package/dist/esm/router/runtime/plugin.node.mjs +1 -1
- package/dist/esm-node/cli/template.server.mjs +1 -2
- package/dist/esm-node/core/browser/hydrate.mjs +13 -11
- package/dist/esm-node/core/server/requestHandler.mjs +2 -0
- package/dist/esm-node/core/server/stream/beforeTemplate.mjs +3 -3
- package/dist/esm-node/core/server/stream/createReadableStream.mjs +51 -8
- package/dist/esm-node/core/server/stream/createReadableStream.worker.mjs +37 -15
- package/dist/esm-node/core/server/stream/deferredScript.mjs +12 -6
- package/dist/esm-node/core/server/stream/deferredScriptOutputCoordinator.mjs +333 -0
- package/dist/esm-node/core/server/string/loadable.mjs +4 -4
- package/dist/esm-node/router/cli/code/nestedRoutes.mjs +1 -2
- package/dist/esm-node/router/cli/code/templates.mjs +1 -1
- package/dist/esm-node/router/cli/code/utils.mjs +2 -2
- package/dist/esm-node/router/runtime/DeferredDataScripts.node.mjs +13 -10
- package/dist/esm-node/router/runtime/plugin.node.mjs +1 -1
- package/dist/types/core/server/stream/deferredScript.d.ts +1 -1
- package/dist/types/core/server/stream/deferredScriptOutputCoordinator.d.ts +72 -0
- package/package.json +12 -12
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PassThrough, Readable, Transform } from "stream";
|
|
2
|
+
import { StringDecoder } from "string_decoder";
|
|
2
3
|
import { storage } from "@modern-js/runtime-utils/node";
|
|
3
4
|
import { SSR_HYDRATION_ID_PREFIX } from "@modern-js/utils/universal/constants";
|
|
4
5
|
import { ESCAPED_SHELL_STREAM_END_MARK } from "../../../common.mjs";
|
|
@@ -6,6 +7,7 @@ import { RenderLevel } from "../../constants.mjs";
|
|
|
6
7
|
import { getGlobalInternalRuntimeContext } from "../../context/index.mjs";
|
|
7
8
|
import { getMonitors } from "../../context/monitors/index.mjs";
|
|
8
9
|
import { enqueueFromEntries } from "./deferredScript.mjs";
|
|
10
|
+
import { DeferredScriptOutputCoordinator } from "./deferredScriptOutputCoordinator.mjs";
|
|
9
11
|
import { ShellChunkStatus, getReadableStreamFromString, resolveStreamingMode } from "./shared.mjs";
|
|
10
12
|
import { getTemplates } from "./template.mjs";
|
|
11
13
|
const defaultExtender = {
|
|
@@ -54,7 +56,14 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
54
56
|
entryName,
|
|
55
57
|
styledComponentsStyleTags
|
|
56
58
|
}).then(({ shellAfter, shellBefore })=>{
|
|
59
|
+
const decoder = new StringDecoder('utf8');
|
|
57
60
|
const pendingScripts = [];
|
|
61
|
+
const outputState = {};
|
|
62
|
+
let deferredResolversComplete = Promise.resolve();
|
|
63
|
+
const writeReact = (content)=>{
|
|
64
|
+
if (!outputState.coordinator) throw new Error("Deferred script coordinator is not ready");
|
|
65
|
+
outputState.coordinator.writeReact(content);
|
|
66
|
+
};
|
|
58
67
|
const body = new Transform({
|
|
59
68
|
transform (chunk, _encoding, callback) {
|
|
60
69
|
try {
|
|
@@ -66,17 +75,49 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
66
75
|
const beforeMark = concatedChunk.slice(0, markerIndex);
|
|
67
76
|
const afterMark = concatedChunk.slice(markerIndex + ESCAPED_SHELL_STREAM_END_MARK.length);
|
|
68
77
|
shellChunkStatus = ShellChunkStatus.FINISH;
|
|
69
|
-
|
|
70
|
-
if (afterMark)
|
|
71
|
-
if (
|
|
78
|
+
writeReact(`${shellBefore}${beforeMark}${shellAfter}`);
|
|
79
|
+
if (afterMark) writeReact(afterMark);
|
|
80
|
+
if (outputState.coordinator) {
|
|
81
|
+
outputState.coordinator.markShellFinished();
|
|
82
|
+
for (const script of pendingScripts)outputState.coordinator.enqueueResolver(script);
|
|
83
|
+
pendingScripts.length = 0;
|
|
84
|
+
}
|
|
72
85
|
}
|
|
73
|
-
} else
|
|
86
|
+
} else {
|
|
87
|
+
const decodedChunk = decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
88
|
+
writeReact(decodedChunk);
|
|
89
|
+
}
|
|
74
90
|
callback();
|
|
75
91
|
} catch (e) {
|
|
76
92
|
e instanceof Error ? callback(e) : callback(new Error('Received unknown error when streaming'));
|
|
77
93
|
}
|
|
94
|
+
},
|
|
95
|
+
flush (callback) {
|
|
96
|
+
try {
|
|
97
|
+
writeReact(decoder.end());
|
|
98
|
+
} catch (error) {
|
|
99
|
+
callback(error instanceof Error ? error : new Error('Received unknown error when streaming'));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
deferredResolversComplete.then(()=>{
|
|
103
|
+
try {
|
|
104
|
+
outputState.coordinator?.finish();
|
|
105
|
+
callback();
|
|
106
|
+
} catch (error) {
|
|
107
|
+
callback(error instanceof Error ? error : new Error('Received unknown error when streaming'));
|
|
108
|
+
}
|
|
109
|
+
}, (error)=>{
|
|
110
|
+
callback(error instanceof Error ? error : new Error('Received unknown error when streaming'));
|
|
111
|
+
});
|
|
78
112
|
}
|
|
79
113
|
});
|
|
114
|
+
const coordinator = new DeferredScriptOutputCoordinator((content)=>{
|
|
115
|
+
body.push(content);
|
|
116
|
+
});
|
|
117
|
+
outputState.coordinator = coordinator;
|
|
118
|
+
body.once('close', ()=>{
|
|
119
|
+
coordinator.abort();
|
|
120
|
+
});
|
|
80
121
|
const passThrough = new PassThrough();
|
|
81
122
|
const stream = Readable.toWeb(body);
|
|
82
123
|
resolve(stream);
|
|
@@ -84,23 +125,25 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
84
125
|
extenders.forEach((extender)=>{
|
|
85
126
|
if (extender.processStream) processedStream = extender.processStream(processedStream);
|
|
86
127
|
});
|
|
87
|
-
reactStreamingPipe(passThrough);
|
|
88
|
-
processedStream.pipe(body);
|
|
89
128
|
try {
|
|
90
129
|
const storageContext = storage.useContext?.();
|
|
91
130
|
const activeDeferreds = storageContext?.activeDeferreds;
|
|
131
|
+
const deferredScriptKeys = storageContext?.deferredScriptKeys;
|
|
92
132
|
const entries = activeDeferreds instanceof Map ? Array.from(activeDeferreds.entries()) : [];
|
|
93
133
|
if (entries.length > 0) {
|
|
94
134
|
const enqueueScript = (s)=>{
|
|
95
|
-
if (
|
|
135
|
+
if (!outputState.coordinator) throw new Error("Deferred script coordinator is not ready");
|
|
136
|
+
if (shellChunkStatus === ShellChunkStatus.FINISH) outputState.coordinator.enqueueResolver(s);
|
|
96
137
|
else pendingScripts.push(s);
|
|
97
138
|
};
|
|
98
|
-
enqueueFromEntries(entries, config.nonce,
|
|
139
|
+
deferredResolversComplete = enqueueFromEntries(entries, deferredScriptKeys, config.nonce, enqueueScript);
|
|
99
140
|
}
|
|
100
141
|
} catch (err) {
|
|
101
142
|
const monitors = getMonitors();
|
|
102
143
|
monitors.error("cannot inject router data script", err);
|
|
103
144
|
}
|
|
145
|
+
reactStreamingPipe(passThrough);
|
|
146
|
+
processedStream.pipe(body);
|
|
104
147
|
});
|
|
105
148
|
},
|
|
106
149
|
onShellError (error) {
|
|
@@ -3,6 +3,7 @@ import { storage } from "@modern-js/runtime-utils/node";
|
|
|
3
3
|
import { ESCAPED_SHELL_STREAM_END_MARK } from "../../../common.mjs";
|
|
4
4
|
import { RenderLevel } from "../../constants.mjs";
|
|
5
5
|
import { enqueueFromEntries } from "./deferredScript.mjs";
|
|
6
|
+
import { DeferredScriptOutputCoordinator } from "./deferredScriptOutputCoordinator.mjs";
|
|
6
7
|
import { ShellChunkStatus, encodeForWebStream, getReadableStreamFromString, resolveStreamingMode } from "./shared.mjs";
|
|
7
8
|
import { getTemplates } from "./template.mjs";
|
|
8
9
|
const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
@@ -35,10 +36,13 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
35
36
|
const { waitForAllReady } = resolveStreamingMode(request, forceStreamToString);
|
|
36
37
|
if (waitForAllReady) await readableOriginal.allReady;
|
|
37
38
|
const reader = readableOriginal.getReader();
|
|
39
|
+
let coordinator;
|
|
38
40
|
const stream = new ReadableStream({
|
|
39
41
|
start (controller) {
|
|
42
|
+
const decoder = new TextDecoder();
|
|
40
43
|
const pendingScripts = [];
|
|
41
44
|
let isClosed = false;
|
|
45
|
+
let deferredResolversComplete = Promise.resolve();
|
|
42
46
|
const safeEnqueue = (chunk)=>{
|
|
43
47
|
if (isClosed) return;
|
|
44
48
|
try {
|
|
@@ -55,38 +59,55 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
55
59
|
} catch {}
|
|
56
60
|
}
|
|
57
61
|
};
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
};
|
|
62
|
-
const enqueueScript = (script)=>{
|
|
63
|
-
if (shellChunkStatus === ShellChunkStatus.FINISH) safeEnqueue(encodeForWebStream(script));
|
|
64
|
-
else pendingScripts.push(script);
|
|
65
|
-
};
|
|
62
|
+
coordinator = new DeferredScriptOutputCoordinator((content)=>{
|
|
63
|
+
safeEnqueue(encodeForWebStream(content));
|
|
64
|
+
});
|
|
66
65
|
const storageContext = storage.useContext?.();
|
|
67
66
|
const activeDeferreds = storageContext?.activeDeferreds;
|
|
67
|
+
const deferredScriptKeys = storageContext?.deferredScriptKeys;
|
|
68
68
|
const entries = activeDeferreds instanceof Map ? Array.from(activeDeferreds.entries()) : [];
|
|
69
|
-
if (entries.length > 0) enqueueFromEntries(entries, config.nonce,
|
|
69
|
+
if (entries.length > 0) deferredResolversComplete = enqueueFromEntries(entries, deferredScriptKeys, config.nonce, (script)=>{
|
|
70
|
+
if (!coordinator) throw new Error("Deferred script coordinator is not ready");
|
|
71
|
+
if (shellChunkStatus === ShellChunkStatus.FINISH) coordinator.enqueueResolver(script);
|
|
72
|
+
else pendingScripts.push(script);
|
|
73
|
+
});
|
|
70
74
|
async function push() {
|
|
71
75
|
try {
|
|
72
76
|
const { done, value } = await reader.read();
|
|
73
|
-
if (done)
|
|
77
|
+
if (done) {
|
|
78
|
+
const trailingText = decoder.decode();
|
|
79
|
+
if (trailingText) coordinator?.writeReact(trailingText);
|
|
80
|
+
await deferredResolversComplete;
|
|
81
|
+
coordinator?.finish();
|
|
82
|
+
closeController();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
74
85
|
if (isClosed) return;
|
|
75
86
|
if (shellChunkStatus !== ShellChunkStatus.FINISH) {
|
|
76
|
-
chunkVec.push(
|
|
87
|
+
chunkVec.push(decoder.decode(value, {
|
|
88
|
+
stream: true
|
|
89
|
+
}));
|
|
77
90
|
const concatedChunk = chunkVec.join('');
|
|
78
91
|
const markerIndex = concatedChunk.indexOf(ESCAPED_SHELL_STREAM_END_MARK);
|
|
79
92
|
if (-1 !== markerIndex) {
|
|
80
93
|
const beforeMark = concatedChunk.slice(0, markerIndex);
|
|
81
94
|
const afterMark = concatedChunk.slice(markerIndex + ESCAPED_SHELL_STREAM_END_MARK.length);
|
|
82
95
|
shellChunkStatus = ShellChunkStatus.FINISH;
|
|
83
|
-
|
|
84
|
-
if (afterMark)
|
|
85
|
-
|
|
96
|
+
coordinator?.writeReact(`${shellBefore}${beforeMark}${shellAfter}`);
|
|
97
|
+
if (afterMark) coordinator?.writeReact(afterMark);
|
|
98
|
+
coordinator?.markShellFinished();
|
|
99
|
+
for (const script of pendingScripts)coordinator?.enqueueResolver(script);
|
|
100
|
+
pendingScripts.length = 0;
|
|
86
101
|
}
|
|
87
|
-
} else
|
|
102
|
+
} else {
|
|
103
|
+
const decodedChunk = decoder.decode(value, {
|
|
104
|
+
stream: true
|
|
105
|
+
});
|
|
106
|
+
if (decodedChunk) coordinator?.writeReact(decodedChunk);
|
|
107
|
+
}
|
|
88
108
|
if (!isClosed) push();
|
|
89
109
|
} catch (error) {
|
|
110
|
+
coordinator?.abort();
|
|
90
111
|
if (!isClosed) {
|
|
91
112
|
isClosed = true;
|
|
92
113
|
try {
|
|
@@ -98,6 +119,7 @@ const createReadableStreamFromElement = async (request, rootElement, options)=>{
|
|
|
98
119
|
push();
|
|
99
120
|
},
|
|
100
121
|
cancel (reason) {
|
|
122
|
+
coordinator?.abort();
|
|
101
123
|
reader.cancel(reason).catch(()=>{});
|
|
102
124
|
}
|
|
103
125
|
});
|
|
@@ -40,23 +40,29 @@ function buildDeferredDataScript(nonce, args) {
|
|
|
40
40
|
const nonceAttr = nonce ? ` nonce="${nonce}"` : '';
|
|
41
41
|
return `<script async${nonceAttr} data-fn-name="r" data-script-src="modern-run-router-data-fn" data-fn-args='${escaped}' suppressHydrationWarning>${runRouterDataFnStr}</script>`;
|
|
42
42
|
}
|
|
43
|
-
function enqueueFromEntries(entries, nonce, emit) {
|
|
43
|
+
function enqueueFromEntries(entries, deferredScriptKeys, nonce, emit) {
|
|
44
|
+
const pendingResolvers = [];
|
|
44
45
|
entries.forEach(([routeId, value])=>{
|
|
45
46
|
if (!isDeferredDataLike(value)) return;
|
|
46
|
-
const
|
|
47
|
-
|
|
47
|
+
const serializedKeys = deferredScriptKeys ? deferredScriptKeys.get(routeId) ?? [] : value.pendingKeys ?? [];
|
|
48
|
+
serializedKeys.forEach((key)=>{
|
|
48
49
|
const tracked = value.data?.[key];
|
|
49
|
-
if (isPromiseLike(tracked)) tracked.then((val)=>
|
|
50
|
+
if (isPromiseLike(tracked)) pendingResolvers.push(Promise.resolve(tracked).then((val)=>{
|
|
51
|
+
emit(buildDeferredDataScript(nonce, [
|
|
50
52
|
routeId,
|
|
51
53
|
key,
|
|
52
54
|
val
|
|
53
|
-
]))
|
|
55
|
+
]));
|
|
56
|
+
}, (err)=>{
|
|
57
|
+
emit(buildDeferredDataScript(nonce, [
|
|
54
58
|
routeId,
|
|
55
59
|
key,
|
|
56
60
|
void 0,
|
|
57
61
|
toErrorInfo(err)
|
|
58
|
-
]))
|
|
62
|
+
]));
|
|
63
|
+
}));
|
|
59
64
|
});
|
|
60
65
|
});
|
|
66
|
+
return Promise.all(pendingResolvers).then(()=>void 0);
|
|
61
67
|
}
|
|
62
68
|
export { buildDeferredDataScript, enqueueFromEntries, escapeAttr, isDeferredDataLike, isPromiseLike, isRecord, toErrorInfo };
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { SSR_HYDRATION_ID_PREFIX } from "@modern-js/utils/universal/constants";
|
|
2
|
+
const VOID_ELEMENTS = new Set([
|
|
3
|
+
'area',
|
|
4
|
+
'base',
|
|
5
|
+
'br',
|
|
6
|
+
'col',
|
|
7
|
+
'embed',
|
|
8
|
+
'hr',
|
|
9
|
+
'img',
|
|
10
|
+
'input',
|
|
11
|
+
'link',
|
|
12
|
+
'meta',
|
|
13
|
+
'param',
|
|
14
|
+
'source',
|
|
15
|
+
'track',
|
|
16
|
+
'wbr'
|
|
17
|
+
]);
|
|
18
|
+
const RAW_TEXT_ELEMENTS = new Set([
|
|
19
|
+
'iframe',
|
|
20
|
+
'noembed',
|
|
21
|
+
'noframes',
|
|
22
|
+
"script",
|
|
23
|
+
'style',
|
|
24
|
+
'textarea',
|
|
25
|
+
'title',
|
|
26
|
+
'xmp'
|
|
27
|
+
]);
|
|
28
|
+
const REACT_SEGMENT_ID_PATTERN = new RegExp(`^${SSR_HYDRATION_ID_PREFIX}S:[0-9a-f]+$`);
|
|
29
|
+
const TABLE_SEGMENT_CHILDREN = new Set([
|
|
30
|
+
'tbody',
|
|
31
|
+
'tr',
|
|
32
|
+
'colgroup'
|
|
33
|
+
]);
|
|
34
|
+
function isAsciiAlpha(char) {
|
|
35
|
+
return /[A-Za-z]/.test(char);
|
|
36
|
+
}
|
|
37
|
+
function isWhitespace(char) {
|
|
38
|
+
return /[\t\n\f\r ]/.test(char);
|
|
39
|
+
}
|
|
40
|
+
function getSuffix(value, pattern) {
|
|
41
|
+
const start = Math.max(0, value.length - pattern.length);
|
|
42
|
+
for(let index = start; index < value.length; index++){
|
|
43
|
+
const suffix = value.slice(index);
|
|
44
|
+
if (pattern.startsWith(suffix)) return suffix;
|
|
45
|
+
}
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
class DeferredScriptOutputCoordinator {
|
|
49
|
+
enqueueResolver(script) {
|
|
50
|
+
if (this.closed) return;
|
|
51
|
+
this.pendingResolvers.push(script);
|
|
52
|
+
this.flushResolvers();
|
|
53
|
+
}
|
|
54
|
+
markShellFinished() {
|
|
55
|
+
if (this.closed) return;
|
|
56
|
+
this.shellFinished = true;
|
|
57
|
+
this.flushResolvers();
|
|
58
|
+
}
|
|
59
|
+
writeReact(chunk) {
|
|
60
|
+
if (this.closed || !chunk) return;
|
|
61
|
+
this.flushResolvers();
|
|
62
|
+
let emittedUntil = 0;
|
|
63
|
+
for(let index = 0; index < chunk.length; index++){
|
|
64
|
+
const wasSafe = this.isSafeBoundary();
|
|
65
|
+
this.consume(chunk[index]);
|
|
66
|
+
if (!wasSafe && this.isSafeBoundary() && this.pendingResolvers.length) {
|
|
67
|
+
this.emit(chunk.slice(emittedUntil, index + 1));
|
|
68
|
+
emittedUntil = index + 1;
|
|
69
|
+
this.flushResolvers();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (emittedUntil < chunk.length) this.emit(chunk.slice(emittedUntil));
|
|
73
|
+
}
|
|
74
|
+
finish() {
|
|
75
|
+
if (this.closed) return;
|
|
76
|
+
this.flushResolvers();
|
|
77
|
+
this.closed = true;
|
|
78
|
+
if (this.pendingResolvers.length > 0) throw new Error("Cannot emit deferred resolver scripts because the React stream ended outside a safe HTML boundary.");
|
|
79
|
+
}
|
|
80
|
+
abort() {
|
|
81
|
+
this.closed = true;
|
|
82
|
+
this.pendingResolvers.length = 0;
|
|
83
|
+
}
|
|
84
|
+
isSafeBoundary() {
|
|
85
|
+
return this.shellFinished && 'data' === this.state && 0 === this.openElements.length && !this.awaitingContinuationProtocol;
|
|
86
|
+
}
|
|
87
|
+
flushResolvers() {
|
|
88
|
+
if (!this.isSafeBoundary() || 0 === this.pendingResolvers.length) return;
|
|
89
|
+
while(this.pendingResolvers.length > 0){
|
|
90
|
+
const resolver = this.pendingResolvers.shift();
|
|
91
|
+
if (resolver) this.emit(resolver);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
consume(char) {
|
|
95
|
+
switch(this.state){
|
|
96
|
+
case 'data':
|
|
97
|
+
if ('<' === char) this.state = 'tag-open';
|
|
98
|
+
return;
|
|
99
|
+
case 'tag-open':
|
|
100
|
+
if ('/' === char) {
|
|
101
|
+
this.currentTagName = '';
|
|
102
|
+
this.state = 'end-tag-name';
|
|
103
|
+
} else if ('!' === char) {
|
|
104
|
+
this.declarationPrefix = '';
|
|
105
|
+
this.state = 'markup-declaration';
|
|
106
|
+
} else if ('?' === char) this.state = 'bogus-comment';
|
|
107
|
+
else if (isAsciiAlpha(char)) {
|
|
108
|
+
this.startTag(char);
|
|
109
|
+
this.state = 'start-tag-name';
|
|
110
|
+
} else this.state = 'data';
|
|
111
|
+
return;
|
|
112
|
+
case 'start-tag-name':
|
|
113
|
+
if (isWhitespace(char)) this.state = 'before-attribute-name';
|
|
114
|
+
else if ('/' === char) this.state = 'self-closing-start-tag';
|
|
115
|
+
else if ('>' === char) this.completeStartTag(false);
|
|
116
|
+
else this.currentTagName += char.toLowerCase();
|
|
117
|
+
return;
|
|
118
|
+
case 'before-attribute-name':
|
|
119
|
+
if (isWhitespace(char)) return;
|
|
120
|
+
if ('/' === char) this.state = 'self-closing-start-tag';
|
|
121
|
+
else if ('>' === char) this.completeStartTag(false);
|
|
122
|
+
else {
|
|
123
|
+
this.startAttribute(char);
|
|
124
|
+
this.state = 'attribute-name';
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
case 'attribute-name':
|
|
128
|
+
if (isWhitespace(char)) this.state = 'after-attribute-name';
|
|
129
|
+
else if ('=' === char) {
|
|
130
|
+
this.currentAttributeHasValue = true;
|
|
131
|
+
this.state = 'before-attribute-value';
|
|
132
|
+
} else if ('/' === char) {
|
|
133
|
+
this.completeAttribute();
|
|
134
|
+
this.state = 'self-closing-start-tag';
|
|
135
|
+
} else if ('>' === char) this.completeStartTag(false);
|
|
136
|
+
else this.currentAttributeName += char.toLowerCase();
|
|
137
|
+
return;
|
|
138
|
+
case 'after-attribute-name':
|
|
139
|
+
if (isWhitespace(char)) return;
|
|
140
|
+
if ('=' === char) {
|
|
141
|
+
this.currentAttributeHasValue = true;
|
|
142
|
+
this.state = 'before-attribute-value';
|
|
143
|
+
} else if ('/' === char) {
|
|
144
|
+
this.completeAttribute();
|
|
145
|
+
this.state = 'self-closing-start-tag';
|
|
146
|
+
} else if ('>' === char) this.completeStartTag(false);
|
|
147
|
+
else {
|
|
148
|
+
this.completeAttribute();
|
|
149
|
+
this.startAttribute(char);
|
|
150
|
+
this.state = 'attribute-name';
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
case 'before-attribute-value':
|
|
154
|
+
if (isWhitespace(char)) return;
|
|
155
|
+
if ('"' === char) this.state = 'attribute-value-double-quoted';
|
|
156
|
+
else if ("'" === char) this.state = 'attribute-value-single-quoted';
|
|
157
|
+
else if ('>' === char) this.completeStartTag(false);
|
|
158
|
+
else {
|
|
159
|
+
this.currentAttributeValue += char;
|
|
160
|
+
this.state = 'attribute-value-unquoted';
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
case 'attribute-value-double-quoted':
|
|
164
|
+
if ('"' === char) {
|
|
165
|
+
this.completeAttribute();
|
|
166
|
+
this.state = 'before-attribute-name';
|
|
167
|
+
} else this.currentAttributeValue += char;
|
|
168
|
+
return;
|
|
169
|
+
case 'attribute-value-single-quoted':
|
|
170
|
+
if ("'" === char) {
|
|
171
|
+
this.completeAttribute();
|
|
172
|
+
this.state = 'before-attribute-name';
|
|
173
|
+
} else this.currentAttributeValue += char;
|
|
174
|
+
return;
|
|
175
|
+
case 'attribute-value-unquoted':
|
|
176
|
+
if (isWhitespace(char)) {
|
|
177
|
+
this.completeAttribute();
|
|
178
|
+
this.state = 'before-attribute-name';
|
|
179
|
+
} else if ('>' === char) this.completeStartTag(false);
|
|
180
|
+
else this.currentAttributeValue += char;
|
|
181
|
+
return;
|
|
182
|
+
case 'self-closing-start-tag':
|
|
183
|
+
if ('>' === char) this.completeStartTag(true);
|
|
184
|
+
else if (!isWhitespace(char)) {
|
|
185
|
+
this.startAttribute(char);
|
|
186
|
+
this.state = 'attribute-name';
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
case 'end-tag-name':
|
|
190
|
+
if (isWhitespace(char)) this.state = 'after-end-tag-name';
|
|
191
|
+
else if ('>' === char) this.completeEndTag();
|
|
192
|
+
else this.currentTagName += char.toLowerCase();
|
|
193
|
+
return;
|
|
194
|
+
case 'after-end-tag-name':
|
|
195
|
+
if ('>' === char) this.completeEndTag();
|
|
196
|
+
return;
|
|
197
|
+
case 'markup-declaration':
|
|
198
|
+
if (this.declarationPrefix.length < 2 && '-' === char) {
|
|
199
|
+
this.declarationPrefix += char;
|
|
200
|
+
if ('--' === this.declarationPrefix) {
|
|
201
|
+
this.commentTail = '';
|
|
202
|
+
this.state = 'comment';
|
|
203
|
+
}
|
|
204
|
+
} else this.state = '>' === char ? 'data' : 'declaration';
|
|
205
|
+
return;
|
|
206
|
+
case 'declaration':
|
|
207
|
+
if ('>' === char) this.state = 'data';
|
|
208
|
+
return;
|
|
209
|
+
case 'comment':
|
|
210
|
+
this.commentTail = `${this.commentTail}${char}`.slice(-3);
|
|
211
|
+
if ('-->' === this.commentTail) this.state = 'data';
|
|
212
|
+
return;
|
|
213
|
+
case 'bogus-comment':
|
|
214
|
+
if ('>' === char) this.state = 'data';
|
|
215
|
+
return;
|
|
216
|
+
case 'raw-text':
|
|
217
|
+
this.consumeRawText(char);
|
|
218
|
+
return;
|
|
219
|
+
case 'raw-end-tag':
|
|
220
|
+
if ('>' === char) this.completeRawTextEndTag();
|
|
221
|
+
else if (!isWhitespace(char)) {
|
|
222
|
+
this.state = 'raw-text';
|
|
223
|
+
this.rawTextSearch = getSuffix(`${this.rawTextSearch}${char.toLowerCase()}`, `</${this.rawTextElement}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
startTag(char) {
|
|
228
|
+
this.currentTagName = char.toLowerCase();
|
|
229
|
+
this.currentAttributes = new Map();
|
|
230
|
+
this.currentAttributeName = '';
|
|
231
|
+
this.currentAttributeValue = '';
|
|
232
|
+
this.currentAttributeHasValue = false;
|
|
233
|
+
}
|
|
234
|
+
startAttribute(char) {
|
|
235
|
+
this.currentAttributeName = char.toLowerCase();
|
|
236
|
+
this.currentAttributeValue = '';
|
|
237
|
+
this.currentAttributeHasValue = false;
|
|
238
|
+
}
|
|
239
|
+
completeAttribute() {
|
|
240
|
+
if (!this.currentAttributeName) return;
|
|
241
|
+
this.currentAttributes.set(this.currentAttributeName, this.currentAttributeHasValue ? this.currentAttributeValue : true);
|
|
242
|
+
this.currentAttributeName = '';
|
|
243
|
+
this.currentAttributeValue = '';
|
|
244
|
+
this.currentAttributeHasValue = false;
|
|
245
|
+
}
|
|
246
|
+
completeStartTag(selfClosing) {
|
|
247
|
+
this.completeAttribute();
|
|
248
|
+
const tagName = this.currentTagName;
|
|
249
|
+
const id = this.currentAttributes.get('id');
|
|
250
|
+
const hasReactSegmentId = 'string' == typeof id && REACT_SEGMENT_ID_PATTERN.test(id);
|
|
251
|
+
const hasHiddenAttribute = this.currentAttributes.has('hidden');
|
|
252
|
+
const isHiddenSegmentTable = 'table' === tagName && hasHiddenAttribute && !hasReactSegmentId;
|
|
253
|
+
const isTableSegmentChild = hasReactSegmentId && TABLE_SEGMENT_CHILDREN.has(tagName) && this.openElements[this.openElements.length - 1]?.isSegmentTableWrapper === true;
|
|
254
|
+
const isContinuationContainer = hasReactSegmentId && ('div' === tagName && hasHiddenAttribute || ('svg' === tagName || 'math' === tagName) && void 0 !== id && 'true' === this.currentAttributes.get('aria-hidden') && 'display:none' === this.currentAttributes.get('style') || 'table' === tagName && hasHiddenAttribute || isTableSegmentChild);
|
|
255
|
+
this.continuationProtocolScript = "script" === tagName && this.awaitingContinuationProtocol;
|
|
256
|
+
this.continuationProtocolTail = '';
|
|
257
|
+
this.hasContinuationProtocolCall = false;
|
|
258
|
+
if (!selfClosing && !VOID_ELEMENTS.has(tagName)) this.openElements.push({
|
|
259
|
+
tagName,
|
|
260
|
+
isContinuationContainer,
|
|
261
|
+
isSegmentTableWrapper: isHiddenSegmentTable
|
|
262
|
+
});
|
|
263
|
+
this.currentTagName = '';
|
|
264
|
+
this.currentAttributes = new Map();
|
|
265
|
+
this.currentAttributeName = '';
|
|
266
|
+
this.currentAttributeValue = '';
|
|
267
|
+
this.currentAttributeHasValue = false;
|
|
268
|
+
if (!selfClosing && RAW_TEXT_ELEMENTS.has(tagName)) {
|
|
269
|
+
this.rawTextElement = tagName;
|
|
270
|
+
this.rawTextSearch = '';
|
|
271
|
+
this.state = 'raw-text';
|
|
272
|
+
} else this.state = 'data';
|
|
273
|
+
}
|
|
274
|
+
completeEndTag() {
|
|
275
|
+
this.closeOpenElement(this.currentTagName);
|
|
276
|
+
this.currentTagName = '';
|
|
277
|
+
this.state = 'data';
|
|
278
|
+
}
|
|
279
|
+
consumeRawText(char) {
|
|
280
|
+
const rawTextElement = this.rawTextElement;
|
|
281
|
+
if (!rawTextElement) {
|
|
282
|
+
this.state = 'data';
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const endTagPrefix = `</${rawTextElement}`;
|
|
286
|
+
if (this.continuationProtocolScript) {
|
|
287
|
+
this.continuationProtocolTail = `${this.continuationProtocolTail}${char}`.slice(-4);
|
|
288
|
+
if (/\$(?:RC|RS|RX|RB|RR)\b/.test(this.continuationProtocolTail)) this.hasContinuationProtocolCall = true;
|
|
289
|
+
}
|
|
290
|
+
this.rawTextSearch = getSuffix(`${this.rawTextSearch}${char.toLowerCase()}`, endTagPrefix);
|
|
291
|
+
if (this.rawTextSearch === endTagPrefix) this.state = 'raw-end-tag';
|
|
292
|
+
}
|
|
293
|
+
completeRawTextEndTag() {
|
|
294
|
+
const rawTextElement = this.rawTextElement;
|
|
295
|
+
if (rawTextElement) this.closeOpenElement(rawTextElement);
|
|
296
|
+
if ("script" === rawTextElement && this.continuationProtocolScript && this.hasContinuationProtocolCall) this.awaitingContinuationProtocol = false;
|
|
297
|
+
this.rawTextElement = void 0;
|
|
298
|
+
this.rawTextSearch = '';
|
|
299
|
+
this.continuationProtocolScript = false;
|
|
300
|
+
this.continuationProtocolTail = '';
|
|
301
|
+
this.hasContinuationProtocolCall = false;
|
|
302
|
+
this.state = 'data';
|
|
303
|
+
}
|
|
304
|
+
closeOpenElement(tagName) {
|
|
305
|
+
const matchingIndex = this.openElements.map((element)=>element.tagName).lastIndexOf(tagName);
|
|
306
|
+
if (-1 === matchingIndex) return;
|
|
307
|
+
const closedElements = this.openElements.splice(matchingIndex);
|
|
308
|
+
const closedContinuation = closedElements.some((element)=>element.isContinuationContainer);
|
|
309
|
+
if (closedContinuation) this.awaitingContinuationProtocol = true;
|
|
310
|
+
}
|
|
311
|
+
constructor(emit){
|
|
312
|
+
this.emit = emit;
|
|
313
|
+
this.pendingResolvers = [];
|
|
314
|
+
this.openElements = [];
|
|
315
|
+
this.shellFinished = false;
|
|
316
|
+
this.closed = false;
|
|
317
|
+
this.state = 'data';
|
|
318
|
+
this.currentTagName = '';
|
|
319
|
+
this.currentAttributes = new Map();
|
|
320
|
+
this.currentAttributeName = '';
|
|
321
|
+
this.currentAttributeValue = '';
|
|
322
|
+
this.currentAttributeHasValue = false;
|
|
323
|
+
this.declarationPrefix = '';
|
|
324
|
+
this.commentTail = '';
|
|
325
|
+
this.rawTextSearch = '';
|
|
326
|
+
this.continuationProtocolScript = false;
|
|
327
|
+
this.continuationProtocolTail = '';
|
|
328
|
+
this.hasContinuationProtocolCall = false;
|
|
329
|
+
this.awaitingContinuationProtocol = false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
export { DeferredScriptOutputCoordinator };
|
|
@@ -7,8 +7,8 @@ const extname = (uri)=>{
|
|
|
7
7
|
const generateChunks = (chunks, ext)=>chunks.filter((chunk)=>Boolean(chunk.url)).filter((chunk)=>extname(chunk.url).slice(1) === ext);
|
|
8
8
|
const checkIsInline = (chunk, enableInline)=>{
|
|
9
9
|
if ('production' !== process.env.NODE_ENV) return false;
|
|
10
|
-
if (enableInline instanceof RegExp) return enableInline.test(chunk.url);
|
|
11
|
-
return Boolean(enableInline);
|
|
10
|
+
else if (enableInline instanceof RegExp) return enableInline.test(chunk.url);
|
|
11
|
+
else return Boolean(enableInline);
|
|
12
12
|
};
|
|
13
13
|
const readAsset = async (chunk)=>{
|
|
14
14
|
const fs = await import("fs/promises");
|
|
@@ -76,7 +76,7 @@ class LoadableCollector {
|
|
|
76
76
|
const scripts = await Promise.all(chunks.filter((chunk)=>!existedScript.includes(chunk.url) && !this.existsAssets?.includes(chunk.path)).map(async (chunk)=>{
|
|
77
77
|
const script = `<script${attributes} src="${chunk.url}"></script>`;
|
|
78
78
|
if (checkIsNode() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<script>${content}</script>`).catch((_)=>script);
|
|
79
|
-
return script;
|
|
79
|
+
else return script;
|
|
80
80
|
}));
|
|
81
81
|
chunkSet.jsChunk += scripts.filter((script)=>Boolean(script)).join('');
|
|
82
82
|
}
|
|
@@ -87,7 +87,7 @@ class LoadableCollector {
|
|
|
87
87
|
const css = await Promise.all(chunks.filter((chunk)=>!hasStylesheetLink(template, chunk.url) && !this.existsAssets?.includes(chunk.path)).map(async (chunk)=>{
|
|
88
88
|
const link = `<link${atrributes} href="${chunk.url}" rel="stylesheet" />`;
|
|
89
89
|
if (checkIsNode() && checkIsInline(chunk, inlineStyles)) return readAsset(chunk).then((content)=>`<style>${content}</style>`).catch((_)=>link);
|
|
90
|
-
return link;
|
|
90
|
+
else return link;
|
|
91
91
|
}));
|
|
92
92
|
chunkSet.cssChunk += css.filter((css)=>Boolean(css)).join('');
|
|
93
93
|
}
|
|
@@ -42,8 +42,7 @@ const optimizeRoute = (routeTree)=>{
|
|
|
42
42
|
children: optimizedChildren
|
|
43
43
|
}
|
|
44
44
|
];
|
|
45
|
-
}
|
|
46
|
-
{
|
|
45
|
+
} else {
|
|
47
46
|
const newRoutes = children.map((child)=>{
|
|
48
47
|
const routePath = routeTree.path ? `${routeTree.path}${child.path ? `/${child.path}` : ''}` : child.path || '';
|
|
49
48
|
const newRoute = {
|
|
@@ -10,7 +10,7 @@ const walkDirectory = (dir)=>fs.readdirSync(dir).reduce((previous, filename)=>{
|
|
|
10
10
|
...previous,
|
|
11
11
|
...walkDirectory(filePath)
|
|
12
12
|
];
|
|
13
|
-
return [
|
|
13
|
+
else return [
|
|
14
14
|
...previous,
|
|
15
15
|
filePath
|
|
16
16
|
];
|
|
@@ -27,7 +27,7 @@ const isPageComponentFile = (filePath)=>{
|
|
|
27
27
|
};
|
|
28
28
|
const replaceWithAlias = (base, filePath, alias)=>{
|
|
29
29
|
if (filePath.includes(base)) return normalizeToPosixPath(path.join(alias, path.relative(base, filePath)));
|
|
30
|
-
return filePath;
|
|
30
|
+
else return filePath;
|
|
31
31
|
};
|
|
32
32
|
const parseModule = async ({ source, filename })=>{
|
|
33
33
|
let content = source;
|