chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { OUT_DIR } from "./build/constants.mjs";
|
||||
import prepareOutDir from "../../../scripts/prepareOutDir.mjs";
|
||||
import { colors, createLogger } from "../../../scripts/logger.mjs";
|
||||
import { buildAll } from "./build/buildAll.mjs";
|
||||
|
||||
const log = createLogger("transformers");
|
||||
|
||||
log.section("BUILD");
|
||||
log.info("Building transformers.js with esbuild...\n");
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
prepareOutDir(OUT_DIR);
|
||||
|
||||
await buildAll(log);
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = (endTime - startTime).toFixed(2);
|
||||
log.success(`All builds completed in ${colors.bright}${duration}ms${colors.reset}\n`);
|
||||
} catch (error) {
|
||||
log.error(`Build failed: ${error.message}`);
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { build as esbuild } from "esbuild";
|
||||
import path from "node:path";
|
||||
import { stripNodePrefixPlugin } from "./plugins/stripNodePrefixPlugin.mjs";
|
||||
import { ignoreModulesPlugin } from "./plugins/ignoreModulesPlugin.mjs";
|
||||
import { postBuildPlugin } from "./plugins/postBuildPlugin.mjs";
|
||||
import { externalNodeBuiltinsPlugin } from "./plugins/externalNodeBuiltinsPlugin.mjs";
|
||||
import { OUT_DIR, ROOT_DIR, getEsbuildProdConfig } from "./constants.mjs";
|
||||
import { reportSize } from "../../../../scripts/reportSize.mjs";
|
||||
import { colors } from "../../../../scripts/logger.mjs";
|
||||
import { BUILD_TARGETS } from "./targets.mjs";
|
||||
|
||||
/**
|
||||
* Helper function to create build configurations.
|
||||
*/
|
||||
async function buildTarget(
|
||||
{
|
||||
name = "",
|
||||
suffix = ".js",
|
||||
format = "esm", // 'esm' | 'cjs'
|
||||
ignoreModules = [],
|
||||
externalModules = [],
|
||||
usePostBuild = false,
|
||||
},
|
||||
log,
|
||||
) {
|
||||
const platform = format === "cjs" ? "node" : "neutral";
|
||||
|
||||
const regularFile = `transformers${name}${suffix}`;
|
||||
const minFile = `transformers${name}.min${suffix}`;
|
||||
|
||||
const plugins = [];
|
||||
// Add ignoreModulesPlugin FIRST so it can catch modules before stripNodePrefixPlugin marks them as external
|
||||
if (ignoreModules.length > 0) {
|
||||
plugins.push(ignoreModulesPlugin(ignoreModules));
|
||||
}
|
||||
plugins.push(stripNodePrefixPlugin());
|
||||
plugins.push(externalNodeBuiltinsPlugin());
|
||||
if (usePostBuild) {
|
||||
plugins.push(postBuildPlugin(OUT_DIR, ROOT_DIR));
|
||||
}
|
||||
|
||||
log.build(`Building ${colors.bright}${regularFile}${colors.reset}...`);
|
||||
await esbuild({
|
||||
...getEsbuildProdConfig(ROOT_DIR),
|
||||
platform,
|
||||
format,
|
||||
outfile: path.join(OUT_DIR, regularFile),
|
||||
external: externalModules,
|
||||
plugins,
|
||||
});
|
||||
reportSize(path.join(OUT_DIR, regularFile), log);
|
||||
|
||||
log.build(`Building ${colors.bright}${minFile}${colors.reset}...`);
|
||||
await esbuild({
|
||||
...getEsbuildProdConfig(ROOT_DIR),
|
||||
platform,
|
||||
format,
|
||||
outfile: path.join(OUT_DIR, minFile),
|
||||
minify: true,
|
||||
external: externalModules,
|
||||
plugins,
|
||||
legalComments: "none",
|
||||
});
|
||||
reportSize(path.join(OUT_DIR, minFile), log);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all targets for production
|
||||
*/
|
||||
export async function buildAll(log) {
|
||||
for (const target of BUILD_TARGETS) {
|
||||
log.section(target.name);
|
||||
await buildTarget(target.config, log);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { context } from "esbuild";
|
||||
import path from "node:path";
|
||||
import { stripNodePrefixPlugin } from "./plugins/stripNodePrefixPlugin.mjs";
|
||||
import { ignoreModulesPlugin } from "./plugins/ignoreModulesPlugin.mjs";
|
||||
import { postBuildPlugin } from "./plugins/postBuildPlugin.mjs";
|
||||
import { externalNodeBuiltinsPlugin } from "./plugins/externalNodeBuiltinsPlugin.mjs";
|
||||
import { rebuildPlugin } from "../../../../scripts/rebuildPlugin.mjs";
|
||||
import { OUT_DIR, ROOT_DIR, getEsbuildDevConfig } from "./constants.mjs";
|
||||
import { BUILD_TARGETS } from "./targets.mjs";
|
||||
|
||||
/**
|
||||
* Create an esbuild context for a single build target
|
||||
*/
|
||||
async function createBuildContext(targetName, targetConfig, log) {
|
||||
const { name, suffix, format, ignoreModules, externalModules, usePostBuild } = targetConfig;
|
||||
const platform = format === "cjs" ? "node" : "neutral";
|
||||
const outputFile = `transformers${name}${suffix}`;
|
||||
|
||||
const plugins = [];
|
||||
// Add ignoreModulesPlugin FIRST so it can catch modules before stripNodePrefixPlugin marks them as external
|
||||
if (ignoreModules.length > 0) {
|
||||
plugins.push(ignoreModulesPlugin(ignoreModules));
|
||||
}
|
||||
plugins.push(stripNodePrefixPlugin());
|
||||
plugins.push(externalNodeBuiltinsPlugin());
|
||||
if (usePostBuild) {
|
||||
plugins.push(postBuildPlugin(OUT_DIR, ROOT_DIR));
|
||||
}
|
||||
plugins.push(rebuildPlugin(targetName, log));
|
||||
|
||||
return context({
|
||||
...getEsbuildDevConfig(ROOT_DIR),
|
||||
platform,
|
||||
format,
|
||||
outfile: path.join(OUT_DIR, outputFile),
|
||||
external: externalModules,
|
||||
plugins,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all targets in watch mode for development
|
||||
* @returns {Promise<Array>} Array of esbuild contexts that can be disposed later
|
||||
*/
|
||||
export async function buildAllWithWatch(log) {
|
||||
log.dim("Creating build contexts...\n");
|
||||
|
||||
// Create contexts for all targets
|
||||
const contexts = await Promise.all(BUILD_TARGETS.map((target) => createBuildContext(target.name, target.config, log)));
|
||||
|
||||
log.dim("Starting initial build...\n");
|
||||
|
||||
// Wait for the initial builds to complete
|
||||
await Promise.all(contexts.map((ctx) => ctx.rebuild()));
|
||||
|
||||
// Start watching all targets
|
||||
await Promise.all(contexts.map((ctx) => ctx.watch()));
|
||||
|
||||
return contexts;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export const DIST_FOLDER = "dist";
|
||||
export const NODE_IGNORE_MODULES = ["onnxruntime-web"];
|
||||
export const NODE_EXTERNAL_MODULES = [
|
||||
"onnxruntime-common",
|
||||
"onnxruntime-node",
|
||||
"sharp",
|
||||
// node:* modules are handled by externalNodeBuiltinsPlugin
|
||||
];
|
||||
|
||||
export const WEB_IGNORE_MODULES = ["onnxruntime-node", "sharp", "fs", "path", "url", "stream", "stream/promises"];
|
||||
export const WEB_EXTERNAL_MODULES = ["onnxruntime-common", "onnxruntime-web"];
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
export const ROOT_DIR = path.join(__dirname, "../..");
|
||||
export const OUT_DIR = path.join(ROOT_DIR, DIST_FOLDER);
|
||||
|
||||
export const getEsbuildDevConfig = (rootDir) => ({
|
||||
bundle: true,
|
||||
treeShaking: true,
|
||||
logLevel: "info",
|
||||
entryPoints: [path.join(rootDir, "src/transformers.js")],
|
||||
platform: "neutral",
|
||||
format: "esm",
|
||||
sourcemap: true,
|
||||
logOverride: {
|
||||
// Suppress import.meta warning for CJS builds - it's handled gracefully in the code
|
||||
"empty-import-meta": "silent",
|
||||
},
|
||||
});
|
||||
|
||||
export const getEsbuildProdConfig = (rootDir) => ({
|
||||
...getEsbuildDevConfig(rootDir),
|
||||
logLevel: "warning",
|
||||
sourcemap: false,
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Plugin to automatically mark all node:* imports as external.
|
||||
* This prevents having to manually list all Node.js built-in modules.
|
||||
*/
|
||||
export const externalNodeBuiltinsPlugin = () => ({
|
||||
name: "external-node-builtins",
|
||||
setup(build) {
|
||||
// Mark all node:* imports as external
|
||||
build.onResolve({ filter: /^node:/ }, (args) => ({
|
||||
path: args.path,
|
||||
external: true,
|
||||
}));
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Plugin to ignore/exclude certain modules by returning an empty module.
|
||||
* Equivalent to webpack's resolve.alias with false value.
|
||||
*/
|
||||
export const ignoreModulesPlugin = (modules = []) => ({
|
||||
name: "ignore-modules",
|
||||
setup(build) {
|
||||
// Escape special regex characters in module names
|
||||
const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const escapedModules = modules.map(escapeRegex);
|
||||
|
||||
// Match both "module" and "node:module" patterns
|
||||
const patterns = escapedModules.flatMap((mod) => [mod, `node:${mod}`]);
|
||||
const filter = new RegExp(`^(${patterns.join("|")})$`);
|
||||
|
||||
build.onResolve({ filter }, (args) => {
|
||||
return { path: args.path, namespace: "ignore-modules" };
|
||||
});
|
||||
build.onLoad({ filter: /.*/, namespace: "ignore-modules" }, (args) => {
|
||||
switch (args.path) {
|
||||
case "node:stream":
|
||||
return {
|
||||
contents: `
|
||||
const noop = () => {};
|
||||
export default {};
|
||||
export const Readable = { fromWeb: noop };
|
||||
`,
|
||||
};
|
||||
case "node:stream/promises":
|
||||
return {
|
||||
contents: `
|
||||
const noop = () => {};
|
||||
export default {};
|
||||
export const pipeline = noop;
|
||||
`,
|
||||
};
|
||||
case "node:fs":
|
||||
case "node:path":
|
||||
case "node:url":
|
||||
case "sharp":
|
||||
case "onnxruntime-node":
|
||||
default:
|
||||
return {
|
||||
contents: `export default {};`,
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import path from "node:path";
|
||||
import { copyFileSync, unlinkSync, existsSync } from "node:fs";
|
||||
import { colors, createLogger } from "../../../../../scripts/logger.mjs";
|
||||
|
||||
const log = createLogger("transformers");
|
||||
|
||||
/**
|
||||
* Plugin to post-process build files.
|
||||
* Equivalent to webpack's PostBuildPlugin.
|
||||
*/
|
||||
export const postBuildPlugin = (distDir, rootDir) => {
|
||||
// it should copy the files only once. In watch mode for example it should not rerun every time
|
||||
let completed = false;
|
||||
|
||||
return {
|
||||
name: "post-build",
|
||||
setup(build) {
|
||||
build.onEnd(() => {
|
||||
if (completed) return;
|
||||
completed = true;
|
||||
|
||||
const ORT_JSEP_FILE = "ort-wasm-simd-threaded.jsep.mjs";
|
||||
const ORT_BUNDLE_FILE = "ort.bundle.min.mjs";
|
||||
|
||||
// 1. Remove unnecessary files
|
||||
const file = path.join(distDir, ORT_BUNDLE_FILE);
|
||||
if (existsSync(file)) unlinkSync(file);
|
||||
|
||||
// 2. Copy unbundled JSEP file
|
||||
const ORT_SOURCE_DIR = path.join(rootDir, "node_modules/onnxruntime-web/dist");
|
||||
const src = path.join(ORT_SOURCE_DIR, ORT_JSEP_FILE);
|
||||
|
||||
if (existsSync(src)) {
|
||||
const dest = path.join(distDir, ORT_JSEP_FILE);
|
||||
copyFileSync(src, dest);
|
||||
log.success(`${colors.gray}Copied ${ORT_JSEP_FILE}${colors.reset}`);
|
||||
} else {
|
||||
log.warning(`Could not find ${ORT_JSEP_FILE} in node_modules`);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Plugin to strip the "node:" prefix from module requests.
|
||||
* Equivalent to webpack's StripNodePrefixPlugin.
|
||||
*/
|
||||
export const stripNodePrefixPlugin = () => ({
|
||||
name: "strip-node-prefix",
|
||||
setup(build) {
|
||||
build.onResolve({ filter: /^node:/ }, (args) => {
|
||||
return {
|
||||
path: args.path.replace(/^node:/, ""),
|
||||
external: true,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { NODE_IGNORE_MODULES, NODE_EXTERNAL_MODULES, WEB_IGNORE_MODULES, WEB_EXTERNAL_MODULES } from "./constants.mjs";
|
||||
|
||||
/**
|
||||
* Build target configuration
|
||||
* Each target defines a specific build variant
|
||||
*/
|
||||
export const BUILD_TARGETS = [
|
||||
{
|
||||
name: "Bundle Build (ESM)",
|
||||
config: {
|
||||
name: "",
|
||||
suffix: ".js",
|
||||
format: "esm",
|
||||
ignoreModules: WEB_IGNORE_MODULES,
|
||||
usePostBuild: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Web Build (ESM)",
|
||||
config: {
|
||||
name: ".web",
|
||||
suffix: ".js",
|
||||
format: "esm",
|
||||
ignoreModules: WEB_IGNORE_MODULES,
|
||||
externalModules: WEB_EXTERNAL_MODULES,
|
||||
usePostBuild: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Node Build (ESM)",
|
||||
config: {
|
||||
name: ".node",
|
||||
suffix: ".mjs",
|
||||
format: "esm",
|
||||
ignoreModules: NODE_IGNORE_MODULES,
|
||||
externalModules: NODE_EXTERNAL_MODULES,
|
||||
usePostBuild: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Node Build (CJS)",
|
||||
config: {
|
||||
name: ".node",
|
||||
suffix: ".cjs",
|
||||
format: "cjs",
|
||||
ignoreModules: NODE_IGNORE_MODULES,
|
||||
externalModules: NODE_EXTERNAL_MODULES,
|
||||
usePostBuild: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,113 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { unlinkSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { OUT_DIR } from "./build/constants.mjs";
|
||||
import prepareOutDir from "../../../scripts/prepareOutDir.mjs";
|
||||
import { colors, createLogger } from "../../../scripts/logger.mjs";
|
||||
import { buildAllWithWatch } from "./build/buildAllWithWatch.mjs";
|
||||
|
||||
const log = createLogger("transformers");
|
||||
const startTime = performance.now();
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT_DIR = path.join(__dirname, "..");
|
||||
|
||||
prepareOutDir(OUT_DIR);
|
||||
|
||||
// Remove tsbuildinfo to force TypeScript to rebuild type declarations
|
||||
try {
|
||||
unlinkSync(`${ROOT_DIR}/types/tsconfig.tsbuildinfo`);
|
||||
} catch (err) {
|
||||
// File doesn't exist, that's fine
|
||||
}
|
||||
|
||||
log.section("BUILD");
|
||||
log.info("Building transformers.js with esbuild in watch mode...");
|
||||
|
||||
// Build all targets with watch mode
|
||||
const contexts = await buildAllWithWatch(log);
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = (endTime - startTime).toFixed(2);
|
||||
log.success(`All builds completed in ${colors.bright}${duration}ms${colors.reset}`);
|
||||
|
||||
// Generate initial TypeScript declarations, then start watch mode
|
||||
log.section("TYPES");
|
||||
log.info("Generating initial type declarations...");
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const tscBuild = spawn("tsc", ["--build"], {
|
||||
cwd: ROOT_DIR,
|
||||
stdio: "pipe",
|
||||
shell: true,
|
||||
});
|
||||
|
||||
tscBuild.stdout.on("data", (data) => {
|
||||
const output = data.toString().trim();
|
||||
if (output && output.includes("error")) {
|
||||
log.dim(output);
|
||||
}
|
||||
});
|
||||
|
||||
tscBuild.stderr.on("data", (data) => {
|
||||
const output = data.toString().trim();
|
||||
if (output) {
|
||||
log.error(output);
|
||||
}
|
||||
});
|
||||
|
||||
tscBuild.on("exit", (code) => {
|
||||
if (code === 0) {
|
||||
log.done("Type declarations generated");
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`TypeScript build failed with code ${code}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
log.info("Starting TypeScript watch mode...\n");
|
||||
|
||||
const tscWatch = spawn("tsc", ["--build", "--watch", "--preserveWatchOutput"], {
|
||||
cwd: ROOT_DIR,
|
||||
stdio: "pipe",
|
||||
shell: true,
|
||||
});
|
||||
|
||||
tscWatch.stdout.on("data", (data) => {
|
||||
const output = data.toString().trim();
|
||||
if (output) {
|
||||
output.split("\n").forEach((line) => {
|
||||
// Filter out verbose output, only show important messages
|
||||
if (
|
||||
line.includes("error") ||
|
||||
line.includes("File change detected") ||
|
||||
line.includes("Found 0 errors") ||
|
||||
(line.includes("Found") && line.includes("error"))
|
||||
) {
|
||||
log.dim(line);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
tscWatch.stderr.on("data", (data) => {
|
||||
const output = data.toString().trim();
|
||||
if (output) {
|
||||
output.split("\n").forEach((line) => {
|
||||
log.error(line);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
log.dim(`Watching for changes...\n`);
|
||||
|
||||
// Keep process alive and cleanup
|
||||
process.on("SIGINT", async () => {
|
||||
log.warning(`\nStopping watch mode...`);
|
||||
tscWatch.kill();
|
||||
await Promise.all(contexts.map((ctx) => ctx.dispose()));
|
||||
log.dim("Goodbye!");
|
||||
process.exit(0);
|
||||
});
|
||||
Reference in New Issue
Block a user