Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Munter committed Sep 4, 2017
0 parents commit 3f3b0c2
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
node_modules
7 changes: 7 additions & 0 deletions LICENSE.md
@@ -0,0 +1,7 @@
Copyright 2017 Peter Brandt Müller

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
70 changes: 70 additions & 0 deletions README.md
@@ -0,0 +1,70 @@
subfont
=======

A command line tool to statically analyse your page in order to generate the most optimal web font subsets, then inject them into your page.

Speed up your time to first meaningful paint by reducing the web font payload and critical path to the font files.

Subfont will:
- Automatically figure out what characters are used from each font
- Create an exact subset of used characters of each font
- Add the font subsets to your pages with browser preload hints for reduced time to first meaningful paint
- Give the subsetted fonts new names and prepend them in front of the original fonts in your `font-family` definitions (enables missing glyph fallback)
- Async load your original `@font-face` declaring CSS at the bottom of your page, moving it off the critical path

Currently supported font services:
- Google fonts


Installation
-----

```
npm install -g subfont
```


Recommended usage
-----------------

Run subfont on the files you are ready to deploy to a static file hosting service. If these are build artifacts from another build system, and not the original files, run `subfont path/to/artifacts/index.html -i` to have `subfont` clobber the dist files in their original location.

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.


Other usages
------------

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`.


You can also have subfont scrape a website directly using http and write the output to local disk. This use is likely to fail in a number of ways and should mostly considered a demo feature if you just want to give the tool a quick go to see what it will do to your page. Run `subfont https://yourpage.me -o path/to/outputDir`.


Command line options
--------------------

```
$ subfont -h
Create optimal font subsets from your actual font usage.
subfont [options] <htmlFile(s) | url(s)>
Options:
-h, --help Show this help [default: false]
--root Path to your web root (will be deduced from your
input files if not specified)
-o, --output Directory where results should be written to
-i, --in-place Modify HTML-files in-place. Only use on build
artifacts [default: false]
--inline-subsets Inline fonts as data-URIs inside the @font-face
declaration [default: false]
--format Webfont format. Available options are `woff` and
`woff2` [default: "woff"]
-d, --debug Verbose insigts into font glyph detection
[default: false]
```

License
-------

MIT
146 changes: 146 additions & 0 deletions bin/subfont
@@ -0,0 +1,146 @@
#!/usr/bin/env node

var optimist = require('optimist');

var commandLineOptions = optimist
.usage('Create optimal font subsets from your actual font usage.\n$0 [options] <htmlFile(s) | url(s)>')
.options('h', {
alias: 'help',
describe: 'Show this help',
type: 'boolean',
default: false
})
.options('root', {
describe: 'Path to your web root (will be deduced from your input files if not specified)',
type: 'string',
demand: false
})
.options('o', {
alias: 'output',
describe: 'Directory where results should be written to',
type: 'string',
demand: false
})
.options('i', {
alias: 'in-place',
describe: 'Modify HTML-files in-place. Only use on build artifacts',
type: 'boolean',
default: false
})
.options('inline-subsets', {
describe: 'Inline fonts as data-URIs inside the @font-face declaration',
type: 'boolean',
default: false
})
.options('format', {
describe: 'Webfont format. Available options are `woff` and `woff2`',
type: 'string',
default: 'woff',
demand: false
})
.options('d', {
alias: 'debug',
describe: 'Verbose insigts into font glyph detection',
type: 'boolean',
default: false
})
.wrap(72)
.argv;

if (commandLineOptions.h) {
optimist.showHelp();
process.exit(1);
}

var urlTools = require('urltools');
var rootUrl = commandLineOptions.root && urlTools.urlOrFsPathToUrl(commandLineOptions.root, true);
var outRoot = commandLineOptions.output && urlTools.urlOrFsPathToUrl(commandLineOptions.output, true);
var inPlace = commandLineOptions['in-place'];
var inlineSubsets = commandLineOptions['inline-subsets'];
var format = commandLineOptions.format;
var debug = commandLineOptions.debug;
var inputUrls;

if (commandLineOptions._.length > 0) {
inputUrls = commandLineOptions._.map(function (urlOrFsPath) {
return urlTools.urlOrFsPathToUrl(String(urlOrFsPath), false);
});
if (!rootUrl) {
rootUrl = urlTools.findCommonUrlPrefix(inputUrls);

if (rootUrl.indexOf('file:') === -1) {
rootUrl = urlTools.ensureTrailingSlash(rootUrl);
}

if (rootUrl) {
console.error('Guessing --root from input files: ' + rootUrl);
}
}
} else if (rootUrl && /^file:/.test(rootUrl)) {
inputUrls = [rootUrl + '**/*.html'];
console.error('No input files specified, defaulting to ' + inputUrls[0]);
} else {
console.error('No input files and no --root specified (or it isn\'t file:), cannot proceed.\n');
optimist.showHelp();
process.exit(1);
}

if (inputUrls[0].indexOf('file:') === -1 && !outRoot) {
console.error('--output has to be specified when using non-file input urls');
process.exit(1);
}

if (!inPlace && !outRoot) {
console.error('Either --output or --in-place has to be specified');
process.exit(1);
}

var AssetGraph = require('assetgraph');
var query = AssetGraph.query;

var assetGraphConfig = {
root: rootUrl
};

if (rootUrl.indexOf('file:') === -1) {
assetGraphConfig.canonicalRoot = rootUrl + '/';
}

new AssetGraph({ root: rootUrl })
.logEvents()
.loadAssets(inputUrls)
.populate({
followRelations: {
crossorigin: false
}
})
.subsetGoogleFonts({
debug: debug,
inlineSubsets: inlineSubsets,
format: format
})

.if(rootUrl.indexOf('file:') === -1)
.queue(function rootRelativeRelations(assetGraph) {
assetGraph.findRelations().forEach(function (relation) {
if (relation.hrefType === 'protocolRelative' || relation.hrefType === 'absolute') {
relation.hrefType = 'rootRelative';
}
})
})
.moveAssets({ type: 'Html', isLoaded: true, isInline: false, fileName: query.or('', undefined) }, function (asset, assetGraph) {
var nextSuffixToTry = 0;
var url;
do {
url = asset.url.replace(rootUrl, outRoot).replace(/\/?$/, '/') + 'index' + (nextSuffixToTry ? '-' + nextSuffixToTry : '') + asset.defaultExtension;
nextSuffixToTry += 1;
} while (assetGraph.findAssets({ url: url }).length > 0);

return url;
})
.endif()

.writeAssetsToDisc({ isLoaded: true }, outRoot, rootUrl)
.run(function (assetGraph) {
console.log('Output written to', outRoot);
});
33 changes: 33 additions & 0 deletions package.json
@@ -0,0 +1,33 @@
{
"name": "subfont",
"version": "0.0.0",
"description": "Command line tool to inject Google font subsets of glyphs in use on your pages",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"bin": "bin/subfont",
"repository": {
"type": "git",
"url": "git+https://github.com/Munter/subfont.git"
},
"keywords": [
"google",
"font",
"webfont",
"subset",
"commandline",
"cli",
"automation"
],
"author": "Peter Müller <munter@fumle.dk>",
"license": "MIT",
"bugs": {
"url": "https://github.com/Munter/subfont/issues"
},
"homepage": "https://github.com/Munter/subfont#readme",
"dependencies": {
"assetgraph": "^v3.8.0",
"optimist": "^0.6.1",
"urltools": "^0.3.5"
}
}

0 comments on commit 3f3b0c2

Please sign in to comment.