octane 0.2.11 → 0.2.13
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/README.md +5 -0
- package/dist/behavior-root.d.ts +13 -3
- package/dist/behavior-root.js +28 -5
- package/dist/cjs/behavior-root.cjs +32 -6
- package/dist/cjs/class-names.cjs +49 -0
- package/dist/cjs/constants.cjs +9 -0
- package/dist/cjs/context-epoch.cjs +38 -0
- package/dist/cjs/css.cjs +6 -27
- package/dist/cjs/data-encoding.cjs +160 -0
- package/dist/cjs/dom-binding-handoff.cjs +93 -0
- package/dist/cjs/dom-binding-protocol.cjs +115 -0
- package/dist/cjs/dom-binding-styles.cjs +292 -0
- package/dist/cjs/dom-bindings.cjs +700 -0
- package/dist/cjs/dom-focused-move.cjs +66 -0
- package/dist/cjs/dom-stage.cjs +896 -0
- package/dist/cjs/dom-tables.cjs +4 -94
- package/dist/cjs/error-codes.client.generated.cjs +33 -0
- package/dist/cjs/error-codes.server.generated.cjs +24 -0
- package/dist/cjs/hydration/control-capture.cjs +244 -0
- package/dist/cjs/hydration/event-capture.cjs +125 -43
- package/dist/cjs/hydration/interaction-config.cjs +50 -2
- package/dist/cjs/hydration-markers.cjs +37 -0
- package/dist/cjs/independent-hydration-protocol.cjs +106 -0
- package/dist/cjs/index.cjs +9 -0
- package/dist/cjs/runtime.cjs +6397 -1683
- package/dist/cjs/runtime.server.cjs +1465 -213
- package/dist/cjs/server/early-signals.cjs +45 -0
- package/dist/cjs/server/index.cjs +47 -0
- package/dist/cjs/server/rpc-batch.cjs +149 -0
- package/dist/cjs/server/rpc-stream.cjs +146 -0
- package/dist/cjs/server/rpc.cjs +34 -15
- package/dist/cjs/server/signal-query-observation.cjs +158 -0
- package/dist/cjs/server/streamed-region.cjs +52 -0
- package/dist/cjs/server/streamed-signals.cjs +512 -0
- package/dist/cjs/server-call.cjs +103 -0
- package/dist/cjs/server-rpc-batch-client.cjs +141 -0
- package/dist/cjs/server-rpc-client.cjs +94 -27
- package/dist/cjs/server-rpc-protocol.cjs +141 -0
- package/dist/cjs/server-rpc-stream-client.cjs +118 -0
- package/dist/cjs/signals/actions.cjs +474 -0
- package/dist/cjs/signals/client.cjs +4 -1
- package/dist/cjs/signals/computations.cjs +460 -0
- package/dist/cjs/signals/control-binding.cjs +368 -0
- package/dist/cjs/signals/derived-facade.cjs +50 -0
- package/dist/cjs/signals/document-owner.cjs +71 -0
- package/dist/cjs/signals/early-values.cjs +155 -0
- package/dist/cjs/signals/encoding.cjs +6 -95
- package/dist/cjs/signals/engine.cjs +276 -12
- package/dist/cjs/signals/errors.cjs +18 -0
- package/dist/cjs/signals/facade.cjs +477 -0
- package/dist/cjs/signals/graph.cjs +166 -20
- package/dist/cjs/signals/handle-protocol.cjs +36 -0
- package/dist/cjs/signals/index.cjs +81 -2
- package/dist/cjs/signals/native-read-client.cjs +10 -0
- package/dist/cjs/signals/owner-context.cjs +112 -0
- package/dist/cjs/signals/query-attempt-observer.cjs +68 -0
- package/dist/cjs/signals/query-facade.cjs +72 -0
- package/dist/cjs/signals/read-protocol.cjs +54 -0
- package/dist/cjs/signals/requests.cjs +287 -14
- package/dist/cjs/signals/scalar-computations.cjs +76 -0
- package/dist/cjs/signals/server.cjs +4 -1
- package/dist/cjs/signals/transition-candidate.cjs +567 -0
- package/dist/cjs/signals/transition-state.cjs +106 -0
- package/dist/cjs/signals/types.cjs +50 -0
- package/dist/cjs/stream-protocol.cjs +3 -0
- package/dist/cjs/streamed-signals-protocol.cjs +159 -0
- package/dist/cjs/style-values.cjs +122 -0
- package/dist/cjs/trusted-html.cjs +30 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/class-names.d.ts +5 -0
- package/dist/class-names.js +25 -0
- package/dist/compiler/bundler.js +51 -22
- package/dist/compiler/compile.js +2534 -581
- package/dist/compiler/dom-binding-program.js +935 -0
- package/dist/compiler/dom-binding-request.js +57 -0
- package/dist/compiler/dom-bindings.js +2245 -0
- package/dist/compiler/hook-deps.js +759 -10
- package/dist/compiler/hydrate-boundaries.js +358 -15
- package/dist/compiler/index.d.ts +39 -2
- package/dist/compiler/index.js +1 -0
- package/dist/compiler/native-attribute-reads.js +127 -0
- package/dist/compiler/native-change-diagnostics.js +32 -38
- package/dist/compiler/native-read-diagnostics.js +69 -8
- package/dist/compiler/native-read-types.js +80 -34
- package/dist/compiler/parser.node.js +1 -1
- package/dist/compiler/renderer-boundaries.js +40 -0
- package/dist/compiler/server-context.js +118 -0
- package/dist/compiler/signal-attempt-reads.js +479 -0
- package/dist/compiler/signal-declarations.js +557 -0
- package/dist/compiler/signal-start-reads.js +413 -0
- package/dist/compiler/slot-hooks.d.ts +3 -1
- package/dist/compiler/slot-hooks.js +168 -34
- package/dist/compiler/strong-auto-memo.js +90 -0
- package/dist/compiler/strong-html.js +143 -0
- package/dist/compiler/strong-mode.js +764 -66
- package/dist/compiler/strong-template-policy.js +307 -0
- package/dist/compiler/template-ir.js +200 -0
- package/dist/compiler/typescript.d.ts +2 -0
- package/dist/compiler/typescript.js +2 -0
- package/dist/compiler/vite.d.ts +3 -1
- package/dist/compiler/vite.js +122 -4
- package/dist/compiler/volar.LICENSES.txt +1 -1
- package/dist/compiler/volar.d.ts +6 -1
- package/dist/compiler/volar.js +750 -1433
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +10 -0
- package/dist/context-epoch.d.ts +2 -0
- package/dist/context-epoch.js +13 -0
- package/dist/css.d.ts +2 -5
- package/dist/css.js +2 -23
- package/dist/data-encoding.d.ts +9 -0
- package/dist/data-encoding.js +134 -0
- package/dist/dom-binding-classes.d.ts +15 -0
- package/dist/dom-binding-classes.js +129 -0
- package/dist/dom-binding-controls.d.ts +2 -0
- package/dist/dom-binding-controls.js +6 -0
- package/dist/dom-binding-handoff.d.ts +55 -0
- package/dist/dom-binding-handoff.js +62 -0
- package/dist/dom-binding-program.d.ts +300 -0
- package/dist/dom-binding-program.js +1625 -0
- package/dist/dom-binding-projections.d.ts +15 -0
- package/dist/dom-binding-projections.js +187 -0
- package/dist/dom-binding-protocol.d.ts +13 -0
- package/dist/dom-binding-protocol.js +88 -0
- package/dist/dom-binding-signals.d.ts +21 -0
- package/dist/dom-binding-signals.js +109 -0
- package/dist/dom-binding-styles.d.ts +17 -0
- package/dist/dom-binding-styles.js +272 -0
- package/dist/dom-bindings.d.ts +89 -0
- package/dist/dom-bindings.js +670 -0
- package/dist/dom-focused-move.d.ts +6 -0
- package/dist/dom-focused-move.js +42 -0
- package/dist/dom-stage.d.ts +52 -0
- package/dist/dom-stage.js +872 -0
- package/dist/dom-tables.d.ts +1 -3
- package/dist/dom-tables.js +1 -91
- package/dist/error-codes.client.generated.d.ts +9 -0
- package/dist/error-codes.client.generated.js +33 -0
- package/dist/error-codes.server.generated.d.ts +6 -0
- package/dist/error-codes.server.generated.js +24 -0
- package/dist/hydration/control-capture.d.ts +62 -0
- package/dist/hydration/control-capture.js +223 -0
- package/dist/hydration/document-lifecycle.d.ts +23 -0
- package/dist/hydration/document-lifecycle.js +80 -0
- package/dist/hydration/event-capture.d.ts +20 -1
- package/dist/hydration/event-capture.js +128 -42
- package/dist/hydration/independent-island.d.ts +45 -0
- package/dist/hydration/independent-island.js +233 -0
- package/dist/hydration/index.d.ts +8 -1
- package/dist/hydration/index.js +55 -1
- package/dist/hydration/interaction-config.d.ts +5 -0
- package/dist/hydration/interaction-config.js +44 -1
- package/dist/hydration/stream-delivery.d.ts +32 -0
- package/dist/hydration/stream-delivery.js +333 -0
- package/dist/hydration/stream-receiver.d.ts +26 -0
- package/dist/hydration/stream-receiver.js +204 -0
- package/dist/hydration/stream-result-receiver.d.ts +70 -0
- package/dist/hydration/stream-result-receiver.js +246 -0
- package/dist/hydration/streamed-signals.d.ts +44 -0
- package/dist/hydration/streamed-signals.js +186 -0
- package/dist/hydration/types.d.ts +6 -0
- package/dist/hydration-markers.d.ts +7 -0
- package/dist/hydration-markers.js +10 -0
- package/dist/independent-hydration-protocol.d.ts +31 -0
- package/dist/independent-hydration-protocol.js +80 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/internal/signal-read.d.ts +1 -0
- package/dist/internal/signal-read.js +4 -0
- package/dist/jsx-runtime-strong.d.ts +33 -0
- package/dist/jsx-runtime.d.ts +58 -2
- package/dist/public-types.d.ts +15 -1
- package/dist/runtime.d.ts +297 -23
- package/dist/runtime.js +6405 -1679
- package/dist/runtime.server.d.ts +110 -27
- package/dist/runtime.server.js +1458 -213
- package/dist/server/early-signals.d.ts +14 -0
- package/dist/server/early-signals.js +34 -0
- package/dist/server/index.d.ts +11 -2
- package/dist/server/index.js +50 -1
- package/dist/server/rpc-batch.d.ts +17 -0
- package/dist/server/rpc-batch.js +129 -0
- package/dist/server/rpc-stream.d.ts +9 -0
- package/dist/server/rpc-stream.js +125 -0
- package/dist/server/rpc.d.ts +5 -1
- package/dist/server/rpc.js +37 -13
- package/dist/server/signal-query-observation.d.ts +3 -0
- package/dist/server/signal-query-observation.js +134 -0
- package/dist/server/streamed-region.d.ts +15 -0
- package/dist/server/streamed-region.js +30 -0
- package/dist/server/streamed-signals.d.ts +45 -0
- package/dist/server/streamed-signals.js +488 -0
- package/dist/server-call.d.ts +41 -0
- package/dist/server-call.js +75 -0
- package/dist/server-rpc-batch-client.d.ts +7 -0
- package/dist/server-rpc-batch-client.js +119 -0
- package/dist/server-rpc-client.d.ts +3 -1
- package/dist/server-rpc-client.js +99 -27
- package/dist/server-rpc-protocol.d.ts +47 -0
- package/dist/server-rpc-protocol.js +109 -0
- package/dist/server-rpc-stream-client.d.ts +5 -0
- package/dist/server-rpc-stream-client.js +98 -0
- package/dist/signals/actions.d.ts +12 -0
- package/dist/signals/actions.js +466 -0
- package/dist/signals/client.d.ts +2 -0
- package/dist/signals/client.js +1 -0
- package/dist/signals/computations.d.ts +37 -0
- package/dist/signals/computations.js +455 -0
- package/dist/signals/control-binding.d.ts +31 -0
- package/dist/signals/control-binding.js +353 -0
- package/dist/signals/derived-facade.d.ts +3 -0
- package/dist/signals/derived-facade.js +25 -0
- package/dist/signals/document-owner.d.ts +10 -0
- package/dist/signals/document-owner.js +43 -0
- package/dist/signals/early-values.d.ts +24 -0
- package/dist/signals/early-values.js +124 -0
- package/dist/signals/encoding.d.ts +1 -3
- package/dist/signals/encoding.js +3 -93
- package/dist/signals/engine.d.ts +53 -4
- package/dist/signals/engine.js +275 -16
- package/dist/signals/errors.d.ts +7 -0
- package/dist/signals/errors.js +16 -0
- package/dist/signals/facade.d.ts +74 -0
- package/dist/signals/facade.js +452 -0
- package/dist/signals/graph.d.ts +52 -6
- package/dist/signals/graph.js +174 -19
- package/dist/signals/handle-protocol.d.ts +4 -0
- package/dist/signals/handle-protocol.js +11 -0
- package/dist/signals/index.d.ts +12 -3
- package/dist/signals/index.js +71 -2
- package/dist/signals/native-read-client.d.ts +3 -0
- package/dist/signals/native-read-client.js +10 -0
- package/dist/signals/owner-context.d.ts +22 -0
- package/dist/signals/owner-context.js +77 -0
- package/dist/signals/query-attempt-observer.d.ts +49 -0
- package/dist/signals/query-attempt-observer.js +41 -0
- package/dist/signals/query-facade.d.ts +3 -0
- package/dist/signals/query-facade.js +49 -0
- package/dist/signals/read-protocol.d.ts +37 -2
- package/dist/signals/read-protocol.js +44 -0
- package/dist/signals/requests.d.ts +38 -5
- package/dist/signals/requests.js +298 -15
- package/dist/signals/scalar-computations.d.ts +2 -0
- package/dist/signals/scalar-computations.js +60 -0
- package/dist/signals/server.d.ts +2 -0
- package/dist/signals/server.js +1 -0
- package/dist/signals/transition-candidate.d.ts +71 -0
- package/dist/signals/transition-candidate.js +557 -0
- package/dist/signals/transition-state.d.ts +51 -0
- package/dist/signals/transition-state.js +70 -0
- package/dist/signals/types.d.ts +95 -5
- package/dist/signals/types.js +20 -0
- package/dist/stream-protocol.d.ts +0 -5
- package/dist/stream-protocol.js +3 -0
- package/dist/streamed-signals-protocol.d.ts +60 -0
- package/dist/streamed-signals-protocol.js +130 -0
- package/dist/style-values.d.ts +3 -0
- package/dist/style-values.js +96 -0
- package/dist/trusted-html.d.ts +17 -0
- package/dist/trusted-html.js +6 -0
- package/dist/universal-core.d.ts +6 -0
- package/dist/universal-core.js +214 -202
- package/dist/version.js +1 -1
- package/package.json +48 -5
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { createLexicalAnalysis } from './compile-universal.js';
|
|
2
|
+
import {
|
|
3
|
+
checkStrongHTMLValue,
|
|
4
|
+
strongHTMLSpreadWriter,
|
|
5
|
+
STRONG_UNTRUSTED_HTML,
|
|
6
|
+
STRONG_HTML_MESSAGE,
|
|
7
|
+
mayHaveStrongHTML,
|
|
8
|
+
} from './strong-html.js';
|
|
9
|
+
import { createRendererRegionResolver } from './renderer-boundaries.js';
|
|
10
|
+
|
|
11
|
+
const COMPAT_EXPORTS = new Set(['flushSync', 'unstable_batchedUpdates', 'StrictMode']);
|
|
12
|
+
const SUPPRESSION_PROPS = new Set(['suppressHydrationWarning', 'suppressNativeChangeWarning']);
|
|
13
|
+
const FUNCTIONS = new Set(['ArrowFunctionExpression', 'FunctionExpression', 'FunctionDeclaration']);
|
|
14
|
+
const SKIP_KEYS = new Set(['type', 'loc', 'start', 'end', 'range', 'metadata', 'parent']);
|
|
15
|
+
|
|
16
|
+
// These checks share the Strong visitor's lexical bindings. The policy itself
|
|
17
|
+
// never annotates the parser tree or changes the output of valid programs.
|
|
18
|
+
export function createStrongTemplatePolicy({
|
|
19
|
+
ast,
|
|
20
|
+
report,
|
|
21
|
+
resolve,
|
|
22
|
+
unwrap,
|
|
23
|
+
callableValue,
|
|
24
|
+
staticPrimitiveValue,
|
|
25
|
+
source,
|
|
26
|
+
filename,
|
|
27
|
+
options,
|
|
28
|
+
}) {
|
|
29
|
+
const pending = [];
|
|
30
|
+
const seen = new WeakMap();
|
|
31
|
+
const jsxReturns = new WeakMap();
|
|
32
|
+
// Import and property names may contain Unicode/hex/identity escapes or
|
|
33
|
+
// quoted line continuations. Newline string escapes cannot spell these names.
|
|
34
|
+
const mayHaveHTML =
|
|
35
|
+
mayHaveStrongHTML(source) &&
|
|
36
|
+
(source.includes('createElement') || /\\(?:u|x|[ceaElm]|[\r\n\u2028\u2029])/.test(source));
|
|
37
|
+
const templateDialect = /\.tsrx(?:[?#]|$)/.test(filename);
|
|
38
|
+
const domAt =
|
|
39
|
+
options.rendererRegionResolver ?? createRendererRegionResolver(ast, source, filename, options);
|
|
40
|
+
function add(code, node, message) {
|
|
41
|
+
let codes = seen.get(node);
|
|
42
|
+
if (codes?.has(code)) return;
|
|
43
|
+
if (!codes) seen.set(node, (codes = new Set()));
|
|
44
|
+
codes.add(code);
|
|
45
|
+
pending.push({ code, node, message });
|
|
46
|
+
}
|
|
47
|
+
function checkKey(expression, scope) {
|
|
48
|
+
const node = unwrap(expression);
|
|
49
|
+
if (!node || typeof node !== 'object' || FUNCTIONS.has(node.type)) return;
|
|
50
|
+
if (Array.isArray(node)) {
|
|
51
|
+
for (const child of node) checkKey(child, scope);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (node.type === 'Identifier' && resolve(scope, node.name)?.strongIndex === true) {
|
|
55
|
+
add(
|
|
56
|
+
'OCTANE_STRONG_INDEX_KEY',
|
|
57
|
+
node,
|
|
58
|
+
'An `@for` key must identify the item independently of its position. Use a stable item ID instead of the loop index.',
|
|
59
|
+
);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Looking up an item by position does not make the returned identity a
|
|
63
|
+
// positional key. Only the receiver, not the lookup index, contributes.
|
|
64
|
+
if (node.type === 'MemberExpression') {
|
|
65
|
+
checkKey(node.object, scope);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (node.type === 'Property') {
|
|
69
|
+
checkKey(node.value, scope);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const key in node)
|
|
73
|
+
if (!SKIP_KEYS.has(key) && !key.startsWith('_octane')) checkKey(node[key], scope);
|
|
74
|
+
}
|
|
75
|
+
function expressionHasJSX(node, aliasIsJSX) {
|
|
76
|
+
const value = unwrap(node);
|
|
77
|
+
if (!value || FUNCTIONS.has(value.type)) return false;
|
|
78
|
+
if (value.type === 'Identifier' && aliasIsJSX) return aliasIsJSX(value);
|
|
79
|
+
if (
|
|
80
|
+
value.type === 'JSXElement' ||
|
|
81
|
+
value.type === 'JSXFragment' ||
|
|
82
|
+
value.type === 'JSXCodeBlock'
|
|
83
|
+
)
|
|
84
|
+
return true;
|
|
85
|
+
if (value.type === 'ConditionalExpression')
|
|
86
|
+
return (
|
|
87
|
+
expressionHasJSX(value.consequent, aliasIsJSX) ||
|
|
88
|
+
expressionHasJSX(value.alternate, aliasIsJSX)
|
|
89
|
+
);
|
|
90
|
+
if (value.type === 'LogicalExpression')
|
|
91
|
+
return expressionHasJSX(value.left, aliasIsJSX) || expressionHasJSX(value.right, aliasIsJSX);
|
|
92
|
+
if (value.type === 'SequenceExpression')
|
|
93
|
+
return expressionHasJSX(value.expressions.at(-1), aliasIsJSX);
|
|
94
|
+
if (value.type === 'ArrayExpression')
|
|
95
|
+
return value.elements.some((element) => expressionHasJSX(element, aliasIsJSX));
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
function returnsJSX(fn) {
|
|
99
|
+
if (jsxReturns.has(fn)) return jsxReturns.get(fn);
|
|
100
|
+
const constants = [];
|
|
101
|
+
const returnValues = [];
|
|
102
|
+
function returned(value) {
|
|
103
|
+
if (!value || typeof value !== 'object') return false;
|
|
104
|
+
if (Array.isArray(value)) return value.some(returned);
|
|
105
|
+
if (
|
|
106
|
+
FUNCTIONS.has(value.type) ||
|
|
107
|
+
value.type === 'ClassDeclaration' ||
|
|
108
|
+
value.type === 'ClassExpression'
|
|
109
|
+
)
|
|
110
|
+
return false;
|
|
111
|
+
if (value.type === 'ReturnStatement') {
|
|
112
|
+
returnValues.push(value.argument);
|
|
113
|
+
return expressionHasJSX(value.argument);
|
|
114
|
+
}
|
|
115
|
+
if (value.type === 'VariableDeclaration' && value.kind === 'const')
|
|
116
|
+
constants.push(...value.declarations);
|
|
117
|
+
for (const key in value)
|
|
118
|
+
if (!SKIP_KEYS.has(key) && !key.startsWith('_octane') && returned(value[key])) return true;
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
let result = fn.body.type === 'BlockStatement' ? returned(fn.body) : expressionHasJSX(fn.body);
|
|
122
|
+
// Resolve local const aliases only when a mapper contains a JSX-valued
|
|
123
|
+
// initializer. Ordinary data maps pay no additional lexical analysis.
|
|
124
|
+
if (!result && constants.some((declaration) => expressionHasJSX(declaration.init))) {
|
|
125
|
+
const lexical = createLexicalAnalysis(fn);
|
|
126
|
+
const values = new Map();
|
|
127
|
+
for (const declaration of constants) {
|
|
128
|
+
if (declaration.id.type !== 'Identifier') continue;
|
|
129
|
+
const owner = lexical.resolveBinding(
|
|
130
|
+
lexical.nodeScopes.get(declaration.id),
|
|
131
|
+
declaration.id.name,
|
|
132
|
+
)?.scope;
|
|
133
|
+
if (!owner) continue;
|
|
134
|
+
let bindings = values.get(owner);
|
|
135
|
+
if (!bindings) values.set(owner, (bindings = new Map()));
|
|
136
|
+
bindings.set(declaration.id.name, declaration.init);
|
|
137
|
+
}
|
|
138
|
+
const active = new Set();
|
|
139
|
+
function aliasIsJSX(identifier) {
|
|
140
|
+
const owner = lexical.resolveBinding(
|
|
141
|
+
lexical.nodeScopes.get(identifier),
|
|
142
|
+
identifier.name,
|
|
143
|
+
)?.scope;
|
|
144
|
+
const initializer = values.get(owner)?.get(identifier.name);
|
|
145
|
+
if (!initializer || active.has(initializer)) return false;
|
|
146
|
+
active.add(initializer);
|
|
147
|
+
const found = expressionHasJSX(initializer, aliasIsJSX);
|
|
148
|
+
active.delete(initializer);
|
|
149
|
+
return found;
|
|
150
|
+
}
|
|
151
|
+
result = returnValues.some((value) => expressionHasJSX(value, aliasIsJSX));
|
|
152
|
+
}
|
|
153
|
+
jsxReturns.set(fn, result);
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
function callbackReturnsJSX(callback) {
|
|
157
|
+
if (callback?.kind === 'callback') return returnsJSX(callback.node);
|
|
158
|
+
return callback?.kind === 'callback-choice' && callback.values.some(callbackReturnsJSX);
|
|
159
|
+
}
|
|
160
|
+
function suppression(name, node) {
|
|
161
|
+
if (SUPPRESSION_PROPS.has(name))
|
|
162
|
+
add(
|
|
163
|
+
'OCTANE_STRONG_SUPPRESSION_PROP',
|
|
164
|
+
node,
|
|
165
|
+
`Remove \`${name}\` in Strong mode. Fix the hydration mismatch or use native per-edit \`onInput\` handling instead of suppressing the diagnostic.`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
function spreadSuppressions(expression, scope) {
|
|
169
|
+
const value = unwrap(expression);
|
|
170
|
+
if (value?.type === 'ObjectExpression') {
|
|
171
|
+
for (const property of value.properties) {
|
|
172
|
+
if (property.type === 'SpreadElement') spreadSuppressions(property.argument, scope);
|
|
173
|
+
else
|
|
174
|
+
suppression(
|
|
175
|
+
property.computed
|
|
176
|
+
? staticPrimitiveValue(property.key, scope)
|
|
177
|
+
: (property.key.name ?? property.key.value),
|
|
178
|
+
property.key,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
} else if (value?.type === 'ConditionalExpression') {
|
|
182
|
+
spreadSuppressions(value.consequent, scope);
|
|
183
|
+
spreadSuppressions(value.alternate, scope);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function visit(node, scope) {
|
|
187
|
+
if (
|
|
188
|
+
node.type === 'ImportDeclaration' &&
|
|
189
|
+
(node.source?.value === 'octane' || node.source?.value === 'octane/server') &&
|
|
190
|
+
node.importKind !== 'type'
|
|
191
|
+
) {
|
|
192
|
+
for (const specifier of node.specifiers ?? []) {
|
|
193
|
+
const name = specifier.imported?.name ?? specifier.imported?.value;
|
|
194
|
+
if (specifier.importKind !== 'type' && COMPAT_EXPORTS.has(name))
|
|
195
|
+
add(
|
|
196
|
+
'OCTANE_STRONG_COMPAT_IMPORT',
|
|
197
|
+
specifier,
|
|
198
|
+
`\`${name}\` is a compatibility API. Strong mode uses Octane's normal scheduling and component semantics; remove this import.`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
} else if (node.type === 'VariableDeclaration') {
|
|
202
|
+
for (const declaration of node.declarations) {
|
|
203
|
+
const value = unwrap(declaration.init);
|
|
204
|
+
if (
|
|
205
|
+
declaration.id.type !== 'ObjectPattern' ||
|
|
206
|
+
value?.type !== 'Identifier' ||
|
|
207
|
+
resolve(scope, value.name)?.kind !== 'namespace'
|
|
208
|
+
)
|
|
209
|
+
continue;
|
|
210
|
+
for (const property of declaration.id.properties) {
|
|
211
|
+
if (property.type !== 'Property') continue;
|
|
212
|
+
const name = property.computed
|
|
213
|
+
? staticPrimitiveValue(property.key, scope)
|
|
214
|
+
: (property.key.name ?? property.key.value);
|
|
215
|
+
if (COMPAT_EXPORTS.has(name))
|
|
216
|
+
add(
|
|
217
|
+
'OCTANE_STRONG_COMPAT_IMPORT',
|
|
218
|
+
property.key,
|
|
219
|
+
`\`${name}\` is a compatibility API. Remove this access in Strong mode.`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} else if (node.type === 'MemberExpression') {
|
|
224
|
+
const object = unwrap(node.object);
|
|
225
|
+
const name = node.computed ? staticPrimitiveValue(node.property, scope) : node.property?.name;
|
|
226
|
+
if (
|
|
227
|
+
object?.type === 'Identifier' &&
|
|
228
|
+
resolve(scope, object.name)?.kind === 'namespace' &&
|
|
229
|
+
COMPAT_EXPORTS.has(name)
|
|
230
|
+
)
|
|
231
|
+
add(
|
|
232
|
+
'OCTANE_STRONG_COMPAT_IMPORT',
|
|
233
|
+
node,
|
|
234
|
+
`\`${name}\` is a compatibility API. Remove this access in Strong mode.`,
|
|
235
|
+
);
|
|
236
|
+
} else if (node.type === 'CallExpression') {
|
|
237
|
+
const callee = unwrap(node.callee);
|
|
238
|
+
const calleeBinding =
|
|
239
|
+
mayHaveHTML && callee?.type === 'Identifier' ? resolve(scope, callee.name) : null;
|
|
240
|
+
const receiver = callee?.type === 'MemberExpression' ? unwrap(callee.object) : null;
|
|
241
|
+
const name =
|
|
242
|
+
callee?.type === 'MemberExpression'
|
|
243
|
+
? callee.computed
|
|
244
|
+
? staticPrimitiveValue(callee.property, scope)
|
|
245
|
+
: callee.property?.name
|
|
246
|
+
: null;
|
|
247
|
+
const isElementFactory =
|
|
248
|
+
mayHaveHTML &&
|
|
249
|
+
((calleeBinding?.kind === 'hook' && calleeBinding.hook === 'createElement') ||
|
|
250
|
+
(name === 'createElement' &&
|
|
251
|
+
receiver?.type === 'Identifier' &&
|
|
252
|
+
resolve(scope, receiver.name)?.kind === 'namespace'));
|
|
253
|
+
if (
|
|
254
|
+
isElementFactory &&
|
|
255
|
+
typeof staticPrimitiveValue(node.arguments[0], scope) === 'string' &&
|
|
256
|
+
domAt(node)
|
|
257
|
+
) {
|
|
258
|
+
checkStrongHTMLValue(strongHTMLSpreadWriter(node.arguments[1]), (value) =>
|
|
259
|
+
add(STRONG_UNTRUSTED_HTML, value, STRONG_HTML_MESSAGE),
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
if (
|
|
263
|
+
templateDialect &&
|
|
264
|
+
callee?.type === 'MemberExpression' &&
|
|
265
|
+
name === 'map' &&
|
|
266
|
+
callbackReturnsJSX(callableValue(node.arguments?.[0], scope))
|
|
267
|
+
)
|
|
268
|
+
add(
|
|
269
|
+
'OCTANE_STRONG_MAP_JSX',
|
|
270
|
+
node,
|
|
271
|
+
'Use a keyed `@for` block to render a list instead of `.map()` returning JSX. Choose a stable item identity for its key.',
|
|
272
|
+
);
|
|
273
|
+
} else if (node.type === 'JSXOpeningElement') {
|
|
274
|
+
let root = node.name;
|
|
275
|
+
let member = null;
|
|
276
|
+
while (root?.type === 'JSXMemberExpression') {
|
|
277
|
+
member = root;
|
|
278
|
+
root = root.object;
|
|
279
|
+
}
|
|
280
|
+
if (
|
|
281
|
+
member &&
|
|
282
|
+
root?.type === 'JSXIdentifier' &&
|
|
283
|
+
resolve(scope, root.name)?.kind === 'namespace' &&
|
|
284
|
+
COMPAT_EXPORTS.has(member.property.name)
|
|
285
|
+
)
|
|
286
|
+
add(
|
|
287
|
+
'OCTANE_STRONG_COMPAT_IMPORT',
|
|
288
|
+
member,
|
|
289
|
+
`\`${member.property.name}\` is a compatibility API. Remove this access in Strong mode.`,
|
|
290
|
+
);
|
|
291
|
+
if (node.name?.type !== 'JSXIdentifier' || !/^[a-z]/.test(node.name.name) || !domAt(node))
|
|
292
|
+
return;
|
|
293
|
+
for (const attribute of node.attributes ?? []) {
|
|
294
|
+
if (attribute.type === 'JSXSpreadAttribute') spreadSuppressions(attribute.argument, scope);
|
|
295
|
+
else suppression(attribute.name?.name, attribute.name);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
visit,
|
|
301
|
+
enterKey: checkKey,
|
|
302
|
+
exitKey() {},
|
|
303
|
+
finish() {
|
|
304
|
+
for (const { code, node, message } of pending) report(code, node, message);
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Template IR + origin recording (`inspect: true`). Static template HTML is
|
|
3
|
+
// runtime DATA, not JavaScript syntax: the browser-facing `template(html)` ABI
|
|
4
|
+
// intentionally stays a string. The compiler represents that data as a
|
|
5
|
+
// structured tree of elements and typed data chunks until allocTemplate
|
|
6
|
+
// serializes the completed template exactly once. Child templates compose by
|
|
7
|
+
// transferring nodes, so no intermediate HTML is joined and no origin offsets
|
|
8
|
+
// need to be shifted during construction. Origin spans remain local to their
|
|
9
|
+
// chunk; the one serializer shifts them into final logical-HTML coordinates.
|
|
10
|
+
//
|
|
11
|
+
// Origin collection stays gated on `ctx.inspect`. The normal compile path pays
|
|
12
|
+
// for the template chunks (the structural representation replacing string
|
|
13
|
+
// concatenation) but not for source-span arrays.
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {{ start: number, end: number, srcStart: number, srcEnd: number,
|
|
18
|
+
* kind: 'tag-open' | 'tag-close' | 'attr-name' | 'attr-value' | 'text' }} TemplateOrigin
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {'syntax' | 'tag-open' | 'tag-close' | 'attribute' | 'text' |
|
|
23
|
+
* 'anchor' | 'fragment-open' | 'fragment-close' | 'raw'} TemplatePartKind
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {{
|
|
28
|
+
* type: 'TemplatePart',
|
|
29
|
+
* kind: TemplatePartKind,
|
|
30
|
+
* value: string,
|
|
31
|
+
* origins: TemplateOrigin[] | null,
|
|
32
|
+
* length: number,
|
|
33
|
+
* }} TemplatePart
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {{
|
|
38
|
+
* type: 'TemplateElement',
|
|
39
|
+
* tag: string,
|
|
40
|
+
* namespace: string,
|
|
41
|
+
* synthetic: boolean,
|
|
42
|
+
* opening: TemplatePart,
|
|
43
|
+
* attributes: TemplatePart[],
|
|
44
|
+
* openingEnd: TemplatePart,
|
|
45
|
+
* children: TemplateNode[],
|
|
46
|
+
* closing: TemplatePart | null,
|
|
47
|
+
* length: number,
|
|
48
|
+
* }} TemplateElement
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/** @typedef {TemplatePart | TemplateElement} TemplateNode */
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @typedef {{
|
|
55
|
+
* type: 'Template',
|
|
56
|
+
* parts: TemplateNode[],
|
|
57
|
+
* length: number,
|
|
58
|
+
* }} TemplateIR
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/** @returns {TemplateIR} */
|
|
62
|
+
export function createTemplateIr() {
|
|
63
|
+
return { type: 'Template', parts: [], length: 0 };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} value
|
|
68
|
+
* @param {TemplatePartKind} [kind]
|
|
69
|
+
* @param {TemplateOrigin[] | null} [origins]
|
|
70
|
+
* @returns {TemplatePart}
|
|
71
|
+
*/
|
|
72
|
+
export function createTemplatePart(value, kind = 'raw', origins = null) {
|
|
73
|
+
return { type: 'TemplatePart', kind, value, origins, length: value.length };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @param {TemplateIR} template @param {TemplateNode} node */
|
|
77
|
+
export function appendTemplateNode(template, node) {
|
|
78
|
+
template.parts.push(node);
|
|
79
|
+
template.length += node.length;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {TemplateIR} template
|
|
84
|
+
* @param {string} value
|
|
85
|
+
* @param {TemplatePartKind} [kind]
|
|
86
|
+
* @param {TemplateOrigin[] | null} [origins]
|
|
87
|
+
*/
|
|
88
|
+
export function appendTemplatePart(template, value, kind = 'raw', origins = null) {
|
|
89
|
+
if (value === '') return;
|
|
90
|
+
appendTemplateNode(template, createTemplatePart(value, kind, origins));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** @param {TemplateIR} into @param {TemplateIR} child */
|
|
94
|
+
export function appendTemplateIr(into, child) {
|
|
95
|
+
if (child.length === 0) return;
|
|
96
|
+
for (const part of child.parts) into.parts.push(part);
|
|
97
|
+
into.length += child.length;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {string} value
|
|
102
|
+
* @param {TemplatePartKind} [kind]
|
|
103
|
+
* @param {TemplateOrigin[] | null} [origins]
|
|
104
|
+
* @returns {TemplateIR}
|
|
105
|
+
*/
|
|
106
|
+
export function templatePart(value, kind = 'raw', origins = null) {
|
|
107
|
+
const template = createTemplateIr();
|
|
108
|
+
appendTemplatePart(template, value, kind, origins);
|
|
109
|
+
return template;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {string} tag
|
|
114
|
+
* @param {string} namespace
|
|
115
|
+
* @param {boolean} voidElement
|
|
116
|
+
* @param {TemplateIR} attributes
|
|
117
|
+
* @param {TemplateIR} children
|
|
118
|
+
* @param {TemplateOrigin[] | null} openOrigins
|
|
119
|
+
* @param {TemplateOrigin[] | null} closeOrigins
|
|
120
|
+
* @param {boolean} [synthetic]
|
|
121
|
+
* @returns {TemplateIR}
|
|
122
|
+
*/
|
|
123
|
+
export function templateElement(
|
|
124
|
+
tag,
|
|
125
|
+
namespace,
|
|
126
|
+
voidElement,
|
|
127
|
+
attributes,
|
|
128
|
+
children,
|
|
129
|
+
openOrigins,
|
|
130
|
+
closeOrigins,
|
|
131
|
+
synthetic = false,
|
|
132
|
+
) {
|
|
133
|
+
const opening = createTemplatePart(`<${tag}`, 'tag-open', openOrigins);
|
|
134
|
+
const openingEnd = createTemplatePart(voidElement ? '/>' : '>', 'syntax');
|
|
135
|
+
const closing = voidElement ? null : createTemplatePart(`</${tag}>`, 'tag-close', closeOrigins);
|
|
136
|
+
const node = {
|
|
137
|
+
type: 'TemplateElement',
|
|
138
|
+
tag,
|
|
139
|
+
namespace,
|
|
140
|
+
synthetic,
|
|
141
|
+
opening,
|
|
142
|
+
attributes: /** @type {TemplatePart[]} */ (attributes.parts),
|
|
143
|
+
openingEnd,
|
|
144
|
+
children: children.parts,
|
|
145
|
+
closing,
|
|
146
|
+
length:
|
|
147
|
+
opening.value.length +
|
|
148
|
+
attributes.length +
|
|
149
|
+
openingEnd.value.length +
|
|
150
|
+
children.length +
|
|
151
|
+
(closing === null ? 0 : closing.value.length),
|
|
152
|
+
};
|
|
153
|
+
const template = createTemplateIr();
|
|
154
|
+
appendTemplateNode(template, node);
|
|
155
|
+
return template;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Serialize the completed template once. The IR already contains the exact
|
|
160
|
+
* runtime bytes; this pass only joins them and converts chunk-local origins to
|
|
161
|
+
* final logical-HTML offsets.
|
|
162
|
+
*
|
|
163
|
+
* @param {TemplateIR} template
|
|
164
|
+
*/
|
|
165
|
+
export function serializeTemplateIr(template) {
|
|
166
|
+
const values = [];
|
|
167
|
+
let origins = null;
|
|
168
|
+
let offset = 0;
|
|
169
|
+
const appendPart = (part) => {
|
|
170
|
+
values.push(part.value);
|
|
171
|
+
if (part.origins !== null) {
|
|
172
|
+
origins ??= [];
|
|
173
|
+
for (const origin of part.origins) {
|
|
174
|
+
origins.push({
|
|
175
|
+
start: origin.start + offset,
|
|
176
|
+
end: origin.end + offset,
|
|
177
|
+
srcStart: origin.srcStart,
|
|
178
|
+
srcEnd: origin.srcEnd,
|
|
179
|
+
kind: origin.kind,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
offset += part.value.length;
|
|
184
|
+
};
|
|
185
|
+
const visit = (node) => {
|
|
186
|
+
if (node.type === 'TemplatePart') {
|
|
187
|
+
appendPart(node);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
appendPart(node.opening);
|
|
191
|
+
for (const attribute of node.attributes) appendPart(attribute);
|
|
192
|
+
appendPart(node.openingEnd);
|
|
193
|
+
for (const child of node.children) visit(child);
|
|
194
|
+
if (node.closing !== null) appendPart(node.closing);
|
|
195
|
+
};
|
|
196
|
+
for (const node of template.parts) {
|
|
197
|
+
visit(node);
|
|
198
|
+
}
|
|
199
|
+
return { html: values.join(''), origins };
|
|
200
|
+
}
|
|
@@ -23,6 +23,8 @@ export interface TextTypeProjectOptions {
|
|
|
23
23
|
root?: string;
|
|
24
24
|
/** The same renderer configuration used by the Octane compiler. */
|
|
25
25
|
renderers?: unknown;
|
|
26
|
+
/** The same native attribute contracts used by runtime compilation. */
|
|
27
|
+
knownAttributeSpreads?: readonly import('./index.js').KnownAttributeSpread[];
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export interface TextTypeProject {
|
|
@@ -421,6 +421,7 @@ export function createTextTypeProject(options) {
|
|
|
421
421
|
try {
|
|
422
422
|
compilation = compileToVolarMappings(source, rendererFilename(file, rendererRoot), {
|
|
423
423
|
renderers,
|
|
424
|
+
knownAttributeSpreads: options.knownAttributeSpreads,
|
|
424
425
|
});
|
|
425
426
|
} catch {
|
|
426
427
|
// Broken authored syntax is not evidence. Passing the authored text
|
|
@@ -495,6 +496,7 @@ export function createTextTypeProject(options) {
|
|
|
495
496
|
options: program.getCompilerOptions(),
|
|
496
497
|
references: loadConfig().projectReferences ?? [],
|
|
497
498
|
renderers: renderers.signature,
|
|
499
|
+
knownAttributeSpreads: options.knownAttributeSpreads,
|
|
498
500
|
rendererRoot,
|
|
499
501
|
roots: roots(),
|
|
500
502
|
inputs,
|
package/dist/compiler/vite.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
|
-
import type { OctaneCssModuleConstants } from './index.js';
|
|
2
|
+
import type { KnownAttributeSpread, OctaneCssModuleConstants } from './index.js';
|
|
3
3
|
|
|
4
4
|
export type { OctaneCssModuleConstants } from './index.js';
|
|
5
5
|
|
|
@@ -87,6 +87,8 @@ export interface OctaneVitePluginOptions {
|
|
|
87
87
|
* @default false
|
|
88
88
|
*/
|
|
89
89
|
strong?: boolean;
|
|
90
|
+
/** Trusted adapter output shapes; imported calls keep one evaluation per native host. */
|
|
91
|
+
knownAttributeSpreads?: readonly KnownAttributeSpread[];
|
|
90
92
|
/**
|
|
91
93
|
* @experimental Infer primitive child text from a TypeScript project in
|
|
92
94
|
* one-shot production builds. Serve and watched builds retain syntax
|