UNPKG

create-amp-page

Version:

Full fledged static side generator composed out of extendable gulp tasks, optimized for - but not limited to - AMP.

203 lines (189 loc) 9.04 kB
import {imageSize} from 'image-size' import path from 'path' import gulp from 'gulp' import logger from 'gulplog' import crypto from 'crypto' import fs from 'fs' import through2 from 'through2' import gulpRename from 'gulp-rename' import {mediaOptimizer} from '../mediaTask/mediaOptimizer.js' import sharp from 'sharp' const getRelativeMediaPath = (src, distMedia) => src.replace(new RegExp(distMedia, 'i'), '') const containsRelativeSize = (size) => size.indexOf('%') !== -1 || size.indexOf('vw') !== -1 export const {resizeUsedImages, getImage, clearGetMediaCache} = (() => { const imageRefs = {current: {}} const clearGetMediaCache = function clearGetMediaCache(done) { imageRefs.current = {} if(done) { done() } } const getImage = (mediaPath, distMedia) => ({ name: 'getImage', func: (src, srcset) => { if(!imageRefs.current[src] || !imageRefs.current[src].hash) { let dimensions = {width: 0, height: 0} let hash try { if(src.indexOf('//') !== -1) { // absolute src, can not be handled return undefined } const srcPath = path.join(mediaPath, getRelativeMediaPath(src, distMedia)) dimensions = imageSize(srcPath) try { const fileData = fs.readFileSync(srcPath) hash = crypto.createHash('sha1').update(fileData).digest('base64') } catch(e) { throw e } } catch(e) { if(process.env.NODE_ENV !== 'production') { console.warn('getImage', e) } else { console.error('getImage', e) throw e } } if(hash) { imageRefs.current[src] = { src, width: dimensions.width, height: dimensions.height, hash, srcset: {}, } } } if(imageRefs.current[src] && srcset) { if(Array.isArray(srcset)) { srcset.forEach(set => { if(set.w && !set.h) { if(!containsRelativeSize(set.w)) { imageRefs.current[src].srcset[set.w + 'w'] = {width: set.w} } } else if(!set.w && set.h) { if(!containsRelativeSize(set.h)) { imageRefs.current[src].srcset[set.h + 'h'] = {height: set.h} } } else if(set.w && set.h) { if(!containsRelativeSize(set.w) && !containsRelativeSize(set.h)) { imageRefs.current[src].srcset[set.h + 'w' + set.h + 'h'] = {width: set.w, height: set.h} } } }) } else { logger.error('srcset is set, but no array at src `' + src + '`, it is typeof ' + (typeof srcset)) } } return imageRefs.current[src] }, }) // not a twig function but used directly here to resize images after content processing const resizeUsedImages = ({media, dist, distMedia, imageminPlugins}) => function resizeUsedImages() { const resizer = [] if(imageRefs.current) { Object.keys(imageRefs.current).forEach(imgSrc => { const img = imageRefs.current[imgSrc] // check for hash: only when image could be accessed if(img.hash && img.srcset) { const aspect = (img.width / img.height) const makeResizing = (width, height, suffix) => { width = parseInt(width) height = parseInt(height) if(!width || !height || isNaN(width) || isNaN(height)) { if(process.env.NODE_ENV !== 'production') { console.warn('must-have-sizes') } else { throw ('must-have-sizes') } } const srcPath = path.join(media, getRelativeMediaPath(img.src, distMedia)) const distFileName = addImageSuffixFn(getRelativeMediaPath(img.src, distMedia), suffix) const distPath = path.join(dist, distMedia, distFileName) if(fs.existsSync(distPath)) { logger.info('File exists skipped: ' + distPath) return Promise.resolve() } return new Promise((resolve, reject) => { gulp .on('error', err => { if(process.env.NODE_ENV !== 'production') { console.log(err) resolve() } else { reject(err) } }) .src(srcPath) .pipe(through2.obj(async (file, _, cb) => { if(file.isBuffer()) { sharp(file.contents) .resize(width, height) .toBuffer() .then((data) => { file.contents = data cb(null, file) }) .catch((e) => { if(process.env.NODE_ENV !== 'production') { console.warn(e) cb(null, file) } else { cb(e) } }) } else { cb(null, file) } })) .pipe(gulpRename(distFileName)) .pipe(mediaOptimizer(media, imageminPlugins)) .pipe(gulp.dest(path.join(dist, distMedia))) .on('end', () => { logger.info('Resized: ' + img.src + ' @ ' + width + 'x' + height) resolve() }) }) } Object.keys(img.srcset).forEach(imgSrc => { const {width, height} = img.srcset[imgSrc] if(width && !height) { resizer.push(makeResizing( width, width / aspect, '_' + width + 'w', )) } else if(!width && height) { resizer.push(makeResizing( height * aspect, height, '_' + height + 'h', )) } else if(width && height) { resizer.push(makeResizing( width, height, '_' + width + 'w' + height + 'h', )) } }, ) } }) } return Promise.all(resizer).then(() => undefined) } return {resizeUsedImages, getImage, clearGetMediaCache} })() // todo: rename to better `domain` function for adding size suffix const addImageSuffixFn = (src = '', suffix = '') => { const lowerSrc = src.toLowerCase() const exts = ['.jpg', '.jpeg', '.png'] // `.svg` must not be suffixed with `width` (as always relative), so here it is ignored in general // todo: add also generic exts.forEach(foundExt => { if(lowerSrc.endsWith(foundExt)) { src = src.substr(0, src.length - foundExt.length) + suffix + foundExt } }) return src } export const addImageSuffix = { name: 'addImageSuffix', func: addImageSuffixFn, }