subfont 6.4.1 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,23 @@
1
- ### v6.4.1 (2022-01-10)
1
+ ### v6.5.0 (2022-07-09)
2
+
3
+ #### Pull requests
4
+
5
+ - [#162](https://github.com/Munter/subfont/pull/162) update changelog with proper version for 6.5.0 ([Jacob Lamont](mailto:cob@jacoblamont.com))
6
+
7
+ #### Commits to master
8
+
9
+ - [Fix extractReferencedCustomPropertyNames when there's a default value](https://github.com/Munter/subfont/commit/506375504384c1c292348427e98213482aadcae0) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
10
+ - [Update font-tracer to ^3.2.0](https://github.com/Munter/subfont/commit/5028f72482be20a72a1301c4218820f89a950bdf) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
11
+
12
+ ### v6.5.0 (2022-06-13)
13
+
14
+ - [#161](https://github.com/Munter/subfont/pull/161) Add support for specifying additional characters to include in the subsets ([Andreas Lind](mailto:andreas.lind@workday.com), [Andreas Lind](mailto:andreaslindpetersen@gmail.com))
15
+
16
+ ### v6.4.2 (2022-04-13)
17
+
18
+ - [Handle escape sequences in CSS strings](https://github.com/Munter/subfont/commit/6c0493b3e0a96e2651e4c37cdf4e45fdb090acc4) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
19
+
20
+ ### v6.4.1 (2022-01-11)
2
21
 
3
22
  - [Update font-tracer to ^3.1.0, fixes \#157](https://github.com/Munter/subfont/commit/c98c0d8a2723c2f68b11dadb6c33c34c708355f4) ([Andreas Lind](mailto:andreas.lind@workday.com))
4
23
 
@@ -264,16 +283,13 @@
264
283
  #### Pull requests
265
284
 
266
285
  - [#76](https://github.com/Munter/subfont/pull/76) Fix the prettier setup ([Andreas Lind](mailto:andreas.lind@peakon.com))
286
+ - [#75](https://github.com/Munter/subfont/pull/75) Fix omitFallbacks with Google Web Fonts ([Andreas Lind](mailto:andreas.lind@peakon.com))
267
287
 
268
288
  #### Commits to master
269
289
 
270
290
  - [Switch to the official css-font-parser now that bramstein\/css-font-parser\#7 has been merged and released](https://github.com/Munter/subfont/commit/457c7f0e4cef0a8c1bd8f816c23ace64c9987424) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
271
291
  - [Don't populate source map relations](https://github.com/Munter/subfont/commit/5c07218b6f1dcc6fad88702a3bcb7b33bf9df54e) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
272
292
 
273
- ### v4.1.2 (2020-01-09)
274
-
275
- - [#75](https://github.com/Munter/subfont/pull/75) Fix omitFallbacks with Google Web Fonts ([Andreas Lind](mailto:andreas.lind@peakon.com))
276
-
277
293
  ### v4.1.1 (2020-01-04)
278
294
 
279
295
  - [Add regression test](https://github.com/Munter/subfont/commit/46eddce9c09268dbde459b1f98fe5cec9e4c98f5) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
@@ -304,8 +320,7 @@
304
320
  - [Add vscode debugger launch configuration for the test suite](https://github.com/Munter/subfont/commit/f8f9abc42909c556765555cc49f44eb40a9194db) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
305
321
  - [Guard against an already detached relation when cleaning up](https://github.com/Munter/subfont/commit/6392fc359222772c9033a58a9020e3b35487d019) ([Andreas Lind](mailto:andreaslindpetersen@gmail.com))
306
322
 
307
- ### v4.0.3 (2019-11-02)
308
-
323
+ ### v4.0.3
309
324
  #### Pull requests
310
325
 
311
326
  - [#67](https://github.com/Munter/subfont/pull/67) Only warn about missing fonttools install if we are actually trying t… ([Peter Müller](mailto:munter@fumle.dk))
package/README.md CHANGED
@@ -43,6 +43,24 @@ Run subfont on the files you are ready to deploy to a static file hosting servic
43
43
 
44
44
  If you want to run directly against your raw original files, it is recommended to create a recursive copy of your files which you run `subfont` on. This keeps your original authoring abstraction unchanged.
45
45
 
46
+ ## Including additional characters in the subsets
47
+
48
+ If you have a use case where the automatic tracing doesn't find all the characters you need, you can tell subfont to include specific characters in the subsets by adding a custom `-subfont-text` property to the respective `@font-face` declarations.
49
+
50
+ Example where all numerical digits are added to the bold italic Roboto variant:
51
+
52
+ ```css
53
+ @font-face {
54
+ font-family: Roboto;
55
+ font-style: italic;
56
+ font-weight: 700;
57
+ src: url(roboto.woff) format('woff');
58
+ -subfont-text: '0123456789';
59
+ }
60
+ ```
61
+
62
+ An easier, but less fine-grained option is to use the `--text` switch to include a set of characters in all created subsets.
63
+
46
64
  ## Other usages
47
65
 
48
66
  You can have subfont output a copy of your input files to a new directory. This uses [Assetgraph](https://github.com/assetgraph/assetgraph) to trace a dependency graph of your website and writes it to your specified output directory. Be aware of any errors or warnings that might indicate Assetgraph having problems with your code, and be sure to double check that the expected files are in the output directory. Run `subfont path/to/index.html -o path/to/outputDir`.
@@ -72,6 +90,8 @@ Options:
72
90
  the formats based on the browser capabilities as specified
73
91
  via --browsers or the browserslist configuration.
74
92
  [array] [choices: "woff2", "woff", "truetype"]
93
+ --text Additional characters to include in the subset for every
94
+ @font-face found on the page [string]
75
95
  --fallbacks Include fallbacks so the original font will be loaded when
76
96
  dynamic content gets injected at runtime. Disable with
77
97
  --no-fallbacks [boolean] [default: true]
@@ -7,7 +7,7 @@ function extractReferencedCustomPropertyNames(cssValue) {
7
7
  if (
8
8
  node.type === 'function' &&
9
9
  node.value === 'var' &&
10
- node.nodes.length === 1 &&
10
+ node.nodes.length >= 1 &&
11
11
  node.nodes[0].type === 'word' &&
12
12
  /^--/.test(node.nodes[0].value)
13
13
  ) {
@@ -46,6 +46,11 @@ module.exports = function parseCommandLineOptions(argv) {
46
46
  );
47
47
  },
48
48
  })
49
+ .options('text', {
50
+ describe:
51
+ 'Additional characters to include in the subset for every @font-face found on the page',
52
+ type: 'string',
53
+ })
49
54
  .options('fallbacks', {
50
55
  describe:
51
56
  'Include fallbacks so the original font will be loaded when dynamic content gets injected at runtime. Disable with --no-fallbacks',
package/lib/subfont.js CHANGED
@@ -24,6 +24,7 @@ module.exports = async function subfont(
24
24
  fallbacks = true,
25
25
  dynamic = false,
26
26
  browsers,
27
+ text,
27
28
  },
28
29
  console
29
30
  ) {
@@ -224,6 +225,7 @@ module.exports = async function subfont(
224
225
  formats,
225
226
  omitFallbacks: !fallbacks,
226
227
  hrefType: relativeUrls ? 'relative' : 'rootRelative',
228
+ text,
227
229
  dynamic,
228
230
  console,
229
231
  });
@@ -103,50 +103,84 @@ function groupTextsByFontFamilyProps(
103
103
  htmlOrSvgAsset,
104
104
  globalTextByPropsArray,
105
105
  pageTextByPropsArray,
106
- availableFontFaceDeclarations
106
+ availableFontFaceDeclarations,
107
+ text
107
108
  ) {
108
- const snappedTexts = _.flatMapDeep(globalTextByPropsArray, (textAndProps) => {
109
- const isOnPage = pageTextByPropsArray.includes(textAndProps);
110
- const family = textAndProps.props['font-family'];
111
- if (family === undefined) {
112
- return [];
109
+ const snappedTexts = [];
110
+
111
+ for (const fontFaceDeclaration of availableFontFaceDeclarations) {
112
+ const {
113
+ relations,
114
+ '-subfont-text': subfontText,
115
+ ...props
116
+ } = fontFaceDeclaration;
117
+ if (subfontText !== undefined) {
118
+ delete fontFaceDeclaration['-subfont-text'];
119
+ snappedTexts.push({
120
+ htmlOrSvgAsset,
121
+ fontRelations: relations,
122
+ fontUrl: getPreferredFontUrl(relations),
123
+ preload: false,
124
+ text: unquote(subfontText),
125
+ props,
126
+ });
113
127
  }
114
- // Find all the families in the traced font-family that we have @font-face declarations for:
115
- const families = fontFamily
116
- .parse(family)
117
- .filter((family) =>
118
- availableFontFaceDeclarations.some(
119
- (fontFace) =>
120
- fontFace['font-family'].toLowerCase() === family.toLowerCase()
121
- )
122
- );
123
-
124
- return families.map((family) => {
125
- const activeFontFaceDeclaration = fontSnapper(
126
- availableFontFaceDeclarations,
127
- {
128
- ...textAndProps.props,
129
- 'font-family': fontFamily.stringify([family]),
130
- }
131
- );
128
+ if (text !== undefined) {
129
+ snappedTexts.push({
130
+ htmlOrSvgAsset,
131
+ fontRelations: relations,
132
+ fontUrl: getPreferredFontUrl(relations),
133
+ preload: false,
134
+ text,
135
+ props,
136
+ });
137
+ }
138
+ }
132
139
 
133
- if (!activeFontFaceDeclaration) {
140
+ snappedTexts.push(
141
+ ..._.flatMapDeep(globalTextByPropsArray, (textAndProps) => {
142
+ const isOnPage = pageTextByPropsArray.includes(textAndProps);
143
+ const family = textAndProps.props['font-family'];
144
+ if (family === undefined) {
134
145
  return [];
135
146
  }
147
+ // Find all the families in the traced font-family that we have @font-face declarations for:
148
+ const families = fontFamily
149
+ .parse(family)
150
+ .filter((family) =>
151
+ availableFontFaceDeclarations.some(
152
+ (fontFace) =>
153
+ fontFace['font-family'].toLowerCase() === family.toLowerCase()
154
+ )
155
+ );
136
156
 
137
- const { relations, ...props } = activeFontFaceDeclaration;
138
- const fontUrl = getPreferredFontUrl(relations);
157
+ return families.map((family) => {
158
+ const activeFontFaceDeclaration = fontSnapper(
159
+ availableFontFaceDeclarations,
160
+ {
161
+ ...textAndProps.props,
162
+ 'font-family': fontFamily.stringify([family]),
163
+ }
164
+ );
139
165
 
140
- return {
141
- htmlOrSvgAsset: textAndProps.htmlOrSvgAsset,
142
- text: textAndProps.text,
143
- props,
144
- fontRelations: relations,
145
- fontUrl,
146
- preload: isOnPage,
147
- };
148
- });
149
- }).filter((textByProps) => textByProps && textByProps.fontUrl);
166
+ if (!activeFontFaceDeclaration) {
167
+ return [];
168
+ }
169
+
170
+ const { relations, ...props } = activeFontFaceDeclaration;
171
+ const fontUrl = getPreferredFontUrl(relations);
172
+
173
+ return {
174
+ htmlOrSvgAsset: textAndProps.htmlOrSvgAsset,
175
+ text: textAndProps.text,
176
+ props,
177
+ fontRelations: relations,
178
+ fontUrl,
179
+ preload: isOnPage,
180
+ };
181
+ });
182
+ }).filter((textByProps) => textByProps && textByProps.fontUrl)
183
+ );
150
184
 
151
185
  const textsByFontUrl = _.groupBy(snappedTexts, 'fontUrl');
152
186
 
@@ -546,6 +580,7 @@ async function subsetFonts(
546
580
  onlyInfo,
547
581
  dynamic,
548
582
  console = global.console,
583
+ text,
549
584
  } = {}
550
585
  ) {
551
586
  if (!validFontDisplayValues.includes(fontDisplay)) {
@@ -713,10 +748,27 @@ async function subsetFonts(
713
748
  htmlOrSvgAsset,
714
749
  globalTextByProps,
715
750
  textByProps,
716
- accumulatedFontFaceDeclarations
751
+ accumulatedFontFaceDeclarations,
752
+ text
717
753
  );
718
754
  }
719
755
 
756
+ for (const fontFaceDeclarations of fontFaceDeclarationsByHtmlOrSvgAsset.values()) {
757
+ for (const fontFaceDeclaration of fontFaceDeclarations) {
758
+ const firstRelation = fontFaceDeclaration.relations[0];
759
+ const subfontTextNode = firstRelation.node.nodes.find(
760
+ (childNode) =>
761
+ childNode.type === 'decl' &&
762
+ childNode.prop.toLowerCase() === '-subfont-text'
763
+ );
764
+
765
+ if (subfontTextNode) {
766
+ subfontTextNode.remove();
767
+ firstRelation.from.markDirty();
768
+ }
769
+ }
770
+ }
771
+
720
772
  if (omitFallbacks) {
721
773
  for (const htmlOrSvgAsset of htmlOrSvgAssets) {
722
774
  const accumulatedFontFaceDeclarations =
package/lib/unquote.js CHANGED
@@ -1,3 +1,13 @@
1
+ function unescapeCssString(str) {
2
+ return str.replace(
3
+ /\\([0-9a-f]{1,6})(\s?)/gi,
4
+ ($0, hexChars, followingWhitespace) =>
5
+ `${String.fromCharCode(parseInt(hexChars, 16))}${
6
+ hexChars.length === 6 ? followingWhitespace : ''
7
+ }`
8
+ );
9
+ }
10
+
1
11
  module.exports = function unquote(str) {
2
12
  if (typeof str !== 'string') {
3
13
  return str;
@@ -7,7 +17,7 @@ module.exports = function unquote(str) {
7
17
  /^'([^']*)'$|^"([^"]*)"$/,
8
18
  ($0, singleQuoted, doubleQuoted) =>
9
19
  typeof singleQuoted === 'string'
10
- ? singleQuoted.replace(/\\'/g, "'")
11
- : doubleQuoted.replace(/\\"/g, '"')
20
+ ? unescapeCssString(singleQuoted.replace(/\\'/g, "'"))
21
+ : unescapeCssString(doubleQuoted.replace(/\\"/g, '"'))
12
22
  );
13
23
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "subfont",
3
- "version": "6.4.1",
3
+ "version": "6.6.0",
4
4
  "description": "Speeds up your pages initial paint by automatically subsetting local or Google fonts and loading them optimally",
5
5
  "engines": {
6
6
  "node": ">=10.0.0"
@@ -54,7 +54,7 @@
54
54
  "css-list-helpers": "^2.0.0",
55
55
  "font-family-papandreou": "^0.2.0-patch2",
56
56
  "font-snapper": "^1.2.0",
57
- "font-tracer": "^3.1.0",
57
+ "font-tracer": "^3.2.0",
58
58
  "fontkit": "^1.8.0",
59
59
  "fontverter": "^2.0.0",
60
60
  "gettemporaryfilepath": "^1.0.1",