Create @mediabunny/mp3-encoder, restructure and extend build system

This commit is contained in:
Vanilagy
2025-08-10 00:45:30 +02:00
parent 7a2a23bcb0
commit cbb2681d33
38 changed files with 2913 additions and 112 deletions
+125
View File
@@ -0,0 +1,125 @@
import * as esbuild from 'esbuild';
import process from 'node:process';
import PluginExternalGlobal from 'esbuild-plugin-external-global';
import { inlineWorkerPlugin } from './esbuild/inlined-workers.js';
/** Creates UMD and ESM variants, each unminified and minified. */
const createVariants = async (
entryPoint: string,
globalName: string,
outfileBase: string,
umdExtension: string,
specificUmdConfig: esbuild.BuildOptions = {},
specificEsmConfig: esbuild.BuildOptions = {},
) => {
const baseConfig: esbuild.BuildOptions = {
entryPoints: [entryPoint],
bundle: true,
logLevel: 'info',
banner: {
js: `/*!
* Copyright (c) 2025-present, Vanilagy and contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/`,
},
legalComments: 'none',
};
const umdConfig: esbuild.BuildOptions = {
...baseConfig,
format: 'iife',
globalName,
footer: {
js:
`if (typeof module === "object" && typeof module.exports === "object") Object.assign(module.exports, ${globalName})`,
},
};
const esmConfig: esbuild.BuildOptions = {
...baseConfig,
format: 'esm',
};
const umdVariant = await esbuild.context({
...umdConfig,
...specificUmdConfig,
outfile: `${outfileBase}.${umdExtension}`,
});
const esmVariant = await esbuild.context({
...esmConfig,
...specificEsmConfig,
outfile: `${outfileBase}.mjs`,
});
const umdMinifiedVariant = await esbuild.context({
...umdConfig,
...specificUmdConfig,
outfile: `${outfileBase}.min.${umdExtension}`,
minify: true,
});
const esmMinifiedVariant = await esbuild.context({
...esmConfig,
...specificEsmConfig,
outfile: `${outfileBase}.min.mjs`,
minify: true,
});
return [umdVariant, esmVariant, umdMinifiedVariant, esmMinifiedVariant];
};
const mediabunnyVariants = await createVariants(
'src/index.ts',
'Mediabunny',
'dist/bundles/mediabunny',
'cjs',
);
const mp3EncoderVariants = await createVariants(
'packages/mp3-encoder/src/index.ts',
'MediabunnyMp3Encoder',
'packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder',
'js', // The bundles are purely for the browser, not for Node (due to the peer dependecy)
{
plugins: [
PluginExternalGlobal.externalGlobalPlugin({
mediabunny: 'Mediabunny',
}),
inlineWorkerPlugin({
define: {
'import.meta.url': '""',
},
legalComments: 'none',
}),
],
},
{
external: ['mediabunny'],
plugins: [
inlineWorkerPlugin({
define: {
'import.meta.url': '""',
},
legalComments: 'none',
}),
],
},
);
const contexts = [
...mediabunnyVariants,
...mp3EncoderVariants,
];
if (process.argv[2] === '--watch') {
await Promise.all(contexts.map(ctx => ctx.watch()));
} else {
for (const ctx of contexts) {
await ctx.rebuild();
await ctx.dispose();
}
}
+4 -1
View File
@@ -23,17 +23,20 @@ const checkFile = (filePath: string) => {
const checkDirectory = (dirPath: string) => {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
checkDirectory(fullPath);
} else if (entry.name.endsWith('.ts')) {
} else if (entry.name.endsWith('.ts') || entry.name.endsWith('.c')) {
checkFile(fullPath);
}
}
};
checkDirectory(path.join(__dirname, '..', 'src'));
checkDirectory(path.join(__dirname, '..', 'packages', 'mp3-encoder', 'src'));
checkDirectory(path.join(__dirname, '..', 'shared'));
if (missingFiles.length > 0) {
console.error('Files missing license header:');
+98
View File
@@ -0,0 +1,98 @@
// Adapted from https://github.com/mitschabaude/esbuild-plugin-inline-worker
import * as esbuild from 'esbuild';
import findCacheDir from 'find-cache-dir';
import fs from 'node:fs';
import path from 'node:path';
export function inlineWorkerPlugin(extraConfig: esbuild.BuildOptions): esbuild.Plugin {
return {
name: 'esbuild-plugin-inline-worker',
setup(build) {
build.onLoad(
{ filter: /\.worker\.(js|jsx|ts|tsx)$/ },
async ({ path: workerPath }) => {
const workerCode = await buildWorker(workerPath, extraConfig);
return {
contents: `import inlineWorker from '__inline-worker'
export default function Worker() {
return inlineWorker(${JSON.stringify(workerCode)});
}
`,
loader: 'js',
};
},
);
const inlineWorkerFunctionCode = `
export default async function inlineWorker(scriptText) {
if (typeof Worker !== 'undefined' && typeof Bun === 'undefined') {
// Browser, Deno
const blob = new Blob([scriptText], { type: "text/javascript" });
const url = URL.createObjectURL(blob);
const worker = new Worker(url, { type: typeof Deno !== 'undefined' ? 'module' : undefined }); // module for Deno
URL.revokeObjectURL(url);
return worker;
} else {
// Node, Bun (Bun's Worker is flaky, worker_threads works much better)
let Worker;
try {
Worker = (await import('worker_threads')).Worker;
} catch {
const workerModule = 'worker_threads';
Worker = require(workerModule).Worker;
}
const worker = new Worker(scriptText, { eval: true });
return worker;
}
}
`;
build.onResolve({ filter: /^__inline-worker$/ }, ({ path }) => {
return { path, namespace: 'inline-worker' };
});
build.onLoad({ filter: /.*/, namespace: 'inline-worker' }, () => {
return { contents: inlineWorkerFunctionCode, loader: 'js' };
});
},
};
}
const cacheDir = findCacheDir({
name: 'esbuild-plugin-inline-worker',
create: true,
});
if (cacheDir === undefined) {
throw new Error('Cache directory not found.');
}
async function buildWorker(workerPath: string, extraConfig: esbuild.BuildOptions) {
const scriptNameParts = path.basename(workerPath).split('.');
scriptNameParts.pop();
scriptNameParts.push('js');
const scriptName = scriptNameParts.join('.');
const bundlePath = path.resolve(cacheDir!, scriptName);
if (extraConfig) {
delete extraConfig.entryPoints;
delete extraConfig.outfile;
delete extraConfig.outdir;
}
await esbuild.build({
entryPoints: [workerPath],
bundle: true,
minify: true,
outfile: bundlePath,
target: 'es2017',
format: 'esm',
...extraConfig,
});
return fs.promises.readFile(bundlePath, { encoding: 'utf-8' });
}