|
| 1 | +/* eslint-disable no-console */ |
1 | 2 | const { basename, extname, resolve } = require('path');
|
2 |
| -const { cpSync, readdirSync, readFileSync, rmSync, mkdirSync } = require('fs'); |
| 3 | +const { rmSync, mkdirSync } = require('fs'); |
| 4 | +const { cp, readdir, readFile } = require('fs/promises'); |
| 5 | +const sharp = require('sharp'); |
3 | 6 |
|
4 | 7 | const { buildRecipes } = require('./src/buildRecipes');
|
5 | 8 | const buildRecipeIndex = require('./src/buildRecipeIndex');
|
6 | 9 | const configs = require('./config');
|
7 | 10 |
|
8 |
| -const MARKDOWN_EXT = '.md'; |
| 11 | +const THUMBNAIL_WIDTH = 260; |
9 | 12 |
|
10 |
| -function getRecipeFileList({ recipesPath } = {}) { |
11 |
| - return readdirSync(recipesPath) |
12 |
| - .filter(fileName => extname(fileName) === MARKDOWN_EXT) |
13 |
| - .map(fileName => ({ |
14 |
| - file: resolve(recipesPath, fileName), |
15 |
| - name: basename(fileName, MARKDOWN_EXT), |
16 |
| - })); |
17 |
| -} |
18 |
| - |
19 |
| -function readTemplates({ templatesPath }) { |
20 |
| - return { |
21 |
| - indexTemplate: readFileSync(resolve(templatesPath, 'index.html'), { encoding: 'utf8' }), |
22 |
| - recipeTemplate: readFileSync(resolve(templatesPath, 'recipe.html'), { encoding: 'utf8' }), |
23 |
| - }; |
24 |
| -} |
| 13 | +const filterByExtension = (fileList, recipesPath, allowedExtensions) => fileList |
| 14 | + .filter(fileName => allowedExtensions.includes(extname(fileName))) |
| 15 | + .map(fileName => ({ |
| 16 | + file: resolve(recipesPath, fileName), |
| 17 | + fileName, |
| 18 | + name: basename(fileName, extname(fileName)), |
| 19 | + })); |
25 | 20 |
|
26 | 21 | function setupOutputDir(outputPath) {
|
27 | 22 | rmSync(outputPath, { recursive: true, force: true });
|
28 | 23 | mkdirSync(outputPath);
|
| 24 | + mkdirSync(resolve(outputPath, 'sources')); |
| 25 | + mkdirSync(resolve(outputPath, 'images')); |
| 26 | + mkdirSync(resolve(outputPath, 'images/thumbnails')); |
| 27 | +} |
29 | 28 |
|
| 29 | +function copyStatic({staticPath, imagesPath, outputPath, recipesPath}) { |
| 30 | + Promise.all([ |
| 31 | + cp(staticPath, outputPath, { recursive: true }), |
| 32 | + cp(imagesPath, resolve(outputPath, 'images'), { recursive: true }), |
| 33 | + cp(recipesPath, resolve(outputPath, 'sources'), { recursive: true }), |
| 34 | + ]) |
| 35 | + .catch((err) => { |
| 36 | + console.error(err); |
| 37 | + }); |
30 | 38 | }
|
31 | 39 |
|
32 |
| -function copyStatic(staticPath, imagesPath, outputPath) { |
33 |
| - cpSync(staticPath, outputPath, { recursive: true }); |
34 |
| - cpSync(imagesPath, resolve(outputPath, 'images'), { recursive: true }); |
| 40 | +function makeThumbnails(outputPath, images) { |
| 41 | + images |
| 42 | + .forEach(({ file, name }) => { |
| 43 | + const thumbnailPath = resolve(outputPath, `images/thumbnails/${name}.jpg`); |
| 44 | + sharp(file) |
| 45 | + .resize(THUMBNAIL_WIDTH) |
| 46 | + .jpeg({ quality: 70 }) |
| 47 | + .toFile(thumbnailPath) |
| 48 | + .catch(err => console.error(`Problem generating ${thumbnailPath}`, err)); |
| 49 | + }); |
35 | 50 | }
|
36 | 51 |
|
37 | 52 | function main(configs) {
|
| 53 | + const startTime = new Date(); |
| 54 | + const imagesPath = resolve(__dirname, configs.imageDir); |
| 55 | + const outputPath = resolve(__dirname, configs.outputDir); |
| 56 | + const recipesPath = resolve(__dirname, configs.recipeDir); |
| 57 | + const staticPath = resolve(__dirname, './src/static/'); |
| 58 | + const templatesPath = resolve(__dirname, './src/templates/'); |
| 59 | + |
38 | 60 | const options = {
|
39 | 61 | ...configs,
|
40 |
| - imagesPath: resolve(__dirname, configs.imageDir), |
41 |
| - outputPath: resolve(__dirname, configs.outputDir), |
42 |
| - recipesPath: resolve(__dirname, configs.recipeDir), |
43 |
| - staticPath: resolve(__dirname, './src/static/'), |
44 |
| - templatesPath: resolve(__dirname, './src/templates/'), |
| 62 | + imagesPath, |
| 63 | + outputPath, |
| 64 | + recipesPath, |
| 65 | + staticPath, |
| 66 | + templatesPath, |
45 | 67 | };
|
46 | 68 |
|
47 |
| - const fileList = getRecipeFileList(options); |
48 |
| - const { indexTemplate, recipeTemplate } = readTemplates(options); |
| 69 | + Promise.all([ |
| 70 | + readdir(recipesPath), |
| 71 | + readdir(imagesPath), |
| 72 | + readFile(resolve(templatesPath, 'index.html'), { encoding: 'utf8' }), |
| 73 | + readFile(resolve(templatesPath, 'recipe.html'), { encoding: 'utf8' }), |
| 74 | + ]) |
| 75 | + .then(([markdownFiles, images, indexTemplate, recipeTemplate]) => { |
| 76 | + markdownFiles = filterByExtension(markdownFiles, recipesPath, ['.md']); |
| 77 | + images = filterByExtension(images, imagesPath, ['.jpg', '.jpeg', '.png', '.webp', '.avif']); |
49 | 78 |
|
50 |
| - setupOutputDir(options.outputPath); |
51 |
| - copyStatic(options.staticPath, options.imagesPath, options.outputPath); |
52 |
| - buildRecipes(recipeTemplate, options, fileList); |
53 |
| - buildRecipeIndex(indexTemplate, options, fileList); |
| 79 | + setupOutputDir(outputPath); |
| 80 | + copyStatic({staticPath, imagesPath, outputPath, recipesPath}); |
| 81 | + makeThumbnails(outputPath, images); |
| 82 | + buildRecipes(recipeTemplate, options, markdownFiles, images); |
| 83 | + buildRecipeIndex(indexTemplate, options, markdownFiles, images); |
54 | 84 |
|
55 |
| - // eslint-disable-next-line no-console |
56 |
| - console.log(`Processed ${fileList.length} recipes`); |
| 85 | + const endTime = new Date(); |
| 86 | + console.log(`Processed ${markdownFiles.length} recipes in ${endTime - startTime}ms`); |
| 87 | + }) |
| 88 | + .catch((err) => { |
| 89 | + console.error(err); |
| 90 | + }); |
57 | 91 | }
|
58 | 92 |
|
59 | 93 | main(configs);
|
0 commit comments