Mirror: A frag-canvas custom element to apply Shadertoy fragment shaders to a canvas or image/video element
at main 8.0 kB view raw
1import fs from 'node:fs/promises'; 2import path from 'node:path/posix'; 3import { fileURLToPath } from 'node:url'; 4import { readFileSync } from 'node:fs'; 5import { createRequire, isBuiltin } from 'node:module'; 6 7import * as prettier from 'prettier'; 8import commonjs from '@rollup/plugin-commonjs'; 9import resolve from '@rollup/plugin-node-resolve'; 10import babel from '@rollup/plugin-babel'; 11import terser from '@rollup/plugin-terser'; 12import cjsCheck from 'rollup-plugin-cjs-check'; 13import dts from 'rollup-plugin-dts'; 14 15const __dirname = path.dirname(fileURLToPath(import.meta.url)); 16 17const normalize = name => [] 18 .concat(name) 19 .join(' ') 20 .replace(/[@\s/.]+/g, ' ') 21 .trim() 22 .replace(/\s+/, '-') 23 .toLowerCase(); 24 25const extension = name => { 26 if (/\.d.ts$/.test(name)) { 27 return '.d.ts'; 28 } else { 29 return path.extname(name); 30 } 31}; 32 33const meta = JSON.parse(readFileSync('package.json')); 34const name = normalize(meta.name); 35 36const externalModules = [ 37 ...Object.keys(meta.dependencies || {}), 38 ...Object.keys(meta.peerDependencies || {}), 39]; 40 41const moduleRe = /^(?!node:|[.{1,2}\/])(@[\w.-]+\/)?[\w.-]+/; 42const externalRe = new RegExp(`^(${externalModules.join('|')})($|/)`); 43 44const exports = {}; 45for (const key in meta.exports) { 46 const entry = meta.exports[key]; 47 if (typeof entry === 'object' && !!entry.source) { 48 const entryPath = normalize(key); 49 const entryName = normalize([name, entryPath]); 50 exports[entryName] = { 51 path: entryPath, 52 ...entry, 53 }; 54 } 55} 56 57const externals = new Set(); 58 59const commonConfig = { 60 input: Object.entries(exports).reduce((input, [exportName, entry]) => { 61 input[exportName] = entry.source; 62 return input; 63 }, {}), 64 onwarn: () => {}, 65 external(id) { 66 const isExternal = isBuiltin(id) || (externalModules.length && externalRe.test(id)); 67 if (!isExternal && moduleRe.test(id)) 68 externals.add(id); 69 return isExternal; 70 }, 71 treeshake: { 72 unknownGlobalSideEffects: false, 73 tryCatchDeoptimization: false, 74 moduleSideEffects: false, 75 }, 76}; 77 78const commonPlugins = [ 79 resolve({ 80 extensions: ['.mjs', '.js', '.ts'], 81 mainFields: ['module', 'jsnext', 'main'], 82 preferBuiltins: false, 83 browser: true, 84 }), 85 86 commonjs({ 87 ignoreGlobal: true, 88 include: /\/node_modules\//, 89 }), 90]; 91 92const commonOutput = { 93 dir: './', 94 exports: 'auto', 95 sourcemap: true, 96 sourcemapExcludeSources: false, 97 hoistTransitiveImports: false, 98 indent: false, 99 freeze: false, 100 strict: false, 101 generatedCode: { 102 preset: 'es5', 103 reservedNamesAsProps: false, 104 objectShorthand: false, 105 constBindings: false, 106 }, 107}; 108 109const outputPlugins = [ 110 { 111 name: 'outputPackageJsons', 112 async writeBundle() { 113 for (const key in exports) { 114 const entry = exports[key]; 115 if (entry.path) { 116 const output = path.relative(entry.path, process.cwd()); 117 const json = JSON.stringify({ 118 name: key, 119 private: true, 120 version: '0.0.0', 121 main: path.join(output, entry.require), 122 module: path.join(output, entry.import), 123 types: path.join(output, entry.types), 124 source: path.join(output, entry.source), 125 exports: { 126 '.': { 127 types: path.join(output, entry.types), 128 import: path.join(output, entry.import), 129 require: path.join(output, entry.require), 130 source: path.join(output, entry.source), 131 }, 132 }, 133 }, null, 2); 134 135 await fs.mkdir(entry.path, { recursive: true }); 136 await fs.writeFile(path.join(entry.path, 'package.json'), json); 137 } 138 } 139 }, 140 }, 141 142 { 143 name: 'outputBundledLicenses', 144 async writeBundle() { 145 const require = createRequire(import.meta.url); 146 const rootLicense = path.join(__dirname, '../LICENSE.md'); 147 const outputLicense = path.resolve('LICENSE.md'); 148 if (rootLicense === outputLicense) return; 149 const licenses = new Map(); 150 for (const packageName of [...externals].sort()) { 151 let license; 152 let metaPath; 153 let meta; 154 try { 155 metaPath = require.resolve(path.join(packageName, '/package.json')); 156 meta = require(metaPath); 157 } catch (_error) { 158 continue; 159 } 160 const packagePath = path.dirname(metaPath); 161 let licenseName = (await fs.readdir(packagePath).catch(() => [])) 162 .find((name) => /^licen[sc]e/i.test(name)); 163 if (!licenseName) { 164 const match = /^SEE LICENSE IN (.*)/i.exec(meta.license || ''); 165 licenseName = match ? match[1] : meta.license; 166 } 167 try { 168 license = await fs.readFile(path.join(packagePath, licenseName), 'utf8'); 169 } catch (_error) { 170 license = meta.author 171 ? `${licenseName}, Copyright (c) ${meta.author.name || meta.author}` 172 : `${licenseName}, See license at: ${meta.repository.url || meta.repository}`; 173 } 174 licenses.set(packageName, license); 175 } 176 let output = (await fs.readFile(rootLicense, 'utf8')).trim(); 177 for (const [packageName, licenseText] of licenses) 178 output += `\n\n## ${packageName}\n\n${licenseText.trim()}`; 179 await fs.writeFile(outputLicense, output); 180 }, 181 }, 182 183 cjsCheck(), 184 185 terser({ 186 warnings: true, 187 ecma: 2015, 188 keep_fnames: true, 189 ie8: false, 190 compress: { 191 pure_getters: true, 192 toplevel: true, 193 booleans_as_integers: false, 194 keep_fnames: true, 195 keep_fargs: true, 196 if_return: false, 197 ie8: false, 198 sequences: false, 199 loops: false, 200 conditionals: false, 201 join_vars: false, 202 }, 203 mangle: { 204 module: true, 205 keep_fnames: true, 206 }, 207 output: { 208 beautify: true, 209 braces: true, 210 indent_level: 2, 211 }, 212 }), 213]; 214 215export default [ 216 { 217 ...commonConfig, 218 plugins: [ 219 ...commonPlugins, 220 babel({ 221 babelrc: false, 222 babelHelpers: 'bundled', 223 extensions: ['mjs', 'js', 'jsx', 'ts', 'tsx'], 224 exclude: 'node_modules/**', 225 presets: [], 226 plugins: [ 227 '@babel/plugin-transform-typescript', 228 '@babel/plugin-transform-block-scoping', 229 ], 230 }), 231 ], 232 output: [ 233 { 234 ...commonOutput, 235 format: 'esm', 236 chunkFileNames(chunk) { 237 return `dist/chunks/[name]-chunk${extension(chunk.name) || '.mjs'}`; 238 }, 239 entryFileNames(chunk) { 240 return chunk.isEntry 241 ? path.normalize(exports[chunk.name].import) 242 : `dist/[name].mjs`; 243 }, 244 plugins: outputPlugins, 245 }, 246 { 247 ...commonOutput, 248 format: 'cjs', 249 esModule: true, 250 externalLiveBindings: true, 251 chunkFileNames(chunk) { 252 return `dist/chunks/[name]-chunk${extension(chunk.name) || '.js'}`; 253 }, 254 entryFileNames(chunk) { 255 return chunk.isEntry 256 ? path.normalize(exports[chunk.name].require) 257 : `dist/[name].js`; 258 }, 259 plugins: outputPlugins, 260 }, 261 ], 262 }, 263 264 { 265 ...commonConfig, 266 plugins: [ 267 ...commonPlugins, 268 dts(), 269 ], 270 output: { 271 ...commonOutput, 272 sourcemap: false, 273 format: 'dts', 274 chunkFileNames(chunk) { 275 return `dist/chunks/[name]-chunk${extension(chunk.name) || '.d.ts'}`; 276 }, 277 entryFileNames(chunk) { 278 return chunk.isEntry 279 ? path.normalize(exports[chunk.name].types) 280 : `dist/[name].d.ts`; 281 }, 282 plugins: [ 283 { 284 renderChunk(code, chunk) { 285 if (chunk.fileName.endsWith('d.ts')) { 286 return prettier.format(code, { 287 filepath: chunk.fileName, 288 parser: 'typescript', 289 singleQuote: true, 290 tabWidth: 2, 291 printWidth: 100, 292 trailingComma: 'es5', 293 }); 294 } 295 }, 296 }, 297 ], 298 }, 299 }, 300];