diff --git a/lib/parseCommandLineOptions.js b/lib/parseCommandLineOptions.js
index d705c153..a15473a0 100644
--- a/lib/parseCommandLineOptions.js
+++ b/lib/parseCommandLineOptions.js
@@ -26,6 +26,18 @@ module.exports = function parseCommandLineOptions(argv) {
type: 'string',
demand: false,
})
+ .options('browsers', {
+ describe:
+ "Override your projects browserslist configuration to specify which browsers to support. Controls font formats and polyfill. Defaults to browserslist's default query if your project has no browserslist configuration",
+ type: 'string',
+ demand: false,
+ })
+ .options('formats', {
+ describe:
+ 'Font formats to use when subsetting. The default is to select the formats based on the browser capabilities as specified via --browsers or the browserslist configuration.',
+ type: 'array',
+ choices: ['woff2', 'woff', 'truetype'],
+ })
.options('fallbacks', {
describe:
'Include fallbacks so the original font will be loaded when dynamic content gets injected at runtime. Disable with --no-fallbacks',
@@ -61,12 +73,6 @@ module.exports = function parseCommandLineOptions(argv) {
default: 'swap',
choices: ['auto', 'block', 'swap', 'fallback', 'optional'],
})
- .options('formats', {
- describe: 'Font formats to use when subsetting.',
- type: 'array',
- default: ['woff2', 'woff'],
- choices: ['woff2', 'woff', 'truetype'],
- })
.options('subset-per-page', {
describe: 'Create a unique subset for each page.',
type: 'boolean',
diff --git a/lib/subfont.js b/lib/subfont.js
index 8c867500..81da31df 100644
--- a/lib/subfont.js
+++ b/lib/subfont.js
@@ -1,5 +1,6 @@
const AssetGraph = require('assetgraph');
const prettyBytes = require('pretty-bytes');
+const browsersList = require('browserslist');
const _ = require('lodash');
const urlTools = require('urltools');
const util = require('util');
@@ -16,7 +17,7 @@ module.exports = async function subfont(
inlineFonts = false,
inlineCss = false,
fontDisplay = 'swap',
- formats = ['woff2', 'woff'],
+ formats,
subsetPerPage = false,
inPlace = false,
inputFiles = [],
@@ -24,9 +25,44 @@ module.exports = async function subfont(
fallbacks = true,
dynamic = false,
harfbuzz = false,
+ browsers,
},
console
) {
+ let selectedBrowsers;
+ if (browsers) {
+ selectedBrowsers = browsersList(browsers);
+ } else {
+ // Will either pick up the browserslist config or use the defaults query
+ selectedBrowsers = browsersList();
+ }
+
+ if (!formats) {
+ formats = ['woff2'];
+ if (
+ _.intersection(
+ browsersList('supports woff, not supports woff2'),
+ selectedBrowsers
+ ).length > 0
+ ) {
+ formats.push('woff');
+ }
+ if (
+ _.intersection(
+ browsersList('supports ttf, not supports woff'),
+ selectedBrowsers
+ ).length > 0
+ ) {
+ formats.push('truetype');
+ }
+ }
+
+ const jsPreload =
+ _.intersection(
+ browsersList('supports font-loading, not supports link-rel-preload'),
+ selectedBrowsers
+ ).length > 0;
+
let rootUrl = root && urlTools.urlOrFsPathToUrl(root, true);
const outRoot = output && urlTools.urlOrFsPathToUrl(output, true);
let inputUrls;
@@ -185,6 +221,7 @@ module.exports = async function subfont(
fontDisplay,
subsetPerPage,
formats,
+ jsPreload,
omitFallbacks: !fallbacks,
harfbuzz,
dynamic,
diff --git a/lib/subsetFonts.js b/lib/subsetFonts.js
index bc3f368d..b1c1b725 100644
--- a/lib/subsetFonts.js
+++ b/lib/subsetFonts.js
@@ -647,6 +647,7 @@ async function subsetFonts(
{
formats = ['woff2', 'woff'],
subsetPath = 'subfont/',
+ jsPreload = true,
omitFallbacks = false,
subsetPerPage,
inlineFonts,
@@ -1041,65 +1042,67 @@ These glyphs are used on your site, but they don't exist in the font you applied
}
);
- // Generate JS fallback for browser that don't support
- const preloadData = unsubsettedFontUsagesToPreload.map(
- (fontUsage, idx) => {
- const preloadRelation = preloadRelations[idx];
-
- const formatMap = {
- '.woff': 'woff',
- '.woff2': 'woff2',
- '.ttf': 'truetype',
- '.svg': 'svg',
- '.eot': 'embedded-opentype',
- };
- const name = fontUsage.props['font-family'];
- const props = Object.keys(initialValueByProp).reduce(
- (result, prop) => {
- if (
- fontUsage.props[prop] !==
- normalizeFontPropertyValue(prop, initialValueByProp[prop])
- ) {
- result[prop] = fontUsage.props[prop];
- }
- return result;
- },
- {}
- );
+ if (jsPreload) {
+ // Generate JS fallback for browser that don't support
+ const preloadData = unsubsettedFontUsagesToPreload.map(
+ (fontUsage, idx) => {
+ const preloadRelation = preloadRelations[idx];
+
+ const formatMap = {
+ '.woff': 'woff',
+ '.woff2': 'woff2',
+ '.ttf': 'truetype',
+ '.svg': 'svg',
+ '.eot': 'embedded-opentype',
+ };
+ const name = fontUsage.props['font-family'];
+ const props = Object.keys(initialValueByProp).reduce(
+ (result, prop) => {
+ if (
+ fontUsage.props[prop] !==
+ normalizeFontPropertyValue(prop, initialValueByProp[prop])
+ ) {
+ result[prop] = fontUsage.props[prop];
+ }
+ return result;
+ },
+ {}
+ );
- return `new FontFace(
+ return `new FontFace(
"${name}",
"url('" + "${preloadRelation.href}".toString('url') + "') format('${
- formatMap[preloadRelation.to.extension]
- }')",
+ formatMap[preloadRelation.to.extension]
+ }')",
${JSON.stringify(props)}
).load().then(void 0, function () {});`;
- }
- );
+ }
+ );
- const originalFontJsPreloadAsset = htmlAsset.addRelation(
- {
- type: 'HtmlScript',
- hrefType: 'inline',
- to: {
- type: 'JavaScript',
- text: `try{${preloadData.join('')}}catch(e){}`,
+ const originalFontJsPreloadAsset = htmlAsset.addRelation(
+ {
+ type: 'HtmlScript',
+ hrefType: 'inline',
+ to: {
+ type: 'JavaScript',
+ text: `try{${preloadData.join('')}}catch(e){}`,
+ },
},
- },
- 'before',
- insertionPoint
- ).to;
+ 'before',
+ insertionPoint
+ ).to;
+
+ for (const [
+ idx,
+ relation,
+ ] of originalFontJsPreloadAsset.outgoingRelations.entries()) {
+ relation.hrefType = 'rootRelative';
+ relation.to = preloadRelations[idx].to;
+ relation.refreshHref();
+ }
- for (const [
- idx,
- relation,
- ] of originalFontJsPreloadAsset.outgoingRelations.entries()) {
- relation.hrefType = 'rootRelative';
- relation.to = preloadRelations[idx].to;
- relation.refreshHref();
+ originalFontJsPreloadAsset.minify();
}
-
- originalFontJsPreloadAsset.minify();
}
if (subsetFontUsages.length === 0) {
@@ -1238,7 +1241,7 @@ These glyphs are used on your site, but they don't exist in the font you applied
);
let cssAssetInsertion = cssRelation;
- if (!inlineFonts) {
+ if (jsPreload && !inlineFonts) {
// JS-based font preloading for browsers without support
const fontFaceContructorCalls = [];
diff --git a/package.json b/package.json
index 075eefed..536b2a9f 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
"dependencies": {
"@gustavnikolaj/async-main-wrap": "^3.0.1",
"assetgraph": "^6.1.1",
+ "browserslist": "^4.13.0",
"css-font-parser": "^0.3.0",
"css-font-weight-names": "^0.2.1",
"css-list-helpers": "^2.0.0",
diff --git a/test/subfont.js b/test/subfont.js
index 0efe05ea..915a4adc 100644
--- a/test/subfont.js
+++ b/test/subfont.js
@@ -6,6 +6,7 @@ const httpception = require('httpception');
const AssetGraph = require('assetgraph');
const proxyquire = require('proxyquire');
const pathModule = require('path');
+
const openSansBold = require('fs').readFileSync(
pathModule.resolve(
__dirname,
@@ -793,4 +794,116 @@ describe('subfont', function () {
});
});
});
+
+ describe('configuring via browserslist', function () {
+ // /~https://github.com/browserslist/browserslist#best-practices
+ it('should default to woff+woff2 and jsPreload:true when no config is given, due to the browserslist defaults', async function () {
+ const dir = pathModule.resolve(
+ __dirname,
+ '..',
+ 'testdata',
+ 'pageWithStrictCsp'
+ );
+ const root = encodeURI(`file://${dir}`);
+ const mockSubsetFonts = sinon.stub().resolves({ fontInfo: [] });
+
+ const originalDir = process.cwd();
+ process.chdir(dir);
+
+ try {
+ await proxyquire('../lib/subfont', {
+ '../lib/subsetFonts': mockSubsetFonts,
+ })(
+ {
+ root,
+ inputFiles: [`${root}/index.html`],
+ silent: true,
+ dryRun: true,
+ },
+ mockConsole
+ );
+ expect(mockSubsetFonts, 'to have calls satisfying', () => {
+ mockSubsetFonts(expect.it('to be an object'), {
+ formats: ['woff2', 'woff'],
+ jsPreload: true,
+ });
+ });
+ } finally {
+ process.chdir(originalDir);
+ }
+ });
+
+ it('should prefer the browsers config option over browserslist configured in package.json', async function () {
+ const dir = pathModule.resolve(
+ __dirname,
+ '..',
+ 'testdata',
+ 'browserslistInPackageJson'
+ );
+ const root = encodeURI(`file://${dir}`);
+ const mockSubsetFonts = sinon.stub().resolves({ fontInfo: [] });
+
+ const originalDir = process.cwd();
+ process.chdir(dir);
+
+ try {
+ await proxyquire('../lib/subfont', {
+ '../lib/subsetFonts': mockSubsetFonts,
+ })(
+ {
+ root,
+ inputFiles: [`${root}/index.html`],
+ silent: true,
+ dryRun: true,
+ browsers: 'IE 11, Chrome 80',
+ },
+ mockConsole
+ );
+ expect(mockSubsetFonts, 'to have calls satisfying', () => {
+ mockSubsetFonts(expect.it('to be an object'), {
+ formats: ['woff2', 'woff'],
+ jsPreload: false,
+ });
+ });
+ } finally {
+ process.chdir(originalDir);
+ }
+ });
+
+ it('should pick up the browserslist configuration from package.json', async function () {
+ const dir = pathModule.resolve(
+ __dirname,
+ '..',
+ 'testdata',
+ 'browserslistInPackageJson'
+ );
+ const root = encodeURI(`file://${dir}`);
+ const mockSubsetFonts = sinon.stub().resolves({ fontInfo: [] });
+
+ const originalDir = process.cwd();
+ process.chdir(dir);
+
+ try {
+ await proxyquire('../lib/subfont', {
+ '../lib/subsetFonts': mockSubsetFonts,
+ })(
+ {
+ root,
+ inputFiles: [`${root}/index.html`],
+ silent: true,
+ dryRun: true,
+ },
+ mockConsole
+ );
+ expect(mockSubsetFonts, 'to have calls satisfying', () => {
+ mockSubsetFonts(expect.it('to be an object'), {
+ formats: ['woff2', 'truetype'],
+ jsPreload: true,
+ });
+ });
+ } finally {
+ process.chdir(originalDir);
+ }
+ });
+ });
});
diff --git a/test/subsetFonts.js b/test/subsetFonts.js
index 9aaf71bc..dcec6e61 100644
--- a/test/subsetFonts.js
+++ b/test/subsetFonts.js
@@ -2856,6 +2856,29 @@ describe('subsetFonts', function () {
expect(warnings, 'to satisfy', []);
});
+ describe('with jsPreload:false', function () {
+ it('should not add the JavaScript-based preload "polyfill"', async function () {
+ const assetGraph = new AssetGraph({
+ root: pathModule.resolve(
+ __dirname,
+ // '../testdata/subsetFonts/local-single/'
+ '../testdata/subsetFonts/unused-variant/'
+ ),
+ });
+ const [htmlAsset] = await assetGraph.loadAssets('index.html');
+ await assetGraph.populate({
+ followRelations: {
+ crossorigin: false,
+ },
+ });
+ await subsetFonts(assetGraph, {
+ jsPreload: false,
+ });
+
+ expect(htmlAsset.text, 'not to contain', 'new FontFace');
+ });
+ });
+
it('should error out on multiple @font-face declarations with the same family/weight/style/stretch', async function () {
httpception();
diff --git a/testdata/browserslistInPackageJson/index.html b/testdata/browserslistInPackageJson/index.html
new file mode 100644
index 00000000..69e9da41
--- /dev/null
+++ b/testdata/browserslistInPackageJson/index.html
@@ -0,0 +1,2 @@
+
+
diff --git a/testdata/browserslistInPackageJson/package.json b/testdata/browserslistInPackageJson/package.json
new file mode 100644
index 00000000..de5a8013
--- /dev/null
+++ b/testdata/browserslistInPackageJson/package.json
@@ -0,0 +1,3 @@
+{
+ "browserslist": ["Chrome >= 36", "Safari 5"]
+}