From 21a9fd14dbe285ea2b10df585f0819d5d2642a32 Mon Sep 17 00:00:00 2001 From: Kazuki Yamada Date: Sun, 11 Aug 2024 12:19:36 +0900 Subject: [PATCH] =?UTF-8?q?chore(11ty):=20config=E3=82=92ts=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eleventy.js => eleventy.config.ts | 78 +++++++++++++++----------- package.json | 6 +- src/@types/eleventy-fetch.d.ts | 24 ++++++++ src/common/constants.js | 1 + src/common/eleventy-cache-option.js | 11 ---- src/common/eleventy-cache-option.ts | 12 ++++ src/feed/utils/feed-image-precacher.ts | 14 +++-- tsconfig.json | 1 + yarn.lock | 14 ++++- 9 files changed, 108 insertions(+), 53 deletions(-) rename .eleventy.js => eleventy.config.ts (61%) create mode 100644 src/@types/eleventy-fetch.d.ts delete mode 100644 src/common/eleventy-cache-option.js create mode 100644 src/common/eleventy-cache-option.ts diff --git a/.eleventy.js b/eleventy.config.ts similarity index 61% rename from .eleventy.js rename to eleventy.config.ts index f83e2ca7edbe..f1067d730f91 100644 --- a/.eleventy.js +++ b/eleventy.config.ts @@ -1,16 +1,20 @@ -const htmlmin = require('html-minifier-terser'); -const Image = require('@11ty/eleventy-img'); -const path = require('path'); -const ts = require('typescript'); -const eleventyCacheOption = require('./src/common/eleventy-cache-option'); -const CleanCSS = require("clean-css"); -const EleventyFetch = require("@11ty/eleventy-fetch"); -const sharpIco = require("sharp-ico"); -const url = require('url'); - -Image.concurrency = 50; - -const minifyHtmlTransform = (content, outputPath) => { +import htmlmin from 'html-minifier-terser'; +import EleventyImage from '@11ty/eleventy-img'; +import EleventyFetch from "@11ty/eleventy-fetch"; +import path from 'path'; +import ts from 'typescript'; +import { imageCacheOptions } from './src/common/eleventy-cache-option'; +import CleanCSS from "clean-css"; +import { Sharp } from 'sharp'; +import sharpIco, {ImageData} from "sharp-ico"; +import url from 'url'; +import Eleventy from '@11ty/eleventy'; + +const ELEVENTY_FETCH_CONCURRENCY = 50; + +EleventyImage.concurrency = ELEVENTY_FETCH_CONCURRENCY; + +const minifyHtmlTransform = (content: string, outputPath: string) => { if(outputPath && outputPath.endsWith('.html')) { return htmlmin.minify(content, { // オプション参考: https://github.com/terser/html-minifier-terser#options-quick-reference @@ -26,16 +30,16 @@ const minifyHtmlTransform = (content, outputPath) => { return content; } -const imageThumbnailShortcode = async (src, alt, pathPrefix = '') => { - let metadata = null; +const imageThumbnailShortcode = async (src: string, alt: string, pathPrefix: string = '') => { + let metadata: EleventyImage.Metadata; try { - metadata = await Image(src, { + metadata = await EleventyImage(src, { widths: [150, 450], formats: ["webp", "jpeg"], outputDir: 'public/images/feed-thumbnails', urlPath: `${pathPrefix}images/feed-thumbnails/`, - cacheOptions: eleventyCacheOption, + cacheOptions: imageCacheOptions, sharpWebpOptions: { quality: 50, }, @@ -49,7 +53,7 @@ const imageThumbnailShortcode = async (src, alt, pathPrefix = '') => { return `${alt}` } - return Image.generateHTML(metadata, { + return EleventyImage.generateHTML(metadata, { alt, sizes: '100vw', loading: 'lazy', @@ -57,19 +61,25 @@ const imageThumbnailShortcode = async (src, alt, pathPrefix = '') => { }); } -const imageIconShortcode = async (src, alt, pathPrefix = '') => { +const imageIconShortcode = async (src: string, alt: string, pathPrefix: string = '') => { const parsedUrl = url.parse(src); - const fileName = path.basename(parsedUrl.pathname); + const fileName = path.basename(parsedUrl.pathname || ''); const fileExtension = path.extname(fileName).toLowerCase(); - let imageSrc = src; - let metadata = null; + let imageSrc: EleventyImage.ImageSource = src; + let metadata: EleventyImage.Metadata; if (fileExtension === '.ico') { try { - const icoBuffer = await EleventyFetch(src, eleventyCacheOption); - const sharpInstances = await sharpIco.sharpsFromIco(icoBuffer); - const sharpInstance = sharpInstances.sort((a, b) => b.width - a.width)[0]; - imageSrc = await sharpInstance.png().toBuffer(); + const icoBuffer = await EleventyFetch(src, { + type: 'buffer', + duration: imageCacheOptions.duration, + concurrency: ELEVENTY_FETCH_CONCURRENCY, + }); + const sharpIcoImages = await sharpIco.sharpsFromIco(icoBuffer, {}, true) as ImageData[]; + const sharpIcoImage = sharpIcoImages.sort((a, b) => b.width - a.width)[0]; + if (sharpIcoImage.image) { + imageSrc = await sharpIcoImage.image.png().toBuffer(); + } } catch (error) { // エラーが起きたら画像なし console.error('[image-icon-short-code] Error processing ICO:', src, error); @@ -78,12 +88,12 @@ const imageIconShortcode = async (src, alt, pathPrefix = '') => { } try { - metadata = await Image(imageSrc, { + metadata = await EleventyImage(imageSrc, { widths: [16], formats: ["png"], outputDir: 'public/images/feed-icons', urlPath: `${pathPrefix}images/feed-icons/`, - cacheOptions: eleventyCacheOption, + cacheOptions: imageCacheOptions, sharpPngOptions: { quality: 50, } @@ -94,27 +104,27 @@ const imageIconShortcode = async (src, alt, pathPrefix = '') => { return `` } - return Image.generateHTML(metadata, { + return EleventyImage.generateHTML(metadata, { alt, loading: 'lazy', decoding: 'async', }); } -const relativeUrlFilter = (url) => { +const relativeUrlFilter = (url: string) => { const relativeUrl = path.relative(url, '/'); return relativeUrl === '' ? './' : `${relativeUrl}/`; } -const minifyCssFilter = (css) => { +const minifyCssFilter = (css: string) => { return new CleanCSS({}).minify(css).styles; } -const supportTypeScriptTemplate = (eleventyConfig) => { +const supportTypeScriptTemplate = (eleventyConfig: Eleventy.UserConfig) => { eleventyConfig.addTemplateFormats('ts'); eleventyConfig.addExtension('ts', { outputFileExtension: 'js', - compile: async (inputContent) => { + compile: async (inputContent: string) => { return async () => { const result = ts.transpileModule(inputContent, { compilerOptions: { module: ts.ModuleKind.CommonJS }}); return result.outputText; @@ -123,7 +133,7 @@ const supportTypeScriptTemplate = (eleventyConfig) => { }); } -module.exports = function (eleventyConfig) { +module.exports = function (eleventyConfig: Eleventy.UserConfig) { // static assets eleventyConfig.addPassthroughCopy('src/site/images'); eleventyConfig.addPassthroughCopy('src/site/feeds'); diff --git a/package.json b/package.json index 2323869009a1..7ca736947c09 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "build": "yarn feed-generate && yarn site-build", "feed-generate": "node --require esbuild-register src/feed/generate-feed-command.ts", "register-index": "node --require esbuild-register src/feed/register-index-command.ts", - "site-build": "eleventy", - "site-serve": "eleventy --serve", + "site-build": "node --require esbuild-register ./node_modules/.bin/eleventy --config=eleventy.config.ts", + "site-serve": "node --require esbuild-register ./node_modules/.bin/eleventy --config=eleventy.config.ts --serve", "lint": "eslint ./src ./tests --max-warnings 0 --cache --format friendly --fix && tsc --noEmit", "test": "vitest run", "test-coverage": "vitest run --coverage", @@ -41,6 +41,7 @@ "@supercharge/promise-pool": "^3.1.0", "@types/11ty__eleventy-img": "^4.0.0", "@types/async-retry": "^1.4.3", + "@types/clean-css": "^4.2.11", "@types/eslint": "^9.0.0", "@types/eslint-config-prettier": "^6.11.2", "@types/eslint-plugin-prettier": "^3.1.0", @@ -57,6 +58,7 @@ "async-retry": "^1.3.3", "await-to-js": "^3.0.0", "axios": "^1.6.0", + "clean-css": "^5.3.3", "dayjs": "^1.11.10", "esbuild-register": "^3.5.0", "eslint": "^9.1.1", diff --git a/src/@types/eleventy-fetch.d.ts b/src/@types/eleventy-fetch.d.ts new file mode 100644 index 000000000000..c57d2773ae7f --- /dev/null +++ b/src/@types/eleventy-fetch.d.ts @@ -0,0 +1,24 @@ +declare module '@11ty/eleventy-fetch' { + import { Buffer } from 'buffer'; + + export type FetchType = 'json' | 'buffer' | 'text'; + + export interface EleventyFetchOptions { + type?: TType; + directory?: string; + concurrency?: number; + fetchOptions?: Node.RequestInit; + dryRun?: boolean; + removeUrlQueryParams?: boolean; + verbose?: boolean; + hashLength?: number; + duration?: string; + formatUrlForDisplay?: (url: string) => string; + } + + export function EleventyFetch(url: string, options?: EleventyFetchOptions<'buffer'>): Promise; + export function EleventyFetch(url: string, options: EleventyFetchOptions<'json'>): Promise; + export function EleventyFetch(url: string, options: EleventyFetchOptions<'text'>): Promise; + + export default EleventyFetch; +} diff --git a/src/common/constants.js b/src/common/constants.js index 928dfe4d8189..0fbb19087456 100644 --- a/src/common/constants.js +++ b/src/common/constants.js @@ -1,3 +1,4 @@ +// @ts-check const siteUrlStem = 'https://yamadashy.github.io/tech-blog-rss-feed'; const siteUrl = `${siteUrlStem}/`; diff --git a/src/common/eleventy-cache-option.js b/src/common/eleventy-cache-option.js deleted file mode 100644 index 265fbebb9931..000000000000 --- a/src/common/eleventy-cache-option.js +++ /dev/null @@ -1,11 +0,0 @@ -const constants = require('./constants'); - -module.exports = { - duration: '3d', - type: 'buffer', - fetchOptions: { - headers: { - 'user-agent': constants.requestUserAgent, - }, - }, -}; diff --git a/src/common/eleventy-cache-option.ts b/src/common/eleventy-cache-option.ts new file mode 100644 index 000000000000..a53c482c967c --- /dev/null +++ b/src/common/eleventy-cache-option.ts @@ -0,0 +1,12 @@ +import constants from './constants'; +import EleventyImage from '@11ty/eleventy-img'; + +export const imageCacheOptions: EleventyImage.CacheOptions = { + duration: '3d', + type: 'buffer', + fetchOptions: { + headers: { + 'User-Agent': constants.requestUserAgent, + }, + }, +}; diff --git a/src/feed/utils/feed-image-precacher.ts b/src/feed/utils/feed-image-precacher.ts index 86fbbbb45cc0..d7d6d751bc55 100644 --- a/src/feed/utils/feed-image-precacher.ts +++ b/src/feed/utils/feed-image-precacher.ts @@ -1,9 +1,9 @@ import { PromisePool } from '@supercharge/promise-pool'; import { logger } from './logger'; -import eleventyCacheOption from '../../common/eleventy-cache-option'; +import { imageCacheOptions } from '../../common/eleventy-cache-option'; import { to } from 'await-to-js'; import { CustomRssParserFeed, CustomRssParserItem, OgObjectMap } from './feed-crawler'; -const EleventyFetch = require('@11ty/eleventy-fetch'); +import EleventyFetch from '@11ty/eleventy-fetch'; export class ImagePrecacher { public async fetchAndCacheFeedImages( @@ -29,12 +29,16 @@ export class ImagePrecacher { const ogImageUrlsLength = ogImageUrls.length; let fetchProcessCounter = 1; - EleventyFetch.concurrency = concurrency; - await PromisePool.for(ogImageUrls) .withConcurrency(concurrency) .process(async (ogImageUrl) => { - const [error] = await to(EleventyFetch(ogImageUrl, eleventyCacheOption)); + const [error] = await to( + EleventyFetch(ogImageUrl, { + type: 'buffer', + duration: imageCacheOptions.duration, + concurrency, + }), + ); if (error) { logger.error('[cache-og-image] error', `${fetchProcessCounter++}/${ogImageUrlsLength}`, ogImageUrl); logger.trace(error); diff --git a/tsconfig.json b/tsconfig.json index 7202e3bce881..050c6f124975 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,5 +24,6 @@ "src/**/*", "tests/**/*", "vitest.config.ts", + "eleventy.config.ts", ] } diff --git a/yarn.lock b/yarn.lock index 21bc8c2f60c0..2720a7ab001a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3466,6 +3466,16 @@ __metadata: languageName: node linkType: hard +"@types/clean-css@npm:^4.2.11": + version: 4.2.11 + resolution: "@types/clean-css@npm:4.2.11" + dependencies: + "@types/node": "npm:*" + source-map: "npm:^0.6.0" + checksum: 10/385337a881c7870664d8987f12b9c814d835104dcf5f1737b74ab759ca68424ce93636cbff73ea9c41290c3dc2a92a4cc6246869bd9982255cfa28dcc2ccec93 + languageName: node + linkType: hard + "@types/connect@npm:*": version: 3.4.38 resolution: "@types/connect@npm:3.4.38" @@ -5134,7 +5144,7 @@ __metadata: languageName: node linkType: hard -"clean-css@npm:~5.3.2": +"clean-css@npm:^5.3.3, clean-css@npm:~5.3.2": version: 5.3.3 resolution: "clean-css@npm:5.3.3" dependencies: @@ -12617,6 +12627,7 @@ __metadata: "@supercharge/promise-pool": "npm:^3.1.0" "@types/11ty__eleventy-img": "npm:^4.0.0" "@types/async-retry": "npm:^1.4.3" + "@types/clean-css": "npm:^4.2.11" "@types/eslint": "npm:^9.0.0" "@types/eslint-config-prettier": "npm:^6.11.2" "@types/eslint-plugin-prettier": "npm:^3.1.0" @@ -12633,6 +12644,7 @@ __metadata: async-retry: "npm:^1.3.3" await-to-js: "npm:^3.0.0" axios: "npm:^1.6.0" + clean-css: "npm:^5.3.3" dayjs: "npm:^1.11.10" esbuild-register: "npm:^3.5.0" eslint: "npm:^9.1.1"