UNPKG

5.12 kBJavaScriptView Raw
1import fs from 'fs'
2import path from 'path'
3import crypto from 'crypto'
4import { makeRe } from 'minimatch'
5import imagemin from 'imagemin'
6import mkdirp from 'mkdirp'
7import promisify from 'util.promisify'
8
9export const readFile = promisify(fs.readFile)
10const writeFileAsync = promisify(fs.writeFile)
11const mkdirpAsync = promisify(mkdirp)
12
13/**
14 * Optimizes a single image, returning the orignal if the "optimized" version is larger
15 * @param {Object} imageData
16 * @param {Object} imageminOptions
17 * @return {Promise(asset)}
18 */
19export async function optimizeImage (imageData, imageminOptions) {
20 // Ensure that the contents i have are in the form of a buffer
21 const imageBuffer = (Buffer.isBuffer(imageData) ? imageData : Buffer.from(imageData, 'utf8'))
22 // And get the original size for comparison later to make sure it actually got smaller
23 const originalSize = imageBuffer.length
24
25 // Await for imagemin to do the compression
26 const optimizedImageBuffer = await imagemin.buffer(imageBuffer, imageminOptions)
27
28 // If the optimization actually produced a smaller file, then return the optimized version
29 if (optimizedImageBuffer.length < originalSize) {
30 return optimizedImageBuffer
31 } else {
32 // otherwize return the orignal
33 return imageBuffer
34 }
35}
36
37/**
38 * Tests a filename to see if it matches any of the given test functions
39 * This function is curried, pass in the first 3 params first, then the next 2
40 * for each test needed
41 * @param {RegExp|RegExp[]|Function|Function[]|String|String[]} rawTestValue
42 * @param {Number} minFileSize
43 * @param {Number} maxFileSize
44 * @return {Boolean}
45 */
46export function buildTestFunction (rawTestValue, minFileSize, maxFileSize) {
47 const testFunctions = compileRegex(rawTestValue)
48 /**
49 * @param {String} filename
50 * @param {assetSource} assetSource
51 * @return {Boolean}
52 */
53 return (filename, assetSource) => {
54 for (let func of testFunctions) {
55 if (func(filename) === true) {
56 return assetSource.length > minFileSize && assetSource.length <= maxFileSize
57 }
58 }
59 return false
60 }
61}
62
63/**
64 * hashes file contents to make sure I can uniquely store a file even with absolute paths
65 * @param {string} content File contents
66 * @return {string} A hash of the full file contents
67 */
68export function hashContent (content) {
69 return crypto.createHash('sha1').update(content).digest('hex')
70}
71
72/**
73 * Invokes the passed in argument if it's a function
74 * @param {Function|Any} func
75 * @return {Any}
76 */
77export function invokeIfFunction (func) {
78 if (typeof func === 'function') {
79 return func()
80 } else {
81 return func
82 }
83}
84
85/**
86 * Gets the buffer of the file from cache. If it doesn't exist or the cache is
87 * not enabled, it will invoke elseFunc and use it's result as the result of the
88 * function, saving the result in the cache
89 * @param {String} cacheFolder
90 * @param {String} content
91 * @param {Function} elseFunc
92 * @return {Buffer}
93 */
94export async function getFromCacheIfPossible (cacheFolder, content, elseFunc) {
95 let cacheFilePath
96 if (cacheFolder !== null) {
97 cacheFilePath = path.resolve(cacheFolder, hashContent(content))
98 if (await exists(cacheFilePath)) {
99 return readFile(cacheFilePath)
100 }
101 }
102
103 const fileBuffer = await elseFunc()
104 if (cacheFolder !== null) {
105 await writeFile(cacheFilePath, fileBuffer)
106 }
107 return fileBuffer
108}
109
110/**
111 * checks if a file/directory is accessable
112 * @param {any} directory
113 * @returns
114 */
115export async function exists (directory) {
116 return new Promise((resolve, reject) => {
117 fs.access(directory, fs.constants.R_OK | fs.constants.W_OK, (err) => {
118 if (err) {
119 resolve(false)
120 } else {
121 resolve(true)
122 }
123 })
124 })
125}
126
127/**
128 * async wrapper for writeFile that will create the directory if it does not already exist
129 * @param {String} filename
130 * @param {Buffer} buffer
131 * @returns
132 */
133export async function writeFile (filename, buffer) {
134 const directory = path.dirname(filename)
135 // if the directory doesn't exist, create it
136 if (!(await exists(directory))) {
137 await mkdirpAsync(directory)
138 }
139
140 return writeFileAsync(filename, buffer)
141}
142
143/**
144 * Compiles a regex, glob, function, or an array of any of them to an array of functions
145 * @param {RegExp|RegExp[]|Function|Function[]|String|String[]} rawTestValue
146 * @return {Function[]}
147 */
148function compileRegex (rawTestValue) {
149 const tests = Array.isArray(rawTestValue) ? rawTestValue : [rawTestValue]
150
151 return tests.map((test) => {
152 if (typeof test === 'function') {
153 // if it's a function, just return this
154 return test
155 } else if (test instanceof RegExp) {
156 // If it's a regex return it wrapped in a function
157 return (filename) => test.test(filename)
158 } else if (typeof test === 'string') {
159 // If it's a string, let minimatch convert it to a regex then wrap that in a function
160 const regex = makeRe(test)
161 return (filename) => regex.test(filename)
162 } else {
163 throw new Error('test parameter must be a regex, glob string, function, or an array of any of them')
164 }
165 })
166}