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.
@@ -0,0 +1,3 @@
1
+ export { compile } from './compile.js';
2
+ export { octane } from './vite.js';
3
+ export { compileToVolarMappings } from './volar.js';
@@ -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
+ }
@@ -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';