bunch of tweaks to get TS working

This commit is contained in:
Erin 2023-07-28 07:48:01 -04:00
parent 33c6ca723c
commit 0dbd89f21d
7 changed files with 424 additions and 22 deletions

178
build/index.js Normal file
View file

@ -0,0 +1,178 @@
import { readFileSync } from 'node:fs';
import { resolve, dirname, relative, join, basename, extname } from 'node:path';
/** The kind of an entrypoint in an extension manifest. */
var ManifestEntrypointKind;
(function (ManifestEntrypointKind) {
/** Javascript run as part of a content script */
ManifestEntrypointKind[ManifestEntrypointKind["CONTENT_SCRIPT_JS"] = 0] = "CONTENT_SCRIPT_JS";
/** CSS applied as part of a content script */
ManifestEntrypointKind[ManifestEntrypointKind["CONTENT_SCRIPT_CSS"] = 1] = "CONTENT_SCRIPT_CSS";
/** Javascript run in the background page of an MV2 extension */
ManifestEntrypointKind[ManifestEntrypointKind["BACKGROUND_SCRIPT"] = 2] = "BACKGROUND_SCRIPT";
/** Javascript run the service worker of an MV3 extension */
ManifestEntrypointKind[ManifestEntrypointKind["BACKGROUND_SERVICE_WORKER"] = 3] = "BACKGROUND_SERVICE_WORKER";
/** Image file loaded as the extension's icon at a particular size */
ManifestEntrypointKind[ManifestEntrypointKind["ICON"] = 4] = "ICON";
/** Path listed in the `web_accessible_resources` of an MV2 extension */
ManifestEntrypointKind[ManifestEntrypointKind["WEB_ACCESSIBLE_RESOURCE_V2"] = 5] = "WEB_ACCESSIBLE_RESOURCE_V2";
/** Path listed in the `web_accessible_resources` of an MV3 extension */
ManifestEntrypointKind[ManifestEntrypointKind["WEB_ACCESSIBLE_RESOURCE_V3"] = 6] = "WEB_ACCESSIBLE_RESOURCE_V3";
})(ManifestEntrypointKind || (ManifestEntrypointKind = {}));
/**
* 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.
*/
const getScriptEntrypoints = (manifest) => {
var _a, _b, _c;
return [
...((_a = manifest.content_scripts) !== null && _a !== void 0 ? _a : []).flatMap(script => {
var _a;
return ((_a = script.js) !== null && _a !== void 0 ? _a : []).map((path, i) => ({
type: ManifestEntrypointKind.CONTENT_SCRIPT_JS,
path,
replacePath: (newPath) => script.js.splice(i, 1, newPath),
}));
}),
...(manifest.manifest_version === 2
? (((_b = manifest.background) === null || _b === void 0 ? void 0 : _b.scripts) || []).map((path, i) => ({
type: ManifestEntrypointKind.BACKGROUND_SCRIPT,
path,
replacePath: (newPath) => manifest.background.scripts.splice(i, 1, newPath),
}))
: (((_c = manifest.background) === null || _c === void 0 ? void 0 : _c.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.
*/
const getAssetEntrypoints = (manifest) => {
var _a, _b, _c;
return [
...((_a = manifest.content_scripts) !== null && _a !== void 0 ? _a : []).flatMap(script => {
var _a;
return ((_a = script.css) !== null && _a !== void 0 ? _a : []).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
? ((_b = manifest.web_accessible_resources) !== null && _b !== void 0 ? _b : []).map((path, i) => ({
type: ManifestEntrypointKind.WEB_ACCESSIBLE_RESOURCE_V2,
path,
replacePath: (newPath) => manifest.web_accessible_resources[i] = newPath,
}))
: ((_c = manifest.web_accessible_resources) !== null && _c !== void 0 ? _c : []).flatMap(entry => entry.resources.map((path, i) => ({
type: ManifestEntrypointKind.WEB_ACCESSIBLE_RESOURCE_V3,
path,
replacePath: (newPath) => entry.resources.splice(i, 1, newPath),
}))))
];
};
/* eslint-env node */
function buildConfig({ manifest: manifestPathRelative, outDir, scriptPlugins = [], sourcemap, }) {
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 = join(dirname(filepath), 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) {
// return join(dirname(entryPath), uniqueFileNameSegment(entryPath,
// ext));
return uniqueFileNameSegment(entryPath, ext);
}
const scripts = getScriptEntrypoints(manifestContent);
const assets = getAssetEntrypoints(manifestContent);
return [
// Process each script entrypoint independently
...scripts.map(({ path, replacePath }) => {
// Figure out where this bundle will live in the output
const outPath = getOutputFilename(path, 'js');
// Rewrite the manifest with that path
replacePath(outPath);
// Build the bundle
return {
input: relative(process.cwd(), join(manifestDirname, path)),
output: {
file: join(outDir, outPath),
format: 'iife',
sourcemap,
},
plugins: scriptPlugins,
};
}),
// Special step that processes the manifest and injects other assets
{
input: manifestPathRelative,
output: {
file: join(outDir, 'manifest.json'),
},
plugins: [
{
name: '_manifest-asset-processing',
// emit other assets
buildStart() {
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: outPath,
source: readFileSync(join(manifestDirname, path)),
});
});
},
// hacks to make sure the manifest is emitted as bare JSON
load: id => id === manifestPath ? 'debugger;' : null,
renderChunk: (_, chunk) => chunk.facadeModuleId === manifestPath
? JSON.stringify(manifestContent, null, '\t')
: null,
},
],
},
];
}
export { buildConfig };

View file

@ -41,18 +41,18 @@ export const getScriptEntrypoints = (manifest: chrome.runtime.Manifest): Manifes
...(manifest.content_scripts ?? []).flatMap(script => (script.js ?? []).map((path, i) => ({
type: ManifestEntrypointKind.CONTENT_SCRIPT_JS,
path,
replacePath: newPath => script.js!.splice(i, 1, newPath),
replacePath: (newPath: string) => 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),
replacePath: (newPath: string) => 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,
replacePath: (newPath: string) => manifest.background!.service_worker = newPath,
}] : [])
),
];
@ -67,23 +67,23 @@ export const getAssetEntrypoints = (manifest: chrome.runtime.Manifest): Manifest
...(manifest.content_scripts ?? []).flatMap(script => (script.css ?? []).map((path, i) => ({
type: ManifestEntrypointKind.CONTENT_SCRIPT_CSS,
path,
replacePath: newPath => script.css!.splice(i, 1, newPath),
replacePath: (newPath: string) => script.css!.splice(i, 1, newPath),
}))),
...Object.entries(manifest.icons || {}).map(([iconSize, path]) => ({
type: ManifestEntrypointKind.ICON,
path,
replacePath: newPath => manifest.icons![iconSize] = newPath,
replacePath: (newPath: string) => manifest.icons![iconSize as unknown as number] = 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,
replacePath: (newPath: string) => 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),
replacePath: (newPath: string) => entry.resources.splice(i, 1, newPath),
})))
)
];

View file

@ -2,19 +2,27 @@
import {readFileSync} from 'node:fs';
import {resolve, basename, extname, dirname, relative, join} from 'node:path';
import {getAssetEntrypoints, getScriptEntrypoints} from './entrypoints';
import {type RollupOptions, type Plugin} from 'rollup';
export function buildConfig ({
manifest: manifestPathRelative,
outDir,
scriptPlugins = [],
sourcemap,
}) {
}: {
manifest: string;
outDir: string;
scriptPlugins: Plugin[];
sourcemap: boolean | 'inline' | 'hidden';
}): RollupOptions[] {
const manifestPath = resolve(process.cwd(), manifestPathRelative);
const manifestDirname = dirname(manifestPath);
// Load the manifest
let manifestContent;
let manifestContent: chrome.runtime.Manifest;
try {
manifestContent = JSON.parse(readFileSync(manifestPath, {encoding: 'utf-8'}));
} catch (error) {
@ -22,15 +30,15 @@ export function buildConfig ({
}
const uniqueFileNameSegmentCache = new Map();
function uniqueFileNameSegment (filepath, ext = extname(filepath).slice(1)) {
function uniqueFileNameSegment (filepath: string, 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 idealName = join(dirname(filepath), basename(filepath, extname(filepath)));
const buildName = (str, n) => n ? `${str}_${n}.${ext}` : `${str}.${ext}`;
const buildName = (str: string, n: number) => n ? `${str}_${n}.${ext}` : `${str}.${ext}`;
let uniquenessNum = 0;
const existingNames = [...uniqueFileNameSegmentCache.values()];
@ -43,9 +51,10 @@ export function buildConfig ({
return finalName;
}
function getOutputFilename (entryPath, ext?) {
const base = relative(manifestDirname, dirname(entryPath));
return join(base, uniqueFileNameSegment(entryPath, ext));
function getOutputFilename (entryPath: string, ext?: string) {
// return join(dirname(entryPath), uniqueFileNameSegment(entryPath,
// ext));
return uniqueFileNameSegment(entryPath, ext);
}
const scripts = getScriptEntrypoints(manifestContent);
@ -62,10 +71,10 @@ export function buildConfig ({
// Build the bundle
return {
input: relative(process.cwd(), path),
input: relative(process.cwd(), join(manifestDirname, path)),
output: {
file: join(outDir, outPath),
format: 'iife',
format: 'iife' as const,
sourcemap,
},
plugins: scriptPlugins,
@ -80,6 +89,7 @@ export function buildConfig ({
},
plugins: [
{
name: '_manifest-asset-processing',
// emit other assets
buildStart () {
assets.forEach(({path, replacePath}) => {
@ -93,7 +103,7 @@ export function buildConfig ({
this.emitFile({
type: 'asset',
fileName: outPath,
source: readFileSync(path),
source: readFileSync(join(manifestDirname, path)),
});
});
},

192
package-lock.json generated
View file

@ -8,9 +8,66 @@
"name": "rollup-create-webext-config",
"version": "0.0.0",
"devDependencies": {
"@types/chrome": "^0.0.242"
"@rollup/plugin-typescript": "^11.1.2",
"@tsconfig/recommended": "^1.0.2",
"@types/chrome": "^0.0.242",
"rollup": "^3.26.3"
}
},
"node_modules/@rollup/plugin-typescript": {
"version": "11.1.2",
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.2.tgz",
"integrity": "sha512-0ghSOCMcA7fl1JM+0gYRf+Q/HWyg+zg7/gDSc+fRLmlJWcW5K1I+CLRzaRhXf4Y3DRyPnnDo4M2ktw+a6JcDEg==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"resolve": "^1.22.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^2.14.0||^3.0.0",
"tslib": "*",
"typescript": ">=3.7.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
},
"tslib": {
"optional": true
}
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
"integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
"dev": true,
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@tsconfig/recommended": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@tsconfig/recommended/-/recommended-1.0.2.tgz",
"integrity": "sha512-dbHBtbWBOjq0/otpopAE02NT2Cm05Qe2JsEKeCf/wjSYbI2hz8nCqnpnOJWHATgjDz4fd3dchs3Wy1gQGjfN6w==",
"dev": true
},
"node_modules/@types/chrome": {
"version": "0.0.242",
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.242.tgz",
@ -21,6 +78,12 @@
"@types/har-format": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
"dev": true
},
"node_modules/@types/filesystem": {
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.32.tgz",
@ -41,6 +104,133 @@
"resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.11.tgz",
"integrity": "sha512-T232/TneofqK30AD1LRrrf8KnjLvzrjWDp7eWST5KoiSzrBfRsLrWDPk4STQPW4NZG6v2MltnduBVmakbZOBIQ==",
"dev": true
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/is-core-module": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
"integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
"dev": true,
"dependencies": {
"has": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
"integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
"dev": true,
"dependencies": {
"is-core-module": "^2.11.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/rollup": {
"version": "3.26.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.3.tgz",
"integrity": "sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=14.18.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/typescript": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
"dev": true,
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}

View file

@ -1,8 +1,12 @@
{
"name": "rollup-create-webext-config",
"version": "0.0.0",
"main": "index.mjs",
"main": "build/index.js",
"type": "module",
"devDependencies": {
"@types/chrome": "^0.0.242"
"@rollup/plugin-typescript": "^11.1.2",
"@tsconfig/recommended": "^1.0.2",
"@types/chrome": "^0.0.242",
"rollup": "^3.26.3"
}
}

17
rollup.config.js Normal file
View file

@ -0,0 +1,17 @@
import {defineConfig} from 'rollup';
import typescript from '@rollup/plugin-typescript';
export default defineConfig({
input: 'index.ts',
output: {
format: 'es',
dir: 'build',
},
external: [
'node:fs',
'node:path',
],
plugins: [
typescript(),
],
});

3
tsconfig.json Normal file
View file

@ -0,0 +1,3 @@
{
"extends": "@tsconfig/recommended/tsconfig.json"
}