emsdk-env 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  /*!
2
2
  * name: emsdk-env
3
- * version: 0.7.0
3
+ * version: 0.9.0
4
4
  * description: Emscripten environment builder
5
5
  * author: Kouji Matsui (@kekyo@mi.kekyo.net)
6
6
  * license: MIT
7
7
  * repository.url: https://github.com/kekyo/emsdk-env
8
- * git.commit.hash: ddec7e7cc6c59a23b5c87cbd7f99f44e4da3e4b7
8
+ * git.commit.hash: 73d7e217903f020ef22daa9289362dcd19bddac1
9
9
  */
10
10
 
11
11
  "use strict";
@@ -575,6 +575,11 @@ const DEFAULT_IMPORT_LIB_DIR = "lib";
575
575
  const DEFAULT_WASM_BUILD_DIR = path.join(os.tmpdir(), "emsdk-env");
576
576
  const DEFAULT_EMSDK_TARGET_VERSION = "latest";
577
577
  const DEFAULT_WASM_OPT_ARGS = ["-Oz"];
578
+ const DEFAULT_GENERATED_LOADER_OUT_FILE = path.join(
579
+ "src",
580
+ "generated",
581
+ "wasm-loader.ts"
582
+ );
578
583
  let buildSequence = 0;
579
584
  const padNumber = (value, length = 2) => String(value).padStart(length, "0");
580
585
  const formatTimestamp = (date) => {
@@ -601,9 +606,53 @@ const normalizePrepareOptions = (options) => {
601
606
  ...rest
602
607
  };
603
608
  };
609
+ const parseStringKeyValueInput = (values) => {
610
+ const parsed = {};
611
+ for (const entry of values) {
612
+ const index = entry.indexOf("=");
613
+ if (index === -1) {
614
+ parsed[entry] = void 0;
615
+ continue;
616
+ }
617
+ const key = entry.slice(0, index);
618
+ const value = entry.slice(index + 1);
619
+ parsed[key] = value;
620
+ }
621
+ return parsed;
622
+ };
623
+ const isDefineMap = (input) => input instanceof Map;
624
+ const normalizeDefineInput = (input) => {
625
+ if (!input) {
626
+ return {};
627
+ }
628
+ if (Array.isArray(input)) {
629
+ return parseStringKeyValueInput(input);
630
+ }
631
+ if (isDefineMap(input)) {
632
+ return Object.fromEntries(input);
633
+ }
634
+ return { ...input };
635
+ };
636
+ const isLinkDirectiveMap = (input) => input instanceof Map;
637
+ const normalizeLinkDirectiveInput = (input) => {
638
+ if (!input) {
639
+ return {};
640
+ }
641
+ if (Array.isArray(input)) {
642
+ return parseStringKeyValueInput(input);
643
+ }
644
+ if (isLinkDirectiveMap(input)) {
645
+ return Object.fromEntries(input);
646
+ }
647
+ return { ...input };
648
+ };
604
649
  const mergeDefines = (common, target) => ({
605
- ...common != null ? common : {},
606
- ...target != null ? target : {}
650
+ ...normalizeDefineInput(common),
651
+ ...normalizeDefineInput(target)
652
+ });
653
+ const mergeLinkDirectives = (common, target) => ({
654
+ ...normalizeLinkDirectiveInput(common),
655
+ ...normalizeLinkDirectiveInput(target)
607
656
  });
608
657
  const resolveWasmOptEnabled = (common, target) => {
609
658
  var _a, _b;
@@ -611,10 +660,62 @@ const resolveWasmOptEnabled = (common, target) => {
611
660
  };
612
661
  const resolveWasmOptArgs = (common, target, env) => {
613
662
  var _a, _b;
614
- const commonArgs = (_a = common == null ? void 0 : common.args) != null ? _a : DEFAULT_WASM_OPT_ARGS;
615
- const targetArgs = (_b = target == null ? void 0 : target.args) != null ? _b : [];
663
+ const commonArgs = (_a = common == null ? void 0 : common.options) != null ? _a : DEFAULT_WASM_OPT_ARGS;
664
+ const targetArgs = (_b = target == null ? void 0 : target.options) != null ? _b : [];
616
665
  const mergedArgs = [...commonArgs, ...targetArgs];
617
- return expandArray(mergedArgs, env, "wasmOpt.args");
666
+ return expandArray(mergedArgs, env, "wasmOpt.options");
667
+ };
668
+ const stripOuterQuotes = (value) => {
669
+ const trimmed = value.trim();
670
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
671
+ return trimmed.slice(1, -1);
672
+ }
673
+ return trimmed;
674
+ };
675
+ const extractWasmBinaryFile = (value) => {
676
+ if (value.startsWith("WASM_BINARY_FILE=")) {
677
+ return value.slice("WASM_BINARY_FILE=".length);
678
+ }
679
+ const match = value.match(/^(?:-s|--settings)(?:=)?WASM_BINARY_FILE=(.+)$/);
680
+ if (match) {
681
+ return match[1];
682
+ }
683
+ return void 0;
684
+ };
685
+ const resolveWasmBinaryFileFromLinkOptions = (linkOptions) => {
686
+ for (let index = 0; index < linkOptions.length; index += 1) {
687
+ const option = linkOptions[index];
688
+ if (!option) {
689
+ continue;
690
+ }
691
+ if (option === "-s" || option === "--settings") {
692
+ const next = linkOptions[index + 1];
693
+ if (!next) {
694
+ continue;
695
+ }
696
+ const extracted2 = extractWasmBinaryFile(next);
697
+ if (extracted2) {
698
+ return stripOuterQuotes(extracted2);
699
+ }
700
+ }
701
+ const extracted = extractWasmBinaryFile(option);
702
+ if (extracted) {
703
+ return stripOuterQuotes(extracted);
704
+ }
705
+ }
706
+ return void 0;
707
+ };
708
+ const resolveWasmOptInputFile = (resolvedOutFile, resolvedLinkOptions) => {
709
+ const wasmBinaryFile = resolveWasmBinaryFileFromLinkOptions(resolvedLinkOptions);
710
+ if (wasmBinaryFile) {
711
+ return path.isAbsolute(wasmBinaryFile) ? wasmBinaryFile : path.resolve(path.dirname(resolvedOutFile), wasmBinaryFile);
712
+ }
713
+ const parsed = path.parse(resolvedOutFile);
714
+ if (parsed.ext.toLowerCase() === ".wasm") {
715
+ return resolvedOutFile;
716
+ }
717
+ const baseName = parsed.name.toLowerCase().endsWith(".wasm") ? parsed.name : `${parsed.name}.wasm`;
718
+ return path.join(parsed.dir, baseName);
618
719
  };
619
720
  const resolvePath = (rootDir, value) => path.isAbsolute(value) ? value : path.resolve(rootDir, value);
620
721
  const expandPlaceholders = (value, env, label) => value.replace(/\{([A-Z0-9_]+)\}/g, (_match, key) => {
@@ -636,6 +737,28 @@ const resolveDefines = (defines, env) => {
636
737
  }
637
738
  return resolved;
638
739
  };
740
+ const resolveLinkDirectiveValue = (value, env, label) => {
741
+ if (typeof value === "string") {
742
+ return expandPlaceholders(value, env, label);
743
+ }
744
+ if (Array.isArray(value)) {
745
+ return value.map(
746
+ (entry, index) => expandPlaceholders(entry, env, `${label}[${index}]`)
747
+ );
748
+ }
749
+ return value;
750
+ };
751
+ const resolveLinkDirectives = (directives, env) => {
752
+ const resolved = {};
753
+ for (const [key, value] of Object.entries(directives)) {
754
+ resolved[key] = resolveLinkDirectiveValue(
755
+ value,
756
+ env,
757
+ `linkDirectives.${key}`
758
+ );
759
+ }
760
+ return resolved;
761
+ };
639
762
  const resolveIncludeDirs = (includeDirs, env, rootDir) => {
640
763
  const expanded = expandArray(includeDirs, env, "includeDirs");
641
764
  return expanded.map((value) => resolvePath(rootDir, value));
@@ -654,7 +777,18 @@ const resolveSourcesFromPatterns = async (patterns, env, srcDir, label) => {
654
777
  sources.sort();
655
778
  return sources;
656
779
  };
657
- const buildDefineFlags = (defines) => Object.entries(defines).map(([key, value]) => `-D${key}=${String(value)}`);
780
+ const buildDefineFlags = (defines) => Object.entries(defines).flatMap(
781
+ ([key, value]) => value === null || value === void 0 ? [`-D${key}`] : [`-D${key}=${String(value)}`]
782
+ );
783
+ const serializeLinkDirectiveValue = (value) => Array.isArray(value) ? JSON.stringify(value) : String(value);
784
+ const buildLinkDirectiveFlags = (directives) => {
785
+ if (Object.keys(directives).length === 0) {
786
+ return [];
787
+ }
788
+ return Object.entries(directives).flatMap(
789
+ ([key, value]) => value === null || value === void 0 ? ["-s", key] : ["-s", `${key}=${serializeLinkDirectiveValue(value)}`]
790
+ );
791
+ };
658
792
  const buildExportFlags = (exports$1) => {
659
793
  if (exports$1.length === 0) {
660
794
  return [];
@@ -707,6 +841,248 @@ const dedupeValues = (values) => {
707
841
  }
708
842
  return deduped;
709
843
  };
844
+ const isSubPath = (parentDir, targetPath) => {
845
+ const rel = path.relative(parentDir, targetPath);
846
+ if (rel === "") {
847
+ return true;
848
+ }
849
+ return !rel.startsWith("..") && !path.isAbsolute(rel);
850
+ };
851
+ const readTextIfExists = async (filePath) => {
852
+ try {
853
+ return await promises.readFile(filePath, "utf8");
854
+ } catch (error) {
855
+ const nodeError = error;
856
+ if (nodeError.code === "ENOENT") {
857
+ return void 0;
858
+ }
859
+ throw error;
860
+ }
861
+ };
862
+ const writeTextIfChanged = async (filePath, content) => {
863
+ const existing = await readTextIfExists(filePath);
864
+ if (existing === content) {
865
+ return false;
866
+ }
867
+ await ensureDirectory(path.dirname(filePath));
868
+ await promises.writeFile(filePath, content, "utf8");
869
+ return true;
870
+ };
871
+ const toPascalCaseIdentifier = (value) => {
872
+ const tokens = value.split(/[^A-Za-z0-9]+/).map((token) => token.trim()).filter((token) => token.length > 0);
873
+ if (tokens.length === 0) {
874
+ throw new Error(`Cannot derive loader function name from target: ${value}`);
875
+ }
876
+ const pascal = tokens.map((token) => token.charAt(0).toUpperCase() + token.slice(1)).join("");
877
+ return /^[0-9]/.test(pascal) ? `Target${pascal}` : pascal;
878
+ };
879
+ const toRelativeImportSpecifier = (fromFile, toFile) => {
880
+ const rel = path.relative(path.dirname(fromFile), toFile).replace(/\\/g, "/");
881
+ return rel.startsWith(".") ? rel : `./${rel}`;
882
+ };
883
+ const buildGeneratedLoaderContent = (generatedLoaderFile, targets) => {
884
+ const targetBlocks = targets.map((target) => {
885
+ const specifier = JSON.stringify(
886
+ toRelativeImportSpecifier(generatedLoaderFile, target.outFile)
887
+ );
888
+ return `export const ${target.functionName} = async <T extends object>(
889
+ options?: TargetWasmLoadOptions
890
+ ): Promise<WasmInstance<T>> => {
891
+ const source = options?.url ?? new URL(${specifier}, import.meta.url);
892
+ return await loadWasm<T>(source, {
893
+ imports: options?.imports,
894
+ });
895
+ };`;
896
+ }).join("\n\n");
897
+ return `// Generated by emsdk-env. DO NOT EDIT.
898
+
899
+ export type WasmSource =
900
+ | string
901
+ | URL
902
+ | ArrayBuffer
903
+ | ArrayBufferView
904
+ | Response;
905
+
906
+ export interface WasmLoadOptions {
907
+ readonly imports?: WebAssembly.Imports;
908
+ }
909
+
910
+ export interface TargetWasmLoadOptions extends WasmLoadOptions {
911
+ readonly url?: string | URL;
912
+ }
913
+
914
+ export interface WasmInstance<T extends object> {
915
+ readonly exports: T;
916
+ readonly memory: WebAssembly.Memory;
917
+ readonly table: WebAssembly.Table | undefined;
918
+ readonly rawExports: WebAssembly.Exports;
919
+ readonly module: WebAssembly.Module;
920
+ readonly instance: WebAssembly.Instance;
921
+ readonly initialize: (() => unknown) | undefined;
922
+ readonly start: (() => unknown) | undefined;
923
+ }
924
+
925
+ const resolveWasmBytes = async (source: WasmSource): Promise<ArrayBuffer> => {
926
+ if (typeof Response !== 'undefined' && source instanceof Response) {
927
+ return await source.arrayBuffer();
928
+ }
929
+ if (source instanceof URL || typeof source === 'string') {
930
+ const response = await fetch(source);
931
+ if (!response.ok) {
932
+ throw new Error(\`Failed to fetch wasm: \${response.url}\`);
933
+ }
934
+ return await response.arrayBuffer();
935
+ }
936
+ if (ArrayBuffer.isView(source)) {
937
+ const view = new Uint8Array(
938
+ source.buffer,
939
+ source.byteOffset,
940
+ source.byteLength
941
+ );
942
+ return view.slice().buffer;
943
+ }
944
+ return source;
945
+ };
946
+
947
+ const getImportValue = (
948
+ imports: WebAssembly.Imports | undefined,
949
+ moduleName: string,
950
+ name: string
951
+ ) => {
952
+ const moduleImports = imports?.[moduleName];
953
+ if (!moduleImports || typeof moduleImports !== 'object') {
954
+ return undefined;
955
+ }
956
+ return (moduleImports as Record<string, unknown>)[name];
957
+ };
958
+
959
+ const resolveMemory = (
960
+ module: WebAssembly.Module,
961
+ instance: WebAssembly.Instance,
962
+ imports: WebAssembly.Imports | undefined
963
+ ) => {
964
+ let memory: WebAssembly.Memory | undefined;
965
+ for (const entry of WebAssembly.Module.exports(module)) {
966
+ if (entry.kind !== 'memory') {
967
+ continue;
968
+ }
969
+ if (memory) {
970
+ throw new Error('Multiple wasm memories are not supported.');
971
+ }
972
+ const value = instance.exports[entry.name];
973
+ if (!(value instanceof WebAssembly.Memory)) {
974
+ throw new Error(\`Export is not a WebAssembly.Memory: \${entry.name}\`);
975
+ }
976
+ memory = value;
977
+ }
978
+ if (memory) {
979
+ return memory;
980
+ }
981
+ for (const entry of WebAssembly.Module.imports(module)) {
982
+ if (entry.kind !== 'memory') {
983
+ continue;
984
+ }
985
+ if (memory) {
986
+ throw new Error('Multiple wasm memories are not supported.');
987
+ }
988
+ const value = getImportValue(imports, entry.module, entry.name);
989
+ if (!(value instanceof WebAssembly.Memory)) {
990
+ throw new Error(
991
+ \`Imported value is not a WebAssembly.Memory: \${entry.module}.\${entry.name}\`
992
+ );
993
+ }
994
+ memory = value;
995
+ }
996
+ if (!memory) {
997
+ throw new Error('WASM memory export/import was not resolved.');
998
+ }
999
+ return memory;
1000
+ };
1001
+
1002
+ const resolveTable = (
1003
+ module: WebAssembly.Module,
1004
+ instance: WebAssembly.Instance,
1005
+ imports: WebAssembly.Imports | undefined
1006
+ ) => {
1007
+ let table: WebAssembly.Table | undefined;
1008
+ for (const entry of WebAssembly.Module.exports(module)) {
1009
+ if (entry.kind !== 'table') {
1010
+ continue;
1011
+ }
1012
+ if (table) {
1013
+ throw new Error('Multiple wasm tables are not supported.');
1014
+ }
1015
+ const value = instance.exports[entry.name];
1016
+ if (!(value instanceof WebAssembly.Table)) {
1017
+ throw new Error(\`Export is not a WebAssembly.Table: \${entry.name}\`);
1018
+ }
1019
+ table = value;
1020
+ }
1021
+ if (table) {
1022
+ return table;
1023
+ }
1024
+ for (const entry of WebAssembly.Module.imports(module)) {
1025
+ if (entry.kind !== 'table') {
1026
+ continue;
1027
+ }
1028
+ if (table) {
1029
+ throw new Error('Multiple wasm tables are not supported.');
1030
+ }
1031
+ const value = getImportValue(imports, entry.module, entry.name);
1032
+ if (!(value instanceof WebAssembly.Table)) {
1033
+ throw new Error(
1034
+ \`Imported value is not a WebAssembly.Table: \${entry.module}.\${entry.name}\`
1035
+ );
1036
+ }
1037
+ table = value;
1038
+ }
1039
+ return table;
1040
+ };
1041
+
1042
+ export const loadWasm = async <T extends object>(
1043
+ source: WasmSource,
1044
+ options?: WasmLoadOptions
1045
+ ): Promise<WasmInstance<T>> => {
1046
+ const bytes = await resolveWasmBytes(source);
1047
+ const module = await WebAssembly.compile(bytes);
1048
+ const instance = await WebAssembly.instantiate(module, options?.imports ?? {});
1049
+
1050
+ const functionExports: Record<string, (...args: unknown[]) => unknown> = {};
1051
+ for (const entry of WebAssembly.Module.exports(module)) {
1052
+ if (entry.kind !== 'function') {
1053
+ continue;
1054
+ }
1055
+ const value = instance.exports[entry.name];
1056
+ if (typeof value !== 'function') {
1057
+ throw new Error(\`Export is not a function: \${entry.name}\`);
1058
+ }
1059
+ functionExports[entry.name] = value as (...args: unknown[]) => unknown;
1060
+ }
1061
+
1062
+ const initialize =
1063
+ typeof instance.exports._initialize === 'function'
1064
+ ? (instance.exports._initialize as () => unknown)
1065
+ : undefined;
1066
+ const start =
1067
+ typeof instance.exports._start === 'function'
1068
+ ? (instance.exports._start as () => unknown)
1069
+ : undefined;
1070
+
1071
+ return {
1072
+ exports: functionExports as T,
1073
+ memory: resolveMemory(module, instance, options?.imports),
1074
+ table: resolveTable(module, instance, options?.imports),
1075
+ rawExports: instance.exports,
1076
+ module,
1077
+ instance,
1078
+ initialize,
1079
+ start,
1080
+ };
1081
+ };
1082
+
1083
+ ${targetBlocks}
1084
+ `;
1085
+ };
710
1086
  const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
711
1087
  const resolvePackageJsonPath = async (startPath, packageName) => {
712
1088
  let current = path.dirname(startPath);
@@ -807,6 +1183,42 @@ const resolveImportDirectories = async (rootDir, imports) => {
807
1183
  libDirs: dedupeValues(libDirs)
808
1184
  };
809
1185
  };
1186
+ const resolveGeneratedLoaderOutFile = (rootDir, env, generatedLoader) => {
1187
+ var _a;
1188
+ if (!(generatedLoader == null ? void 0 : generatedLoader.enable)) {
1189
+ return void 0;
1190
+ }
1191
+ const rawOutFile = expandPlaceholders(
1192
+ (_a = generatedLoader.outFile) != null ? _a : DEFAULT_GENERATED_LOADER_OUT_FILE,
1193
+ env,
1194
+ "generatedLoader.outFile"
1195
+ );
1196
+ return resolvePath(rootDir, rawOutFile);
1197
+ };
1198
+ const resolveGeneratedLoaderWatchDirs = (rootDir, srcDir, commonIncludeDirs, env, targetEntries, importIncludeDirs) => {
1199
+ const dirs = [srcDir, ...resolveIncludeDirs(commonIncludeDirs, env, rootDir)];
1200
+ for (const [targetName, target] of targetEntries) {
1201
+ if (!target.includeDirs || target.includeDirs.length === 0) {
1202
+ continue;
1203
+ }
1204
+ const targetEnv = {
1205
+ ...env,
1206
+ TARGET_NAME: targetName
1207
+ };
1208
+ dirs.push(...resolveIncludeDirs(target.includeDirs, targetEnv, rootDir));
1209
+ }
1210
+ dirs.push(...importIncludeDirs);
1211
+ return dedupeValues(dirs);
1212
+ };
1213
+ const validateGeneratedLoaderOutFile = (generatedLoaderFile, watchDirs) => {
1214
+ for (const dir of watchDirs) {
1215
+ if (isSubPath(dir, generatedLoaderFile)) {
1216
+ throw new Error(
1217
+ `generatedLoader.outFile must not be placed under watched directory: ${generatedLoaderFile}`
1218
+ );
1219
+ }
1220
+ }
1221
+ };
810
1222
  const buildCompileArgs = (options, includeDirs, defines, env, rootDir) => {
811
1223
  const resolvedOptions = expandArray(options, env, "options");
812
1224
  const includeArgs = resolveIncludeDirs(includeDirs, env, rootDir).map(
@@ -889,6 +1301,22 @@ const buildWasm = async (options) => {
889
1301
  const importIncludeDirs = importDirectories.includeDirs;
890
1302
  const importLibDirs = importDirectories.libDirs;
891
1303
  const linkLibDirs = dedupeValues([libDir, ...importLibDirs]);
1304
+ const generatedLoaderFile = resolveGeneratedLoaderOutFile(
1305
+ rootDir,
1306
+ envWithDirs,
1307
+ options.generatedLoader
1308
+ );
1309
+ if (generatedLoaderFile) {
1310
+ const watchDirs = resolveGeneratedLoaderWatchDirs(
1311
+ rootDir,
1312
+ srcDir,
1313
+ commonIncludeDirs,
1314
+ envWithDirs,
1315
+ targetEntries,
1316
+ importIncludeDirs
1317
+ );
1318
+ validateGeneratedLoaderOutFile(generatedLoaderFile, watchDirs);
1319
+ }
892
1320
  logger.debug(`Detected rootDir: '${rootDir}'`);
893
1321
  logger.debug(`Detected srcDir: '${srcDir}'`);
894
1322
  logger.debug(`Detected outDir: '${outDir}'`);
@@ -905,6 +1333,9 @@ const buildWasm = async (options) => {
905
1333
  logger.debug(
906
1334
  `Detected importLibDirs: [${importLibDirs.map((p) => `'${p}'`).join(",")}]`
907
1335
  );
1336
+ if (generatedLoaderFile) {
1337
+ logger.debug(`Detected generatedLoaderFile: '${generatedLoaderFile}'`);
1338
+ }
908
1339
  await ensureDirectory(outDir);
909
1340
  await ensureDirectory(libDir);
910
1341
  await ensureDirectory(buildDir);
@@ -927,6 +1358,7 @@ const buildWasm = async (options) => {
927
1358
  return wasmOptCommand;
928
1359
  };
929
1360
  const outFiles = {};
1361
+ let resultGeneratedLoaderFile;
930
1362
  const buildTargets = async (expectedType) => {
931
1363
  var _a2;
932
1364
  for (const [targetName, target] of targetEntries) {
@@ -940,6 +1372,11 @@ const buildWasm = async (options) => {
940
1372
  `linkOptions is not supported for archive target: ${targetName}`
941
1373
  );
942
1374
  }
1375
+ if (target.linkDirectives !== void 0) {
1376
+ throw new Error(
1377
+ `linkDirectives is not supported for archive target: ${targetName}`
1378
+ );
1379
+ }
943
1380
  if (target.exports !== void 0) {
944
1381
  throw new Error(
945
1382
  `exports is not supported for archive target: ${targetName}`
@@ -955,6 +1392,7 @@ const buildWasm = async (options) => {
955
1392
  ...ensureArray(common.linkOptions),
956
1393
  ...ensureArray(target.linkOptions)
957
1394
  ];
1395
+ const mergedLinkDirectives = targetType === "archive" ? {} : mergeLinkDirectives(common.linkDirectives, target.linkDirectives);
958
1396
  const mergedExports = targetType === "archive" ? [] : [...ensureArray(common.exports), ...ensureArray(target.exports)];
959
1397
  const wasmOptEnabled = targetType === "archive" ? false : resolveWasmOptEnabled(common.wasmOpt, target.wasmOpt);
960
1398
  const baseCompileOptions = [
@@ -1014,7 +1452,12 @@ const buildWasm = async (options) => {
1014
1452
  const targetBuildDir = path.resolve(buildRunDir, targetName);
1015
1453
  await promises.rm(targetBuildDir, { recursive: true, force: true });
1016
1454
  await ensureDirectory(targetBuildDir);
1017
- const resolvedLinkOptions = targetType === "archive" ? [] : expandArray(mergedLinkOptions, targetEnv, "linkOptions");
1455
+ const resolvedLinkDirectives = targetType === "archive" ? {} : resolveLinkDirectives(mergedLinkDirectives, targetEnv);
1456
+ const linkDirectiveArgs = buildLinkDirectiveFlags(resolvedLinkDirectives);
1457
+ const resolvedLinkOptions = targetType === "archive" ? [] : [
1458
+ ...linkDirectiveArgs,
1459
+ ...expandArray(mergedLinkOptions, targetEnv, "linkOptions")
1460
+ ];
1018
1461
  const resolvedExports = targetType === "archive" ? [] : expandArray(mergedExports, targetEnv, "exports");
1019
1462
  const exportArgs = buildExportFlags(resolvedExports);
1020
1463
  const resolvedWasmOptArgs = wasmOptEnabled ? resolveWasmOptArgs(common.wasmOpt, target.wasmOpt, targetEnv) : [];
@@ -1026,7 +1469,6 @@ const buildWasm = async (options) => {
1026
1469
  rootDir
1027
1470
  );
1028
1471
  const groupCompileArgs = sourceGroups.map((group) => {
1029
- var _a3;
1030
1472
  const groupOptions = [
1031
1473
  ...baseCompileOptions,
1032
1474
  ...ensureArray(group == null ? void 0 : group.options)
@@ -1035,7 +1477,7 @@ const buildWasm = async (options) => {
1035
1477
  ...baseIncludeDirs,
1036
1478
  ...ensureArray(group == null ? void 0 : group.includeDirs)
1037
1479
  ];
1038
- const groupDefines = mergeDefines(baseDefines, (_a3 = group == null ? void 0 : group.defines) != null ? _a3 : {});
1480
+ const groupDefines = mergeDefines(baseDefines, group == null ? void 0 : group.defines);
1039
1481
  return buildCompileArgs(
1040
1482
  groupOptions,
1041
1483
  groupIncludeDirs,
@@ -1152,9 +1594,18 @@ const buildWasm = async (options) => {
1152
1594
  emsdkOptions.signal
1153
1595
  );
1154
1596
  if (wasmOptEnabled) {
1155
- const tempOutFile = `${resolvedOutFile}.opt`;
1156
- const wasmOptArgs = [
1597
+ const wasmOptInput = resolveWasmOptInputFile(
1157
1598
  resolvedOutFile,
1599
+ resolvedLinkOptions
1600
+ );
1601
+ if (!await pathExists(wasmOptInput)) {
1602
+ throw new Error(
1603
+ `wasm-opt enabled but wasm binary not found: ${wasmOptInput}`
1604
+ );
1605
+ }
1606
+ const tempOutFile = `${wasmOptInput}.opt`;
1607
+ const wasmOptArgs = [
1608
+ wasmOptInput,
1158
1609
  "-o",
1159
1610
  tempOutFile,
1160
1611
  ...resolvedWasmOptArgs
@@ -1169,8 +1620,8 @@ const buildWasm = async (options) => {
1169
1620
  buildEnv,
1170
1621
  emsdkOptions.signal
1171
1622
  );
1172
- await promises.rm(resolvedOutFile, { force: true });
1173
- await promises.rename(tempOutFile, resolvedOutFile);
1623
+ await promises.rm(wasmOptInput, { force: true });
1624
+ await promises.rename(tempOutFile, wasmOptInput);
1174
1625
  }
1175
1626
  }
1176
1627
  outFiles[targetName] = resolvedOutFile;
@@ -1179,6 +1630,40 @@ const buildWasm = async (options) => {
1179
1630
  try {
1180
1631
  await buildTargets("archive");
1181
1632
  await buildTargets("wasm");
1633
+ if (generatedLoaderFile) {
1634
+ const generatedTargets = [];
1635
+ const functionNames = /* @__PURE__ */ new Set();
1636
+ for (const [targetName, target] of targetEntries) {
1637
+ if (resolveTargetType(target.type) !== "wasm") {
1638
+ continue;
1639
+ }
1640
+ const outFile = outFiles[targetName];
1641
+ if (!outFile) {
1642
+ continue;
1643
+ }
1644
+ const functionName = `load${toPascalCaseIdentifier(targetName)}Wasm`;
1645
+ if (functionNames.has(functionName)) {
1646
+ throw new Error(
1647
+ `Generated loader function name collision: ${functionName}`
1648
+ );
1649
+ }
1650
+ functionNames.add(functionName);
1651
+ generatedTargets.push({
1652
+ targetName,
1653
+ functionName,
1654
+ outFile
1655
+ });
1656
+ }
1657
+ const content = buildGeneratedLoaderContent(
1658
+ generatedLoaderFile,
1659
+ generatedTargets
1660
+ );
1661
+ const didWrite = await writeTextIfChanged(generatedLoaderFile, content);
1662
+ logger.info(
1663
+ didWrite ? `Generated loader: ${path.relative(rootDir, generatedLoaderFile)}` : `Generated loader unchanged: ${path.relative(rootDir, generatedLoaderFile)}`
1664
+ );
1665
+ resultGeneratedLoaderFile = generatedLoaderFile;
1666
+ }
1182
1667
  } finally {
1183
1668
  if (cleanupBuildDir) {
1184
1669
  await promises.rm(buildRunDir, { recursive: true, force: true });
@@ -1186,10 +1671,11 @@ const buildWasm = async (options) => {
1186
1671
  }
1187
1672
  return {
1188
1673
  emsdkRoot,
1189
- outFiles
1674
+ outFiles,
1675
+ ...resultGeneratedLoaderFile ? { generatedLoaderFile: resultGeneratedLoaderFile } : {}
1190
1676
  };
1191
1677
  };
1192
1678
  exports.buildWasm = buildWasm;
1193
1679
  exports.createConsoleLogger = createConsoleLogger;
1194
1680
  exports.prepareEmsdk = prepareEmsdk;
1195
- //# sourceMappingURL=build-CjKDHGn4.cjs.map
1681
+ //# sourceMappingURL=build-CgmcFNSR.cjs.map