File
Blob: scripts/generate-emoji-data.ts
| 1 | import { mkdir, rm, writeFile } from "node:fs/promises"; |
| 2 | import path from "node:path"; |
| 3 | import { createRequire } from "node:module"; |
| 4 | import { fileURLToPath } from "node:url"; |
| 5 | import { emojis as tiptapEmojis, type EmojiItem } from "@tiptap/extension-emoji"; |
| 6 | |
| 7 | const require = createRequire(import.meta.url); |
| 8 | |
| 9 | interface EmojibaseItem { |
| 10 | emoji?: string; |
| 11 | group?: number; |
| 12 | } |
| 13 | |
| 14 | const APPLE_ASSET_PREFIX = "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/"; |
| 15 | const VARIATION_SELECTOR_RE = /[\uFE0E\uFE0F]/gu; |
| 16 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
| 17 | const sharedGeneratedDir = path.resolve(__dirname, "../src/shared/emoji/generated"); |
| 18 | const clientGeneratedDir = path.resolve(__dirname, "../src/client/lib/emoji/generated"); |
| 19 | |
| 20 | function normalizeEmoji(emoji: string): string { |
| 21 | return emoji.replace(VARIATION_SELECTOR_RE, ""); |
| 22 | } |
| 23 | |
| 24 | function getAppleAssetName(item: EmojiItem): string | null { |
| 25 | if (!item.fallbackImage?.startsWith(APPLE_ASSET_PREFIX)) { |
| 26 | return null; |
| 27 | } |
| 28 | |
| 29 | return item.fallbackImage.slice(APPLE_ASSET_PREFIX.length); |
| 30 | } |
| 31 | |
| 32 | function renderModule(header: string, value: unknown): string { |
| 33 | return `${header}${JSON.stringify(value, null, 2)} as const;\n`; |
| 34 | } |
| 35 | |
| 36 | async function main() { |
| 37 | const emojibaseData = require("emojibase-data/en/data.json") as EmojibaseItem[]; |
| 38 | const emojibaseMessages = require("emojibase-data/en/messages.json") as { |
| 39 | groups: Record<string, { message?: string }>; |
| 40 | }; |
| 41 | const iconData: Record<string, string> = {}; |
| 42 | const groupOverrides: Record<string, string> = {}; |
| 43 | const baseEmojiMap = new Map<string, EmojiItem>(); |
| 44 | |
| 45 | for (const item of tiptapEmojis) { |
| 46 | if (!item.emoji) continue; |
| 47 | |
| 48 | const normalizedEmoji = normalizeEmoji(item.emoji); |
| 49 | baseEmojiMap.set(normalizedEmoji, item); |
| 50 | |
| 51 | const asset = getAppleAssetName(item); |
| 52 | if (asset) { |
| 53 | iconData[normalizedEmoji] = asset; |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | for (const emojibaseItem of emojibaseData) { |
| 58 | if (!emojibaseItem.emoji) { |
| 59 | continue; |
| 60 | } |
| 61 | |
| 62 | const baseEmoji = normalizeEmoji(emojibaseItem.emoji); |
| 63 | const baseItem = baseEmojiMap.get(baseEmoji); |
| 64 | if (!baseItem) continue; |
| 65 | |
| 66 | const groupMessage = |
| 67 | typeof emojibaseItem.group === "number" |
| 68 | ? (emojibaseMessages.groups[String(emojibaseItem.group)]?.message ?? "") |
| 69 | : ""; |
| 70 | if (!baseItem.group && groupMessage) { |
| 71 | groupOverrides[baseEmoji] = groupMessage; |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | await mkdir(sharedGeneratedDir, { recursive: true }); |
| 76 | await mkdir(clientGeneratedDir, { recursive: true }); |
| 77 | |
| 78 | await writeFile( |
| 79 | path.join(sharedGeneratedDir, "emoji-icon-data.ts"), |
| 80 | renderModule( |
| 81 | `// This file is generated by scripts/generate-emoji-data.ts\n\nexport const EMOJI_ICON_DATA = `, |
| 82 | iconData, |
| 83 | ), |
| 84 | ); |
| 85 | |
| 86 | await writeFile( |
| 87 | path.join(clientGeneratedDir, "emoji-group-overrides.ts"), |
| 88 | renderModule( |
| 89 | `// This file is generated by scripts/generate-emoji-data.ts\n\nexport const EMOJI_GROUP_OVERRIDES = `, |
| 90 | groupOverrides, |
| 91 | ), |
| 92 | ); |
| 93 | |
| 94 | // Older versions of this script wrote both maps under the client directory. |
| 95 | // Clean up the stale icon-data file if a previous checkout still has it so |
| 96 | // it does not shadow the shared module via tsconfig path resolution. |
| 97 | await rm(path.join(clientGeneratedDir, "emoji-icon-data.ts"), { force: true }); |
| 98 | await rm(path.join(clientGeneratedDir, "emoji-tone-variants.ts"), { force: true }); |
| 99 | } |
| 100 | |
| 101 | await main(); |