|
| 1 | +import path from "path"; |
| 2 | +import fs from "fs"; |
| 3 | +import fsExtra from "fs-extra"; |
| 4 | +import { glob } from "glob"; |
| 5 | +import CleanCSS from "clean-css"; |
| 6 | +import imageDataURI from "image-data-uri"; |
| 7 | +import emojiData from "emoji-datasource-google" with { type: "json" }; |
| 8 | + |
| 9 | +/** |
| 10 | + * Plugin to clean the entire dist folder before build. |
| 11 | + */ |
| 12 | +export function cleanPlugin() { |
| 13 | + return { |
| 14 | + name: "clean", |
| 15 | + buildStart() { |
| 16 | + const distPath = path.resolve(__dirname, "dist"); |
| 17 | + fsExtra.removeSync(distPath); |
| 18 | + }, |
| 19 | + }; |
| 20 | +} |
| 21 | + |
| 22 | +/** |
| 23 | + * Plugin to copy and rename emoji images from emoji-datasource-google. |
| 24 | + */ |
| 25 | +export function copyImagesPlugin() { |
| 26 | + return { |
| 27 | + name: "copy-images", |
| 28 | + async buildStart() { |
| 29 | + const imageDest = path.resolve(__dirname, "dist/images/basic"); |
| 30 | + await fsExtra.remove(imageDest); |
| 31 | + await fsExtra.ensureDir(imageDest); |
| 32 | + |
| 33 | + // Build a map where key = original image filename, value = emoji info |
| 34 | + const emojiMap = emojiData.reduce((acc, emoji) => { |
| 35 | + acc[emoji.image] = emoji; |
| 36 | + return acc; |
| 37 | + }, {}); |
| 38 | + const availableFiles = Object.keys(emojiMap); |
| 39 | + const srcDir = path.resolve( |
| 40 | + __dirname, |
| 41 | + "node_modules/emoji-datasource-google/img/google/64", |
| 42 | + ); |
| 43 | + |
| 44 | + // Copy primary emoji file (renaming to its short_name) |
| 45 | + const files = glob.sync(`${srcDir}/*.png`); |
| 46 | + for (const file of files) { |
| 47 | + const base = path.basename(file); |
| 48 | + if (availableFiles.includes(base)) { |
| 49 | + const emoji = emojiMap[base]; |
| 50 | + const destFile = path.resolve(imageDest, `${emoji.short_name}.png`); |
| 51 | + await fsExtra.copy(file, destFile); |
| 52 | + } |
| 53 | + } |
| 54 | + // Copy extra aliases (if any) |
| 55 | + for (const base of availableFiles) { |
| 56 | + const emoji = emojiMap[base]; |
| 57 | + const aliases = emoji.short_names.filter((n) => n !== emoji.short_name); |
| 58 | + const srcFile = path.resolve(srcDir, base); |
| 59 | + if (aliases.length > 0 && fs.existsSync(srcFile)) { |
| 60 | + for (const alias of aliases) { |
| 61 | + const destFile = path.resolve(imageDest, `${alias}.png`); |
| 62 | + await fsExtra.copy(srcFile, destFile); |
| 63 | + } |
| 64 | + } |
| 65 | + } |
| 66 | + }, |
| 67 | + }; |
| 68 | +} |
| 69 | + |
| 70 | +/** |
| 71 | + * Plugin to copy CSS files from src/css/basic to dist/css/basic and minify them. |
| 72 | + */ |
| 73 | +export function copyStylesPlugin() { |
| 74 | + return { |
| 75 | + name: "copy-styles", |
| 76 | + async buildStart() { |
| 77 | + const srcStylesDir = path.resolve(__dirname, "src/css/basic"); |
| 78 | + const destStylesDir = path.resolve(__dirname, "dist/css/basic"); |
| 79 | + await fsExtra.ensureDir(destStylesDir); |
| 80 | + const files = glob.sync(`${srcStylesDir}/*.css`); |
| 81 | + for (const file of files) { |
| 82 | + const destFile = path.resolve(destStylesDir, path.basename(file)); |
| 83 | + await fsExtra.copy(file, destFile); |
| 84 | + // Minify the CSS |
| 85 | + const css = fs.readFileSync(file, "utf8"); |
| 86 | + const minified = new CleanCSS().minify(css).styles; |
| 87 | + const minFile = path.resolve( |
| 88 | + destStylesDir, |
| 89 | + path.basename(file, ".css") + ".min.css", |
| 90 | + ); |
| 91 | + fs.writeFileSync(minFile, minified, "utf8"); |
| 92 | + } |
| 93 | + }, |
| 94 | + }; |
| 95 | +} |
| 96 | + |
| 97 | +/** |
| 98 | + * Plugin to generate CSS with data URIs for a set of emoticons. |
| 99 | + */ |
| 100 | +export function dataURIPlugin() { |
| 101 | + return { |
| 102 | + name: "generate-data-uri-css", |
| 103 | + async closeBundle() { |
| 104 | + const imageDir = path.resolve(__dirname, "dist/images/basic"); |
| 105 | + const cssDataURIPath = path.resolve(__dirname, "dist/css/data-uri"); |
| 106 | + await fsExtra.ensureDir(cssDataURIPath); |
| 107 | + const files = glob.sync(`${imageDir}/*.png`); |
| 108 | + // Emoticon list (as in your gulpfile) |
| 109 | + const emoticons = [ |
| 110 | + "smile", |
| 111 | + "scream", |
| 112 | + "smirk", |
| 113 | + "grinning", |
| 114 | + "stuck_out_tongue_closed_eyes", |
| 115 | + "stuck_out_tongue_winking_eye", |
| 116 | + "rage", |
| 117 | + "frowning", |
| 118 | + "sob", |
| 119 | + "kissing_heart", |
| 120 | + "wink", |
| 121 | + "pensive", |
| 122 | + "confounded", |
| 123 | + "flushed", |
| 124 | + "relaxed", |
| 125 | + "mask", |
| 126 | + "heart", |
| 127 | + "broken_heart", |
| 128 | + ]; |
| 129 | + let cssContent = ""; |
| 130 | + let emoticonsContent = ""; |
| 131 | + // For each image that is in the emoticon list, generate a CSS rule with its data URI. |
| 132 | + for (const file of files) { |
| 133 | + const name = path.basename(file, ".png"); |
| 134 | + const dataUri = await imageDataURI.encodeFromFile(file, "PNG"); |
| 135 | + const str = `.emoji-${name === "+1" ? "plus1" : name} { background-image: url("${dataUri}"); }\n`; |
| 136 | + cssContent += str; |
| 137 | + if (emoticons.includes(name)) emoticonsContent += str; |
| 138 | + } |
| 139 | + // Write unminified CSS file |
| 140 | + fs.writeFileSync( |
| 141 | + path.resolve(cssDataURIPath, "emojify.css"), |
| 142 | + cssContent, |
| 143 | + "utf8", |
| 144 | + ); |
| 145 | + // Write minified version |
| 146 | + fs.writeFileSync( |
| 147 | + path.resolve(cssDataURIPath, "emojify.min.css"), |
| 148 | + new CleanCSS().minify(cssContent).styles, |
| 149 | + "utf8", |
| 150 | + ); |
| 151 | + |
| 152 | + const cssFile = path.resolve(cssDataURIPath, "emojify-emoticons.css"); |
| 153 | + fs.writeFileSync( |
| 154 | + path.resolve(cssDataURIPath, "emojify-emoticons.css"), |
| 155 | + emoticonsContent, |
| 156 | + "utf8", |
| 157 | + ); |
| 158 | + fs.writeFileSync( |
| 159 | + path.resolve(cssDataURIPath, "emojify-emoticons.min.css"), |
| 160 | + new CleanCSS().minify(emoticonsContent).styles, |
| 161 | + "utf8", |
| 162 | + ); |
| 163 | + }, |
| 164 | + }; |
| 165 | +} |
| 166 | + |
| 167 | +/** |
| 168 | + * Plugin to replace the token "\/*##EMOJILIST*\/\n'';" with a string |
| 169 | + * built from the names of the emoji images that were copied. |
| 170 | + */ |
| 171 | +export function replaceEmojiListPlugin() { |
| 172 | + return { |
| 173 | + name: "replace-emoji-list", |
| 174 | + generateBundle(options, bundle) { |
| 175 | + const imageDir = path.resolve(__dirname, "dist/images/basic"); |
| 176 | + let emojiString = ""; |
| 177 | + if (fs.existsSync(imageDir)) { |
| 178 | + const files = fs.readdirSync(imageDir); |
| 179 | + emojiString = files |
| 180 | + .filter((f) => f.endsWith(".png")) |
| 181 | + .map((f) => path.basename(f, ".png")) |
| 182 | + .join(","); |
| 183 | + } |
| 184 | + // For each JS chunk in the bundle, do the replacement. |
| 185 | + for (const fileName in bundle) { |
| 186 | + const chunk = bundle[fileName]; |
| 187 | + if (chunk.type === "chunk") { |
| 188 | + chunk.code = chunk.code.replace(/#EMOJILIST#/, `${emojiString}`); |
| 189 | + } |
| 190 | + } |
| 191 | + }, |
| 192 | + }; |
| 193 | +} |
0 commit comments