commit 6ed07ab982f001c3c3f2858cabf49c4322592ed6 Author: Erin Date: Fri Jul 28 04:26:25 2023 -0400 initial vaguely working commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..adedbe7 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# rollup-create-webext-config diff --git a/index.mjs b/index.mjs new file mode 100644 index 0000000..4bf045c --- /dev/null +++ b/index.mjs @@ -0,0 +1,156 @@ +/* eslint-env node */ + +import {readFileSync} from 'node:fs'; +import {resolve, basename, extname, dirname, relative, join} from 'node:path'; + +export function createConfig (manifestPathRelative, options, createConfig) { + const manifestPath = resolve(process.cwd(), manifestPathRelative); + const manifestDirname = dirname(manifestPath); + + // Load the manifest + let manifestContent; + try { + manifestContent = JSON.parse(readFileSync(manifestPath, {encoding: 'utf-8'})); + } catch (error) { + throw new Error('Failed to load manifest'); + } + + const uniqueFileNameSegmentCache = new Map(); + function uniqueFileNameSegment (filepath, ext = extname(filepath).slice(1)) { + // console.log('getting segment for', filepath); + const cached = uniqueFileNameSegmentCache.get(filepath); + if (cached) { + return cached; + } + const idealName = basename(filepath, extname(filepath)); + + const buildName = (str, n) => n ? `${str}_${n}.${ext}` : `${str}.${ext}`; + + let uniquenessNum = 0; + const existingNames = [...uniqueFileNameSegmentCache.values()]; + while (existingNames.some(existingName => existingName.toLowerCase() === buildName(idealName, uniquenessNum).toLowerCase())) { + uniquenessNum += 1; + } + + const finalName = buildName(idealName, uniquenessNum); + uniqueFileNameSegmentCache.set(filepath, finalName); + return finalName; + } + + function getOutputFilename (entryPath, ext) { + const base = relative(manifestDirname, dirname(entryPath)); + return join(base, `${uniqueFileNameSegment(entryPath, ext)}`); + } + + function getOutputPathRelative (outputDir, platform, entryPath, ext) { + return join(outputDir, platform, getOutputFilename(entryPath, ext)); + } + + const scriptEntrypointAbsolutePaths = []; + const styleEntrypointAbsolutePathss = []; + const otherAssetAbsolutePaths = []; + // Gather all JS entry points specified in the manifest + (manifestContent.content_scripts || []).forEach(({css, js}) => { + css && css.forEach((filename, i) => { + const id = resolve(manifestDirname, filename); + styleEntrypointAbsolutePathss.push(id); + css.splice(i, 1, getOutputFilename(id, 'css')); + }); + js && js.forEach((filename, i) => { + const id = resolve(manifestDirname, filename); + scriptEntrypointAbsolutePaths.push(id); + js.splice(i, 1, getOutputFilename(id, 'js')); + }); + }); + manifestContent.background?.scripts && manifestContent.background.scripts.forEach((filename, i) => { + const id = resolve(manifestDirname, filename); + scriptEntrypointAbsolutePaths.push(id); + manifestContent.background.scripts.splice(i, 1, getOutputFilename(id)); + }); + if (manifestContent.background?.service_worker) { + const id = resolve(manifestDirname, manifestContent.background.service_worker); + scriptEntrypointAbsolutePaths.push(id); + manifestContent.background.service_worker = getOutputFilename(id); + } + manifestContent.icons && Object.entries(manifestContent.icons).forEach(([size, filename]) => { + const id = resolve(manifestDirname, filename); + // console.log('icon:', size, filename, getOutputFilename(id)); + otherAssetAbsolutePaths.push(id); + manifestContent.icons[size] = getOutputFilename(id); + }); + (manifestContent.web_accessible_resources || []).forEach((entry, i) => { + if (typeof entry === 'string') { + // mv2 - single top-level array of items + const id = resolve(manifestDirname, entry); + otherAssetAbsolutePaths.push(id); + manifestContent.web_accessible_resources.splice(i, 1, getOutputFilename(id)); + } else { + // mv3 - array of objects with `resources` keys + const {resources} = entry; + resources && resources.forEach((filename, j) => { + const id = resolve(manifestDirname, filename); + otherAssetAbsolutePaths.push(id); + resources.splice(j, 1, getOutputFilename(id)); + }); + } + }); + + const platform = 'firefox'; + return [ + ...scriptEntrypointAbsolutePaths.map(entrypointPath => ({ + // Get configuration options for this entrypoint from the caller + ...createConfig(), + // Overwrite input and output options + input: relative(process.cwd(), entrypointPath), + output: { + file: getOutputPathRelative('build', platform, entrypointPath, 'js'), + format: 'iife', + sourcemap: options.sourcemap, + }, + })), + + // A special step that processes the manifest and copies over non-JS + // assets in the meantime + { + input: manifestPathRelative, + output: { + file: `build/${platform}/manifest.json`, + }, + plugins: [ + { + buildStart () { + styleEntrypointAbsolutePathss.forEach(absolutePath => { + this.emitFile({ + type: 'asset', + fileName: getOutputFilename(absolutePath, 'css'), + source: readFileSync(absolutePath, {encoding: 'utf-8'}), + }); + }); + otherAssetAbsolutePaths.forEach(absolutePath => { + // console.log('rendering binary file', absolutePath); + this.emitFile({ + type: 'asset', + fileName: getOutputFilename(absolutePath), + source: readFileSync(absolutePath), + }); + }); + }, + load (id) { + // console.log(id); + if (id === manifestPath) { + return 'debugger;'; + } + return null; + }, + renderChunk (code, chunk) { + // console.log(chunk); + if (chunk.facadeModuleId !== manifestPath) { + return null; + } + return JSON.stringify(manifestContent, null, '\t'); + }, + }, + ], + }, + ]; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5fe262a --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "name": "rollup-create-webext-config", + "version": "0.0.0", + "main": "index.mjs", +}