Skip to content

Commit 46cda66

Browse files
authored
Merge pull request #417 from BeAPI/feat/image-sizes
Watch image tpl folder
2 parents b5b5420 + a3ac6f9 commit 46cda66

File tree

10 files changed

+272
-449
lines changed

10 files changed

+272
-449
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,6 @@ package-lock.json
8282
### config
8383
/vendor/
8484
composer.lock
85+
86+
### conf-img
87+
assets/conf-img/*.json

assets/conf-img/image-locations.json

Lines changed: 0 additions & 28 deletions
This file was deleted.

assets/conf-img/image-sizes.json

Lines changed: 0 additions & 19 deletions
This file was deleted.

config/WebpackImageSizesPlugin.js

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
const chalk = require('chalk')
2+
const path = require('path')
3+
const fs = require('fs')
4+
const sharp = require('sharp')
5+
6+
const logId = '[' + chalk.blue('WebpackImageSizesPlugin') + ']'
7+
8+
class WebpackImageSizesPlugin {
9+
constructor(options) {
10+
// folders
11+
this._confImgFolder = path.resolve(__dirname, '../assets/conf-img') + '/'
12+
this._tplFolder = this._confImgFolder + 'tpl/'
13+
this._defaultImagesFolder = path.resolve(__dirname, '../dist/images') + '/'
14+
15+
// files
16+
this._imageSisesJson = this._confImgFolder + 'image-sizes.json'
17+
this._imageLocationsJson = this._confImgFolder + 'image-locations.json'
18+
this._imageDefault = path.resolve(__dirname, '../src/img/static') + '/default.jpg'
19+
20+
// list of [filnename]: sizes
21+
this._defaultImages = {}
22+
23+
if (options.watch) {
24+
fs.watch(this._tplFolder, () => {
25+
this.generateImageJsonFiles()
26+
this.generateDefaultImages()
27+
})
28+
}
29+
30+
this.generateImageJsonFiles()
31+
}
32+
33+
/**
34+
* apply
35+
*/
36+
apply(compiler) {
37+
compiler.hooks.afterEmit.tapAsync('WebpackImageSizesPlugin', (compilation, callback) => {
38+
this.generateDefaultImages()
39+
callback()
40+
})
41+
}
42+
43+
/**
44+
* Generate image-sises.json and image-location.json
45+
*/
46+
generateImageJsonFiles() {
47+
const that = this
48+
const regex = {
49+
srcset: /data-srcset="(.[^"]*)"/gm,
50+
crop: /crop="(.[^"]*)"/gm,
51+
img: /img-\d*-\d*/gm,
52+
}
53+
54+
const locations = {}
55+
const sizes = {}
56+
57+
let nbLocations = 0
58+
let nbSizes = 0
59+
60+
/**
61+
* reset defaultImages
62+
*/
63+
this._defaultImages = {}
64+
65+
/**
66+
* Return an array of names of tpl files
67+
* @return {array}
68+
*/
69+
function getTemplateFileNames() {
70+
return fs.readdirSync(that._tplFolder).filter((tpl) => {
71+
if (tpl !== 'default-picture.tpl' && tpl !== 'default-picture-caption.tpl') {
72+
return tpl
73+
}
74+
})
75+
}
76+
77+
/**
78+
* Return content of tpl file
79+
* @param {string} templateFileName
80+
* @return {string}
81+
*/
82+
function getTemplateFileContent(templateFileName) {
83+
return fs.readFileSync(that._tplFolder + templateFileName, 'utf8')
84+
}
85+
86+
/**
87+
* Create a json file
88+
* @param {string} destPath
89+
* @param data
90+
* @return undefined
91+
*/
92+
function createJsonFile(destPath, data) {
93+
createFile(destPath, JSON.stringify(data, null, 2))
94+
}
95+
96+
/**
97+
* Remove extension template name
98+
* @param {string} name
99+
* @return {String}
100+
*/
101+
function removeFileExtension(name) {
102+
return name.split('.')[0]
103+
}
104+
105+
/**
106+
* Generate default location name based on image size
107+
* @param {String} size
108+
* @return {String}
109+
*/
110+
function getDefaultImgName(str) {
111+
return `${str.replace('img', 'default')}.jpg`
112+
}
113+
114+
/**
115+
* Check if srcset is retina
116+
* @param {String} src
117+
* @return {Array}
118+
*/
119+
function isRetina(src) {
120+
const retina = []
121+
src.split(',').forEach((val) => {
122+
if (val.includes('2x')) {
123+
retina.push('2x')
124+
} else {
125+
retina.push('')
126+
}
127+
})
128+
return retina
129+
}
130+
131+
/**
132+
* Create file if he does not exist
133+
* @param {String} filename
134+
* @param {String} json
135+
*/
136+
function createFile(filename, json) {
137+
fs.open(filename, 'r', () => {
138+
fs.writeFileSync(filename, json)
139+
})
140+
}
141+
142+
/**
143+
* Get image locations informations from tpl files
144+
*/
145+
function imageLocationsFromTpl() {
146+
const templateFileNames = getTemplateFileNames()
147+
148+
templateFileNames.forEach((tplName) => {
149+
nbLocations += 1
150+
const tplContent = getTemplateFileContent(tplName)
151+
const srcsetArr = tplContent.match(regex.srcset) || []
152+
const cropArr = tplContent.match(regex.crop)
153+
const storage = {
154+
srcsets: [],
155+
default_img: '',
156+
img_base: '',
157+
}
158+
159+
srcsetArr.forEach((src) => {
160+
const dimensions = src.match(regex.img)
161+
const retina = isRetina(src)
162+
const crop = !(cropArr && cropArr[0] === 'crop="false"')
163+
164+
dimensions.forEach((size, index) => {
165+
const splitSize = size.split('-')
166+
const srcsetObj = {
167+
srcset: retina[index],
168+
size,
169+
}
170+
171+
if (sizes[size] && sizes[size].crop !== crop) {
172+
console.log(logId, '\nSize already exists but crop is not equal :', size)
173+
}
174+
175+
if (!sizes[size]) {
176+
sizes[size] = {
177+
width: splitSize[1],
178+
height: splitSize[2],
179+
crop,
180+
}
181+
182+
nbSizes += 1
183+
}
184+
185+
storage.srcsets.push(srcsetObj)
186+
storage.default_img = getDefaultImgName(size)
187+
storage.img_base = size
188+
189+
that._defaultImages[getDefaultImgName(size)] = sizes[size]
190+
})
191+
192+
locations[removeFileExtension(tplName)] = [storage]
193+
})
194+
})
195+
}
196+
197+
/**
198+
* Init
199+
*/
200+
console.log(logId, 'Generate image locations and sizes JSON files')
201+
202+
imageLocationsFromTpl()
203+
204+
createJsonFile(this._imageLocationsJson, [locations])
205+
createJsonFile(this._imageSisesJson, [sizes])
206+
207+
console.log(logId, 'JSON files successfully generated !')
208+
console.log(logId, 'Number of locations:', nbLocations)
209+
console.log(logId, 'Number of sizes:', nbSizes)
210+
211+
return this
212+
}
213+
214+
/**
215+
* generate default images
216+
*/
217+
generateDefaultImages() {
218+
if (!fs.existsSync(this._defaultImagesFolder)) {
219+
fs.mkdirSync(this._defaultImagesFolder, { recursive: true })
220+
}
221+
222+
for (const filename in this._defaultImages) {
223+
const sizes = this._defaultImages[filename]
224+
const outputFile = this._defaultImagesFolder + filename
225+
226+
try {
227+
if (fs.existsSync(outputFile)) {
228+
continue
229+
}
230+
231+
const width = parseInt(sizes.width, 10)
232+
const height = parseInt(sizes.height, 10)
233+
234+
if (width >= 9999 || height >= 9999) {
235+
continue
236+
}
237+
238+
sharp(this._imageDefault)
239+
.resize(width, height, {
240+
fit: 'cover',
241+
position: 'center',
242+
})
243+
.jpeg({ quality: 70, chromaSubsampling: '4:4:4' })
244+
.toFile(outputFile, (err, info) => {
245+
if (err) {
246+
console.error(logId, err)
247+
} else {
248+
console.log(logId, `Created ${info.width}x${info.height} image`)
249+
console.log(logId, 'at', outputFile.split('/themes/')[1] || '')
250+
}
251+
})
252+
} catch (err) {
253+
console.error(logId, err)
254+
}
255+
}
256+
257+
return this
258+
}
259+
}
260+
261+
module.exports = WebpackImageSizesPlugin

0 commit comments

Comments
 (0)