UNPKG

@web-native-js/chtml-cli

Version:
270 lines (257 loc) 9.31 kB
/** * @imports */ import Fs from 'fs'; import Path from 'path'; import _each from '@onephrase/util/obj/each.js'; import _merge from '@onephrase/util/obj/merge.js' import _isObject from '@onephrase/util/js/isObject.js'; import _isNumeric from '@onephrase/util/js/isNumeric.js'; import _isFunction from '@onephrase/util/js/isFunction.js'; import _beforeLast from '@onephrase/util/str/beforeLast.js'; import _before from '@onephrase/util/str/before.js'; import _after from '@onephrase/util/str/after.js'; import _preceding from '@onephrase/util/arr/preceding.js'; import _following from '@onephrase/util/arr/following.js'; import Lexer from '@onephrase/util/str/Lexer.js'; /** * --------------------------- * The Bundler class * --------------------------- */ export default class Bundler { /** * Bundles and saves (multiple). * * @param string|object from * @param string to * @param object params * * @return string|object */ static async bundle(from, to = null, params = {}) { if (_isObject(from)) { var fromNames = Object.keys(from), bundles = {}; var readShift = async () => { var name; if (!(name = fromNames.shift())) { return; } var saveName = to ? to.replace(/\[name\]/g, name) : ''; var bundler = await Bundler.readdir(basePath, params); bundles[name] = bundler.output(saveName); await readShift(); }; await readShift(); return bundles; } return (await Bundler.readdir(from, params)).output(to); } /** * Mounts a Bundler instance over a directory * and runs the directory-reading and files-loading process. * * @param string baseDir * @param object params * * @return void */ static async readdir(basePath, params) { const bundler = new Bundler(basePath, params); // -------------------------------- var load = async (resource, paramsCopy) => { var callLoader = async function(index, resource, recieved) { if (bundler.params.LOADERS && bundler.params.LOADERS[index]) { return await bundler.params.LOADERS[index](resource, recieved, paramsCopy, async (...args) => { return await callLoader(index + 1, resource, ...args); }); } if (!recieved) { //if (arguments.length === 2) { return bundler.load(resource, params); } return recieved; }; return await callLoader(0, resource); }; // -------------------------------- var resources = Fs.readdirSync(bundler.baseDir); var readShift = async () => { var resourceName; if (!(resourceName = resources.shift())) { return; } let resource = Path.join(bundler.baseDir, resourceName); if (Fs.statSync(resource).isDirectory()) { if (!(bundler.params.IGNORE_FOLDERS_BY_PREFIX || []).filter(prfx => resourceName.substr(0, prfx.length) === prfx).length) { var _params = {...bundler.params}; _params.indentation ++; // ------------------- var subBundler = await Bundler.readdir(resource, _params); // ------------------- bundler.outline[subBundler.displayName] = subBundler.outline; // ------------------- bundler.bundle.push(subBundler); } } else { var paramsCopy = _merge({errors:[], info:[],}, bundler.params); if (!bundler.params.loadStart || bundler.params.loadStart(resource) !== false) { var content = await load(resource, paramsCopy); if (bundler.params.loadEnd) { bundler.params.loadEnd(resource, content, paramsCopy.errors, paramsCopy.info); } if (content) { bundler.bundle.push(content); } } } await readShift(); }; await readShift(); return bundler; } /** * A Bundler instance. * * @param string baseDir * @param object params * * @return void */ constructor(baseDir, params = {}) { if (!baseDir.endsWith('/')) { baseDir += '/'; } this.baseDir = baseDir; this.params = params; this.params.ASSETS_PUBLIC_BASE = this.params.ASSETS_PUBLIC_BASE || '/'; this.params.indentation = this.params.indentation || 0; // ---------------------------------------- this.params.getAttributeDefinition = (tag, attributeName) => { var regexes = [' ' + attributeName + '([ ]+)?=([ ]+)?"([^"])+"', " " + attributeName + "([ ]+)?=([ ]+)?'([^'])+'"]; return regexes.reduce((result, regex) => { return result || Lexer.match(tag, regex, {stopChars: '>', useRegex:'i', blocks:[]})[0]; }); }; this.params.defineAttribute = (tag, attributeName, attributeValue) => { var parts = Lexer.split(tag, '>', {limit: 1, blocks:[]}); return parts[0] + ' ' + attributeName + '="' + attributeValue + '">' + parts[1]; }; // ---------------------------------------- this.name = this.params.indentation > 0 ? Path.basename(this.baseDir) : ''; this.displayName = !this.params.SHOW_OUTLINE_NUMBERING && _isNumeric(_before(this.name, '-')) ? _after(this.name, '-') : this.name; // ----------------------------------------- this.outline = {}; this.bundle = []; } /** * Loads a file and appends it * to the bundle on the specified namespace. * * @param string file * @param object params * * @return string|function */ load(file, params) { var ext = Path.extname(file) || ''; var slotName = _beforeLast(Path.basename(file), '.').toLowerCase(); if (ext in Bundler.mime) { return assetsDir => { if (Fs.statSync(file).size < params.MAX_DATA_URL_SIZE) { var url = 'data:' + Bundler.mime[ext] + ';base64,' + Fs.readFileSync(file).toString('base64'); } else { var absFilename = Path.join(assetsDir || this.baseDir, Path.basename(file)); Fs.mkdirSync(Path.dirname(absFilename), {recursive:true}); Fs.copyFileSync(file, absFilename); var assetsPublicFilename = getPublicFilename(absFilename, params.indentation); var url = params.ASSETS_PUBLIC_BASE + assetsPublicFilename; } if (params.PARTIALS_NAMESPACE_ATTR) { return `<img ${params.PARTIALS_NAMESPACE_ATTR}="${slotName}" src="${url}" />`; } return `<img src="${url}" />`; }; } else { var contents = Fs.readFileSync(file).toString(); var contentsTrimmed = contents.trim(); if (contentsTrimmed.startsWith('<') && !contentsTrimmed.startsWith('<!') && !contentsTrimmed.startsWith('<?xml')) { if (params.PARTIALS_NAMESPACE_ATTR && !params.getAttributeDefinition(contentsTrimmed, params.PARTIALS_NAMESPACE_ATTR)) { return params.defineAttribute(contentsTrimmed, params.PARTIALS_NAMESPACE_ATTR, slotName); } return contentsTrimmed; } } } /** * Stringifies the bundle * and optionally saves it to a Path. * * @param string outputFile * @param string outputFile * * @return string */ output(outputFile = null) { // ----------------------------------------- var outputFileExt, outputDir, subOutputDir, src; if (outputFile) { if (!Path.isAbsolute(outputFile)) { outputFile = Path.resolve(this.baseDir, outputFile); } if (outputFileExt = Path.extname(outputFile)) { outputDir = Path.dirname(outputFile); } else { outputDir = Path.join(outputFile, this.name); } } // ----------------------------------------- var subBundles = Object.keys(this.outline); var totalSubBundles = subBundles.length; var t = "\t".repeat(this.params.indentation + 1), t2 = "\t".repeat(this.params.indentation); var contents = "\r\n" + t + this.bundle.map(html => { if (html instanceof Bundler) { var templateHTML = html.output(outputDir); if (this.params.CREATE_OUTLINE_FILE) { templateHTML = this.params.defineAttribute(templateHTML, 'prev', _preceding(subBundles, html.displayName) || ''); templateHTML = this.params.defineAttribute(templateHTML, 'index', (subBundles.indexOf(html.displayName) + 1) + '/' + totalSubBundles); templateHTML = this.params.defineAttribute(templateHTML, 'next', _following(subBundles, html.displayName) || ''); } return templateHTML; } return _isFunction(html) ? html(outputDir) : html; }).join("\r\n\r\n" + t) + "\r\n" + t2; if (this.outlineHTML) { contents += "\r\n" + t + this.outlineHTML + "\r\n" + t2; } // ----------------------------------------- if (outputFileExt) { Fs.mkdirSync(outputDir, {recursive:true}); Fs.writeFileSync(outputFile, contents); if (this.params.CREATE_OUTLINE_FILE) { Fs.writeFileSync(_beforeLast(outputFile, '.') + '.json', JSON.stringify(this.outline, null, 4)); } src = getPublicFilename(outputFile, this.params.indentation); } // ----------------------------------------- return `<template${ (this.name ? ' ' + this.params.TEMPLATE_NAMESPACE_ATTR + '="' + this.displayName + '"' : '') + (src ? ' src="' + src + '"' : '') }>${(!src ? contents : '')}</template>`; } } /** * @var object */ Bundler.mime = { '.ico': 'image/x-icon', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.svg': 'image/svg+xml', }; /** * @var function */ const getPublicFilename = (filename, indentation) => { return filename.replace(/\\/g, '/').split('/').slice(- (indentation + 1)).join('/'); };