PostCSS plugin to simplify font declarations by validating only configured font packs are used, adding fallbacks and transpiling human-readable font declaration values into valid CSS.
Dealing with fonts can be a pain, especially on teams where not everybody knows where to find the exact fonts they are allowed to use. As a result, mistakes are made, inconsistencies are introduced and maintenance becomes a nightmare. PostCSS Font Pack aims to solve this problem with configuration.
Let's start with the following assumptions:
- We're using "Times New Roman" because it's a commonly used web safe font. It also illustrates how to use fonts that need quotes in this plugin.
- We've installed Roboto and already setup its @font-face.
These fonts can be defined in JSON format. You might call it font-packs.json
:
{
"times": {
"family": ["'Times New Roman'", "Times", "serif"],
"propGroups": [
{},
{
"weight": ["bold", 700]
}
]
},
"roboto": {
"family": ["Roboto", "Arial", "sans-serif"],
"propGroups": [
{
"style": "italic",
"weight": ["light", 300],
"stretch": "condensed",
"variant": "small-caps"
}
]
}
}
With the above configuration, we can write our CSS using the font shorthand property:
.foo {
font: bold 1rem/1.2 times;
}
.bar {
font: light condensed italic 1rem/1.2 roboto;
}
This would transpile into the following:
.foo {
font: 700 1rem/1.2 'Times New Roman', Times, serif;
}
.bar {
font: 300 condensed italic 1rem/1.2 Roboto, Arial, sans-serif;
}
Notice the weight was changed from bold
to 700
and from light
to 300
. This came from the configuration's declaration value aliases, which were defined as "weight": ["bold", 700]
and "weight": ["light", 300]
, respectively. You can do this with any of the prop groups, but since style: italic
, stretch: condensed
and variant: small-caps
are already understood by the browser, it only made sense to use an alias for the weight in this case. You could have just as well congired the weight as "weight": 300
, but that's not as human-readable as light
, which the browser doesn't understand.
Also, notice that fallback fonts were added to the font-family
. This allows you to keep your syntax easy to read/write and let the plugin do the dirty work with configuration.
You don't have to use the font shorthand property. You can also write-out each declaration individually or you can use the postcss-nested-props
plugin to enable a nested syntax. Just make sure you unwrap the nested with that plugin before you run this one.
This plugin also handles linting so you can sleep sound knowing that nobody is using fonts or combinations of font declarations that are not supported or otherwise go against the design of the site. The following rules would all throw the same error, "pack not found":
.foo {
font-family: "Futura PT";
}
.bar {
font-family: roboto, sans-serif;
}
.baz {
font: light 1rem/1.2 roboto;
}
Even though the light
weight is found in your configuration, there is no font pack that uses light
without also using italic
and condensed
. You have to use all three of them together to form a pack and to pass linting.
As you can see, this plugin will stop unsupported font declarations dead in their tracks.
If you need to ignore a specific declaration, but don't want to ignore the entire stylesheet, you can do so by preceding the declaration with a special comment:
.foo {
/* postcss-font-pack: ignore-next */
font: "Comic Sans", cursive;
}
This will cause the linter to ignore only the very next selector.
You can also ignore ranges:
/* postcss-font-pack: start-ignore */
.foo {
font: "Comic Sans", cursive;
font-size: 38px;
}
/* postcss-font-pack: end-ignore */
$ npm install postcss-font-pack
postcss([
require('postcss-font-pack')({
packs: require('./font-packs.json')
})
]);
import * as postcssFontPack from 'postcss-font-pack';
postcss([
postcssFontPack({
packs: require('./font-packs.json')
})
]);
Type: { [prop: string]: string; }
Required: false
Default: undefined
A collection of declarations that you would like to ignore. These could be CSS hacks or something else that you really don't want throwing validation errors. Example below:
{
ignoreDeclarations: [
{ font: '0/0 serif' }
]
}
Type: boolean
Required: false
Default: false
When true
, an error will be thrown if you have a rule with one or more font declarations, but without a font size.
.foo {
font-family: roboto;
/* missing required font-size */
}
Regardless of this option, if you have a rule with only a font-size
specified you will get an error:
.foo {
font-size: 1rem;
/* font-size missing required family */
}
Type: Object
Required: true
An object literal where the keys are slugified fonts and the values are font packs. Each font pack consists of a required family
and an optional collection of property groups, named as propGroups
.
Type: string[]
Required: true
If your font slug is times
, this is where you would define the extended font name along with any fallbacks.
Note: If your font name requires quotes, you must add them yourself.
Type: PropGroup[]
Required: false
Define the property combinations that can be used together to reference a font.
Type: PropGroup
Each prop group may contain 0 or more of the following keys:
Each value can be a string
or a string[]
with two values. The first value is a slugified value that you can type in your CSS to reference the associated key. The second value is what the first value will be transpiled into, so make sure they are CSS-valid. The weight
values can additionally be numbers.
If an empty object is provided, this indicates that you want to support this font family with default browser values for weight, style, variant and stretch.
Note: If you don't include an empty object you will be unable to reference a family without also referencing additional properties.
Run the following command:
$ npm test
This will build scripts, run tests and generate a code coverage report. Anything less than 100% coverage will throw an error.
For much faster development cycles, run the following commands in 2 separate processes:
$ npm run build:watch
Compiles TypeScript source into the ./dist
folder and watches for changes.
$ npm run watch
Runs the tests in the ./dist
folder and watches for changes.