rollup-build-webext-config/index.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

137 lines
4.2 KiB
TypeScript
Raw Normal View History

2023-07-28 04:26:25 -04:00
/* eslint-env node */
2023-08-14 14:33:14 -04:00
import {readFile} from 'node:fs/promises';
2023-07-28 04:26:25 -04:00
import {basename, dirname, extname, join, relative, resolve} from 'node:path';
2023-07-28 07:48:01 -04:00
import {type Plugin, type RollupOptions} from 'rollup';
2023-07-28 04:26:25 -04:00
2023-07-28 08:08:07 -04:00
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
*/
2023-08-14 14:33:14 -04:00
export async function buildConfig ({
2023-07-28 04:49:34 -04:00
manifest: manifestPathRelative,
2023-07-28 07:05:49 -04:00
outDir,
2023-07-28 08:00:39 -04:00
plugins = [],
2023-07-28 04:49:34 -04:00
sourcemap,
2023-07-28 07:48:01 -04:00
}: {
manifest: string;
outDir: string;
2023-07-28 08:00:39 -04:00
plugins: Plugin[];
2023-07-28 07:48:01 -04:00
sourcemap: boolean | 'inline' | 'hidden';
2023-08-14 14:33:14 -04:00
}): Promise<RollupOptions[]> {
2023-07-28 04:26:25 -04:00
const manifestPath = resolve(process.cwd(), manifestPathRelative);
const manifestDirname = dirname(manifestPath);
// Load the manifest
2023-07-28 07:48:01 -04:00
let manifestContent: chrome.runtime.Manifest;
2023-07-28 04:26:25 -04:00
try {
2023-08-14 14:33:14 -04:00
manifestContent = JSON.parse(await readFile(manifestPath, {encoding: 'utf-8'}));
2023-07-28 04:26:25 -04:00
} catch (error) {
throw new Error('Failed to load manifest');
}
2023-07-28 07:59:40 -04:00
// 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)) {
2023-07-28 08:29:53 -04:00
// TODO: this function is hell there's gotta be a better way to do literally
// all of this
2023-07-28 04:26:25 -04:00
const cached = uniqueFileNameSegmentCache.get(filepath);
if (cached) {
return cached;
}
2023-07-28 07:48:01 -04:00
const idealName = join(dirname(filepath), basename(filepath, extname(filepath)));
2023-07-28 04:26:25 -04:00
2023-07-28 07:48:01 -04:00
const buildName = (str: string, n: number) => n ? `${str}_${n}.${ext}` : `${str}.${ext}`;
2023-07-28 04:26:25 -04:00
let uniquenessNum = 0;
const existingNames = [...uniqueFileNameSegmentCache.values()];
while (
existingNames.some(existingName =>
existingName.toLowerCase() === buildName(idealName, uniquenessNum).toLowerCase()
2023-08-14 14:07:31 -04:00
)
2023-07-28 04:26:25 -04:00
) {
uniquenessNum += 1;
}
const finalName = buildName(idealName, uniquenessNum);
uniqueFileNameSegmentCache.set(filepath, finalName);
return finalName;
}
const scripts = getScriptEntrypoints(manifestContent);
const assets = getAssetEntrypoints(manifestContent);
2023-07-28 04:26:25 -04:00
return [
// Process each script entrypoint independently
...scripts.map(({path, replacePath}) => {
// Figure out where this bundle will live in the output
2023-07-28 07:59:40 -04:00
const outPath = ensureUniquePath(path, 'js');
2023-07-28 04:26:25 -04:00
// Rewrite the manifest with that path
replacePath(outPath);
// Build the bundle
return {
2023-07-28 07:48:01 -04:00
input: relative(process.cwd(), join(manifestDirname, path)),
output: {
file: join(outDir, outPath),
2023-07-28 07:48:01 -04:00
format: 'iife' as const,
sourcemap,
},
2023-07-28 08:00:39 -04:00
plugins,
};
}),
// Special step that processes the manifest and injects other assets
2023-07-28 04:26:25 -04:00
{
input: manifestPathRelative,
output: {
file: join(outDir, 'manifest.json'),
2023-07-28 04:26:25 -04:00
},
plugins: [
{
2023-07-28 07:48:01 -04:00
name: '_manifest-asset-processing',
2023-07-28 05:28:06 -04:00
// emit other assets
2023-08-14 14:33:14 -04:00
async buildStart () {
await Promise.all(assets.map(async ({path, replacePath}) => {
// Figure out where the asset will live in output
2023-07-28 07:59:40 -04:00
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',
2023-07-28 07:09:39 -04:00
fileName: outPath,
2023-08-14 14:33:14 -04:00
source: await readFile(join(manifestDirname, path)),
});
2023-08-14 14:33:14 -04:00
}));
2023-07-28 04:26:25 -04:00
},
2023-07-28 05:28:06 -04:00
// 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,
2023-07-28 04:26:25 -04:00
},
],
},
];
2023-07-28 04:26:25 -04:00
}