diff --git a/.changeset/calm-cats-guard.md b/.changeset/calm-cats-guard.md new file mode 100644 index 000000000..6e749b024 --- /dev/null +++ b/.changeset/calm-cats-guard.md @@ -0,0 +1,5 @@ +--- +'@callstack/repack': patch +--- + +Prevent guarded require initialization from throwing in strict-mode bundles by safely copying runtime property descriptors. diff --git a/packages/repack/src/plugins/RepackTargetPlugin/implementation/__tests__/guardedRequire.test.ts b/packages/repack/src/plugins/RepackTargetPlugin/implementation/__tests__/guardedRequire.test.ts new file mode 100644 index 000000000..f73dc9ee7 --- /dev/null +++ b/packages/repack/src/plugins/RepackTargetPlugin/implementation/__tests__/guardedRequire.test.ts @@ -0,0 +1,44 @@ +import vm from 'node:vm'; + +const guardedRequireImplementation = require('../guardedRequire'); + +describe('guardedRequire implementation', () => { + it('preserves runtime properties in strict mode', () => { + // Reproduce the strict wrapper emitted when an Rspack runtime is built as + // ESM. The previous direct assignments threw while copying Function + // intrinsics before React Native could execute the application entry. + const result = vm.runInNewContext(` + (function () { + 'use strict'; + var self = {}; + var factories = { page: function () { return 'page'; } }; + var __webpack_require__ = function originalWebpackRequire(moduleId) { + return moduleId; + }; + Object.defineProperty(__webpack_require__, 'm', { + configurable: true, + enumerable: false, + get: function () { return factories; } + }); + __webpack_require__.federation = { name: 'remote' }; + + (${guardedRequireImplementation + .toString() + .replaceAll('$globalObject$', 'self')})(); + + return { + value: __webpack_require__('page'), + factories: __webpack_require__.m, + federation: __webpack_require__.federation, + descriptor: Object.getOwnPropertyDescriptor(__webpack_require__, 'm') + }; + })() + `); + + expect(result.value).toBe('page'); + expect(result.factories.page()).toBe('page'); + expect(result.federation.name).toBe('remote'); + expect(result.descriptor.enumerable).toBe(false); + expect(typeof result.descriptor.get).toBe('function'); + }); +}); diff --git a/packages/repack/src/plugins/RepackTargetPlugin/implementation/guardedRequire.ts b/packages/repack/src/plugins/RepackTargetPlugin/implementation/guardedRequire.ts index dd7834586..00a05cebe 100644 --- a/packages/repack/src/plugins/RepackTargetPlugin/implementation/guardedRequire.ts +++ b/packages/repack/src/plugins/RepackTargetPlugin/implementation/guardedRequire.ts @@ -27,10 +27,29 @@ module.exports = function () { } } - // Copy all properties from the original function to the wrapped function + // Copy the original require's runtime properties without invoking getters. + // Function intrinsics such as `prototype`, `arguments`, and `caller` are + // non-configurable on the wrapper and cannot be redefined. Assigning every + // property directly is also unsafe in strict-mode bundles because some + // function properties are read-only and would throw during startup. Object.getOwnPropertyNames(originalWebpackRequire).forEach((key) => { - // @ts-ignore - guardedWebpackRequire[key] = originalWebpackRequire[key]; + const sourceDescriptor = Object.getOwnPropertyDescriptor( + originalWebpackRequire, + key + ); + const targetDescriptor = Object.getOwnPropertyDescriptor( + guardedWebpackRequire, + key + ); + + if ( + !sourceDescriptor || + (targetDescriptor && !targetDescriptor.configurable) + ) { + return; + } + + Object.defineProperty(guardedWebpackRequire, key, sourceDescriptor); }); // @ts-ignore