diff --git a/.gitignore b/.gitignore
index 9f717d8..3c737af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
-/node_modules
+node_modules
/dist
/dist-docs
.DS_Store
-/docs/.vitepress/cache
\ No newline at end of file
+/docs/.vitepress/cache
+
+packages/mp3-encoder/dist
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..be441af
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+ "files.associations": {
+ "lame.h": "c"
+ }
+}
\ No newline at end of file
diff --git a/api-extractor.json b/api-extractor.json
index 4bd5a02..662a706 100644
--- a/api-extractor.json
+++ b/api-extractor.json
@@ -1,6 +1,6 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
- "mainEntryPointFilePath": "dist/modules/index.d.ts",
+ "mainEntryPointFilePath": "dist/modules/src/index.d.ts",
"bundledPackages": [],
"compiler": {},
"apiReport": {
diff --git a/build.mjs b/build.mjs
deleted file mode 100644
index 4ac1cb5..0000000
--- a/build.mjs
+++ /dev/null
@@ -1,76 +0,0 @@
-import * as esbuild from 'esbuild';
-import process from 'node:process';
-
-const baseConfig = {
- entryPoints: ['src/index.ts'],
- 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 = {
- ...baseConfig,
- format: 'iife',
-
- // The following are hacks to basically make this an UMD module. No native support for that in esbuild as of today
- globalName: 'Mediabunny',
-
- footer: {
- js:
-`if (typeof module === "object" && typeof module.exports === "object") Object.assign(module.exports, Mediabunny)`,
- },
-};
-
-const esmConfig = {
- ...baseConfig,
- format: 'esm',
-};
-
-const ctxUmd = await esbuild.context({
- ...umdConfig,
- outfile: 'dist/bundles/mediabunny.cjs',
-});
-const ctxEsm = await esbuild.context({
- ...esmConfig,
- outfile: 'dist/bundles/mediabunny.mjs',
-});
-const ctxUmdMinified = await esbuild.context({
- ...umdConfig,
- outfile: 'dist/bundles/mediabunny.min.cjs',
- minify: true,
-});
-const ctxEsmMinified = await esbuild.context({
- ...esmConfig,
- outfile: 'dist/bundles/mediabunny.min.mjs',
- minify: true,
-});
-
-if (process.argv[2] === '--watch') {
- await Promise.all([
- ctxUmd.watch(),
- ctxEsm.watch(),
- ctxUmdMinified.watch(),
- ctxEsmMinified.watch(),
- ]);
-} else {
- ctxUmd.rebuild();
- ctxEsm.rebuild();
- ctxUmdMinified.rebuild();
- ctxEsmMinified.rebuild();
-
- await Promise.all([
- ctxUmd.dispose(),
- ctxEsm.dispose(),
- ctxUmdMinified.dispose(),
- ctxEsmMinified.dispose(),
- ]);
-}
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..7084915
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+set -e
+
+# This script must be executed via `npm run build`
+
+# Clear the stuff from last build
+rm -rf dist
+rm -rf packages/mp3-encoder/dist
+
+# Ensure license headers on all source files
+tsx scripts/ensure-license-headers.ts
+
+# Type check & generate .js and .d.ts files
+tsc -p src
+tsc -p packages/mp3-encoder/src
+
+# So that the resulting files use valid ESM imports with file extension. This only runs for the core Mediabunny as only
+# it ships the individual files to npm (for tree shaking, because it's large)
+npm run fix-build-import-paths
+
+# Creates bundles for all packages
+tsx scripts/bundle.ts
+
+# Declaration file rollup and checks
+api-extractor run
+api-extractor run -c packages/mp3-encoder/api-extractor.json
+
+# Checks that all symbols are documented
+tsx scripts/check-docblocks.ts dist/mediabunny.d.ts
+tsx scripts/check-docblocks.ts packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
+
+# Appends stuff to the declaration files to register the global variables these libraries expose
+echo 'export as namespace Mediabunny;' >> dist/mediabunny.d.ts
+echo 'export as namespace MediabunnyMp3Encoder;' >> packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
\ No newline at end of file
diff --git a/dev/convert.html b/dev/convert.html
index 38b9d23..790d8e5 100644
--- a/dev/convert.html
+++ b/dev/convert.html
@@ -1,8 +1,11 @@
+