Move things to src
This commit is contained in:
parent
41a3ad8f6a
commit
d86d678588
3 changed files with 1 additions and 1 deletions
116
src/entrypoints.ts
Normal file
116
src/entrypoints.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/** 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: string) {
|
||||
this.path = newPath;
|
||||
script.js!.splice(i, 1, newPath);
|
||||
},
|
||||
}))
|
||||
),
|
||||
...(manifest.manifest_version === 2
|
||||
? (manifest.background?.scripts || []).map((path, i) => ({
|
||||
type: ManifestEntrypointKind.BACKGROUND_SCRIPT,
|
||||
path,
|
||||
replacePath (newPath: string) {
|
||||
this.path = newPath;
|
||||
manifest.background!.scripts!.splice(i, 1, newPath);
|
||||
},
|
||||
}))
|
||||
: (manifest.background?.service_worker
|
||||
? [{
|
||||
type: ManifestEntrypointKind.BACKGROUND_SERVICE_WORKER,
|
||||
path: manifest.background.service_worker,
|
||||
replacePath (newPath: string) {
|
||||
this.path = 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: string) {
|
||||
this.path = newPath;
|
||||
script.css!.splice(i, 1, newPath);
|
||||
},
|
||||
}))
|
||||
),
|
||||
...Object.entries(manifest.icons || {}).map(([iconSize, path]) => ({
|
||||
type: ManifestEntrypointKind.ICON,
|
||||
path,
|
||||
replacePath (newPath: string) {
|
||||
this.path = newPath;
|
||||
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: string) {
|
||||
this.path = 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: string) {
|
||||
this.path = newPath;
|
||||
entry.resources.splice(i, 1, newPath);
|
||||
},
|
||||
}))
|
||||
)),
|
||||
];
|
||||
136
src/index.ts
Normal file
136
src/index.ts
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/* eslint-env node */
|
||||
|
||||
import {readFile} from 'node:fs/promises';
|
||||
import {basename, dirname, extname, join, relative, resolve} from 'node:path';
|
||||
|
||||
import {type Plugin, type RollupOptions} from 'rollup';
|
||||
|
||||
import {getAssetEntrypoints, getScriptEntrypoints} from './entrypoints';
|
||||
|
||||
/**
|
||||
* Looks through a `manifest.json` file for Javascript entrypoints and other
|
||||
* assets, and returns an array of Rollup configuration. This configuration will
|
||||
* have each entrypoint processed as its own bundle, and will additionally
|
||||
* handle copying the manifest and other assets into the build output, as well
|
||||
* as any necessary path rewriting in the manifest itself.
|
||||
* @param options
|
||||
* @param options.manifest Path to the manifest file to be processed, relative
|
||||
* to the current working directory
|
||||
* @param options.outDir Directory path where build output will go
|
||||
* @param options.plugins Rollup plugins to be used when processing each
|
||||
* Javascript entry point
|
||||
* @param options.sourcemap Controls inclusion of sourcemaps in the output
|
||||
*/
|
||||
export async function buildConfig ({
|
||||
manifest: manifestPathRelative,
|
||||
outDir,
|
||||
plugins = [],
|
||||
sourcemap,
|
||||
}: {
|
||||
manifest: string;
|
||||
outDir: string;
|
||||
plugins: Plugin[];
|
||||
sourcemap: boolean | 'inline' | 'hidden';
|
||||
}): Promise<RollupOptions[]> {
|
||||
const manifestPath = resolve(process.cwd(), manifestPathRelative);
|
||||
const manifestDirname = dirname(manifestPath);
|
||||
|
||||
// Load the manifest
|
||||
let manifestContent: chrome.runtime.Manifest;
|
||||
try {
|
||||
manifestContent = JSON.parse(await readFile(manifestPath, {encoding: 'utf-8'}));
|
||||
} catch (error) {
|
||||
throw new Error('Failed to load manifest');
|
||||
}
|
||||
|
||||
// reserve manifest.json at the root of the build output - needs to be the
|
||||
// actual manifest file we generate
|
||||
const uniqueFileNameSegmentCache = new Map([['\0', 'manifest.json']]);
|
||||
function ensureUniquePath (filepath: string, ext = extname(filepath).slice(1)) {
|
||||
// TODO: this function is hell there's gotta be a better way to do literally
|
||||
// all of this
|
||||
const cached = uniqueFileNameSegmentCache.get(filepath);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const idealName = join(dirname(filepath), basename(filepath, extname(filepath)));
|
||||
|
||||
const buildName = (str: string, n: number) => 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;
|
||||
}
|
||||
|
||||
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 = ensureUniquePath(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' as const,
|
||||
sourcemap,
|
||||
},
|
||||
plugins,
|
||||
};
|
||||
}),
|
||||
|
||||
// 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
|
||||
async buildStart () {
|
||||
await Promise.all(assets.map(async ({path, replacePath}) => {
|
||||
// Figure out where the asset will live in output
|
||||
const outPath = ensureUniquePath(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: await readFile(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,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue