@nolebase/vitepress-plugin-thumbnail-hash
Version:
A VitePress plugin that scan and generate data with blurhash, thumbhash hashing algorithm for images, as well as a standalone component to render images with blurhash and thumbhash.
1 lines • 16 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../../src/vite/utils.ts","../../src/vite/index.ts"],"sourcesContent":["// thumbhash/examples/browser/index.html at main · evanw/thumbhash\n\nimport { subtle } from 'uncrypto'\n\n// https://github.com/evanw/thumbhash/blob/main/examples/browser/index.html\nexport function binaryToBase64(binary: Uint8Array) {\n return btoa(String.fromCharCode(...binary))\n}\n\n/**\n * Hashes the given data using SHA-256 algorithm.\n *\n * Official example by MDN: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest\n * @param {Uint8Array} data - The data to be hashed\n * @returns {Promise<string>} - The SHA-256 hash of the message\n */\nasync function digestUint8ArrayDataSha256(data: Uint8Array) {\n const hashBuffer = await subtle.digest('SHA-256', data) // hash the message\n return Array.from(new Uint8Array(hashBuffer)) // convert buffer to byte array\n}\n\n/**\n * Simulate hash function of rollup.\n *\n * About hashing, please read the documentation of rollup:\n * https://rollupjs.org/configuration-options/#output-hashcharacters\n * https://github.com/rollup/rollup/blob/1b85663fde96d84fceaa2360dba246d3cb92789b/docs/configuration-options/index.md?plain=1#L628\n *\n * For implementation details, please read the source code of rollup:\n * https://github.com/rollup/rollup/blob/1b85663fde96d84fceaa2360dba246d3cb92789b/src/utils/FileEmitter.ts#L259\n * https://github.com/rollup/rollup/blob/1b85663fde96d84fceaa2360dba246d3cb92789b/src/utils/crypto.ts#L12\n *\n * @param {Uint8Array} data - The data to be hashed\n * @param {number} length - The length of the hash\n * @returns {Promise<string>} - The first 10 characters of the SHA-256 hash of the message\n */\nexport async function hash(data: Uint8Array, length = 10) {\n const hashResult = await digestUint8ArrayDataSha256(data)\n const hashBase64 = binaryToBase64(new Uint8Array(hashResult)) // convert bytes to base64 encoded string\n if (length > 0)\n return hashBase64.substring(0, length)\n\n return hashBase64\n}\n\n/**\n * Normalize the base64 string to be used in the URL.\n *\n * @param {string} base64 - The base64 string to be normalized\n * @returns {string} - The normalized base64 string\n */\nexport function normalizeBase64(base64: string) {\n return base64.replace('/', '_').replace('+', '-').replace('=', '-')\n}\n","import type { Plugin } from 'vite'\nimport type { SiteConfig } from 'vitepress'\n\nimport type { ThumbHash, ThumbHashCalculated } from '../types'\nimport { mkdir, readFile, stat, writeFile } from 'node:fs/promises'\n\nimport { join, relative } from 'node:path'\nimport CanvasKitInit from 'canvaskit-wasm'\nimport { cyan, gray } from 'colorette'\nimport ora from 'ora'\nimport { rgbaToThumbHash, thumbHashToDataURL } from 'thumbhash'\n\nimport { glob } from 'tinyglobby'\nimport { normalizePath } from 'vite'\nimport { binaryToBase64, hash, normalizeBase64 } from './utils'\n\ninterface VitePressConfig {\n vitepress: SiteConfig\n}\n\n/**\n * Calculate the thumbhash data for the image.\n *\n * Referenced the following implementations:\n * thumbhash/examples/browser/index.html at main · evanw/thumbhash\n * https://github.com/evanw/thumbhash/blob/main/examples/browser/index.html\n *\n * And the following implementations:\n * vite-plugin-thumbhash/packages/core/index.ts at main · cijiugechu/vite-plugin-thumbhash\n * https://github.com/cijiugechu/vite-plugin-thumbhash/blob/main/packages/core/index.ts\n *\n * @param {Uint8Array} imageData - The image data to be calculated\n * @returns {Promise<Omit<ThumbHash, 'fileName' | 'assetUrl' | 'assetUrlWithBase'>>} - The thumbhash data of the image\n */\nasync function calculateThumbHashForFile(imageData: Uint8Array): Promise<ThumbHashCalculated> {\n const canvasKit = await CanvasKitInit()\n const image = canvasKit.MakeImageFromEncoded(imageData)\n if (!image)\n throw new Error('Failed to make image from encoded data.')\n\n const width = image.width()\n const height = image.height()\n\n const scale = 100 / Math.max(width, height)\n const resizedWidth = Math.round(width * scale)\n const resizedHeight = Math.round(height * scale)\n\n // Paint the image to the canvas.\n const canvas = canvasKit.MakeCanvas(resizedWidth, resizedHeight)\n const context = canvas.getContext('2d')!\n context.drawImage(image as unknown as CanvasImageSource, 0, 0, resizedWidth, resizedHeight)\n // Retrieve back the image data for thumbhash calculation as the\n // form of RGBA matrix.\n const pixels = context.getImageData(0, 0, resizedWidth, resizedHeight)\n\n // Easy calculation of thumbhash data.\n const thumbHashBinary = rgbaToThumbHash(pixels.width, pixels.height, pixels.data)\n // Encode the thumbhash data to base64 and data URL.\n const thumbHashBase64 = binaryToBase64(thumbHashBinary)\n const thumbHashDataURL = await thumbHashToDataURL(thumbHashBinary)\n\n return {\n dataBase64: thumbHashBase64,\n dataUrl: thumbHashDataURL,\n width: resizedWidth,\n height: resizedHeight,\n originalWidth: width,\n originalHeight: height,\n }\n}\n\nfunction createThumbHashDataFromThumbHash(\n rootDir: string,\n assetDir: string,\n baseUrl: string,\n imageFileName: string,\n imageFullFileName: string,\n imageFullHash: string,\n imageFileHash: string,\n thumbHash: ThumbHashCalculated,\n): ThumbHash {\n const fileName = relative(rootDir, imageFileName)\n\n const thumbhashData: ThumbHash = {\n ...thumbHash,\n assetFileName: normalizePath(relative(rootDir, imageFullFileName)),\n assetFullFileName: normalizePath(imageFullFileName),\n assetFullHash: imageFullHash,\n assetFileHash: imageFileHash,\n assetUrl: normalizePath(join(assetDir, fileName)),\n assetUrlWithBase: normalizePath(join(baseUrl, assetDir, fileName)),\n }\n\n // Since assets url is used to refer to the image when rendered\n // in the HTML, we need to ensure that the asset URL starts with a slash\n // where base is not included.\n if (!thumbhashData.assetUrlWithBase.startsWith('/'))\n thumbhashData.assetUrlWithBase = `/${thumbhashData.assetUrlWithBase}`\n\n return thumbhashData\n}\n\nfunction getCacheDir(vitePressCacheDir: string) {\n return join(vitePressCacheDir, '@nolebase', 'vitepress-plugin-thumbnail-hash', 'thumbhashes')\n}\n\nfunction getMapFilename(cacheDir: string) {\n return join(cacheDir, 'map.json')\n}\n\nasync function exists(path: string) {\n try {\n await stat(path)\n return true\n }\n catch (error) {\n if (!(error instanceof Error))\n throw error\n if (!('code' in error))\n throw error\n if (error.code !== 'ENOENT')\n throw error\n\n return false\n }\n}\n\nasync function mkdirIfNotExist(dir: string) {\n const targetDirExists = await exists(dir)\n if (targetDirExists)\n return\n\n await mkdir(dir, { recursive: true })\n}\n\nasync function readCachedMapFile(path: string): Promise<Record<string, ThumbHash>> {\n const targetPathExists = await exists(path)\n if (!targetPathExists)\n return {}\n\n const content = await readFile(path)\n return JSON.parse(content.toString('utf-8'))\n}\n\n/**\n * The Vite plugin to pre-process images and generate thumbhash data for them.\n *\n * @returns {Plugin} - The Vite plugin instance\n */\nexport function ThumbnailHashImages(): Plugin {\n return {\n name: '@nolebase/vitepress-plugin-thumbnail-hash/images',\n enforce: 'pre',\n config() {\n return {\n optimizeDeps: {\n exclude: [\n '@nolebase/vitepress-plugin-thumbnail-hash/client',\n ],\n },\n ssr: {\n noExternal: [\n '@nolebase/vitepress-plugin-thumbnail-hash',\n ],\n },\n }\n },\n async configResolved(config) {\n const root = config.root\n const vitepressConfig = (config as unknown as VitePressConfig).vitepress\n\n const startsAt = Date.now()\n\n const moduleNamePrefix = cyan('@nolebase/vitepress-plugin-thumbnail-hash/images')\n const grayPrefix = gray(':')\n const spinnerPrefix = `${moduleNamePrefix}${grayPrefix}`\n\n const spinner = ora({ discardStdin: false, isEnabled: config.command === 'serve' })\n\n spinner.start(`${spinnerPrefix} Prepare to generate hashes for images...`)\n\n const cacheDir = getCacheDir(vitepressConfig.cacheDir)\n await mkdirIfNotExist(cacheDir)\n const thumbhashMap = await readCachedMapFile(getMapFilename(cacheDir))\n\n spinner.text = `${spinnerPrefix} Searching for images...`\n\n const files = await glob(`${root}/**/*.+(jpg|jpeg|png)`, { onlyFiles: true })\n\n spinner.text = `${spinnerPrefix} Calculating thumbhashes for images...`\n\n const thumbhashes = await Promise.all(files.map(async (file) => {\n const cacheHit: ThumbHash | undefined = thumbhashMap[normalizePath(relative(root, file))]\n if (cacheHit)\n return cacheHit\n\n const readImageRawData = await readFile(file)\n\n // The hash implementation is mirrored and simulated from the rollup.\n // But it's never guaranteed to be the same as the rollup's hash.\n const imageFullHash = await hash(readImageRawData, -1)\n const imageFileHash = normalizeBase64(imageFullHash.substring(0, 10))\n\n // Calculate the thumbhash data for the image as thumbhash demonstrates.\n const calculatedThumbhashData = await calculateThumbHashForFile(readImageRawData)\n\n // Construct the thumbhash data for the image.\n return createThumbHashDataFromThumbHash(\n root,\n vitepressConfig.assetsDir,\n vitepressConfig.site.base,\n file,\n file,\n imageFullHash,\n imageFileHash,\n calculatedThumbhashData,\n )\n }))\n\n spinner.text = `${spinnerPrefix} Aggregating calculated thumbhash data...`\n\n for (const thumbhash of thumbhashes)\n thumbhashMap[thumbhash.assetFileName] = thumbhash\n\n spinner.text = `${spinnerPrefix} Writing thumbhash data to cache...`\n\n await writeFile(getMapFilename(cacheDir), JSON.stringify(thumbhashMap, null, 2))\n\n const elapsed = Date.now() - startsAt\n spinner.succeed(`${spinnerPrefix} Done. ${gray(`(${elapsed}ms)`)}`)\n },\n }\n}\n"],"names":[],"mappings":"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"}