webfonts-loader
Version:
A WebPack loader to automatically generate font files and CSS to make your own icon font
275 lines (234 loc) • 8.46 kB
JavaScript
var loaderUtils = require('loader-utils');
var webfontsGenerator = require('@vusion/webfonts-generator');
var path = require('path');
var glob = require('glob');
var url = require('url');
var hashFiles = require('./utils').hashFiles;
var NativeModule = require('module');
var mimeTypes = {
'eot': 'application/vnd.ms-fontobject',
'svg': 'image/svg+xml',
'ttf': 'application/x-font-ttf',
'woff': 'application/font-woff',
'woff2': 'font/woff2'
};
function getFilesAndDeps (patterns, context) {
var files = [];
var filesDeps = [];
var directoryDeps = [];
function addFile (file) {
filesDeps.push(file);
files.push(path.resolve(context, file));
}
function addByGlob (globExp) {
var globOptions = {
cwd: context
};
var foundFiles = glob.sync(globExp, globOptions);
files = files.concat(foundFiles.map(file => {
return path.resolve(context, file);
}));
var globDirs = glob.sync(path.dirname(globExp) + '/', globOptions);
directoryDeps = directoryDeps.concat(globDirs.map(file => {
return path.resolve(context, file);
}));
}
// Re-work the files array.
patterns.forEach(function (pattern) {
if (glob.hasMagic(pattern)) {
addByGlob(pattern);
} else {
addFile(pattern);
}
});
return {
files: files,
dependencies: {
directories: directoryDeps,
files: filesDeps
}
};
}
// Futureproof webpack option parsing
function wpGetOptions (context) {
if (typeof context.query === 'string') return loaderUtils.getOptions(context);
return context.query;
}
module.exports = function (content) {
this.cacheable();
var options = wpGetOptions(this) || {};
var rawFontConfig;
try {
rawFontConfig = JSON.parse(content);
} catch (ex) {
var module = new NativeModule(this.resourcePath);
module.paths = NativeModule._nodeModulePaths(this.context);
module.filename = this.resourcePath;
module._compile(content, this.resourcePath);
rawFontConfig = module.exports;
}
var fontConfig = Object.assign({}, options, rawFontConfig);
var filesAndDeps = getFilesAndDeps(fontConfig.files, this.context);
filesAndDeps.dependencies.files.forEach(this.addDependency.bind(this));
filesAndDeps.dependencies.directories.forEach(this.addContextDependency.bind(this));
fontConfig.files = filesAndDeps.files;
// With everything set up, let's make an ACTUAL config.
var formats = fontConfig.types || ['eot', 'woff2', 'woff', 'ttf', 'svg'];
if (formats.constructor !== Array) {
formats = [formats];
}
var generatorOptions = {
files: fontConfig.files,
fontName: fontConfig.fontName,
types: formats,
order: formats,
fontHeight: fontConfig.fontHeight || 1000, // Fixes conversion issues with small svgs,
codepoints: fontConfig.codepoints || {},
templateOptions: {
baseSelector: fontConfig.baseSelector || '.icon',
classPrefix: 'classPrefix' in fontConfig ? fontConfig.classPrefix : 'icon-'
},
scssFile: fontConfig.scssFile || false,
dest: fontConfig.dest || '',
html: fontConfig.html || false,
htmlDest: fontConfig.htmlDest || undefined,
cssDest: fontConfig.cssDest ? path.resolve(this.context, fontConfig.cssDest, fontConfig.fontName.toLowerCase() + '.css') : undefined,
cssFontsUrl: fontConfig.cssFontsUrl || '',
embed: fontConfig.embed || false,
formatOptions: fontConfig.formatOptions || {},
writeFiles: false
};
if ('startCodepoint' in fontConfig) generatorOptions.startCodepoint = fontConfig.startCodepoint;
if ('ligature' in fontConfig) generatorOptions.ligature = fontConfig.ligature;
if ('htmlTemplate' in fontConfig) generatorOptions.htmlTemplate = fontConfig.htmlTemplate;
// This originally was in the object notation itself.
// Unfortunately that actually broke my editor's syntax-highlighting...
// ... what a shame.
if (typeof fontConfig.rename === 'function') {
generatorOptions.rename = fontConfig.rename;
} else {
generatorOptions.rename = function (f) {
return path.basename(f, '.svg');
};
}
if (fontConfig.cssContext) {
generatorOptions.cssContext = fontConfig.cssContext;
}
if (fontConfig.htmlContext) {
generatorOptions.htmlContext = fontConfig.htmlContext;
}
if (fontConfig.cssTemplate) {
generatorOptions.cssTemplate = path.resolve(this.context, fontConfig.cssTemplate);
}
if (fontConfig.cssFontsUrl) {
generatorOptions.cssFontsUrl = path.resolve(this.context, fontConfig.cssFontsUrl);
}
if (fontConfig.htmlTemplate) {
generatorOptions.htmlTemplate = path.resolve(this.context, fontConfig.htmlTemplate);
}
if (fontConfig.htmlDest) {
generatorOptions.htmlDest = path.resolve(this.context, fontConfig.htmlDest);
}
if (fontConfig.dest) {
if (fontConfig.dest.endsWith('/')) {
generatorOptions.dest = fontConfig.dest;
} else {
generatorOptions.dest = `${fontConfig.dest}/`;
}
}
// Spit out SCSS file to same path as CSS file to easily use mixins (scssFile must be true)
if (fontConfig.cssDest && fontConfig.scssFile) {
generatorOptions.cssDest = path.resolve(this.context, fontConfig.cssDest, fontConfig.fontName.toLowerCase() + '.scss');
}
// svgicons2svgfont stuff
var keys = [
'fixedWidth',
'centerHorizontally',
'normalize',
'fontHeight',
'round',
'descent'
];
for (var x in keys) {
if (typeof fontConfig[keys[x]] !== 'undefined') {
generatorOptions[keys[x]] = fontConfig[keys[x]];
}
}
var cb = this.async();
var publicPath;
if (typeof options.publicPath === 'string') {
if (options.publicPath === '' || options.publicPath.endsWith('/')) {
publicPath = options.publicPath;
} else {
publicPath = `${options.publicPath}/`;
}
} else {
if (typeof options.publicPath === 'function') {
publicPath = options.publicPath(this.resourcePath, this.rootContext);
} else {
publicPath = this._compilation.outputOptions.publicPath || '/';
}
}
var embed = !!generatorOptions.embed;
if (generatorOptions.cssTemplate) {
this.addDependency(generatorOptions.cssTemplate);
}
if (generatorOptions.cssFontsUrl) {
this.addDependency(generatorOptions.cssFontsUrl);
}
webfontsGenerator(generatorOptions, (err, res) => {
if (err) {
return cb(err);
}
var urls = {};
for (var i in formats) {
var format = formats[i];
var filename = fontConfig.fileName || options.fileName || '[chunkhash]-[fontname].[ext]';
var chunkHash = filename.indexOf('[chunkhash]') !== -1
? hashFiles(generatorOptions.files, options.hashLength) : '';
filename = generatorOptions.dest.concat(filename);
filename = filename
.replace('[chunkhash]', chunkHash)
.replace('[fontname]', generatorOptions.fontName)
.replace('[ext]', format);
if (!embed) {
var formatFilename = loaderUtils.interpolateName(this,
filename,
{
context: this.rootContext || this.options.context || this.context,
content: res[format]
}
);
urls[format] = url.resolve(publicPath, formatFilename.replace(/\\/g, '/'));
this.emitFile(formatFilename, res[format]);
} else {
urls[format] = 'data:' +
mimeTypes[format] +
';charset=utf-8;base64,' +
(Buffer.from(res[format]).toString('base64'));
}
}
var emitCodepointsOptions = fontConfig.emitCodepoints || options.emitCodepoints || null;
if (emitCodepointsOptions) {
var emitCodepoints = require('./emit-codepoints');
emitCodepoints.emitFiles(this, emitCodepointsOptions, generatorOptions, options);
}
if (generatorOptions.html) {
var htmlDest = generatorOptions.htmlDest ? generatorOptions.htmlDest : generatorOptions.fontName + '.html';
htmlDest = generatorOptions.dest.concat(htmlDest);
htmlDest = loaderUtils.interpolateName(this,
htmlDest,
{
context: this.rootContext || this.options.context || this.context
}
);
var relativeUrls = {};
for (var key in urls) {
relativeUrls[key] = path.relative(url.resolve(publicPath, path.dirname(htmlDest.replace(/\\/g, '/'))), urls[key]);
}
var htmlContent = res.generateHtml(relativeUrls);
this.emitFile(htmlDest, htmlContent);
}
cb(null, res.generateCss(urls));
});
};