octane 0.1.18 → 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.
@@ -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,
@@ -10,6 +10,7 @@ const DOM_RENDERER = Object.freeze({ id: 'dom', module: 'octane', target: 'dom'
10
10
  const AUTO_RUNTIME_HOOKS = new Set([
11
11
  'use',
12
12
  'useState',
13
+ 'useLinkedState',
13
14
  'useReducer',
14
15
  'useEffect',
15
16
  'useLayoutEffect',
@@ -31,6 +31,7 @@ const UNIVERSAL_RUNTIME_IMPORTS = new Set([
31
31
  'useImperativeHandle',
32
32
  'useInsertionEffect',
33
33
  'useLayoutEffect',
34
+ 'useLinkedState',
34
35
  'useMemo',
35
36
  'useOptimistic',
36
37
  'useReducer',
@@ -657,7 +658,7 @@ function createLexicalAnalysis(ast) {
657
658
  return;
658
659
  }
659
660
  if (node.type === 'TSModuleDeclaration') {
660
- if (node.declare === true || node.global === true) {
661
+ if (node.declare === true || node.kind === 'global') {
661
662
  if (node.id) declarePattern(node.id, null);
662
663
  return;
663
664
  }
@@ -3021,6 +3022,7 @@ function emitComponentAst(shape, state) {
3021
3022
 
3022
3023
  export const UNIVERSAL_COMPILER_RUNTIME_IMPORTS = new Set([
3023
3024
  ...UNIVERSAL_RUNTIME_IMPORTS,
3025
+ '__useLinkedStateWithGetter',
3024
3026
  '__useReducerWithGetter',
3025
3027
  '__useStateWithGetter',
3026
3028
  'hookSlots',
@@ -3139,7 +3141,17 @@ function hmrHandoffStatements(state, hot, origin) {
3139
3141
  for (const component of state.hmrComponents) {
3140
3142
  const componentOrigin = component.origin ?? origin;
3141
3143
  const existing = b.member(hmrComponentStore(hot, componentOrigin), component.name);
3142
- const test = b.logical('&&', hmrComponentStore(hot, componentOrigin), existing);
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
+ );
3143
3155
  const update = b.stmt(
3144
3156
  b.call(
3145
3157
  b.member(b.member(existing, b.id(state.helpers.hmrSymbol), true), 'update'),
@@ -3386,6 +3398,7 @@ export function lowerUniversalRendererRegionAst(
3386
3398
  Object.fromEntries(
3387
3399
  [
3388
3400
  '__useStateWithGetter',
3401
+ '__useLinkedStateWithGetter',
3389
3402
  '__useReducerWithGetter',
3390
3403
  'useMemo',
3391
3404
  'useBatch',