diff --git a/packages/polyfills-loader/index.js b/packages/polyfills-loader/index.js index 5416a25c..e877a5a6 100644 --- a/packages/polyfills-loader/index.js +++ b/packages/polyfills-loader/index.js @@ -11,6 +11,7 @@ const { injectPolyfillsLoader } = require('./src/inject-polyfills-loader'); const { createPolyfillsLoader } = require('./src/create-polyfills-loader'); +const { createPolyfillsData } = require('./src/create-polyfills-data'); const { noModuleSupportTest, fileTypes, @@ -24,6 +25,7 @@ module.exports = { injectPolyfillsLoader, createPolyfillsLoader, + createPolyfillsData, noModuleSupportTest, fileTypes, createContentHash, diff --git a/packages/polyfills-loader/src/create-polyfills-data.js b/packages/polyfills-loader/src/create-polyfills-data.js index 5415f676..3fcbef30 100644 --- a/packages/polyfills-loader/src/create-polyfills-data.js +++ b/packages/polyfills-loader/src/create-polyfills-data.js @@ -9,7 +9,7 @@ const { createContentHash, noModuleSupportTest, hasFileOfType, fileTypes } = req /** * @param {PolyfillsLoaderConfig} cfg - * @returns {{ coreJs?: PolyfillFile, polyfillFiles: PolyfillFile[] }} + * @returns {PolyfillFile[]} */ function createPolyfillsData(cfg) { const { polyfills = {} } = cfg; @@ -76,9 +76,14 @@ function createPolyfillsData(cfg) { // load systemjs, an es module polyfill, if one of the entries needs it const hasSystemJs = cfg.polyfills && cfg.polyfills.custom && cfg.polyfills.custom.find(c => c.name === 'systemjs'); - if (!hasSystemJs && hasFileOfType(cfg, fileTypes.SYSTEMJS)) { + if ( + polyfills.systemjs || + polyfills.systemjsExtended || + (!hasSystemJs && hasFileOfType(cfg, fileTypes.SYSTEMJS)) + ) { const name = 'systemjs'; - const alwaysLoad = cfg.modern.files.some(f => f.type === fileTypes.SYSTEMJS); + const alwaysLoad = + cfg.modern && cfg.modern.files && cfg.modern.files.some(f => f.type === fileTypes.SYSTEMJS); const test = alwaysLoad || !cfg.legacy ? undefined : cfg.legacy.map(e => e.test).join(' || '); if (polyfills.systemjsExtended) { @@ -177,6 +182,8 @@ function createPolyfillsData(cfg) { }); } + polyfillConfigs.sort((p1, p2) => (p1.order || 50) - (p2.order || 50)); + /** * @description returns the contents of a file at the given path * @param {string} filePath @@ -192,8 +199,6 @@ function createPolyfillsData(cfg) { /** @type {PolyfillFile[]} */ const polyfillFiles = []; - /** @type {PolyfillFile | undefined} */ - let coreJs; for (const polyfillConfig of polyfillConfigs) { if (!polyfillConfig.name || !polyfillConfig.path) { @@ -217,6 +222,7 @@ function createPolyfillsData(cfg) { )}.js`; const polyfillFile = { + name: polyfillConfig.name, type: polyfillConfig.fileType || fileTypes.SCRIPT, path: filePath, content, @@ -224,14 +230,10 @@ function createPolyfillsData(cfg) { initializer: polyfillConfig.initializer, }; - if (polyfillConfig.name !== 'core-js') { - polyfillFiles.push(polyfillFile); - } else { - coreJs = polyfillFile; - } + polyfillFiles.push(polyfillFile); } - return { coreJs, polyfillFiles }; + return polyfillFiles; } module.exports = { diff --git a/packages/polyfills-loader/src/create-polyfills-loader.js b/packages/polyfills-loader/src/create-polyfills-loader.js index 0f0c18d8..9d4f2a3b 100644 --- a/packages/polyfills-loader/src/create-polyfills-loader.js +++ b/packages/polyfills-loader/src/create-polyfills-loader.js @@ -102,7 +102,7 @@ function createLoadFiles(files) { * @param {PolyfillsLoaderConfig} cfg */ function createLoadFilesFunction(cfg) { - const loadResources = createLoadFiles(cfg.modern.files); + const loadResources = cfg.modern && cfg.modern.files ? createLoadFiles(cfg.modern.files) : ''; if (!cfg.legacy || cfg.legacy.length === 0) { return loadResources; } @@ -197,7 +197,10 @@ function createPolyfillsLoaderCode(cfg, polyfills) { * @returns {PolyfillsLoader | null} */ function createPolyfillsLoader(cfg) { - const { coreJs, polyfillFiles } = createPolyfillsData(cfg); + let polyfillFiles = createPolyfillsData(cfg); + /** @type {PolyfillFile | undefined} */ + const coreJs = polyfillFiles.find(pf => pf.name === 'core-js'); + polyfillFiles = polyfillFiles.filter(pf => pf !== coreJs); const { loadPolyfillsCode, generatedFiles } = createPolyfillsLoaderCode(cfg, polyfillFiles); let code = ` diff --git a/packages/polyfills-loader/src/types.d.ts b/packages/polyfills-loader/src/types.d.ts index a4a6ac07..0bdebaad 100644 --- a/packages/polyfills-loader/src/types.d.ts +++ b/packages/polyfills-loader/src/types.d.ts @@ -1,7 +1,7 @@ export interface PolyfillsLoaderConfig { // files to load on modern browsers. loaded when there are no // legacy entrypoints which match - modern: ModernEntrypoint; + modern?: ModernEntrypoint; // legacy entrypoints to load on older browsers, each entrypoint // consists of a test when to load it. tests are executed in the order // that they appear using if else statement where the final else @@ -36,6 +36,8 @@ export interface PolyfillsConfig { intersectionObserver?: boolean; resizeObserver?: boolean; dynamicImport?: boolean; + // systemjs s.js + systemjs?: boolean; // systemjs extended version with import maps systemjsExtended?: boolean; esModuleShims?: boolean; @@ -56,6 +58,8 @@ export interface PolyfillConfig { minify?: boolean; // code used to initialze the module initializer?: string; + // order of the polyfill when loaded, defaults to 50 + order?: number; } export interface ModernEntrypoint { @@ -85,6 +89,8 @@ export interface GeneratedFile extends File { } export interface PolyfillFile extends GeneratedFile { + // name of the polyfill + name: string; // runtime feature detection to load this polyfill test?: string; // code run after the polyfill is loaded to initialize the polyfill diff --git a/packages/polyfills-loader/test/create-polyfills-data.test.js b/packages/polyfills-loader/test/create-polyfills-data.test.js index e007d5cb..e1b968a3 100644 --- a/packages/polyfills-loader/test/create-polyfills-data.test.js +++ b/packages/polyfills-loader/test/create-polyfills-data.test.js @@ -41,25 +41,27 @@ describe('polyfills', () => { }, }; - const { coreJs, polyfillFiles } = createPolyfillsData(config); - cleanupPolyfill(coreJs); + const polyfillFiles = createPolyfillsData(config); polyfillFiles.forEach(p => { expect(p.content).to.be.a('string'); cleanupPolyfill(p); }); - expect(coreJs).to.eql({ - type: fileTypes.SCRIPT, - path: 'polyfills/core-js.js', - test: "!('noModule' in HTMLScriptElement.prototype)", - }); expect(polyfillFiles).to.eql([ { + name: 'core-js', + type: fileTypes.SCRIPT, + path: 'polyfills/core-js.js', + test: "!('noModule' in HTMLScriptElement.prototype)", + }, + { + name: 'fetch', path: 'polyfills/fetch.js', test: "!('fetch' in window)", type: 'script', }, { + name: 'dynamic-import', initializer: "window.dynamicImportPolyfill.initialize({ importFunctionName: 'importShim' });", path: 'polyfills/dynamic-import.js', @@ -68,28 +70,33 @@ describe('polyfills', () => { type: 'script', }, { + name: 'es-module-shims', path: 'polyfills/es-module-shims.js', test: "'noModule' in HTMLScriptElement.prototype", type: 'module', }, { + name: 'intersection-observer', path: 'polyfills/intersection-observer.js', test: "!('IntersectionObserver' in window && 'IntersectionObserverEntry' in window && 'intersectionRatio' in window.IntersectionObserverEntry.prototype)", type: 'script', }, { + name: 'resize-observer', path: 'polyfills/resize-observer.js', test: "!('ResizeObserver' in window)", type: 'script', }, { + name: 'webcomponents', path: 'polyfills/webcomponents.js', test: "!('attachShadow' in Element.prototype) || !('getRootNode' in Element.prototype) || (window.ShadyDOM && window.ShadyDOM.force)", type: 'script', }, { + name: 'custom-elements-es5-adapter', path: 'polyfills/custom-elements-es5-adapter.js', test: "!('noModule' in HTMLScriptElement.prototype) && 'getRootNode' in Element.prototype", type: 'script', @@ -107,14 +114,14 @@ describe('polyfills', () => { abortController: true, }, }; - const { coreJs, polyfillFiles } = createPolyfillsData(config); - cleanupPolyfill(coreJs); + const polyfillFiles = createPolyfillsData(config); polyfillFiles.forEach(p => { expect(p.content).to.be.a('string'); cleanupPolyfill(p); }); expect(polyfillFiles).to.eql([ { + name: 'fetch', path: 'polyfills/fetch.js', test: "!('fetch' in window) || !('Request' in window) || !('signal' in window.Request.prototype)", @@ -133,14 +140,14 @@ describe('polyfills', () => { shadyCssCustomStyle: true, }, }; - const { coreJs, polyfillFiles } = createPolyfillsData(config); - cleanupPolyfill(coreJs); + const polyfillFiles = createPolyfillsData(config); polyfillFiles.forEach(p => { expect(p.content).to.be.a('string'); cleanupPolyfill(p); }); expect(polyfillFiles).to.eql([ { + name: 'webcomponents-shady-css-custom-style', type: fileTypes.SCRIPT, path: 'polyfills/webcomponents-shady-css-custom-style.js', test: "!('attachShadow' in Element.prototype) || !('getRootNode' in Element.prototype)", @@ -163,7 +170,7 @@ describe('polyfills', () => { }, }; - const { polyfillFiles } = createPolyfillsData(config); + const polyfillFiles = createPolyfillsData(config); polyfillFiles.forEach(p => { expect(p.content).to.be.a('string'); cleanupPolyfill(p); @@ -171,6 +178,7 @@ describe('polyfills', () => { expect(polyfillFiles).to.eql([ { + name: 'systemjs', type: fileTypes.SCRIPT, path: 'polyfills/systemjs.js', test: "!('noModule' in HTMLScriptElement.prototype)", @@ -197,8 +205,7 @@ describe('polyfills', () => { }, }; - const { coreJs, polyfillFiles } = createPolyfillsData(config); - cleanupPolyfill(coreJs); + const polyfillFiles = createPolyfillsData(config); polyfillFiles.forEach(p => { expect(p.content).to.be.a('string'); cleanupPolyfill(p); @@ -206,6 +213,7 @@ describe('polyfills', () => { expect(polyfillFiles).to.eql([ { + name: 'systemjs', type: fileTypes.SCRIPT, path: 'polyfills/systemjs.js', test: "'foo' in bar || !('noModule' in HTMLScriptElement.prototype)", @@ -222,8 +230,7 @@ describe('polyfills', () => { }, }; - const { coreJs, polyfillFiles } = createPolyfillsData(config); - cleanupPolyfill(coreJs); + const polyfillFiles = createPolyfillsData(config); polyfillFiles.forEach(p => { expect(p.content).to.be.a('string'); cleanupPolyfill(p); @@ -231,6 +238,7 @@ describe('polyfills', () => { expect(polyfillFiles).to.eql([ { + name: 'systemjs', type: fileTypes.SCRIPT, path: 'polyfills/systemjs.js', }, @@ -243,10 +251,12 @@ describe('polyfills', () => { name: 'polyfill-a', test: "'foo' in window", path: path.resolve(__dirname, 'custom-polyfills/polyfill-a.js'), + order: 30, }, { name: 'polyfill-b', path: path.resolve(__dirname, 'custom-polyfills/polyfill-b.js'), + order: 60, }, ]; @@ -263,28 +273,54 @@ describe('polyfills', () => { }, }; - const { coreJs, polyfillFiles } = createPolyfillsData(config); - cleanupPolyfill(coreJs); + const polyfillFiles = createPolyfillsData(config); polyfillFiles.forEach(p => { expect(p.content).to.be.a('string'); cleanupPolyfill(p); }); - expect(coreJs).to.eql({ - type: fileTypes.SCRIPT, - path: 'polyfills/core-js.js', - test: "!('noModule' in HTMLScriptElement.prototype)", - }); expect(polyfillFiles).to.eql([ { + name: 'polyfill-a', type: fileTypes.SCRIPT, path: 'polyfills/polyfill-a.js', test: "'foo' in window", }, { + name: 'core-js', + type: fileTypes.SCRIPT, + path: 'polyfills/core-js.js', + test: "!('noModule' in HTMLScriptElement.prototype)", + }, + { + name: 'polyfill-b', type: fileTypes.SCRIPT, path: 'polyfills/polyfill-b.js', }, ]); }); + + it('loads systemjs separatly if requested', () => { + /** @type {PolyfillsLoaderConfig} */ + const config = { + polyfills: { + hash: false, + systemjs: true, + }, + }; + + const polyfillFiles = createPolyfillsData(config); + polyfillFiles.forEach(p => { + expect(p.content).to.be.a('string'); + cleanupPolyfill(p); + }); + + expect(polyfillFiles).to.eql([ + { + name: 'systemjs', + type: fileTypes.SCRIPT, + path: 'polyfills/systemjs.js', + }, + ]); + }); });