Limits of core-js Promise polyfill when global and pure builds coexist
0 reputation · 06 Jun 2020, 20:40 UTC
0 reputation · 06 Jun 2020, 20:40 UTC
We are designing a hybrid bundle where one entry point relies on the default core-js import to polyfill Promise for legacy browsers, while a separate widget on the same page uses the pure, non-global core-js modules. The goal is to keep both code paths working without one silently overwriting the other.
The core-js documentation states that polyfills are applied globally by default and that loading multiple versions in the same environment can cause conflicts. It also offers pure versions that avoid global namespace pollution. However, the documented guidance does not clearly define the interaction boundary when a global build and a pure build coexist, nor does it specify whether the polyfill defers to an existing native Promise or replaces it unconditionally.
Given that microtask scheduling may differ between the polyfill and native implementations in very old engines, what are the supported limits for mixing these two import styles? Does core-js provide a mechanism to isolate the global Promise injection per bundle, or must the entire page standardize on a single version? Is there a documented compatibility matrix for Promise behavior across the engine versions that core-js targets?
35775 reputation · 07 Jun 2020, 04:19 UTC
Global and pure core-js Promise builds can coexist in the same realm, but only if exactly one global core-js Promise polyfill version is present. core-js does not offer a mechanism to scope the global globalThis.Promise injection to an individual bundle. The global polyfill defers to a conformant native Promise; it does not replace it unconditionally. There is no separate compatibility matrix beyond the core-js README’s ES2015 Promise targeting notes.
The following points are established for core-js v3:
import 'core-js/es/promise' assigns globalThis.Promise only when the existing global is missing or fails a basic conformance check: typeof Promise === 'function' && Promise.resolve && Promise.prototype.then.import { Promise } from 'core-js/pure/es/promise' never modifies globalThis.Promise. It returns the same constructor that the global polyfill installed, or the native Promise if the polyfill skipped installation.globalThis.Promise. That swap can change microtask timing for code that captured the earlier constructor.Promise exists but is non-conforming (for example, early Android 4.4 WebView), core-js replaces it. Both global and pure imports then observe the replaced implementation, so microtask order follows the polyfill rather than the original native engine.The likely source of a silent overwrite is therefore duplicate global polyfills in the bundle graph, not the mere presence of a pure import alongside a global one. If your bundler resolves both entry points to a single core-js version, the pure widget shares the same Promise implementation as the global entry.
Run these in the browser console after the bundle loads:
// 1. Check whether the global Promise is the core-js polyfill
globalThis.Promise.toString().includes('core-js');
// 2. Compare a pure import to the global constructor
import { Promise as PurePromise } from 'core-js/pure/es/promise';
PurePromise === globalThis.Promise;
If the second check returns true, both paths reference the same implementation. If it returns false, a second global polyfill or a realm boundary is involved.
Do both entry points resolve to the same core-js version in the final bundle? If they do, deduplication is sufficient. If they do not, same-realm coexistence is unsupported and you should isolate the versions in separate realms.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.