do the manifest rewriting stuff smart hooly fuck

This commit is contained in:
Erin 2023-07-28 07:07:11 -04:00
parent d750550c8d
commit bd778cbd1c
5 changed files with 177 additions and 78 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules

89
entrypoints.ts Normal file
View file

@ -0,0 +1,89 @@
/** The kind of an entrypoint in an extension manifest. */
enum ManifestEntrypointKind {
/** Javascript run as part of a content script */
CONTENT_SCRIPT_JS,
/** CSS applied as part of a content script */
CONTENT_SCRIPT_CSS,
/** Javascript run in the background page of an MV2 extension */
BACKGROUND_SCRIPT,
/** Javascript run the service worker of an MV3 extension */
BACKGROUND_SERVICE_WORKER,
/** Image file loaded as the extension's icon at a particular size */
ICON,
/** Path listed in the `web_accessible_resources` of an MV2 extension */
WEB_ACCESSIBLE_RESOURCE_V2,
/** Path listed in the `web_accessible_resources` of an MV3 extension */
WEB_ACCESSIBLE_RESOURCE_V3,
}
/**
* A handle to a single entrypoint from a manifest, which allows for rewriting
* the path of the asset in the manifest.
*/
interface ManifestEntrypoint {
type: ManifestEntrypointKind;
/** The full path to the entrypoint file. */
path: string;
/**
* Replaces this entry path with the given new path. This is an in-place
* operation and directly affects the original manifest object.
*/
replacePath: (path: string) => void;
}
/**
* Gets all script entrypoints from a manifest, returning them as
* {@link ManifestEntrypoint} objects which allow for rewriting the path of each
* individual entrypoint in place.
* @param manifest Parsed `manifest.json` data.
*/
export const getScriptEntrypoints = (manifest: chrome.runtime.Manifest): ManifestEntrypoint[] => [
...(manifest.content_scripts ?? []).flatMap(script => (script.js ?? []).map((path, i) => ({
type: ManifestEntrypointKind.CONTENT_SCRIPT_JS,
path,
replacePath: newPath => script.js!.splice(i, 1, newPath),
}))),
...(manifest.manifest_version === 2
? (manifest.background?.scripts || []).map((path, i) => ({
type: ManifestEntrypointKind.BACKGROUND_SCRIPT,
path,
replacePath: newPath => manifest.background!.scripts!.splice(i, 1, newPath),
}))
: (manifest.background?.service_worker ? [{
type: ManifestEntrypointKind.BACKGROUND_SERVICE_WORKER,
path: manifest.background.service_worker,
replacePath: newPath => manifest.background!.service_worker = newPath,
}] : [])
),
];
/**
* Gets all asset entrypoints from a manifest, returning them as
* {@link ManifestEntrypoint} objects which allow for rewriting the path of each
* individual entrypoint in place.
* @param manifest Parsed `manifest.json` data.
*/
export const getAssetEntrypoints = (manifest: chrome.runtime.Manifest): ManifestEntrypoint[] => [
...(manifest.content_scripts ?? []).flatMap(script => (script.css ?? []).map((path, i) => ({
type: ManifestEntrypointKind.CONTENT_SCRIPT_CSS,
path,
replacePath: newPath => script.css!.splice(i, 1, newPath),
}))),
...Object.entries(manifest.icons || {}).map(([iconSize, path]) => ({
type: ManifestEntrypointKind.ICON,
path,
replacePath: newPath => manifest.icons![iconSize] = newPath,
})),
...(manifest.manifest_version === 2
? (manifest.web_accessible_resources ?? []).map((path, i) => ({
type: ManifestEntrypointKind.WEB_ACCESSIBLE_RESOURCE_V2,
path,
replacePath: newPath => manifest.web_accessible_resources![i] = newPath,
}))
: (manifest.web_accessible_resources ?? []).flatMap(entry => entry.resources.map((path, i) => ({
type: ManifestEntrypointKind.WEB_ACCESSIBLE_RESOURCE_V3,
path,
replacePath: newPath => entry.resources.splice(i, 1, newPath),
})))
)
];

114
index.mjs
View file

@ -2,6 +2,7 @@
import {readFileSync} from 'node:fs';
import {resolve, basename, extname, dirname, relative, join} from 'node:path';
import { getAssetEntrypoints, getScriptEntrypoints } from './entrypoints';
export function buildConfig ({
manifest: manifestPathRelative,
@ -47,81 +48,31 @@ export function buildConfig ({
return join(base, uniqueFileNameSegment(entryPath, ext));
}
/** Scans a manifest for entrypoints */
function getEntryPointsFromManifest (manifestContent) {
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));
});
}
});
return {
scripts: scriptEntrypointAbsolutePaths,
assets: [
...styleEntrypointAbsolutePathss,
...otherAssetAbsolutePaths,
],
};
}
const {scripts, assets} = getEntryPointsFromManifest(manifestContent);
const scripts = getScriptEntrypoints(manifestContent);
const assets = getAssetEntrypoints(manifestContent);
return [
...scripts.map(entrypointPath => ({
input: relative(process.cwd(), entrypointPath),
output: {
file: join(outDir, getOutputFilename(entrypointPath, 'js')),
format: 'iife',
sourcemap,
},
plugins: scriptPlugins,
})),
// Process each script entrypoint independently
...scripts.map(({path, replacePath}) => {
// Figure out where this bundle will live in the output
const outPath = getOutputFilename(path, 'js');
// A special step that processes the manifest and copies over non-JS
// assets in the meantime
// Rewrite the manifest with that path
replacePath(outPath);
// Build the bundle
return {
input: relative(process.cwd(), path),
output: {
file: join(outDir, outPath),
format: 'iife',
sourcemap,
},
plugins: scriptPlugins,
};
}),
// Special step that processes the manifest and injects other assets
{
input: manifestPathRelative,
output: {
@ -131,11 +82,20 @@ export function buildConfig ({
{
// emit other assets
buildStart () {
assets.forEach(absolutePath => this.emitFile({
type: 'asset',
fileName: getOutputFilename(absolutePath),
source: readFileSync(absolutePath),
}));
assets.forEach(({path, replacePath}) => {
// Figure out where the asset will live in output
const outPath = getOutputFilename(path);
// Rewrite the manifest with that path
replacePath(outPath);
// Emit the asset as part of the build step
this.emitFile({
type: 'asset',
fileName: getOutputFilename(absolutePath),
source: readFileSync(absolutePath),
});
});
},
// hacks to make sure the manifest is emitted as bare JSON

46
package-lock.json generated Normal file
View file

@ -0,0 +1,46 @@
{
"name": "rollup-create-webext-config",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rollup-create-webext-config",
"version": "0.0.0",
"devDependencies": {
"@types/chrome": "^0.0.242"
}
},
"node_modules/@types/chrome": {
"version": "0.0.242",
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.242.tgz",
"integrity": "sha512-SeMXBSfcAGX9ezTz7Pro7n/AiNdIH3cetkdbM+Kfg3zD24jmbnm0IAEIxzx8ccqrnJenLCfD7fR+4WIYAbeQHw==",
"dev": true,
"dependencies": {
"@types/filesystem": "*",
"@types/har-format": "*"
}
},
"node_modules/@types/filesystem": {
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz",
"integrity": "sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==",
"dev": true,
"dependencies": {
"@types/filewriter": "*"
}
},
"node_modules/@types/filewriter": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz",
"integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==",
"dev": true
},
"node_modules/@types/har-format": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.11.tgz",
"integrity": "sha512-T232/TneofqK30AD1LRrrf8KnjLvzrjWDp7eWST5KoiSzrBfRsLrWDPk4STQPW4NZG6v2MltnduBVmakbZOBIQ==",
"dev": true
}
}
}

View file

@ -1,5 +1,8 @@
{
"name": "rollup-create-webext-config",
"version": "0.0.0",
"main": "index.mjs"
"main": "index.mjs",
"devDependencies": {
"@types/chrome": "^0.0.242"
}
}