const path = require('path') const fs = require('fs') const sharp = require('sharp') const mkdirp = require('mkdirp') const crypto = require('crypto') const slugify = require('slugify') const assign = require('assign-deep') const configStore = require('./../config.js') /** * Media * * change size, optimize and copy media to assets * * @author Björn Hase * @license http://opensource.org/licenses/MIT The MIT License * @link https://gitea.node001.net/HerrHase/siteomat-webpack-plugin.git * */ class Media { constructor(dirPath = NULL) { this._path = dirPath this._DIR_ASSETS = '/assets/' } /** * resolve media * * @param {string} src * @param {object} sizes * @param {Object} [options={}] * @return {string} * */ resolve(src, sizes = {}, options = {}) { let extension = path.extname(src) let sourcePath const filename = slugify(path.basename(src, extension)) // check for images in path sourcePath = this._getSourcePath(src) // getting sharp const process = sharp(sourcePath) if (extension === '.gif') { process .gif({ reoptimise: true }) } else { // change extension extension = '.webp' process .webp({ lossless: true }) } // destination const destinationPath = this._getDestinationPath(sourcePath) // create files to write const filesToWrite = this._getFilesToWrite(filename, extension, destinationPath, sizes) // results contains only path as strings const results = {} // create path if not exists if (!fs.existsSync(configStore.get('destination') + destinationPath)) { mkdirp.sync(configStore.get('destination') + destinationPath) } filesToWrite.forEach((file) => { if (!fs.existsSync(configStore.get('destination') + file.path)) { this._writeFile(file, process, options) } results[file.name] = file.path }) return this._reduce(results) } /** * get source path * * @param {String} src * @return {String} * */ _getSourcePath(src) { let sourcePath = configStore.get('source') + '/' + src if (Array.isArray(this._path)) { if (fs.existsSync(configStore.get('source') + this._path[0] + '/' + src)) { sourcePath = configStore.get('source') + this._path[0] + '/' + src } else if (fs.existsSync(configStore.get('source') + this._path[1] + '/' + src)) { sourcePath = configStore.get('source') + this._path[1] + '/' + src } } else if (this._path && fs.existsSync(configStore.get('source') + this._path + '/' + src)) { sourcePath = configStore.get('source') + this._path + '/' + src } return sourcePath } /** * if only full is in results, reduce object to string * * @param {Object} results * @return {mixed} * */ _reduce(results) { if (Object.getOwnPropertyNames(results).length === 1) { results = results['full'] } return results } /** * getting files to write * * @param {string} src * @param {string} extension * @param {Object} sizes * @return {string} */ _getFilesToWrite(filename, extension, destinationPath, sizes) { const results = [] // add orginal results.push(this._getFile(filename, destinationPath, extension)) // check for sizes if (typeof sizes === 'object' && !Array.isArray(sizes)) { results.push(this._getFile(filename, destinationPath, extension, sizes)) } else if (Array.isArray(sizes)) { sizes.forEach((size) => { results.push(this._getFile(filename, destinationPath, extension, size)) }) } return results } /** * write files to destination * * @param {string} file * @param {object} process * @param {Object} options */ _writeFile(file, process, options) { // if sizes have height or width with no optional parameters then check for merge of options if (file.sizes && (((!file.sizes.height || !file.sizes.width) && Object.getOwnPropertyNames(file.sizes).length === 1) || (file.sizes.height && file.sizes.width && Object.getOwnPropertyNames(file.sizes).length === 2))) { process.resize(this._mergeOptions(file.sizes, options)) // if already options in sizes ignore options } else if (file.sizes) { process.resize(file.sizes) } process.toFile(configStore.get('destination') + file.path) } /** * if options are exists merge them with sizes * * @param {object} sizes * @param {Object} options */ _mergeOptions(sizes, options) { if (Object.getOwnPropertyNames(options).length > 0) { sizes = assign(options, sizes) } return sizes } /** * generate destination path from hash of file * * @param {string} * @return {string} */ _getDestinationPath(sourcePath) { const hash = crypto.createHash('sha1') const file = fs.readFileSync(sourcePath) // getting hash from file hash.update(file) return this._DIR_ASSETS + hash.digest('hex').match(new RegExp('.{1,8}', 'g')).join('/') } /** * create file as object, adding path, name with sizes * * @param {string} filename * @param {object} destinationPath * @param {Object} extension * @param {sizes} sizes * @return {object} */ _getFile(filename, destinationPath, extension, sizes = undefined) { let file = { name: '' } let prefix = '' // check for sizes if (sizes && sizes.width) { file.name += sizes.width } if (sizes && sizes.height) { if (sizes.width) { prefix = 'x' } file.name += prefix + sizes.height } // create path before name is set to orginal as fallback file.path = destinationPath + '/' + filename + file.name + extension if (!file.name) { file.name = 'full' } if (sizes) { file.sizes = sizes } return file } } module.exports = Media