UNPKG

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
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)); }); };