diff --git a/doc/api/Miscellaneous/MultiThreading.md b/doc/api/Miscellaneous/MultiThreading.md index 8f55afe657..1678e39da7 100644 --- a/doc/api/Miscellaneous/MultiThreading.md +++ b/doc/api/Miscellaneous/MultiThreading.md @@ -383,29 +383,13 @@ if (currentModeInfo === null) { ## Note about ES5 Environment -By default, the Worker file will use es2017 features which should be compatible to a -majority of devices. +The Worker file will use es2017 features which should be compatible to a majority of +devices. -However, some older devices might not be compatible with it yet still compatible to the -WebWorker API (and thus the `MULTI_THREAD` feature), yet not with ES2017. For those -platforms, the RxPlayer also provide an ES5 variant of the Worker file: +However, some older devices might not be compatible with ES2017 yet still compatible to +the WebWorker API (and thus could theoretically be compatible with the `MULTI_THREAD` +feature). -- The easiest way is to just import in your application its "embedded" version, exported - through the `"rx-player/experimental/features/embeds"` path: - - ```js - import { EMBEDDED_WORKER_ES5 } from "rx-player/experimental/features/embeds"; - ``` - - This allows to bypass the need to store and serve separately that file. - - If you would prefer more control and a smaller bundle size, you may instead consider the - other following ways to it as a separate file. - -- With every release note published on GitHub as `worker.es5.js` (you should only use the - file linked to the RxPlayer's version you're using), - -- It is also available as `dist/worker.es5.js` from the root directory of the project - published on npm. As such, it might already be found in your project's directory, for - example in the `node_modules` directory (most probably in `node_modules/rx-player/dist/` - depending on your project). +If you need to provide support for the `MULTI_THREAD` feature on those platforms, we +recommend that you use a transpiler tool on that worker file to make it compatible to ES5. +Examples of transpiler tools are [babel](https://babeljs.io/) and [swc](https://swc.rs/). diff --git a/package-lock.json b/package-lock.json index 39fab26088..ec528e7d03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5328,26 +5328,6 @@ "node": ">= 0.6" } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -7034,26 +7014,6 @@ "node": ">= 0.8" } }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/express/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -11125,10 +11085,24 @@ "dev": true }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/safe-regex-test": { "version": "1.0.0", @@ -17767,14 +17741,6 @@ "dev": true, "requires": { "safe-buffer": "5.2.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } } }, "content-type": { @@ -19031,12 +18997,6 @@ "ee-first": "1.1.1" } }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -21961,9 +21921,9 @@ } }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, "safe-regex-test": { diff --git a/package.json b/package.json index aaeda6ee7d..6aae8c71d7 100644 --- a/package.json +++ b/package.json @@ -145,10 +145,10 @@ "build:dev": "./scripts/generate_build.mjs --dev-mode", "build:wasm:debug": "mkdir -p dist && cd ./src/parsers/manifest/dash/wasm-parser && cargo build --target wasm32-unknown-unknown && cp target/wasm32-unknown-unknown/debug/mpd_node_parser.wasm ../../../../../dist/mpd-parser.wasm", "build:wasm:release": "./scripts/build_wasm_release.sh", - "bundle": "webpack --progress --config webpack.config.mjs --env production", - "bundle:min": "webpack --progress --config webpack.config.mjs --env minify --env production", - "bundle:min:watch": "webpack --progress --config webpack.config.mjs -w --env production --env minify", - "bundle:watch": "webpack --progress --config webpack.config.mjs -w --env production", + "bundle": "./scripts/run_bundler.mjs src/index.ts --production-mode dist/rx-player.js", + "bundle:min": "./scripts/run_bundler.mjs src/index.ts --production-mode dist/rx-player.js --minify", + "bundle:min:watch": "./scripts/run_bundler.mjs src/index.ts --production-mode dist/rx-player.js --minify --watch", + "bundle:watch": "./scripts/run_bundler.mjs src/index.ts --production-mode dist/rx-player.js --watch", "certificate": "./scripts/generate_certificate", "check": "npm run check:types && npm run lint && npm run check:types:unit_tests", "check:all": "npm run check:types && npm run lint && npm run lint:demo && npm run lint:tests && npm run test:unit && npm run test:integration && npm run test:memory && node -r esm ./scripts/check_nodejs_import_compatibility.js", diff --git a/scripts/build_demo.mjs b/scripts/build_demo.mjs index 38fa45abc5..9b7f0e6179 100644 --- a/scripts/build_demo.mjs +++ b/scripts/build_demo.mjs @@ -16,8 +16,9 @@ import { pathToFileURL } from "url"; import esbuild from "esbuild"; import rootDirectory from "./utils/project_root_directory.mjs"; import getHumanReadableHours from "./utils/get_human_readable_hours.mjs"; -import buildWorker from "./bundle_worker.mjs"; +import runBundler from "./run_bundler.mjs"; +const WORKER_IN_FILE = join(rootDirectory, "src/worker_entry_point.ts"); const DEMO_OUT_FILE = join(rootDirectory, "demo/full/bundle.js"); const WORKER_OUT_FILE = join(rootDirectory, "demo/full/worker.js"); const WASM_FILE_DEPENDENCY = join(rootDirectory, "dist/mpd-parser.wasm"); @@ -73,7 +74,7 @@ export default function buildDemo(options) { } }); - buildWorker({ + runBundler(WORKER_IN_FILE, { watch, minify, production: !isDevMode, @@ -128,6 +129,7 @@ export default function buildDemo(options) { __LOGGER_LEVEL__: JSON.stringify({ CURRENT_LEVEL: "INFO", }), + __GLOBAL_SCOPE__: JSON.stringify(true), }, }) .then((context) => { diff --git a/scripts/bundle_worker.mjs b/scripts/bundle_worker.mjs deleted file mode 100755 index 45e0fd1e51..0000000000 --- a/scripts/bundle_worker.mjs +++ /dev/null @@ -1,270 +0,0 @@ -#!/usr/bin/env node -/** - * # bundle_worker.mjs - * - * This file allows to create the `dist/worker.js` file representing the code - * that will run in the WebWorker. - * - * You can either run it directly as a script (run `node build_worker.mjs -h` - * to see the different options) or by requiring it as a node module. - * If doing the latter you will obtain a function you will have to run with the - * right options. - */ - -import * as path from "path"; -import { pathToFileURL } from "url"; -import esbuild from "esbuild"; -import TerserPlugin from "terser-webpack-plugin"; -import webpack from "webpack"; -import rootDirectory from "./utils/project_root_directory.mjs"; -import getHumanReadableHours from "./utils/get_human_readable_hours.mjs"; - -// If true, this script is called directly -if (import.meta.url === pathToFileURL(process.argv[1]).href) { - const { argv } = process; - if (argv.includes("-h") || argv.includes("--help")) { - displayHelp(); - process.exit(0); - } - const shouldWatch = argv.includes("-w") || argv.includes("--watch"); - const shouldMinify = argv.includes("-m") || argv.includes("--minify"); - const production = argv.includes("-p") || argv.includes("--production-mode"); - const silent = argv.includes("-s") || argv.includes("--silent"); - buildWorker({ - watch: shouldWatch, - minify: shouldMinify, - production, - silent, - }); - buildWorkerEs5({ - watch: shouldWatch, - minify: shouldMinify, - production, - silent, - }); -} - -/** - * Build the worker with the given options. - * @param {Object} options - * @param {boolean} [options.minify] - If `true`, the output will be minified. - * @param {boolean} [options.production] - If `false`, the code will be compiled - * in "development" mode, which has supplementary assertions. - * @param {boolean} [options.watch] - If `true`, the RxPlayer's files involve - * will be watched and the code re-built each time one of them changes. - * @param {boolean} [options.silent] - If `true`, we won't output logs. - * @param {boolean} [options.outfile] - Destination of the produced bundle. - * `dist/worker.js` by default. - * @returns {Promise} - */ -export default function buildWorker(options) { - const minify = !!options.minify; - const watch = !!options.watch; - const isDevMode = !options.production; - const isSilent = options.silent; - const outfile = options.outfile ?? path.join(rootDirectory, "dist/worker.js"); - - /** Declare a plugin to anounce when a build begins and ends */ - const consolePlugin = { - name: "onEnd", - setup(build) { - build.onStart(() => { - console.log( - `\x1b[33m[${getHumanReadableHours()}]\x1b[0m ` + - "New Worker file build started", - ); - }); - build.onEnd((result) => { - if (result.errors.length > 0 || result.warnings.length > 0) { - const { errors, warnings } = result; - console.log( - `\x1b[33m[${getHumanReadableHours()}]\x1b[0m ` + - `Worker file re-built with ${errors.length} error(s) and ` + - ` ${warnings.length} warning(s) `, - ); - return; - } - console.log( - `\x1b[32m[${getHumanReadableHours()}]\x1b[0m ` + - `Worker file updated at ${outfile}!`, - ); - }); - }, - }; - - const meth = watch ? "context" : "build"; - - // Create a context for incremental builds - return esbuild[meth]({ - entryPoints: [path.join(rootDirectory, "src/worker_entry_point.ts")], - bundle: true, - target: "es2017", - minify, - outfile, - plugins: isSilent ? [] : [consolePlugin], - define: { - "process.env.NODE_ENV": JSON.stringify(isDevMode ? "development" : "production"), - __ENVIRONMENT__: JSON.stringify({ - PRODUCTION: 0, - DEV: 1, - CURRENT_ENV: isDevMode ? 1 : 0, - }), - __LOGGER_LEVEL__: JSON.stringify({ CURRENT_LEVEL: "NONE" }), - }, - }) - .then((context) => { - if (watch) { - return context.watch(); - } - }) - .catch((err) => { - if (!isSilent) { - console.error( - `\x1b[31m[${getHumanReadableHours()}]\x1b[0m Worker file build failed:`, - err, - ); - } - throw err; - }); -} - -export function buildWorkerEs5(options) { - const shouldMinify = !!options.minify; - const watch = !!options.watch; - const isDevMode = !options.production; - const isSilent = options.silent; - const outfile = options.outfile ?? path.join(rootDirectory, "dist/worker.es5.js"); - const outputPath = path.dirname(outfile); - const filename = path.basename(outfile); - - const plugins = [ - new webpack.DefinePlugin({ - __ENVIRONMENT__: { - PRODUCTION: 0, - DEV: 1, - CURRENT_ENV: isDevMode ? 1 : 0, - }, - __LOGGER_LEVEL__: JSON.stringify({ CURRENT_LEVEL: "NONE" }), - }), - ]; - - if (!isSilent) { - console.log( - `\x1b[33m[${getHumanReadableHours()}]\x1b[0m ` + - "New ES5 Worker file build started", - ); - } - - const compiler = webpack({ - mode: isDevMode ? "development" : "production", - entry: [path.join(rootDirectory, "src/worker_entry_point.ts")], - output: { - path: outputPath, - filename, - environment: { - arrowFunction: false, - asyncFunction: false, - bigIntLiteral: false, - const: false, - destructuring: false, - dynamicImport: false, - dynamicImportInWorker: false, - forOf: false, - globalThis: false, - module: false, - optionalChaining: false, - templateLiteral: false, - }, - }, - optimization: { - minimize: shouldMinify, - minimizer: shouldMinify ? [new TerserPlugin()] : [], - }, - performance: { - maxEntrypointSize: shouldMinify ? 600000 : 2500000, - maxAssetSize: shouldMinify ? 600000 : 2500000, - }, - resolve: { - extensions: [".ts", ".tsx", ".js", ".jsx", ".json"], - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: [ - { - loader: "babel-loader", - options: { - cacheDirectory: true, - presets: [["@babel/env", { loose: true, modules: false }]], - plugins: [["@babel/plugin-transform-runtime"]], - }, - }, - { loader: "ts-loader" }, - ], - }, - ], - }, - plugins, - }); - - return new Promise((res, rej) => { - if (watch) { - compiler.watch({}, (err) => { - if (err) { - if (!isSilent) { - console.error( - `\x1b[31m[${getHumanReadableHours()}]\x1b[0m ES5 Worker file build failed:`, - err, - ); - } - rej(err); - } else if (!isSilent) { - console.log( - `\x1b[32m[${getHumanReadableHours()}]\x1b[0m ` + - `ES5 Worker file updated at ${outfile}!`, - ); - } - res(); - }); - return; - } - compiler.run((err) => { - if (err) { - if (!isSilent) { - console.error( - `\x1b[31m[${getHumanReadableHours()}]\x1b[0m ES5 Worker file build failed:`, - err, - ); - } - rej(err); - } else if (!isSilent) { - console.log( - `\x1b[32m[${getHumanReadableHours()}]\x1b[0m ` + - `ES5 Worker file written at ${outfile}!`, - ); - } - res(); - }); - }); -} - -/** - * Display through `console.log` an helping message relative to how to run this - * script. - */ -function displayHelp() { - /* eslint-disable no-console */ - console.log( - /* eslint-disable indent */ - `Usage: node build_worker.mjs [options] -Options: - -h, --help Display this help - -m, --minify Minify the built worker - -p, --production-mode Build all files in production mode (less runtime checks, mostly). - -s, --silent Don't log to stdout/stderr when bundling - -w, --watch Re-build each time either the worker or library files change`, - /* eslint-enable indent */ - ); - /* eslint-enable no-console */ -} diff --git a/scripts/generate_build.mjs b/scripts/generate_build.mjs index 20819abca5..968dbdd263 100755 --- a/scripts/generate_build.mjs +++ b/scripts/generate_build.mjs @@ -24,7 +24,7 @@ import * as path from "path"; import { fileURLToPath, pathToFileURL } from "url"; import { rimraf } from "rimraf"; import generateEmbeds from "./generate_embeds.mjs"; -import buildWorker, { buildWorkerEs5 } from "./bundle_worker.mjs"; +import runBundler from "./run_bundler.mjs"; const currentDirectory = path.dirname(fileURLToPath(import.meta.url)); @@ -35,6 +35,9 @@ const BUILD_ARTEFACTS_TO_REMOVE = [ "src/__GENERATED_CODE", ]; +const WORKER_IN_FILE = path.join(ROOT_DIR, "src/worker_entry_point.ts"); +const WORKER_OUT_FILE = path.join(ROOT_DIR, "dist/worker.js"); + // If true, this script is called directly if (import.meta.url === pathToFileURL(process.argv[1]).href) { const { argv } = process; @@ -79,15 +82,10 @@ async function generateBuild(options = {}) { console.log(" 👷 Bundling worker files..."); await Promise.all([ - buildWorker({ - watch: false, - minify: !devMode, - production: !devMode, - silent: true, - }), - buildWorkerEs5({ + runBundler(WORKER_IN_FILE, { watch: false, minify: !devMode, + outfile: WORKER_OUT_FILE, production: !devMode, silent: true, }), diff --git a/scripts/generate_embeds.mjs b/scripts/generate_embeds.mjs index d160ed07e9..9405c2b057 100644 --- a/scripts/generate_embeds.mjs +++ b/scripts/generate_embeds.mjs @@ -50,13 +50,11 @@ import { fileURLToPath, pathToFileURL } from "url"; const currentDir = dirname(fileURLToPath(import.meta.url)); const originalWasmFilePath = join(currentDir, "../dist/mpd-parser.wasm"); const originalWorkerFilePath = join(currentDir, "../dist/worker.js"); -const originalWorkerEs5FilePath = join(currentDir, "../dist/worker.es5.js"); const codeGenDir = join(currentDir, "../src/__GENERATED_CODE"); const indexPath = join(codeGenDir, "./index.ts"); const mpdEmbedPath = join(codeGenDir, "./embedded_dash_wasm.ts"); const workerEmbedPath = join(codeGenDir, "./embedded_worker.ts"); -const workerEs5EmbedPath = join(codeGenDir, "./embedded_worker_es5.ts"); // If true, this script is called directly if (import.meta.url === pathToFileURL(process.argv[1]).href) { @@ -70,12 +68,7 @@ async function generateEmbeds() { if (!fs.existsSync(codeGenDir)) { fs.mkdirSync(codeGenDir); } - await Promise.all([ - writeWebAssemblyEmbed(), - writeWorkerEmbed(), - writeWorkerEs5Embed(), - writeIndexCode(), - ]); + await Promise.all([writeWebAssemblyEmbed(), writeWorkerEmbed(), writeIndexCode()]); } catch (err) { console.log(err); return Promise.reject(err); @@ -105,22 +98,10 @@ export default blob;`; await writeFile(workerEmbedPath, workerEmbedCode); } -async function writeWorkerEs5Embed() { - const workerEs5Data = await readFile(originalWorkerEs5FilePath, "utf-8"); - const workerEs5EmbedCode = - "const blob = new Blob([" + - `"(function(){" + ${JSON.stringify(workerEs5Data)} + "})()"` + - `], { type: "application/javascript" }); -export { blob as EMBEDDED_WORKER_ES5 }; -export default blob;`; - await writeFile(workerEs5EmbedPath, workerEs5EmbedCode); -} - async function writeIndexCode() { // Hardcode the code declaring and exporting the embedded URL: const indexCode = `export { EMBEDDED_DASH_WASM } from "./embedded_dash_wasm"; -export { EMBEDDED_WORKER } from "./embedded_worker"; -export { EMBEDDED_WORKER_ES5 } from "./embedded_worker_es5";`; +export { EMBEDDED_WORKER } from "./embedded_worker";`; await writeFile(indexPath, indexCode); } diff --git a/scripts/run_bundler.mjs b/scripts/run_bundler.mjs new file mode 100755 index 0000000000..43456733a3 --- /dev/null +++ b/scripts/run_bundler.mjs @@ -0,0 +1,199 @@ +#!/usr/bin/env node +/** + * # run_bundler.mjs + * + * This file allows to create JavaScript bundles for the RxPlayer through our + * bundlers with the right configuration. + * + * You can either run it directly as a script (run `node run_bundler.mjs -h` + * to see the different options) or by requiring it as a node module. + * If doing the latter you will obtain a function you will have to run with the + * right options. + */ + +import * as fs from "fs"; +import * as path from "path"; +import { pathToFileURL } from "url"; +import esbuild from "esbuild"; +import getHumanReadableHours from "./utils/get_human_readable_hours.mjs"; + +// If true, this script is called directly +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + const args = process.argv.slice(2); + if (args.includes("-h") || args.includes("--help")) { + displayHelp(); + process.exit(0); + } + + const inputFile = args[0]; + if (inputFile === undefined) { + console.error("ERROR: no input file provided\n"); + displayHelp(); + process.exit(1); + } + + const normalizedPath = path.normalize(inputFile); + if (!fs.existsSync(normalizedPath)) { + console.error(`ERROR: input file not found: ${normalizedPath}\n`); + displayHelp(); + process.exit(1); + } + + const shouldWatch = args.includes("-w") || args.includes("--watch"); + const shouldMinify = args.includes("-m") || args.includes("--minify"); + const production = args.includes("-p") || args.includes("--production-mode"); + const silent = args.includes("-s") || args.includes("--silent"); + + let outfile; + { + let outputIndex = args.indexOf("-o"); + if (outputIndex < 0) { + outputIndex = args.indexOf("--output"); + } + if (outputIndex >= 0) { + const wantedOutputFile = args[outputIndex + 1]; + if (wantedOutputFile === undefined) { + console.error("ERROR: no output file provided\n"); + displayHelp(); + process.exit(1); + } + outfile = path.normalize(wantedOutputFile); + } + } + + try { + await runBundler(normalizedPath, { + watch: shouldWatch, + minify: shouldMinify, + production, + silent, + outfile, + }); + } catch (err) { + console.error(`ERROR: ${err}\n`); + displayHelp(); + process.exit(1); + } +} + +/** + * Run bundler with the given options. + * @param {string} inputFile + * @param {Object} options + * @param {boolean} [options.minify] - If `true`, the output will be minified. + * @param {boolean} [options.globalScope] - If `true`, enable global scope mode + * (the `__GLOBAL_SCOPE__` global symbol will be set to `true` in the bundle). + * @param {boolean} [options.production] - If `false`, the code will be compiled + * in "development" mode, which has supplementary assertions. + * @param {boolean} [options.watch] - If `true`, the RxPlayer's files involve + * will be watched and the code re-built each time one of them changes. + * @param {boolean} [options.silent] - If `true`, we won't output logs. + * @param {string} [options.outfile] - Destination of the produced es2017 + * bundle. To ignore to skip ES2017 bundle generation. + * @returns {Promise} + */ +export default async function runBundler(inputFile, options) { + const minify = !!options.minify; + const watch = !!options.watch; + const isDevMode = !options.production; + const isSilent = options.silent; + const outfile = options.outfile; + const globalScope = !!options.globalScope; + + if (outfile === undefined) { + throw new Error("No output file has been provided"); + } + + const esbuildStepsPlugin = { + name: "bundler-steps", + setup(build) { + build.onStart(() => logWarning(`Bundling of ${inputFile} started`)); + build.onEnd((result) => { + if (result.errors.length > 0 || result.warnings.length > 0) { + const { errors, warnings } = result; + logWarning( + `File re-bundle of ${inputFile} failed with ${errors.length} error(s) and ` + + ` ${warnings.length} warning(s) `, + ); + return; + } + if (outfile !== undefined) { + logSuccess(`File updated at ${outfile}!`); + } + }); + }, + }; + + const meth = watch ? "context" : "build"; + + // Create a context for incremental builds + try { + const context = await esbuild[meth]({ + entryPoints: [inputFile], + bundle: true, + target: "es2017", + minify, + write: outfile !== undefined, + outfile, + plugins: [esbuildStepsPlugin], + define: { + "process.env.NODE_ENV": JSON.stringify(isDevMode ? "development" : "production"), + __ENVIRONMENT__: JSON.stringify({ + PRODUCTION: 0, + DEV: 1, + CURRENT_ENV: isDevMode ? 1 : 0, + }), + __LOGGER_LEVEL__: JSON.stringify({ CURRENT_LEVEL: isDevMode ? "INFO" : "NONE" }), + __GLOBAL_SCOPE__: JSON.stringify(globalScope), + }, + }); + if (watch) { + return context.watch(); + } + } catch (err) { + logError(`Bundling failed for ${inputFile}:`, err); + throw err; + } + + function logSuccess(msg) { + if (!isSilent) { + console.log(`\x1b[32m[${getHumanReadableHours()}]\x1b[0m`, msg); + } + } + + function logWarning(msg) { + if (!isSilent) { + console.log(`\x1b[33m[${getHumanReadableHours()}]\x1b[0m`, msg); + } + } + + function logError(msg) { + if (!isSilent) { + console.log(`\x1b[31m[${getHumanReadableHours()}]\x1b[0m`, msg); + } + } +} + +/** + * Display through `console.log` an helping message relative to how to run this + * script. + */ +function displayHelp() { + /* eslint-disable no-console */ + console.log( + /* eslint-disable indent */ + `Usage: node run_bundler.mjs input-file [options] +Available options: + -h, --help Display this help message + -m, --minify Minify the built bundle + -o , --output Specify an output file for the ES2017 bundle. To ignore to skip ES2017 + bundle generation. + -p, --production-mode Build all files in production mode (less runtime checks, mostly). + -g, --global-scope If set, enable "global scope mode" (the \`__GLOBAL_SCOPE__\` global + symbol) on the bundle. + -s, --silent Don't log to stdout/stderr when bundling + -w, --watch Re-build each time either the files it depends on changed`, + /* eslint-enable indent */ + ); + /* eslint-enable no-console */ +} diff --git a/src/globals.dev.d.ts b/src/globals.dev.d.ts index 492b1d543a..41fc3e4320 100644 --- a/src/globals.dev.d.ts +++ b/src/globals.dev.d.ts @@ -12,4 +12,6 @@ declare const enum __ENVIRONMENT__ { CURRENT_ENV = DEV, } +declare const __GLOBAL_SCOPE__: boolean | undefined; + declare const __RX_PLAYER_DEBUG_MODE__: boolean | undefined; diff --git a/src/globals.prod.d.ts b/src/globals.prod.d.ts index c6d2b5cd92..3ea5c0197d 100644 --- a/src/globals.prod.d.ts +++ b/src/globals.prod.d.ts @@ -12,4 +12,6 @@ declare const enum __ENVIRONMENT__ { CURRENT_ENV = PRODUCTION, } +declare const __GLOBAL_SCOPE__: boolean | undefined; + declare const __RX_PLAYER_DEBUG_MODE__: boolean | undefined; diff --git a/src/index.ts b/src/index.ts index 3c26a7dec4..cd6c254363 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,6 +37,7 @@ import { } from "./features/list"; import logger from "./log"; import Player from "./main_thread/api"; +import globalScope from "./utils/global_scope"; patchWebkitSourceBuffer(); @@ -60,3 +61,7 @@ if (isDebugModeEnabled()) { logger.setLevel(__LOGGER_LEVEL__.CURRENT_LEVEL); } export default Player; + +if (typeof __GLOBAL_SCOPE__ === "boolean" && __GLOBAL_SCOPE__) { + (globalScope as typeof globalScope & { RxPlayer?: typeof Player }).RxPlayer = Player; +} diff --git a/tests/generate_test_webpack_config.mjs b/tests/generate_test_webpack_config.mjs deleted file mode 100644 index 865a8f5be2..0000000000 --- a/tests/generate_test_webpack_config.mjs +++ /dev/null @@ -1,60 +0,0 @@ -/* eslint-env node */ - -import Webpack from "webpack"; - -/** - * Generate a configuration object ready to be used by Webpack for testing the - * RxPlayer's code. - * @param {Object} args - * @param {Object} args.contentServerInfo - Information on the server that will - * be run to serve test contents, called the "content server". - * @param {string} args.contentServerInfo.url - URL of the content server. - * @param {number} args.contentServerInfo.port - Port that should be used when - * requesting the content server. - * @returns {Object} - The Webpack config - */ -export default function generateTestWebpackConfig({ contentServerInfo }) { - return { - mode: "development", - resolve: { - extensions: [".ts", ".tsx", ".js", ".jsx", ".json"], - fallback: { - assert: false, - util: false, - fs: false, - tls: false, - net: false, - path: false, - zlib: false, - http: false, - https: false, - stream: false, - crypto: false, - }, - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: "ts-loader", - }, - ], - }, - plugins: [ - new Webpack.DefinePlugin({ - __TEST_CONTENT_SERVER__: { - URL: JSON.stringify(contentServerInfo.url), - PORT: JSON.stringify(String(contentServerInfo.port)), - }, - __ENVIRONMENT__: { - PRODUCTION: 0, - DEV: 1, - CURRENT_ENV: 1, - }, - __LOGGER_LEVEL__: { - CURRENT_LEVEL: '"NONE"', - }, - }), - ], - }; -} diff --git a/tests/integration/scenarios/dash_multi_periods.test.js b/tests/integration/scenarios/dash_multi_periods.test.js index 45e649e61f..5824569b40 100644 --- a/tests/integration/scenarios/dash_multi_periods.test.js +++ b/tests/integration/scenarios/dash_multi_periods.test.js @@ -16,7 +16,6 @@ import { checkAfterSleepWithBackoff } from "../../utils/checkAfterSleepWithBacko describe("DASH non-linear multi-periods content (SegmentTemplate)", function () { launchTestsForContent(manifestInfos); launchTestsForContent(manifestInfos, { multithread: true }); - launchTestsForContent(manifestInfos, { multithread: true, es5Worker: true }); }); describe("DASH multi-Period with different choices", function () { diff --git a/tests/integration/scenarios/dash_static.test.js b/tests/integration/scenarios/dash_static.test.js index 4f3db0e65f..16e89e0693 100644 --- a/tests/integration/scenarios/dash_static.test.js +++ b/tests/integration/scenarios/dash_static.test.js @@ -20,28 +20,16 @@ import { checkAfterSleepWithBackoff } from "../../utils/checkAfterSleepWithBacko describe("DASH non-linear content (SegmentTimeline)", function () { launchTestsForContent(segmentTimelineManifestInfos); launchTestsForContent(segmentTimelineManifestInfos, { multithread: true }); - launchTestsForContent(segmentTimelineManifestInfos, { - multithread: true, - es5Worker: true, - }); }); describe("DASH non-linear content multi-codecs (SegmentBase)", function () { launchTestsForContent(segmentBaseMultiCodecsInfos); launchTestsForContent(segmentBaseMultiCodecsInfos, { multithread: true }); - launchTestsForContent(segmentBaseMultiCodecsInfos, { - multithread: true, - es5Worker: true, - }); }); describe("DASH non-linear content not starting at 0 (SegmentTimeline)", function () { launchTestsForContent(notStartingAt0ManifestInfos); launchTestsForContent(notStartingAt0ManifestInfos, { multithread: true }); - launchTestsForContent(notStartingAt0ManifestInfos, { - multithread: true, - es5Worker: true, - }); }); describe("DASH non-linear content with SegmentTemplate inheritance (Period-AdaptationSet)", function () { @@ -49,19 +37,11 @@ describe("DASH non-linear content with SegmentTemplate inheritance (Period-Adapt launchTestsForContent(segmentTemplateInheritancePeriodAS, { multithread: true, }); - launchTestsForContent(segmentTemplateInheritancePeriodAS, { - multithread: true, - es5Worker: true, - }); }); describe("DASH non-linear content with SegmentTemplate inheritance (AdaptationSet-Representation)", function () { launchTestsForContent(segmentTemplateInheritanceASRep); launchTestsForContent(segmentTemplateInheritanceASRep, { multithread: true }); - launchTestsForContent(segmentTemplateInheritanceASRep, { - multithread: true, - es5Worker: true, - }); }); describe("DASH content CENC wrong version in MPD", function () { diff --git a/tests/integration/scenarios/idle.test.js b/tests/integration/scenarios/idle.test.js index ab9916229d..53d295c0e2 100644 --- a/tests/integration/scenarios/idle.test.js +++ b/tests/integration/scenarios/idle.test.js @@ -2,14 +2,12 @@ import RxPlayer from "../../../dist/es2017"; import { MULTI_THREAD } from "../../../dist/es2017/experimental/features/index.js"; import { EMBEDDED_WORKER, - EMBEDDED_WORKER_ES5, EMBEDDED_DASH_WASM, } from "../../../dist/es2017/__GENERATED_CODE/index.js"; import { describe, it, expect, afterAll } from "vitest"; runIdleTests(); runIdleTests({ multithread: true }); -runIdleTests({ multithread: true, es5Worker: true }); /** * Test every methods at an initial, idle state. @@ -20,10 +18,8 @@ runIdleTests({ multithread: true, es5Worker: true }); * @param {Boolean} [options.multithread] - If `true`, those tests will be run * if the RxPlayer runs in multithread mode. * In any other cases, tests will run in monothread mode. - * @param {Boolean} [options.es5Worker] - If `true`, multithread tests will be - * run in the ES5 version of the WebWorker file. */ -function runIdleTests({ multithread, es5Worker } = {}) { +function runIdleTests({ multithread } = {}) { let title = "initial idle state"; if (multithread === true) { RxPlayer.addFeatures([MULTI_THREAD]); @@ -36,7 +32,7 @@ function runIdleTests({ multithread, es5Worker } = {}) { const player = new RxPlayer(); if (multithread === true) { player.attachWorker({ - workerUrl: es5Worker ? EMBEDDED_WORKER_ES5 : EMBEDDED_WORKER, + workerUrl: EMBEDDED_WORKER, dashWasmUrl: EMBEDDED_DASH_WASM, }); } diff --git a/tests/integration/scenarios/initial_playback.test.js b/tests/integration/scenarios/initial_playback.test.js index e307d19f2b..df14d247d7 100644 --- a/tests/integration/scenarios/initial_playback.test.js +++ b/tests/integration/scenarios/initial_playback.test.js @@ -19,7 +19,6 @@ import { MULTI_THREAD } from "../../../src/experimental/features"; import { EMBEDDED_WORKER, EMBEDDED_DASH_WASM, - EMBEDDED_WORKER_ES5, } from "../../../dist/es2017/__GENERATED_CODE/index.js"; import { manifestInfos } from "../../contents/DASH_static_SegmentTimeline"; import sleep from "../../utils/sleep.js"; @@ -31,7 +30,6 @@ import { describe, beforeEach, afterEach, it, expect } from "vitest"; runInitialPlaybackTests(); runInitialPlaybackTests({ multithread: true }); -runInitialPlaybackTests({ multithread: true, es5Worker: true }); /** * Test various cases of errors linked to starting playback. @@ -39,10 +37,8 @@ runInitialPlaybackTests({ multithread: true, es5Worker: true }); * @param {Boolean} [options.multithread] - If `true`, those tests will be run * if the RxPlayer runs in multithread mode. * In any other cases, tests will run in monothread mode. - * @param {Boolean} [options.es5Worker] - If `true`, multithread tests will be - * run in the ES5 version of the WebWorker file. */ -function runInitialPlaybackTests({ multithread, es5Worker } = {}) { +function runInitialPlaybackTests({ multithread } = {}) { let title = "basic playback use cases: non-linear DASH SegmentTimeline"; if (multithread === true) { RxPlayer.addFeatures([MULTI_THREAD]); @@ -56,7 +52,7 @@ function runInitialPlaybackTests({ multithread, es5Worker } = {}) { player = new RxPlayer(); if (multithread === true) { player.attachWorker({ - workerUrl: es5Worker ? EMBEDDED_WORKER_ES5 : EMBEDDED_WORKER, + workerUrl: EMBEDDED_WORKER, dashWasmUrl: EMBEDDED_DASH_WASM, }); } diff --git a/tests/integration/scenarios/loadVideo_options.test.js b/tests/integration/scenarios/loadVideo_options.test.js index 2b2aaa609c..97c931cc57 100644 --- a/tests/integration/scenarios/loadVideo_options.test.js +++ b/tests/integration/scenarios/loadVideo_options.test.js @@ -19,7 +19,6 @@ import RxPlayer from "../../../dist/es2017"; import { MULTI_THREAD } from "../../../dist/es2017/experimental/features/index.js"; import { EMBEDDED_WORKER, - EMBEDDED_WORKER_ES5, EMBEDDED_DASH_WASM, } from "../../../dist/es2017/__GENERATED_CODE/index.js"; import { manifestInfos } from "../../contents/DASH_static_SegmentTimeline"; @@ -38,10 +37,8 @@ runLoadVideoOptionsTests({ multithread: true }); * @param {Boolean} [options.multithread] - If `true`, those tests will be run * if the RxPlayer runs in multithread mode. * In any other cases, tests will run in monothread mode. - * @param {Boolean} [options.es5Worker] - If `true`, multithread tests will be - * run in the ES5 version of the WebWorker file. */ -function runLoadVideoOptionsTests({ multithread, es5Worker } = {}) { +function runLoadVideoOptionsTests({ multithread } = {}) { let title = "loadVideo options"; if (multithread === true) { RxPlayer.addFeatures([MULTI_THREAD]); @@ -55,7 +52,7 @@ function runLoadVideoOptionsTests({ multithread, es5Worker } = {}) { player = new RxPlayer(); if (multithread === true) { player.attachWorker({ - workerUrl: es5Worker ? EMBEDDED_WORKER_ES5 : EMBEDDED_WORKER, + workerUrl: EMBEDDED_WORKER, dashWasmUrl: EMBEDDED_DASH_WASM, }); } diff --git a/tests/integration/utils/launch_tests_for_content.js b/tests/integration/utils/launch_tests_for_content.js index f8cd388ec9..3a06d456ad 100644 --- a/tests/integration/utils/launch_tests_for_content.js +++ b/tests/integration/utils/launch_tests_for_content.js @@ -4,7 +4,6 @@ import { MULTI_THREAD } from "../../../dist/es2017/experimental/features/index.j import { EMBEDDED_WORKER, EMBEDDED_DASH_WASM, - EMBEDDED_WORKER_ES5, } from "../../../dist/es2017/__GENERATED_CODE/index.js"; import sleep from "../../utils/sleep.js"; import waitForState, { @@ -58,13 +57,8 @@ import { checkAfterSleepWithBackoff } from "../../utils/checkAfterSleepWithBacko * @param {Boolean} [options.multithread] - If `true`, those tests will be run * if the RxPlayer runs in multithread mode. * In any other cases, tests will run in monothread mode. - * @param {Boolean} [options.es5Worker] - If `true`, multithread tests will be - * run in the ES5 version of the WebWorker file. */ -export default function launchTestsForContent( - manifestInfos, - { multithread, es5Worker } = {}, -) { +export default function launchTestsForContent(manifestInfos, { multithread } = {}) { let player; if (multithread === true) { @@ -86,7 +80,7 @@ export default function launchTestsForContent( player = new RxPlayer(); if (multithread === true) { player.attachWorker({ - workerUrl: es5Worker ? EMBEDDED_WORKER_ES5 : EMBEDDED_WORKER, + workerUrl: EMBEDDED_WORKER, dashWasmUrl: EMBEDDED_DASH_WASM, }); } diff --git a/tests/performance/run.mjs b/tests/performance/run.mjs index 536d5a8ef2..4216333638 100644 --- a/tests/performance/run.mjs +++ b/tests/performance/run.mjs @@ -751,6 +751,7 @@ function createBundle(options) { __LOGGER_LEVEL__: JSON.stringify({ CURRENT_LEVEL: "INFO", }), + __GLOBAL_SCOPE__: JSON.stringify(false), }, }) .then( diff --git a/webpack.config.mjs b/webpack.config.mjs deleted file mode 100644 index 0c130c4916..0000000000 --- a/webpack.config.mjs +++ /dev/null @@ -1,91 +0,0 @@ -/* eslint-env node */ - -/** - * This file exports a configuration used to generate our "legacy" bundles in - * dist/rx-player.js and dist/rx-player.min (depending on if the minify option - * is set) by using Webpack. - * - * Note that we do not rely at all on this configuration when the usually - * preferred "Minimal" version of the player is imported. - */ - -import TerserPlugin from "terser-webpack-plugin"; -import webpack from "webpack"; - -/** - * Generate the Webpack config used to generate the RxPlayer's legacy bundles. - * @param {Object} env - Arguments. When Webpack is called through the CLI, - * those are created implicitely based on flags. - * @param {boolean} [options.minify] - If `true`, the output will be minified. - * @param {boolean} [options.production] - If `true`, the code will be compiled - * in "production" mode, which contains between other things less runtime - * assertions. - * @returns {Object} - The Webpack configuration - */ -export default (env) => { - const isDevMode = !env.production; - const shouldMinify = !!env.minify; - - const plugins = [ - new webpack.DefinePlugin({ - __ENVIRONMENT__: { - PRODUCTION: 0, - DEV: 1, - CURRENT_ENV: isDevMode ? 1 : 0, - }, - __LOGGER_LEVEL__: { - CURRENT_LEVEL: isDevMode ? '"INFO"' : '"ERROR"', - }, - }), - ]; - - return { - mode: isDevMode ? "development" : "production", - entry: "./src/index.ts", - output: { - library: "RxPlayer", - libraryTarget: "umd", - libraryExport: "default", - filename: shouldMinify ? "rx-player.min.js" : "rx-player.js", - environment: { - arrowFunction: false, - bigIntLiteral: false, - const: false, - destructuring: false, - dynamicImport: false, - forOf: false, - module: false, - }, - }, - optimization: { - minimize: shouldMinify, - minimizer: shouldMinify ? [new TerserPlugin()] : [], - }, - performance: { - maxEntrypointSize: shouldMinify ? 600000 : 2500000, - maxAssetSize: shouldMinify ? 600000 : 2500000, - }, - resolve: { - extensions: [".ts", ".tsx", ".js", ".jsx", ".json"], - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: [ - { - loader: "babel-loader", - options: { - cacheDirectory: true, - presets: [["@babel/env", { loose: true, modules: false }]], - plugins: [["@babel/plugin-transform-runtime"]], - }, - }, - { loader: "ts-loader" }, - ], - }, - ], - }, - plugins, - }; -};