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
Feature Deno Bun Node.js Lock File deno.lock (v4)bun.lockpackage-lock.jsonInstall Command deno install --frozenbun install --frozen-lockfilenpm ciTest Command deno testbun testnode --testTypeScript Native support Native support --experimental-strip-types (22.6+)Build Format ES Module with npm: imports ES Module with versioned externals IIFE with bundled deps Import Style npm:[email protected] [email protected] Bundled in IIFE
Deno Support
Installation
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:
Building
Deno-Specific Build Configuration
The Deno build uses dynamic imports with npm: specifiers:
{
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:
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 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:
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:
The --bun flag uses Bun’s native runtime instead of Node.js compatibility mode for maximum performance.
Building
Bun-Specific Build Configuration
The Bun build uses versioned external imports:
{
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 (),
],
}
Build Hook Configuration
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 (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:
Building
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:
{
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
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:
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
Detection Priority
The build system checks for runtimes in this order:
pnpm
Deno
Bun
npm (Node.js)
Install Dependencies
Execute the appropriate install command with frozen lock files
Bundle JavaScript
Run the bundle script to generate distribution files
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:
This script:
Detects available JavaScript runtime
Installs dependencies
Runs the bundle process
Outputs build artifacts to dist/
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.
Migrates from package-lock.json to bun.lock. rm -rf node_modules
npm install
Generates the base package-lock.json. Imports from package-lock.json to pnpm-lock.yaml.
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:
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:
{
"dependencies" : {
"astring" : "1.9.0" ,
"meriyah" : "6.1.4"
}
}
Dependency Loading Strategies
Deno (ES Module)
Bun (ES Module)
Node.js (Bundled IIFE)
// Dependencies are bundled into the IIFE
var jsc = ( function ( astring , meriyah ) {
// ... solver code ...
})( astring , meriyah );
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
While maintaining identical outputs, each runtime has optimizations:
Native TypeScript execution (no transpilation)
Built-in security sandbox
Optimized npm: specifier resolution
Extremely fast startup time
Native transpilation of TypeScript
Optimized package installation
Widest ecosystem compatibility
Mature tooling support
Traditional npm package resolution
Choosing a Runtime
For Development
Use whichever runtime you have installed. All provide excellent development experiences.
For CI/CD
Consider Bun for fastest build times, or Node.js for maximum compatibility.
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.