Compare commits

...

30 Commits

Author SHA1 Message Date
Amy b6cd0d3f35 Make PR reviewing easier 1 year ago
Amy 06123e8adb Merge branch 'main' into amy-image-optimization 1 year ago
Amy a9ef29c193 Change imagePool constructor argument to cpus().length 1 year ago
Amy f831e96048 Fix dev script 1 year ago
Amy 5b6a3e98ab Change build order 1 year ago
Amy 7408cf421a Add descriptive comment 1 year ago
Amy b933635e6e Change imagePool constructor argument to 4 1 year ago
Amy fa6fe454a9 Revert changes to pipeline steps order 1 year ago
Amy 388bf799ab Change pipeline steps order again 1 year ago
Amy 72cd1ee06c Change pipeline steps order for real 1 year ago
Amy fbbfaf9fff Change pipeline steps order 1 year ago
Amy 6e73bb3eb8 Revert experimental commits 1 year ago
Amy a7bf6f56a3 Fix CI step dependencies 1 year ago
Amy ecfbb721a5 Change order of build steps 1 year ago
Amy 370452f246 Change imagePool constructor param to 2 1 year ago
Amy c031147aad Revert "Remove imagePool constructor parameter" 1 year ago
Amy 609d10732c Combine build and image optimization steps in CI 1 year ago
Amy 5bead562f9 Revert "Remove all libsquoosh code" 1 year ago
Amy f5adb69bdd Remove all libsquoosh code 1 year ago
Amy 92eec34fa7 Remove imagePool constructor parameter 1 year ago
Amy aa32e20c6e Rename npm scripts 1 year ago
Amy 8cd3a7747b Remove unecessary dependency 1 year ago
Amy 3519e87e42 Change dependencies 1 year ago
Amy 60fee0618e Add npm script to optimize images 1 year ago
Amy 9cde6e563e Remove unnecessary code 1 year ago
Amy 16cdbe9db4 Add image resizing using image-size 1 year ago
Amy e8988e0e4c Refactor encoding constants 1 year ago
Amy 4ffad01bbe Replace for-loop with async processing 1 year ago
Amy c37a714c32 Refactor image optimization script using fs-extra 1 year ago
Amy bdd65d2e22 Add basic jpg optimization 1 year ago
  1. 15
      .drone.yml
  2. 6
      .gitignore
  3. 148
      package-lock.json
  4. 11
      package.json
  5. 98
      scripts/optimize-images.ts

@ -23,25 +23,32 @@ steps:
commands:
- npm run lint
- name: build
- name: optimize-images
image: node:16
depends_on:
- install-deps
commands:
- npm run build:web
- npm run build:images
- name: generate-calendar
image: node:16
depends_on:
- install-deps
commands:
- npm run generate:calendar
- npm run build:calendar
- name: build
image: node:16
depends_on:
- optimize-images
commands:
- npm run build:web
- name: export
image: node:16
depends_on:
- build
- generate-calendar
- build
commands:
- npm run export

6
.gitignore vendored

@ -25,4 +25,8 @@ yarn-debug.log*
yarn-error.log*
# Calendar is automatically generated
/public/events.ics
/public/events.ics
# Images should be optimized
# TODO: uncomment this after the PR is reviewed
# /public/images

148
package-lock.json generated

@ -10,6 +10,7 @@
"@mdx-js/loader": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@next/mdx": "11.0.1",
"@squoosh/lib": "^0.4.0",
"date-fns": "^2.11.1",
"date-fns-tz": "^1.1.6",
"next": "11.0.1",
@ -21,6 +22,8 @@
"remark-html": "^12.0.0"
},
"devDependencies": {
"@types/fs-extra": "^9.0.13",
"@types/image-size": "^0.8.0",
"@types/mdx-js__react": "^1.5.3",
"@types/node": "^16.9.1",
"@types/react": "^17.0.14",
@ -32,8 +35,10 @@
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "7.24.0",
"eslint-plugin-react-hooks": "^4.2.0",
"fs-extra": "^10.0.0",
"gray-matter": "^4.0.3",
"ical-generator": "^3.0.0",
"image-size": "^1.0.0",
"postcss": "^8.3.0",
"postcss-calc": "^8.0.0",
"postcss-flexbugs-fixes": "^5.0.2",
@ -990,6 +995,18 @@
"node": ">= 8"
}
},
"node_modules/@squoosh/lib": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@squoosh/lib/-/lib-0.4.0.tgz",
"integrity": "sha512-O1LyugWLZjMI4JZeZMA5vzfhfPjfMZXH5/HmVkRagP8B70wH3uoR7tjxfGNdSavey357MwL8YJDxbGwBBdHp7Q==",
"dependencies": {
"wasm-feature-detect": "^1.2.11",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": " ^12.5.0 || ^14.0.0 || ^16.0.0 "
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
@ -1014,6 +1031,15 @@
"integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
"dev": true
},
"node_modules/@types/fs-extra": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
"integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/hast": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.1.tgz",
@ -1022,6 +1048,16 @@
"@types/unist": "*"
}
},
"node_modules/@types/image-size": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@types/image-size/-/image-size-0.8.0.tgz",
"integrity": "sha512-hMlhu25ji75dXQk2uZkN3pTJ+lWrgKr8M1fTpyyFvuu+SJZBdGa5gDm4BVNobWXHZbOU11mBj0vciYp7qOfAFg==",
"deprecated": "This is a stub types definition. image-size provides its own type definitions, so you do not need this installed.",
"dev": true,
"dependencies": {
"image-size": "*"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.8",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.8.tgz",
@ -3510,6 +3546,20 @@
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
},
"node_modules/fs-extra": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -4549,6 +4599,18 @@
"json5": "lib/cli.js"
}
},
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsx-ast-utils": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz",
@ -8963,6 +9025,15 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"dev": true,
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@ -9104,6 +9175,11 @@
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
},
"node_modules/wasm-feature-detect": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w=="
},
"node_modules/watchpack": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz",
@ -9125,6 +9201,14 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.1.1.tgz",
"integrity": "sha512-Czi3fG883e96T4DLEPRvufrF2ydhOOW1+1a6c3gNjH2aIh50DNFBdfwh2AKoOf1rXvpvavAoA11Qdq9+BKjE0Q==",
"engines": {
"node": ">= 8"
}
},
"node_modules/webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
@ -10052,6 +10136,15 @@
"fastq": "^1.6.0"
}
},
"@squoosh/lib": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@squoosh/lib/-/lib-0.4.0.tgz",
"integrity": "sha512-O1LyugWLZjMI4JZeZMA5vzfhfPjfMZXH5/HmVkRagP8B70wH3uoR7tjxfGNdSavey357MwL8YJDxbGwBBdHp7Q==",
"requires": {
"wasm-feature-detect": "^1.2.11",
"web-streams-polyfill": "^3.0.3"
}
},
"@tsconfig/node10": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
@ -10076,6 +10169,15 @@
"integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
"dev": true
},
"@types/fs-extra": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
"integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/hast": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.1.tgz",
@ -10084,6 +10186,15 @@
"@types/unist": "*"
}
},
"@types/image-size": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@types/image-size/-/image-size-0.8.0.tgz",
"integrity": "sha512-hMlhu25ji75dXQk2uZkN3pTJ+lWrgKr8M1fTpyyFvuu+SJZBdGa5gDm4BVNobWXHZbOU11mBj0vciYp7qOfAFg==",
"dev": true,
"requires": {
"image-size": "*"
}
},
"@types/json-schema": {
"version": "7.0.8",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.8.tgz",
@ -11967,6 +12078,17 @@
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
},
"fs-extra": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -12650,6 +12772,16 @@
"minimist": "^1.2.0"
}
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"jsx-ast-utils": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz",
@ -15975,6 +16107,12 @@
"unist-util-is": "^4.0.0"
}
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"dev": true
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@ -16096,6 +16234,11 @@
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
},
"wasm-feature-detect": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w=="
},
"watchpack": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz",
@ -16110,6 +16253,11 @@
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz",
"integrity": "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw=="
},
"web-streams-polyfill": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.1.1.tgz",
"integrity": "sha512-Czi3fG883e96T4DLEPRvufrF2ydhOOW1+1a6c3gNjH2aIh50DNFBdfwh2AKoOf1rXvpvavAoA11Qdq9+BKjE0Q=="
},
"webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",

@ -6,22 +6,26 @@
"npm": "^7"
},
"scripts": {
"dev": "next dev",
"build": "npm run build:web && npm run generate:calendar",
"dev": "npm run build:images && next dev",
"build": "npm run build:images && npm run build:web && npm run build:calendar",
"build:images": "ts-node ./scripts/optimize-images",
"build:web": "next build",
"build:calendar": "ts-node ./scripts/generate-calendar",
"start": "next start",
"export": "next export",
"lint": "eslint \"{pages,components,lib,hooks,scripts}/**/*.{js,ts,tsx,jsx}\" --quiet",
"lint:fix": "eslint \"{pages,components,lib,hooks,scripts}/**/*.{js,ts,tsx,jsx}\" --quiet --fix",
"generate:calendar": "ts-node ./scripts/generate-calendar",
"check-lockfile": "ts-node ./scripts/check-lockfile"
},
"dependencies": {
"@mdx-js/loader": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@next/mdx": "11.0.1",
"@squoosh/lib": "^0.4.0",
"date-fns": "^2.11.1",
"date-fns-tz": "^1.1.6",
"fs-extra": "^10.0.0",
"image-size": "^1.0.0",
"next": "11.0.1",
"next-mdx-remote": "3.0.4",
"prettier": "^2.3.0",
@ -31,6 +35,7 @@
"remark-html": "^12.0.0"
},
"devDependencies": {
"@types/fs-extra": "^9.0.13",
"@types/mdx-js__react": "^1.5.3",
"@types/node": "^16.9.1",
"@types/react": "^17.0.14",

@ -0,0 +1,98 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
// TODO: upgrade libsquoosh once types are available: https://github.com/GoogleChromeLabs/squoosh/issues/1077
import { cpus } from "os";
import path from "path";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { ImagePool } from "@squoosh/lib";
import fse from "fs-extra";
import { default as getImageDimensions } from "image-size";
const IMAGES_SOURCE_DIRECTORY = "images";
const IMAGES_DESTINATION_DIRECTORY = "public/images";
void optimizeImages();
const IMAGE_MINIMUM_SIZE = 300;
const IMAGE_ENCODE_OPTIONS = { mozjpeg: {} };
const GET_CODEC_FROM_EXTENSION: { [imageExtension: string]: string } = {
jpg: "mozjpeg",
jpeg: "mozjpeg",
};
export async function optimizeImages() {
const imagePaths = await getFilePathsInDirectory(IMAGES_SOURCE_DIRECTORY);
await fse.emptyDir(IMAGES_DESTINATION_DIRECTORY);
const numberOfWorkers = cpus().length;
const imagePool = new ImagePool(numberOfWorkers);
await Promise.all(
imagePaths.map(async (imagePath) => {
const sourcePath = path.join(IMAGES_SOURCE_DIRECTORY, imagePath);
const destinationPath = path.join(
IMAGES_DESTINATION_DIRECTORY,
imagePath
);
const fileExtension = imagePath.split(".").pop() ?? "";
if (!GET_CODEC_FROM_EXTENSION[fileExtension]) {
await fse.copy(sourcePath, destinationPath);
return;
}
const rawImageFile = await fse.readFile(sourcePath);
const ingestedImage = imagePool.ingestImage(rawImageFile);
const { width, height } = getImageDimensions(rawImageFile);
await ingestedImage.decoded;
if (width && height) {
const resizeEnabled =
width > IMAGE_MINIMUM_SIZE && height > IMAGE_MINIMUM_SIZE;
const smallerDimension = width < height ? "width" : "height";
// specifying only one dimension maintains the aspect ratio
const preprocessOptions = {
resize: {
enabled: resizeEnabled,
[smallerDimension]: IMAGE_MINIMUM_SIZE,
},
};
await ingestedImage.preprocess(preprocessOptions);
}
await ingestedImage.encode(IMAGE_ENCODE_OPTIONS);
const encodedImage = await ingestedImage.encodedWith[
GET_CODEC_FROM_EXTENSION[fileExtension]
];
await fse.outputFile(destinationPath, encodedImage.binary);
})
);
await imagePool.close();
}
async function getFilePathsInDirectory(directory: string): Promise<string[]> {
const entries = await fse.readdir(directory, { withFileTypes: true });
return (
await Promise.all(
entries.map(async (entry) => {
if (entry.isDirectory()) {
const subdirectory = path.join(directory, entry.name);
const subentries = await getFilePathsInDirectory(subdirectory);
return subentries.map((subentry) => path.join(entry.name, subentry));
}
return entry.name;
})
)
).flat();
}
Loading…
Cancel
Save