feat(polyfills-loader): expose create-polyfills-data

This commit also:
- introduces ordering when defining polyfills
- allows explicit inclusion of systemjs polyfill
- makes files optional when defining configs
This commit is contained in:
Tom Nys
2020-07-30 22:42:59 +02:00
committed by Lars den Bakker
parent 2c8045c373
commit 7dfb1abb7f
5 changed files with 86 additions and 37 deletions

View File

@@ -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,

View File

@@ -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 = {

View File

@@ -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 = `

View File

@@ -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

View File

@@ -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',
},
]);
});
});