octane 0.1.1
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/LICENSE +21 -0
- package/README.md +20 -0
- package/package.json +50 -0
- package/src/compiler/compile.js +5076 -0
- package/src/compiler/index.js +3 -0
- package/src/compiler/vite.js +47 -0
- package/src/compiler/volar.js +121 -0
- package/src/constants.ts +45 -0
- package/src/index.ts +102 -0
- package/src/runtime.server.ts +586 -0
- package/src/runtime.ts +6740 -0
- package/src/server/index.ts +59 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin for compiling .tsrx files via octane-ts/compiler compiler.
|
|
3
|
+
*
|
|
4
|
+
* Per-module target is chosen from Vite's SSR signal: a module compiled for the
|
|
5
|
+
* server environment uses `mode: 'server'` (SSR HTML output), everything else
|
|
6
|
+
* uses `mode: 'client'` (template-clone DOM runtime). This auto-detection is
|
|
7
|
+
* what a standard Vite SSR setup relies on — the SAME `.tsrx` is compiled to
|
|
8
|
+
* client code for the browser bundle and to server code when loaded through
|
|
9
|
+
* `ssrLoadModule` / an SSR build (see playground/octane-ssr).
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* - `ssr`: force the target for EVERY module — `true` always compiles server
|
|
13
|
+
* mode, `false` always client. Leave it unset (the default) to use the
|
|
14
|
+
* per-module auto-detection above. Useful for a dedicated server build.
|
|
15
|
+
* - `hmr`: defaults to on in serve mode and is always off for SSR; pass
|
|
16
|
+
* `true`/`false` to override the client default.
|
|
17
|
+
*/
|
|
18
|
+
import { compile } from './compile.js';
|
|
19
|
+
|
|
20
|
+
export function octane(options = {}) {
|
|
21
|
+
let hmrEnabled = options.hmr;
|
|
22
|
+
// An explicit override of the per-module SSR auto-detection (true → always
|
|
23
|
+
// server, false → always client). `undefined` keeps auto-detection.
|
|
24
|
+
const forceSsr = options.ssr;
|
|
25
|
+
return {
|
|
26
|
+
name: 'octane',
|
|
27
|
+
enforce: 'pre',
|
|
28
|
+
configResolved(config) {
|
|
29
|
+
if (hmrEnabled === undefined) hmrEnabled = config.command === 'serve';
|
|
30
|
+
},
|
|
31
|
+
transform(code, id, transformOptions) {
|
|
32
|
+
if (!id.endsWith('.tsrx')) return null;
|
|
33
|
+
// Mirror Ripple's mode decision: an explicit `options.ssr` override wins;
|
|
34
|
+
// otherwise Vite's transform-level SSR flag OR the environment consumer
|
|
35
|
+
// (Vite 6+ environment API) marks a server build.
|
|
36
|
+
const ssr =
|
|
37
|
+
forceSsr !== undefined
|
|
38
|
+
? forceSsr
|
|
39
|
+
: transformOptions?.ssr === true || this.environment?.config?.consumer === 'server';
|
|
40
|
+
const out = compile(code, id, {
|
|
41
|
+
hmr: !ssr && !!hmrEnabled,
|
|
42
|
+
mode: ssr ? 'server' : 'client',
|
|
43
|
+
});
|
|
44
|
+
return { code: out.code, map: out.map };
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Volar (IDE language-service) mappings for octane .tsrx files.
|
|
3
|
+
*
|
|
4
|
+
* Editors load this entry point to get a TYPED virtual TSX file the
|
|
5
|
+
* TypeScript language service can analyse — hover, autocomplete, go-to-def,
|
|
6
|
+
* diagnostics — without ever running our template-clone codegen. The TSX
|
|
7
|
+
* output is intentionally NOT the same shape as the runtime emit produced
|
|
8
|
+
* by `compile()`: it's a parallel pipeline that runs `@tsrx/core`'s shared
|
|
9
|
+
* `createJsxTransform` (the same machinery that powers tsrx-react / tsrx-
|
|
10
|
+
* preact's Volar paths) in `typeOnly: true` mode.
|
|
11
|
+
*
|
|
12
|
+
* Caller contract:
|
|
13
|
+
* - Input: the original .tsrx source string + filename.
|
|
14
|
+
* - Output: a `VolarMappingsResult` (see @tsrx/core/types) containing
|
|
15
|
+
* `code` (generated TSX), `mappings` (per-token offsets the language
|
|
16
|
+
* server uses to translate position queries from .tsrx → virtual TSX
|
|
17
|
+
* and back), `cssMappings`, `errors`, and the source AST.
|
|
18
|
+
*
|
|
19
|
+
* Why a separate file: `compile.js` is the runtime-codegen path and ships
|
|
20
|
+
* to every consumer (Vite plugin, build pipeline). The Volar path pulls in
|
|
21
|
+
* extra @tsrx/core surface (`createJsxTransform`, `createVolarMappingsResult`,
|
|
22
|
+
* `dedupeMappings`) that build-time consumers don't need; isolating it
|
|
23
|
+
* keeps the runtime build small.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
createJsxTransform,
|
|
28
|
+
createVolarMappingsResult,
|
|
29
|
+
dedupeMappings,
|
|
30
|
+
parseModule,
|
|
31
|
+
} from '@tsrx/core';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Platform descriptor for `createJsxTransform`. Mirrors `tsrx-react`'s React
|
|
35
|
+
* descriptor with the small set of differences for octane:
|
|
36
|
+
*
|
|
37
|
+
* - `imports.errorBoundary` / `imports.dynamic` point at octane
|
|
38
|
+
* itself (we don't ship separate sub-packages for these — the runtime
|
|
39
|
+
* exports them directly).
|
|
40
|
+
* - `jsx.classAttrName: 'class'` because octane keeps authored
|
|
41
|
+
* `class` instead of rewriting to React's `className`.
|
|
42
|
+
* - `jsx.multiRefStrategy: 'array'` — the octane runtime accepts a
|
|
43
|
+
* plain array of refs natively (see the multi-ref attribute path in
|
|
44
|
+
* `src/runtime.ts`'s ref binding), so no `mergeRefs` helper is needed.
|
|
45
|
+
* - `validation.requireUseServerForAwait: false` — no server-component
|
|
46
|
+
* concept in octane (no top-level await validation gates).
|
|
47
|
+
*
|
|
48
|
+
* `imports.suspense` and `imports.fragment` aren't real components in
|
|
49
|
+
* octane (we lower `@try`/`@pending` to `tryBlock` and fragments to
|
|
50
|
+
* concrete templates), but the descriptor still needs a value because the
|
|
51
|
+
* shared transform emits TSX-level `<Fragment>` / `<Suspense>` wrappers
|
|
52
|
+
* when running in TSX mode. We point them at `octane` so editors at
|
|
53
|
+
* least don't fail to resolve the imports; users won't actually see those
|
|
54
|
+
* names in source. (Volar TSX is virtual — its imports never run.)
|
|
55
|
+
*/
|
|
56
|
+
const OCTANE_PLATFORM = {
|
|
57
|
+
name: 'octane',
|
|
58
|
+
imports: {
|
|
59
|
+
fragment: 'octane-ts',
|
|
60
|
+
suspense: 'octane-ts',
|
|
61
|
+
dynamic: 'octane-ts',
|
|
62
|
+
errorBoundary: 'octane-ts',
|
|
63
|
+
forOfIterableHelper: '@tsrx/core/runtime/iterable',
|
|
64
|
+
},
|
|
65
|
+
jsx: {
|
|
66
|
+
rewriteClassAttr: false,
|
|
67
|
+
classAttrName: 'class',
|
|
68
|
+
multiRefStrategy: 'array',
|
|
69
|
+
},
|
|
70
|
+
validation: {
|
|
71
|
+
requireUseServerForAwait: false,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const octaneTransform = createJsxTransform(OCTANE_PLATFORM);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Compile a .tsrx source string to a Volar `VolarMappingsResult`.
|
|
79
|
+
*
|
|
80
|
+
* Parse → JSX transform (typeOnly) → wrap as Volar payload. We always run
|
|
81
|
+
* with `collect: true` so the parser records errors instead of throwing
|
|
82
|
+
* mid-pipeline; that way a syntactically-broken file still produces a
|
|
83
|
+
* partial virtual TSX the language server can show diagnostics against.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} source
|
|
86
|
+
* @param {string} [filename]
|
|
87
|
+
* @param {{ loose?: boolean }} [options]
|
|
88
|
+
* @returns {import('@tsrx/core/types').VolarMappingsResult}
|
|
89
|
+
*/
|
|
90
|
+
export function compileToVolarMappings(source, filename, options) {
|
|
91
|
+
/** @type {import('@tsrx/core/types').CompileError[]} */
|
|
92
|
+
const errors = [];
|
|
93
|
+
/** @type {import('@tsrx/core/types').AST.CommentWithLocation[]} */
|
|
94
|
+
const comments = [];
|
|
95
|
+
const ast = parseModule(source, filename, {
|
|
96
|
+
...options,
|
|
97
|
+
collect: true,
|
|
98
|
+
loose: !!options?.loose,
|
|
99
|
+
errors,
|
|
100
|
+
comments,
|
|
101
|
+
});
|
|
102
|
+
const transformed = octaneTransform(ast, source, filename, {
|
|
103
|
+
collect: true,
|
|
104
|
+
loose: !!options?.loose,
|
|
105
|
+
typeOnly: true,
|
|
106
|
+
errors,
|
|
107
|
+
comments,
|
|
108
|
+
});
|
|
109
|
+
const result = createVolarMappingsResult({
|
|
110
|
+
ast: transformed.ast,
|
|
111
|
+
ast_from_source: ast,
|
|
112
|
+
source,
|
|
113
|
+
generated_code: transformed.code,
|
|
114
|
+
source_map: transformed.map,
|
|
115
|
+
errors,
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
...result,
|
|
119
|
+
mappings: dedupeMappings(result.mappings),
|
|
120
|
+
};
|
|
121
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hydration marker protocol — the single source of truth shared by the server
|
|
3
|
+
* emit (the `octane-ts/compiler` compiler's server mode) and the client `hydrate`
|
|
4
|
+
* runtime. The server writes these comment markers into the HTML it produces and
|
|
5
|
+
* the client hydration cursor scans for them to align with the server output, so
|
|
6
|
+
* BOTH sides must use byte-identical strings or hydration fails.
|
|
7
|
+
*
|
|
8
|
+
* Values follow the Svelte/Ripple convention (`[` open, `]` close) so the
|
|
9
|
+
* protocol is familiar and the marker comments are compact.
|
|
10
|
+
*
|
|
11
|
+
* Nothing emits these yet — SSR codegen and the hydrate runtime are built in
|
|
12
|
+
* later phases of the SSR plan. This module is the shared home they'll import.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Single-character payload of a block-open comment. */
|
|
16
|
+
export const HYDRATION_START = '[';
|
|
17
|
+
/** Single-character payload of a block-close comment. */
|
|
18
|
+
export const HYDRATION_END = ']';
|
|
19
|
+
|
|
20
|
+
/** Opens a hydratable block (component output / control-flow branch). */
|
|
21
|
+
export const BLOCK_OPEN = `<!--${HYDRATION_START}-->`;
|
|
22
|
+
/** Closes a hydratable block. */
|
|
23
|
+
export const BLOCK_CLOSE = `<!--${HYDRATION_END}-->`;
|
|
24
|
+
/** A bare anchor comment used where the client would otherwise clone a `<!>`. */
|
|
25
|
+
export const EMPTY_COMMENT = '<!---->';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Marker attribute on the inline `<script type="application/json">` that the
|
|
29
|
+
* server emits to carry the JSON-serialized `use(thenable)` values it resolved
|
|
30
|
+
* during render (SSR Phase 4 — Suspense). The client `hydrate()` finds this
|
|
31
|
+
* script by attribute, parses it, and seeds the values back into `use()` (in
|
|
32
|
+
* render order) so a hydrating boundary returns synchronously instead of
|
|
33
|
+
* re-suspending. Shared so server emit and client read stay byte-identical.
|
|
34
|
+
*/
|
|
35
|
+
export const SUSPENSE_SCRIPT_ATTR = 'data-octane-suspense';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Sentinel marker for a `use(thenable)` value that resolved to `undefined`.
|
|
39
|
+
* JSON can't represent `undefined` (an array element round-trips to `null`, an
|
|
40
|
+
* object property is dropped), so the server's seed serializer encodes any
|
|
41
|
+
* `undefined` as `{ [UNDEFINED_SENTINEL_KEY]: true }` and the client's parser
|
|
42
|
+
* reviver decodes it back to `undefined`. Shared so both sides agree, and keyed
|
|
43
|
+
* obscurely enough that real resolved data won't collide.
|
|
44
|
+
*/
|
|
45
|
+
export const UNDEFINED_SENTINEL_KEY = '__octane_new_undefined__';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
2
|
+
|
|
3
|
+
// Source the version from package.json so it can't drift from the published
|
|
4
|
+
// package version (the previous hardcoded literal already had).
|
|
5
|
+
export const version: string = pkg.version;
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
// Public API
|
|
9
|
+
createRoot,
|
|
10
|
+
hydrate,
|
|
11
|
+
flushSync,
|
|
12
|
+
drainPassiveEffects,
|
|
13
|
+
act,
|
|
14
|
+
setIsOctaneActEnvironment,
|
|
15
|
+
type Root,
|
|
16
|
+
|
|
17
|
+
// Hooks
|
|
18
|
+
useState,
|
|
19
|
+
useReducer,
|
|
20
|
+
useEffect,
|
|
21
|
+
useLayoutEffect,
|
|
22
|
+
useInsertionEffect,
|
|
23
|
+
useMemo,
|
|
24
|
+
useCallback,
|
|
25
|
+
useRef,
|
|
26
|
+
useId,
|
|
27
|
+
useImperativeHandle,
|
|
28
|
+
useEffectEvent,
|
|
29
|
+
useSyncExternalStore,
|
|
30
|
+
useDeferredValue,
|
|
31
|
+
useTransition,
|
|
32
|
+
useActionState,
|
|
33
|
+
useFormStatus,
|
|
34
|
+
useOptimistic,
|
|
35
|
+
type FormStatus,
|
|
36
|
+
startTransition,
|
|
37
|
+
setTransitionFallbackTimeout,
|
|
38
|
+
getTransitionFallbackTimeout,
|
|
39
|
+
memo,
|
|
40
|
+
|
|
41
|
+
// Context
|
|
42
|
+
createContext,
|
|
43
|
+
provideContext,
|
|
44
|
+
use,
|
|
45
|
+
useContext,
|
|
46
|
+
type Context,
|
|
47
|
+
|
|
48
|
+
// Suspense / error boundaries as JSX components (alongside the @try directive)
|
|
49
|
+
Suspense,
|
|
50
|
+
ErrorBoundary,
|
|
51
|
+
|
|
52
|
+
// HMR (compiler-emitted when the Vite plugin's hmr option is on)
|
|
53
|
+
hmr,
|
|
54
|
+
HMR,
|
|
55
|
+
|
|
56
|
+
// Compiler-emitted runtime helpers
|
|
57
|
+
template,
|
|
58
|
+
clone,
|
|
59
|
+
htext,
|
|
60
|
+
htextSwap,
|
|
61
|
+
child,
|
|
62
|
+
sibling,
|
|
63
|
+
setText,
|
|
64
|
+
setAttribute,
|
|
65
|
+
setClassName,
|
|
66
|
+
setStyle,
|
|
67
|
+
setSpread,
|
|
68
|
+
setFormAction,
|
|
69
|
+
attachRef,
|
|
70
|
+
queueRefAttach,
|
|
71
|
+
injectStyle,
|
|
72
|
+
headBlock,
|
|
73
|
+
delegateEvents,
|
|
74
|
+
forBlock,
|
|
75
|
+
ifBlock,
|
|
76
|
+
tryBlock,
|
|
77
|
+
switchBlock,
|
|
78
|
+
activityBlock,
|
|
79
|
+
Activity,
|
|
80
|
+
componentSlot,
|
|
81
|
+
componentSlotLite,
|
|
82
|
+
childSlot,
|
|
83
|
+
hostComponent,
|
|
84
|
+
Fragment,
|
|
85
|
+
FragmentInstance,
|
|
86
|
+
mountFragmentRef,
|
|
87
|
+
portal,
|
|
88
|
+
createPortal,
|
|
89
|
+
type PortalDescriptor,
|
|
90
|
+
createElement,
|
|
91
|
+
type ElementDescriptor,
|
|
92
|
+
withScope,
|
|
93
|
+
renderBlock,
|
|
94
|
+
createBlock,
|
|
95
|
+
unmountBlock,
|
|
96
|
+
scheduleRender,
|
|
97
|
+
getCurrentScope,
|
|
98
|
+
getCurrentBlock,
|
|
99
|
+
type ComponentBody,
|
|
100
|
+
type Scope,
|
|
101
|
+
type Block,
|
|
102
|
+
} from './runtime';
|