Skip to main content

Overview

yt-dlp-ejs is designed to work seamlessly across three major JavaScript runtimes. The library maintains feature parity and output consistency across all supported platforms.

Deno

First-class support with npm: specifiers

Bun

Native speed with optimized builds

Node.js

Traditional npm ecosystem support

Runtime Comparison

FeatureDenoBunNode.js
Lock Filedeno.lock (v4)bun.lockpackage-lock.json
Install Commanddeno install --frozenbun install --frozen-lockfilenpm ci
Test Commanddeno testbun testnode --test
TypeScriptNative supportNative support--experimental-strip-types (22.6+)
Build FormatES Module with npm: importsES Module with versioned externalsIIFE with bundled deps
Import Stylenpm:[email protected][email protected]Bundled in IIFE

Deno Support

Installation

deno install --frozen
The --frozen flag ensures reproducible builds by using the exact versions specified in deno.lock.

Running Tests

First, download the player JavaScript test files:
deno run src/yt/solver/test/download.ts
Then run the test suite:
deno test

Building

deno task bundle

Deno-Specific Build Configuration

The Deno build uses dynamic imports with npm: specifiers:
rollup.config.js:202-225
{
  input: "src/yt/solver/dynamic.lib.ts",
  output: {
    name: "lib",
    file: "dist/yt.solver.deno.lib.js",
    format: "es",
  },
  plugins: [
    dynamicImportRewrite(),
    license({
      banner: {
        commentStyle: "ignored",
        content: LICENSE_BANNER,
      },
    }),
    // Use terser to remove comments but do not minify
    terser({
      compress: false,
      mangle: false,
    }),
    prettifyOutput(),
    printHash(),
  ],
}

Dynamic Import Rewrite Plugin

The dynamicImportRewrite plugin transforms package imports for Deno:
rollup.config.js:47-71
function dynamicImportRewrite({ format = "deno" } = {}) {
  return {
    name: "dynamic-import-rewrite-plugin",
    resolveId(source) {
      if (pkg.dependencies[source]) {
        if (format === "deno") {
          return {
            id: `npm:${source}@${pkg.dependencies[source]}`,
            external: true,
          };
        } else if (format === "bun") {
          return {
            id: `${source}@${pkg.dependencies[source]}`,
            external: true,
          };
        }
        return null;
      }
      return null;
    },
    renderDynamicImport() {
      return null;
    },
  };
}
Deno imports use the npm: specifier format: npm:[email protected]

Deno Library Interface

src/yt/solver/dynamic.lib.ts
// Used for generating deno/bun auto-imports
export const lib = {
  meriyah: await import("meriyah"),
  astring: await import("astring"),
};

TypeScript Support

Deno has native TypeScript support - no transpilation configuration needed:
# Run TypeScript directly
deno run src/yt/solver/main.ts

# Test TypeScript files
deno test

Environment Variables

When building with Deno, the build hook sets:
hatch_build.py:44-48
elif deno := shutil.which("deno"):
    name = "deno"
    env["DENO_NO_UPDATE_CHECK"] = "1"
    install = [deno, "install", "--frozen"]
    bundle = [deno, "task", "bundle"]
Always set DENO_NO_UPDATE_CHECK=1 in CI environments to prevent automatic update checks.

Bun Support

Installation

bun install --frozen-lockfile

Running Tests

First, download the player JavaScript test files:
bun --bun run src/yt/solver/test/download.ts
Then run the test suite:
bun test
The --bun flag uses Bun’s native runtime instead of Node.js compatibility mode for maximum performance.

Building

bun --bun run bundle

Bun-Specific Build Configuration

The Bun build uses versioned external imports:
rollup.config.js:226-249
{
  input: "src/yt/solver/dynamic.lib.ts",
  output: {
    name: "lib",
    file: "dist/yt.solver.bun.lib.js",
    format: "es",
  },
  plugins: [
    dynamicImportRewrite({ format: "bun" }),
    license({
      banner: {
        commentStyle: "ignored",
        content: LICENSE_BANNER,
      },
    }),
    // Use terser to remove comments but do not minify
    terser({
      compress: false,
      mangle: false,
    }),
    prettifyOutput(),
    printHash(),
  ],
}
Bun imports use versioned specifiers without the npm: prefix: [email protected]

Build Hook Configuration

hatch_build.py:50-53
elif bun := shutil.which("bun"):
    name = "bun"
    install = [bun, "install", "--frozen-lockfile"]
    bundle = [bun, "--bun", "run", "bundle"]

Lock File Migration

When upgrading packages, Bun’s lock file is generated from npm’s:
# Generate base package-lock.json
rm -rf node_modules
npm install

# Migrate to Bun
bun pm migrate --force

Node.js Support

Requirements

Node.js 22.6+

Required for --experimental-strip-types support

npm

Package manager for dependency installation

Installation

npm ci
npm ci (clean install) ensures reproducible builds by strictly following package-lock.json.

Running Tests

First, download the player JavaScript test files:
node --experimental-strip-types src/yt/solver/test/download.ts
Then run the test suite:
node --test

Building

npm run bundle

TypeScript Support

Node.js 22.6+ supports TypeScript through the experimental strip-types feature:
node --experimental-strip-types src/yt/solver/main.ts
The --experimental-strip-types flag is experimental. For production use, consider using a transpiler like Sucrase (used in the build process).

Build Configuration

Node.js builds use IIFE format with bundled dependencies:
rollup.config.js:82-116
{
  input: "src/yt/solver/main.ts",
  output: {
    name: "jsc",
    globals: {
      astring: "astring",
      input: "input",
      meriyah: "meriyah",
    },
    file: "dist/yt.solver.core.js",
    format: "iife",
  },
  external: ["astring", "meriyah"],
  plugins: [
    nodeResolve(),
    sucrase({
      exclude: ["node_modules/**"],
      transforms: ["typescript"],
    }),
    license({
      banner: {
        commentStyle: "ignored",
        content: LICENSE_BANNER,
      },
    }),
    // Use terser to remove comments but do not minify
    terser({
      compress: false,
      mangle: false,
    }),
    prettifyOutput(),
    printHash(),
  ],
}

Build Hook Configuration

hatch_build.py:55-58
elif npm := shutil.which("npm"):
    name = "npm (node)"
    install = [npm, "ci"]
    bundle = [npm, "run", "bundle"]

Build System

Automatic Runtime Detection

The build system automatically detects and uses available runtimes:
hatch_build.py:36-63
def build_bundle_cmds():
    env = os.environ.copy()

    if pnpm := shutil.which("pnpm"):
        name = "pnpm"
        install = [pnpm, "install", "--frozen-lockfile"]
        bundle = [pnpm, "run", "bundle"]

    elif deno := shutil.which("deno"):
        name = "deno"
        env["DENO_NO_UPDATE_CHECK"] = "1"
        install = [deno, "install", "--frozen"]
        bundle = [deno, "task", "bundle"]

    elif bun := shutil.which("bun"):
        name = "bun"
        install = [bun, "install", "--frozen-lockfile"]
        bundle = [bun, "--bun", "run", "bundle"]

    elif npm := shutil.which("npm"):
        name = "npm (node)"
        install = [npm, "ci"]
        bundle = [npm, "run", "bundle"]

    else:
        return None, None, None

    return name, [install, bundle], env
1

Detection Priority

The build system checks for runtimes in this order:
  1. pnpm
  2. Deno
  3. Bun
  4. npm (Node.js)
2

Install Dependencies

Execute the appropriate install command with frozen lock files
3

Bundle JavaScript

Run the bundle script to generate distribution files
4

Include in Package

Add minified bundles to the Python package
build_data["force_include"].update(
    {
        "dist/yt.solver.core.min.js": "yt_dlp_ejs/yt/solver/core.min.js",
        "dist/yt.solver.lib.min.js": "yt_dlp_ejs/yt/solver/lib.min.js",
    }
)

Manual Build Script

For development without a Python builder:
python hatch_build.py
This script:
  • Detects available JavaScript runtime
  • Installs dependencies
  • Runs the bundle process
  • Outputs build artifacts to dist/
hatch_build.py:66-80
if __name__ == "__main__":
    import sys

    name, cmds, env = build_bundle_cmds()
    if cmds is None:
        print("ERROR: No suitable JavaScript runtime found", file=sys.stderr)
        sys.exit(128)
    print(f"Bundling using {name}...", file=sys.stderr)

    try:
        for cmd in cmds:
            subprocess.check_call(cmd, env=env)
    except subprocess.CalledProcessError as error:
        sys.exit(error.returncode)

Lock Files

Synchronization Requirement

All lock files must be kept in sync across runtimes:
deno install --lockfile-only
Use Deno version 2.3 or earlier for lockfile v4 compatibility.

Upgrade Workflow

When upgrading packages, follow this sequence:
# 1. Upgrade packages automatically (or manually adjust versions)
pnpm upgrade --latest

# 2. Generate base package-lock.json
rm -rf node_modules
npm install

# 3. Migrate to other package managers
pnpm import
bun pm migrate --force

# 4. Generate Deno lock (use deno <2.3 for lockfile v4)
deno install --lockfile-only

# 5. Verify consistency
python check.py
The check.py script verifies that all lock files reference the same dependency versions.

Build Output Consistency

All runtimes must produce identical build outputs:

Hash Verification

The build process computes SHA3-512 hashes of outputs:
rollup.config.js:27-45
function printHash() {
  return {
    name: "hash-output-plugin",
    writeBundle(_options, bundle) {
      for (const [fileName, assetInfo] of Object.entries(bundle)) {
        if (assetInfo.code) {
          try {
            const digest = createHash("sha3-512")
              .update(assetInfo.code)
              .digest("hex");
            console.log(`SHA3-512 for ${assetInfo.fileName}: ${digest}`);
          } catch (err) {
            console.error(`Error hashing ${fileName}:`, err.message);
          }
        }
      }
    },
  };
}
If you notice differences between runtimes’ builds, please open an issue.

Shared Dependencies

Both runtimes and all build configurations share the same core dependencies:
package.json
{
  "dependencies": {
    "astring": "1.9.0",
    "meriyah": "6.1.4"
  }
}

Dependency Loading Strategies

import { parse } from "npm:[email protected]";
import { generate } from "npm:[email protected]";

Testing Across Runtimes

The test suite is designed to run identically across all runtimes:
# Download test fixtures (only needed once)
deno run src/yt/solver/test/download.ts
# or
bun --bun run src/yt/solver/test/download.ts
# or
node --experimental-strip-types src/yt/solver/test/download.ts

# Run tests
deno test        # Deno
bun test         # Bun
node --test      # Node.js

Same Tests

Identical test files run on all platforms

Same Fixtures

Shared player JavaScript test data

Same Results

All runtimes must pass the same assertions

Platform-Specific Optimizations

While maintaining identical outputs, each runtime has optimizations:
  • Native TypeScript execution (no transpilation)
  • Built-in security sandbox
  • Optimized npm: specifier resolution

Choosing a Runtime

1

For Development

Use whichever runtime you have installed. All provide excellent development experiences.
2

For CI/CD

Consider Bun for fastest build times, or Node.js for maximum compatibility.
3

For yt-dlp Integration

The runtime is auto-detected during installation. End users don’t need to choose.
The beauty of yt-dlp-ejs is that you never need to worry about runtime differences - the library ensures identical behavior everywhere.

Build docs developers (and LLMs) love