octane 0.1.19 → 0.1.20
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/compiler/bundler.js +38 -0
- package/dist/compiler/compile-universal.js +11 -1
- package/dist/compiler/compile.js +3 -1
- package/dist/compiler/slot-hooks.js +3 -1
- package/dist/compiler/strong-mode.js +1064 -0
- package/dist/compiler/vite.d.ts +7 -0
- package/dist/compiler/vite.js +5 -0
- package/dist/compiler/volar.js +27 -1
- package/dist/runtime.d.ts +4 -1
- package/dist/runtime.js +311 -22
- package/dist/testing.d.ts +8 -0
- package/dist/testing.js +14 -0
- package/package.json +5 -1
package/dist/compiler/bundler.js
CHANGED
|
@@ -31,6 +31,7 @@ import { findLeadingJsxImportSourcePragma } from './pragma.js';
|
|
|
31
31
|
import { normalizeUniversalRuntime } from './universal-runtime.js';
|
|
32
32
|
import { formatCompileDiagnostic } from './native-change-diagnostics.js';
|
|
33
33
|
import { findVoidComponentImports, findVoidRootImports, slotHooks } from './slot-hooks.js';
|
|
34
|
+
import { assertStrongMode } from './strong-mode.js';
|
|
34
35
|
import {
|
|
35
36
|
assertNoLiveClientOnlyImports,
|
|
36
37
|
createClientOnlyServerStub,
|
|
@@ -306,6 +307,9 @@ export function findVoidComponentExports(source, id) {
|
|
|
306
307
|
|
|
307
308
|
class OctaneBundlerCompiler {
|
|
308
309
|
constructor(options) {
|
|
310
|
+
if (options.strong !== undefined && typeof options.strong !== 'boolean') {
|
|
311
|
+
throw new TypeError('Octane compiler `strong` must be a boolean when provided.');
|
|
312
|
+
}
|
|
309
313
|
this.root = nodePath.resolve(options.root ?? process.cwd());
|
|
310
314
|
try {
|
|
311
315
|
this.realRoot = nodeFs.realpathSync(this.root);
|
|
@@ -318,6 +322,7 @@ class OctaneBundlerCompiler {
|
|
|
318
322
|
hmr: normalizeHmrDialect(options.hmr),
|
|
319
323
|
dev: options.dev,
|
|
320
324
|
profile: options.profile === true,
|
|
325
|
+
strong: options.strong === true,
|
|
321
326
|
universalRuntime: normalizeUniversalRuntime(options.universalRuntime),
|
|
322
327
|
};
|
|
323
328
|
this.renderers = normalizeRendererConfig(options.renderers);
|
|
@@ -449,6 +454,22 @@ class OctaneBundlerCompiler {
|
|
|
449
454
|
return isPathInside(this.root, absoluteFile) || isPathInside(this.realRoot, absoluteFile);
|
|
450
455
|
}
|
|
451
456
|
|
|
457
|
+
_hasApplicationStrongPolicy(file, collected) {
|
|
458
|
+
if (!this._isProjectOwnedSource(file)) return false;
|
|
459
|
+
const absoluteFile = nodePath.isAbsolute(file)
|
|
460
|
+
? nodePath.resolve(file)
|
|
461
|
+
: nodePath.resolve(this.root, file);
|
|
462
|
+
const application = this._nearestOctanePackageRule(this.root);
|
|
463
|
+
const source = this._nearestOctanePackageRule(nodePath.dirname(absoluteFile));
|
|
464
|
+
addMetadata(collected, application);
|
|
465
|
+
addMetadata(collected, source);
|
|
466
|
+
return (
|
|
467
|
+
application.rule === null ||
|
|
468
|
+
source.rule === null ||
|
|
469
|
+
application.rule.root === source.rule.root
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
452
473
|
_isInstalledOctaneSource(file, collected) {
|
|
453
474
|
// Project-owned TS/JS/TSX is always eligible. A linked package is commonly
|
|
454
475
|
// handed to bundlers as its real path, without a node_modules segment, so
|
|
@@ -816,6 +837,13 @@ class OctaneBundlerCompiler {
|
|
|
816
837
|
// of both HMR and dev hydration diagnostics. Server transforms stay byte-for-
|
|
817
838
|
// byte identical even when a shared client/server bundler configuration opts in.
|
|
818
839
|
const profile = environment === 'client' && (options.profile ?? this.defaults.profile) === true;
|
|
840
|
+
// An application's global policy never leaks into installed or linked
|
|
841
|
+
// compatibility packages, including workspace packages nested inside the
|
|
842
|
+
// project root. Modules may still opt themselves in with their own
|
|
843
|
+
// directive, which the authored-source compiler resolves separately.
|
|
844
|
+
const strong =
|
|
845
|
+
(options.strong ?? this.defaults.strong) === true &&
|
|
846
|
+
this._hasApplicationStrongPolicy(file, collected);
|
|
819
847
|
const universalRuntime = normalizeUniversalRuntime(
|
|
820
848
|
options.universalRuntime ?? this.defaults.universalRuntime,
|
|
821
849
|
);
|
|
@@ -892,6 +920,7 @@ class OctaneBundlerCompiler {
|
|
|
892
920
|
dev,
|
|
893
921
|
profile,
|
|
894
922
|
profileFilename,
|
|
923
|
+
...(strong ? { strong: true } : null),
|
|
895
924
|
...(universalRuntime === undefined ? null : { universalRuntime }),
|
|
896
925
|
// Keep the established DOM compiler call byte-for-byte equivalent. A
|
|
897
926
|
// renderer descriptor is an orthogonal compiler input only for the
|
|
@@ -971,6 +1000,14 @@ class OctaneBundlerCompiler {
|
|
|
971
1000
|
return this._passThrough(code, collected);
|
|
972
1001
|
}
|
|
973
1002
|
if (this._hasManualHookSlots(file, collected)) {
|
|
1003
|
+
// Hand-slotted bindings still own their authored policy. Opting one
|
|
1004
|
+
// module in must not require changing its established slot ABI.
|
|
1005
|
+
if (strong || code.includes('use strong')) {
|
|
1006
|
+
const authoredSource = code;
|
|
1007
|
+
assertStrongMode(parseModule(authoredSource, filename), authoredSource, filename, {
|
|
1008
|
+
strong,
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
974
1011
|
return this._passThrough(code, collected);
|
|
975
1012
|
}
|
|
976
1013
|
const profileFilename = profile ? this._profileModuleId(file, collected) : undefined;
|
|
@@ -981,6 +1018,7 @@ class OctaneBundlerCompiler {
|
|
|
981
1018
|
hmr: !!hmr,
|
|
982
1019
|
profile,
|
|
983
1020
|
profileFilename,
|
|
1021
|
+
...(strong ? { strong: true } : null),
|
|
984
1022
|
...(specializeVoidRoot
|
|
985
1023
|
? {
|
|
986
1024
|
isVoidComponentImport: options.isVoidComponentImport,
|
|
@@ -3141,7 +3141,17 @@ function hmrHandoffStatements(state, hot, origin) {
|
|
|
3141
3141
|
for (const component of state.hmrComponents) {
|
|
3142
3142
|
const componentOrigin = component.origin ?? origin;
|
|
3143
3143
|
const existing = b.member(hmrComponentStore(hot, componentOrigin), component.name);
|
|
3144
|
-
|
|
3144
|
+
// Webpack/rspack leave `hot.data` undefined until a previous instance of
|
|
3145
|
+
// the module has disposed, so first evaluation must guard the bag itself.
|
|
3146
|
+
const test = b.logical(
|
|
3147
|
+
'&&',
|
|
3148
|
+
b.logical(
|
|
3149
|
+
'&&',
|
|
3150
|
+
memberPath(hot, ['data'], componentOrigin),
|
|
3151
|
+
hmrComponentStore(hot, componentOrigin),
|
|
3152
|
+
),
|
|
3153
|
+
existing,
|
|
3154
|
+
);
|
|
3145
3155
|
const update = b.stmt(
|
|
3146
3156
|
b.call(
|
|
3147
3157
|
b.member(b.member(existing, b.id(state.helpers.hmrSymbol), true), 'update'),
|
package/dist/compiler/compile.js
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
import { assertNoLiveClientOnlyImports } from './client-only-server.js';
|
|
57
57
|
import { nsForChildren, nsForSelf } from './jsx-namespace.js';
|
|
58
58
|
import { analyzeNativeChangeDiagnostics } from './native-change-diagnostics.js';
|
|
59
|
+
import { assertStrongMode } from './strong-mode.js';
|
|
59
60
|
import { assertUniversalRuntimeTarget, normalizeUniversalRuntime } from './universal-runtime.js';
|
|
60
61
|
|
|
61
62
|
// DOM truth tables shared with the client/server runtimes (via constants.ts) —
|
|
@@ -5653,7 +5654,7 @@ function instrumentProfileComponents(ast, ctx) {
|
|
|
5653
5654
|
* Compile a .tsrx source string into JS targeting `octane`.
|
|
5654
5655
|
* @param {string} source
|
|
5655
5656
|
* @param {string} filename
|
|
5656
|
-
* @param {{ hmr?: boolean | 'vite' | 'webpack', mode?: 'client' | 'server', dev?: boolean, profile?: boolean, profileFilename?: string, autoMemo?: boolean, inlineHookMemo?: boolean, dataCallbackHooks?: readonly string[], renderer?: { id: string, module: string, target: 'dom' | 'universal', server?: string }, rendererBoundaries?: Readonly<Record<string, Readonly<Record<string, { ownerRenderer: string, childRenderer: string, prop: string, server?: string }>>>>, rendererRegistry?: Readonly<Record<string, { module: string, target: 'dom' | 'universal', server?: string }>>, clientOnlyImports?: readonly unknown[], __hydratePrepared?: boolean, __hydrateBoundaryModule?: boolean, __nativeChangeDiagnostics?: readonly unknown[], __nativeChangeAnalysis?: { diagnostics: readonly unknown[], classifications: Map<number, string> } }} [options] —
|
|
5657
|
+
* @param {{ hmr?: boolean | 'vite' | 'webpack', mode?: 'client' | 'server', dev?: boolean, strong?: boolean, profile?: boolean, profileFilename?: string, autoMemo?: boolean, inlineHookMemo?: boolean, dataCallbackHooks?: readonly string[], renderer?: { id: string, module: string, target: 'dom' | 'universal', server?: string }, rendererBoundaries?: Readonly<Record<string, Readonly<Record<string, { ownerRenderer: string, childRenderer: string, prop: string, server?: string }>>>>, rendererRegistry?: Readonly<Record<string, { module: string, target: 'dom' | 'universal', server?: string }>>, clientOnlyImports?: readonly unknown[], __hydratePrepared?: boolean, __hydrateBoundaryModule?: boolean, __nativeChangeDiagnostics?: readonly unknown[], __nativeChangeAnalysis?: { diagnostics: readonly unknown[], classifications: Map<number, string> } }} [options] —
|
|
5657
5658
|
* `dev: true` emits client hydration source-location metadata (per-component
|
|
5658
5659
|
* `__s.locs`/`__s.locFile`) and, in server mode, source-located native-element
|
|
5659
5660
|
* scopes for invalid HTML nesting diagnostics. Both are strictly gated so
|
|
@@ -5705,6 +5706,7 @@ function compileAuthored(source, filename, options, bundlerMetadata) {
|
|
|
5705
5706
|
const analyzedAst = parseModule(source, cleanFilename);
|
|
5706
5707
|
analyzeTsrx(analyzedAst, cleanFilename);
|
|
5707
5708
|
adoptParserAst(analyzedAst);
|
|
5709
|
+
assertStrongMode(analyzedAst, source, cleanFilename, options);
|
|
5708
5710
|
if (bundlerMetadata !== null) bundlerMetadata.hydrateAst = analyzedAst;
|
|
5709
5711
|
return compileInternal(source, filename, options, analyzedAst, mode, bundlerMetadata);
|
|
5710
5712
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { parseModule } from '@tsrx/core';
|
|
18
18
|
import { HOOK_NAMES, hookSlotHash } from './compile.js';
|
|
19
19
|
import { annotateHookCalls } from './hook-deps.js';
|
|
20
|
+
import { assertStrongMode } from './strong-mode.js';
|
|
20
21
|
|
|
21
22
|
// Build a cheap import-presence gate. Precise call identity is annotated by the
|
|
22
23
|
// lexical scope analysis in analyzeHookDependencies below; this gate only avoids
|
|
@@ -990,7 +991,7 @@ function walk(node, owner, st) {
|
|
|
990
991
|
*
|
|
991
992
|
* @param {string} source raw module text
|
|
992
993
|
* @param {string} id module id (embedded in the stable Symbol.for key)
|
|
993
|
-
* @param {{ environment?: 'client' | 'server', hmr?: boolean, profile?: boolean, profileFilename?: string, isVoidComponentImport?: (request: string, imported: string) => boolean }} [options] `hmr: true` (dev serve) emits
|
|
994
|
+
* @param {{ environment?: 'client' | 'server', strong?: boolean, hmr?: boolean, profile?: boolean, profileFilename?: string, isVoidComponentImport?: (request: string, imported: string) => boolean }} [options] `hmr: true` (dev serve) emits
|
|
994
995
|
* `Symbol.for(stableKey)` so a re-imported module resolves the same hook
|
|
995
996
|
* slots (state survives HMR); off (ordinary prod builds and SSR) emits
|
|
996
997
|
* runtime-ranged Symbols. Profiling retains short described Symbols because
|
|
@@ -1010,6 +1011,7 @@ export function slotHooks(source, id, options) {
|
|
|
1010
1011
|
} catch {
|
|
1011
1012
|
return null; // let the normal pipeline surface the parse error
|
|
1012
1013
|
}
|
|
1014
|
+
assertStrongMode(ast, source, id, options);
|
|
1013
1015
|
const importInfo = octaneHookLocals(ast);
|
|
1014
1016
|
const canSpecializeRoot =
|
|
1015
1017
|
!options?.hmr &&
|