Skip to content

Commit

Permalink
MAJOR OVERHAUL part 4: ES6 modules
Browse files Browse the repository at this point in the history
Setting up the loading of ES6 modules to be much more consistent,
hopefully with bundlers working more predictably too.
  • Loading branch information
Yahweasel committed Feb 22, 2024
1 parent 03c67a8 commit 55c3514
Show file tree
Hide file tree
Showing 22 changed files with 1,102 additions and 285 deletions.
413 changes: 362 additions & 51 deletions Makefile

Large diffs are not rendered by default.

105 changes: 76 additions & 29 deletions Makefile.m4
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ EMCC=emcc
MINIFIER=node_modules/.bin/uglifyjs -m
OPTFLAGS=-Oz
THRFLAGS=-pthread
ES6FLAGS=-sEXPORT_ES6=1 -sUSE_ES6_IMPORT_META=1
EFLAGS=\
--memory-init-file 0 \
--pre-js pre.js \
Expand All @@ -40,32 +41,57 @@ all: build-default
include mk/*.mk


build-%: dist/libav-$(LIBAVJS_VERSION)-%.js
true

dist/libav-$(LIBAVJS_VERSION)-%.js: build/libav-$(LIBAVJS_VERSION).js \
build-%: \
dist/libav-$(LIBAVJS_VERSION)-%.js \
dist/libav-%.js \
dist/libav-$(LIBAVJS_VERSION)-%.mjs \
dist/libav-%.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.js \
dist/libav-%.dbg.js \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.mjs \
dist/libav-%.dbg.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.asm.js \
dist/libav-$(LIBAVJS_VERSION)-%.asm.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.asm.js \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.asm.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.wasm.js \
dist/libav-$(LIBAVJS_VERSION)-%.wasm.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.wasm.js \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.wasm.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.thr.js \
dist/libav-$(LIBAVJS_VERSION)-%.thr.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.thr.js \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.thr.mjs \
dist/libav.types.d.ts
true

# Generic rule for frontend builds
# Use: febuildrule(debug infix, target extension, minifier)
define([[[febuildrule]]], [[[
dist/libav-$(LIBAVJS_VERSION)-%$1.$2: build/libav-$(LIBAVJS_VERSION).$2 \
dist/libav-$(LIBAVJS_VERSION)-%$1.wasm.$2 \
node_modules/.bin/uglifyjs
mkdir -p dist
sed "s/@CONFIG/$*/g ; s/@DBG//g" < $< | $(MINIFIER) > $@
-chmod a-x dist/*.wasm
sed "s/@CONFIG/$(*)/g ; s/@DBG/$1/g" < $< | $3 > $(@)
dist/libav-$(LIBAVJS_VERSION)-%.dbg.js: build/libav-$(LIBAVJS_VERSION).js
mkdir -p dist
sed "s/@CONFIG/$*/g ; s/@DBG/.dbg/g" < $< > $@
dist/libav-%$1.$2: dist/libav-$(LIBAVJS_VERSION)-%$1.$2
cp $(<) $(@)
]]])

febuildrule([[[]]], js, [[[$(MINIFIER)]]])
febuildrule([[[]]], mjs, [[[$(MINIFIER)]]])
febuildrule(.dbg, js, cat)
febuildrule(.dbg, mjs, cat)

dist/libav.types.d.ts: build/libav.types.d.ts
cp $< $@

# General build rule for any target
# Use: buildrule(target file name, debug infix, target inst name, CFLAGS)
# Use: buildrule(target file name, debug infix, target inst name, CFLAGS, target file suffix)
define([[[buildrule]]], [[[
dist/libav-$(LIBAVJS_VERSION)-%.$2$1.js: build/ffmpeg-$(FFMPEG_VERSION)/build-$3-%/libavformat/libavformat.a \
dist/libav-$(LIBAVJS_VERSION)-%.$2$1.$5: build/ffmpeg-$(FFMPEG_VERSION)/build-$3-%/libavformat/libavformat.a \
build/exports.json pre.js build/post.js extern-post.js bindings.c
mkdir -p dist
mkdir -p $(@).d
$(EMCC) $(OPTFLAGS) $(EFLAGS) $4 \
-Ibuild/ffmpeg-$(FFMPEG_VERSION) -Ibuild/ffmpeg-$(FFMPEG_VERSION)/build-$3-$(*) \
`test ! -e configs/configs/$(*)/link-flags.txt || cat configs/configs/$(*)/link-flags.txt` \
Expand All @@ -87,32 +113,41 @@ dist/libav-$(LIBAVJS_VERSION)-%.$2$1.js: build/ffmpeg-$(FFMPEG_VERSION)/build-$3
build/ffmpeg-$(FFMPEG_VERSION)/build-$3-$(*)/libavdevice/libavdevice.a \
'` \
build/ffmpeg-$(FFMPEG_VERSION)/build-$3-$(*)/*/lib*.a \
`test ! -e configs/configs/$(*)/libs.txt || sed 's/@TARGET/$3/' configs/configs/$(*)/libs.txt` -o $(@)
sed 's/^\/\/.*include:.*// ; '"s/@VER/$(LIBAVJS_VERSION)/g ; s/@TARGET/$1/g ; s/@DBG/$2/g" $(@) | cat configs/configs/$(*)/license.js - > $(@).tmp
mv $(@).tmp $(@)
if [ -e dist/libav-$(LIBAVJS_VERSION)-$(*).$2$1.wasm.map ] ; then \
./tools/adjust-sourcemap.js dist/libav-$(LIBAVJS_VERSION)-$(*).$2$1.wasm.map \
`test ! -e configs/configs/$(*)/libs.txt || sed 's/@TARGET/$3/' configs/configs/$(*)/libs.txt` -o $(@).d/libav-$(LIBAVJS_VERSION)-$(*).$2$1.$5
if [ -e $(@).d/libav-$(LIBAVJS_VERSION)-$(*).$2$1.wasm.map ] ; then \
./tools/adjust-sourcemap.js $(@).d/libav-$(LIBAVJS_VERSION)-$(*).$2$1.wasm.map \
ffmpeg $(FFMPEG_VERSION) \
libvpx $(LIBVPX_VERSION) \
libaom $(LIBAOM_VERSION); \
fi || ( rm -f $(@) ; false )
sed 's/^\/\/.*include:.*// ; '"s/@VER/$(LIBAVJS_VERSION)/g ; s/@TARGET/$1/g ; s/@DBG/$2/g" $(@).d/libav-$(LIBAVJS_VERSION)-$(*).$2$1.$5 | cat configs/configs/$(*)/license.js - > $(@)
rm -f $(@).d/libav-$(LIBAVJS_VERSION)-$(*).$2$1.$5
-chmod a-x $(@).d/*.wasm
-mv $(@).d/* dist/
rmdir $(@).d
]]])

# asm.js version
buildrule(asm, [[[]]], base, [[[-s WASM=0]]])
buildrule(asm, dbg., base, [[[-g2 -s WASM=0]]])
buildrule(asm, [[[]]], base, [[[-s WASM=0]]], js)
buildrule(asm, [[[]]], base, [[[$(ES6FLAGS) -s WASM=0]]], mjs)
buildrule(asm, dbg., base, [[[-g2 -s WASM=0]]], js)
buildrule(asm, dbg., base, [[[-g2 $(ES6FLAGS) -s WASM=0]]], mjs)
# wasm version with no added features
buildrule(wasm, [[[]]], base, [[[]]])
buildrule(wasm, dbg., base, [[[-gsource-map]]])
buildrule(wasm, [[[]]], base, [[[]]], js)
buildrule(wasm, [[[]]], base, [[[$(ES6FLAGS)]]], mjs)
buildrule(wasm, dbg., base, [[[-gsource-map]]], js)
buildrule(wasm, dbg., base, [[[-gsource-map $(ES6FLAGS)]]], mjs)
# wasm + threads
buildrule(thr, [[[]]], thr, [[[$(THRFLAGS) -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency]]])
buildrule(thr, dbg., thr, [[[-gsource-map $(THRFLAGS) -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency]]])
buildrule(thr, [[[]]], thr, [[[$(THRFLAGS) -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency]]], js)
buildrule(thr, [[[]]], thr, [[[$(ES6FLAGS) $(THRFLAGS) -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency]]], mjs)
buildrule(thr, dbg., thr, [[[-gsource-map $(THRFLAGS) -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency]]], js)
buildrule(thr, dbg., thr, [[[-gsource-map $(ES6FLAGS) $(THRFLAGS) -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency]]], mjs)

build/libav-$(LIBAVJS_VERSION).js: libav.in.js post.in.js funcs.json apply-funcs.js
build/libav-$(LIBAVJS_VERSION).js: libav.in.js post.in.js funcs.json tools/apply-funcs.js
mkdir -p build dist
./apply-funcs.js $(LIBAVJS_VERSION)
./tools/apply-funcs.js $(LIBAVJS_VERSION)

build/exports.json build/post.js: build/libav-$(LIBAVJS_VERSION).js
build/libav.types.d.ts build/libav-$(LIBAVJS_VERSION).mjs build/exports.json build/post.js: build/libav-$(LIBAVJS_VERSION).js
touch $@

node_modules/.bin/uglifyjs:
Expand All @@ -129,7 +164,7 @@ build/inst/thr/cflags.txt:

RELEASE_VARIANTS=\
default default-cli opus opus-af flac flac-af wav wav-af obsolete webm \
webm-cli webm-vp9 webm-vp9-cli vp8-opus vp8-opus-avf, vp9-opus \
webm-cli webm-vp9 webm-vp9-cli vp8-opus vp8-opus-avf vp9-opus \
vp9-opus-avf av1-opus av1-opus-avf webcodecs webcodecs-avf

release: extract
Expand Down Expand Up @@ -202,13 +237,25 @@ print-version:
@printf '%s\n' "$(LIBAVJS_VERSION)"

.PRECIOUS: \
libav.js-$(LIBAVJS_VERSION)-%-release \
build/ffmpeg-$(FFMPEG_VERSION)/build-%/libavformat/libavformat.a \
dist/libav.types.d.ts \
dist/libav-$(LIBAVJS_VERSION)-%.js \
dist/libav-%.js \
dist/libav-$(LIBAVJS_VERSION)-%.mjs \
dist/libav-%.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.js \
dist/libav-%.dbg.js \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.mjs \
dist/libav-%.dbg.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.asm.js \
dist/libav-$(LIBAVJS_VERSION)-%.asm.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.asm.js \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.asm.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.wasm.js \
dist/libav-$(LIBAVJS_VERSION)-%.wasm.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.wasm.js \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.wasm.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.thr.js \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.thr.js
dist/libav-$(LIBAVJS_VERSION)-%.thr.mjs \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.thr.js \
dist/libav-$(LIBAVJS_VERSION)-%.dbg.thr.mjs
92 changes: 51 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ example of using libav.js from a CDN in the browser thread:
<!doctype html>
<html>
<body>
<script type="text/javascript">LibAV = {base: "https://cdn.jsdelivr.net/npm/@libav.js/variant-default@4.10.6/dist"};</script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@libav.js/variant-default@4.10.6/dist/libav-4.10.6.1.1-default.js"></script>
<script type="text/javascript">(async function() {
const libav = await LibAV.LibAV({noworker: true});
Expand Down Expand Up @@ -89,17 +88,19 @@ It's also possible to use libav.js from Node.js, though this isn't a good idea,
since you can presumably use a native version of FFmpeg's libraries. The Node
interface is only provided for internal testing.

Use `.dbg.js` instead of `.js` for a non-minified, more debuggable version.
Use `.dbg.js` instead of `.js` for a non-minified, more debuggable version. Use
`.mjs` for the ES6 module version. Use `.dbg.mjs` for both. You don't need any
combination; e.g., if you only intend to use imports, you do not need any `.js`
files.

libav.js exposes a global variable, LibAV, for all API access. If LibAV is set
before loading the library, libav.js does *not* replace it, but extends it.
This gives you an opportunity to pass in values critical for loading. In
particular, if the base directory (directory in which libav's files are
located) isn't ".", then you must set `LibAV.base` to the correct base
directory, as in the CDN example above. `LibAV.base` does not need to be a full
URL, but should be if loading from another origin. You can set `LibAV.base`
after loading libav.js; it's set up so that you can do it before to make it
easier to avoid race conditions.
libav.js exposes a global variable, `LibAV`, for all API access. If importing as a
module, `LibAV` is the default export.

For certain unusual loading situations, you can set the `LibAV` global variable
before importing. In particular, if the base directory (directory in which
libav's files are located) can't be detected for some reason, then you must set
`LibAV.base` to the correct base. `LibAV.base` does not need to be a full URL,
but should be if loading from another origin.

Bundlers have further concerns. To use libav.js with a bundler, see the section
on bundlers below.
Expand Down Expand Up @@ -221,6 +222,15 @@ libav.js is published to NPM as `libav.js`, and each released variant is
published in a much smaller NPM package as `@libav.js/variant-<variant>`. The
CDN example above uses the `@libav.js/variant-default` package, for example.

### Why the version number in the filenames?

Caching is Hell.

`libav-<variant>.*` is also available in the releases and repository, but you're
highly recommended *not* to use this name on any web installation, as caching
will cause strange nonsense to happen. Use a full version name to avoid caching
madness.


## API

Expand Down Expand Up @@ -461,33 +471,33 @@ connect it to WebCodecs:
Generally speaking, because libav.js needs to adjust its loading procedure based
on the environment it's being loaded in, it's not a good idea to bundle
libav.js. However, if you have to bundle it, it can be done if necessary.
Bundlers such as WebPack, esbuild, Vite, Rollup, etc., may change the names and
location of the LibAV's JavaScript and WebAssembly files or even turn them into
modules. In these cases, the location of the JavaScript and WebAssembly file of
a LibAV variant can be overridden by options set on the `LibAV` object after
loading libav.js, similar to `LibAV.base`. `LibAV.toImport` and `LibAV.wasmurl`
override the URL of the used JavaScript and WebAssembly file respectively. These
are usually located in the libav.js directory and follow the scheme
`libav-VER-CONFIGDBG.TARGET.js` and `libav-VER-CONFIGDBG.TARGET.wasm`,
respectively. The version (`VER`), variant (`CONFIG`) and debug (`DBG`) string
are exposed as `LibAV.VER`, `LibAV.CONFIG` and `LibAV.DBG` respectively after
loading LibAV. However, you can generally successfully load a different variant
or debuggability level, so these are provided to allow you to verify what your
bundler actually bundled. The target corresponds to the browser features
available, and can vary between different browsers or other environments. As
such, it should be determined at runtime, which can be done by calling
`LibAV.target()`. For instance, a possible way to retrieve the URL in a module
can be ``new
URL(`node_modules/libav.js/libav-${globalThis.LibAV.VER}-opus.${target}.wasm`,
import.meta.url).href``, but be sure to consult the documentation of your
bundler. Note the variant `opus` is hard-coded in this case to prevent the
bundler from including all variants.

Some bundlers turn LibAV code from a CommonJS module to an ECMAScript 6 module,
which will if loaded in a worker interfere with LibAV's loading code. In this
case, LibAV's JavaScript code needs to be imported manually before calling the
factory function of the LibAV instance: ``await
import(`../node_modules/libav.js/libav-${globalThis.LibAV.VER}-opus.${target}.js`)``.
Note that dynamically importing ECMAScript 6 modules is supported by all major
browsers, but at the time of this wriging, on Firefox, is protected by a flag
that most users will not have enabled.

libav.js has a frontend (`libav-<version>-<variant>.js`), a factory
(`libav-<version>-<variant>.wasm.js` or `.thr.js`), and, if using WebAssembly, a
backend (`libav-<version>-<variant>.wasm.wasm` or `.thr.wasm`). Any of these can
be overridden, and any of them can be object URLs to bundle everything, though
this will destroy libav.js's ability to load the correct version for the system.

To override the frontend, simply load a different frontend!

To override the factory, you have two choices:

* Pass `toImport`, a string, to `LibAV.LibAV`'s options, e.g.,
`LibAV.LibAV({toImport: "libav-but-better.wasm.js"})`.

* Load the factory yourself, and pass the factory function as the `factory`
option to `LibAV.LibAV`, e.g., `LibAV.LibAV({factory: LibAVFactory})`. By
default, the factory function is exported as `LibAVFactory`, or for ES6
modules, it is the default export of the module.

To override the backend, you can pass the full URL (or object URL) to the
WebAssembly as the option `wasmurl` to `LibAV.LibAV`, e.g.,
`LibAV.LibAV({wasmurl: URL.createObjectURL(...)})`.

Be careful about which versions of things you bundle. The ES6 module version of
libav.js assumes that it will actually *be* an ES6 module, and so will be able
to use, e.g., `import`. If your bundler transforms it into a non-ES6 module, you
must explicitly tell it to import some other way by passing the option `noes6`
to `LibAV.LibAV`, e.g., `LibAV.LibAV({noes6: true})`. Also, if your bundler
converts the ES6 frontend to non-ES6 and you intend to explicitly specify
`toImport`, you must specify the *non-ES6* factory.
4 changes: 1 addition & 3 deletions demo/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
<body>
<script type="text/javascript">(async function() {
try {
const version = "4.10.6.1.1";

const dce = document.createElement.bind(document);
const main = dce("div");
document.body.appendChild(main);
Expand Down Expand Up @@ -58,7 +56,7 @@
LibAV = {base: "../dist"};
await new Promise(res => {
const scr = dce("script");
scr.src = `../dist/libav-${version}-${variant}.js?${Math.random()}`;
scr.src = `../dist/libav-${variant}.js?${Math.random()}`;
scr.onload = res;
scr.onerror = () => {
alert("Failed to load variant!");
Expand Down
20 changes: 4 additions & 16 deletions extern-post.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,13 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

function LibAVGlobe() {
if (typeof globalThis !== "undefined") return globalThis;
else if (typeof self !== "undefined") return self;
return window;
}

if (typeof LibAVFactory !== "undefined")
LibAVGlobe().LibAVFactory = LibAVFactory;

if (/* We're in a worker */
typeof importScripts !== "undefined" &&
/* We're not being loaded with noworker from the main code */
typeof LibAV === "undefined" &&
/* We're not being loaded as a thread */
typeof Module === "undefined"
) (function() {
var globe = LibAVGlobe();
var libav;

Promise.all([]).then(function() {
Expand All @@ -38,12 +28,10 @@ if (/* We're in a worker */
return new Promise(function(res, rej) {
onmessage = function(e) {
if (e && e.data && e.data.config) {
if (!globe.LibAV) globe.LibAV = {};
if (e.data.config.variant)
globe.LibAV.variant = e.data.config.variant;
if (e.data.config.wasmurl)
globe.LibAV.wasmurl = e.data.config.wasmurl;
LibAVFactory().then(res).catch(rej);
LibAVFactory({
wasmurl: e.data.config.wasmurl,
variant: e.data.config.variant
}).then(res).catch(rej);
}
};
});
Expand Down
Loading

0 comments on commit 55c3514

Please sign in to comment.