emsdk-env 0.8.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.
package/README.md CHANGED
@@ -28,6 +28,8 @@ export default defineConfig({
28
28
  plugins: [
29
29
  // Add as a plugin
30
30
  emsdkEnv({
31
+ // Generate a runtime loader code
32
+ generatedLoader: { enable: true },
31
33
  // Build targets
32
34
  targets: {
33
35
  // Generate "add.wasm"
@@ -35,7 +37,9 @@ export default defineConfig({
35
37
  // Compiler options
36
38
  options: ['-O3', '-std=c99'],
37
39
  // Linker options
38
- linkOptions: ['-s', 'STANDALONE_WASM=1', '--no-entry'],
40
+ linkOptions: ['--no-entry'],
41
+ // Linker directives
42
+ linkDirectives: { STANDALONE_WASM: 1 },
39
43
  // Exported symbols
40
44
  exports: ['_add'],
41
45
  },
@@ -86,12 +90,15 @@ project/
86
90
  ├── package.json
87
91
  ├── vite.config.ts
88
92
  ├── src/
93
+ │ ├── generated/
94
+ │ │ └── wasm-loader.ts // (Generate automatically)
89
95
  │ └── wasm/
90
- │ └── add.wasm
96
+ │ └── add.wasm // (Built WASM binary)
91
97
  └── wasm/
92
98
  └── add.c
93
99
  ```
94
100
 
101
+ - `wasm-loader.ts` is helper code that loads and makes WASM binaries usable.
95
102
  - In addition to the above, a temporary build directory is created under the OS temp directory.
96
103
  The default location is `${TMPDIR}/emsdk-env` (typically `/tmp/emsdk-env` on Unix).
97
104
  This directory is used during the build process and is typically deleted after the build completes.
@@ -99,7 +106,36 @@ project/
99
106
 
100
107
  Of course, you can change these. Specify them in the Vite plugin options.
101
108
 
102
- You might find it odd that the built binary is placed in `src/wasm/`, but this is because the Vite server defaults to a path where it can easily access WASM binaries.
109
+ You might find it odd that the built binary is placed in `src/wasm/`,
110
+ but this is because the Vite server defaults to a path where it can easily access WASM binaries.
111
+
112
+ If `generatedLoader.enable` is set to `true`, emsdk-env also generates a WASM loader helper code by default at `src/generated/wasm-loader.ts`.
113
+ That loader can call the final WASM exports directly:
114
+
115
+ ```typescript
116
+ import { loadAddWasm } from './generated/wasm-loader';
117
+
118
+ // WASM exported function declaration (You need to define it)
119
+ interface AddExports {
120
+ add?: (a: number, b: number) => number;
121
+ }
122
+
123
+ // Load WASM binary and instantiates it
124
+ const wasm = await loadAddWasm<AddExports>();
125
+
126
+ // Get `add()` function entry point
127
+ const add = wasm.exports.add;
128
+ if (typeof add !== 'function') {
129
+ throw new Error('add function not found in wasm exports.');
130
+ }
131
+
132
+ // Then use it now
133
+ const result = add(1, 2);
134
+ ```
135
+
136
+ - You need to define WASM export functions yourself.
137
+ When doing so, the symbol name for the exported function is the same as the C/C++ function name in TypeScript,
138
+ but the symbol name specified in `exports: [...]` typically requires an underscore prefix (`add()` --> `_add`).
103
139
 
104
140
  If you plan to operate with the default settings, there is essentially no configuration work required.
105
141
 
@@ -1,17 +1,17 @@
1
1
  /*!
2
2
  * name: emsdk-env
3
- * version: 0.8.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: 8c3f97866a85cc6c99383eff3526c04077032eac
8
+ * git.commit.hash: 73d7e217903f020ef22daa9289362dcd19bddac1
9
9
  */
10
10
 
11
- import { mkdir, access, constants, mkdtemp, rename, rm, readFile } from "fs/promises";
11
+ import { mkdir, access, constants, mkdtemp, rename, rm, writeFile, readFile } from "fs/promises";
12
12
  import { homedir, tmpdir } from "os";
13
13
  import { glob } from "glob";
14
- import { join, resolve, isAbsolute, dirname, relative, parse } from "path";
14
+ import { join, resolve, relative, isAbsolute, dirname, parse } from "path";
15
15
  import { spawn } from "child_process";
16
16
  const __NOOP_HANDLER = () => {
17
17
  };
@@ -552,6 +552,11 @@ const DEFAULT_IMPORT_LIB_DIR = "lib";
552
552
  const DEFAULT_WASM_BUILD_DIR = join(tmpdir(), "emsdk-env");
553
553
  const DEFAULT_EMSDK_TARGET_VERSION = "latest";
554
554
  const DEFAULT_WASM_OPT_ARGS = ["-Oz"];
555
+ const DEFAULT_GENERATED_LOADER_OUT_FILE = join(
556
+ "src",
557
+ "generated",
558
+ "wasm-loader.ts"
559
+ );
555
560
  let buildSequence = 0;
556
561
  const padNumber = (value, length = 2) => String(value).padStart(length, "0");
557
562
  const formatTimestamp = (date) => {
@@ -578,7 +583,7 @@ const normalizePrepareOptions = (options) => {
578
583
  ...rest
579
584
  };
580
585
  };
581
- const parseKeyValueInput = (values) => {
586
+ const parseStringKeyValueInput = (values) => {
582
587
  const parsed = {};
583
588
  for (const entry of values) {
584
589
  const index = entry.indexOf("=");
@@ -592,24 +597,40 @@ const parseKeyValueInput = (values) => {
592
597
  }
593
598
  return parsed;
594
599
  };
595
- const isKeyValueMap = (value) => value instanceof Map;
596
- const normalizeKeyValueInput = (input) => {
600
+ const isDefineMap = (input) => input instanceof Map;
601
+ const normalizeDefineInput = (input) => {
602
+ if (!input) {
603
+ return {};
604
+ }
605
+ if (Array.isArray(input)) {
606
+ return parseStringKeyValueInput(input);
607
+ }
608
+ if (isDefineMap(input)) {
609
+ return Object.fromEntries(input);
610
+ }
611
+ return { ...input };
612
+ };
613
+ const isLinkDirectiveMap = (input) => input instanceof Map;
614
+ const normalizeLinkDirectiveInput = (input) => {
597
615
  if (!input) {
598
616
  return {};
599
617
  }
600
618
  if (Array.isArray(input)) {
601
- return parseKeyValueInput(input);
619
+ return parseStringKeyValueInput(input);
602
620
  }
603
- if (isKeyValueMap(input)) {
621
+ if (isLinkDirectiveMap(input)) {
604
622
  return Object.fromEntries(input);
605
623
  }
606
624
  return { ...input };
607
625
  };
608
626
  const mergeDefines = (common, target) => ({
609
- ...normalizeKeyValueInput(common),
610
- ...normalizeKeyValueInput(target)
627
+ ...normalizeDefineInput(common),
628
+ ...normalizeDefineInput(target)
629
+ });
630
+ const mergeLinkDirectives = (common, target) => ({
631
+ ...normalizeLinkDirectiveInput(common),
632
+ ...normalizeLinkDirectiveInput(target)
611
633
  });
612
- const mergeLinkDirectives = (common, target) => mergeDefines(common, target);
613
634
  const resolveWasmOptEnabled = (common, target) => {
614
635
  var _a, _b;
615
636
  return (_b = (_a = target == null ? void 0 : target.enable) != null ? _a : common == null ? void 0 : common.enable) != null ? _b : false;
@@ -693,14 +714,25 @@ const resolveDefines = (defines, env) => {
693
714
  }
694
715
  return resolved;
695
716
  };
717
+ const resolveLinkDirectiveValue = (value, env, label) => {
718
+ if (typeof value === "string") {
719
+ return expandPlaceholders(value, env, label);
720
+ }
721
+ if (Array.isArray(value)) {
722
+ return value.map(
723
+ (entry, index) => expandPlaceholders(entry, env, `${label}[${index}]`)
724
+ );
725
+ }
726
+ return value;
727
+ };
696
728
  const resolveLinkDirectives = (directives, env) => {
697
729
  const resolved = {};
698
730
  for (const [key, value] of Object.entries(directives)) {
699
- if (typeof value === "string") {
700
- resolved[key] = expandPlaceholders(value, env, `linkDirectives.${key}`);
701
- } else {
702
- resolved[key] = value;
703
- }
731
+ resolved[key] = resolveLinkDirectiveValue(
732
+ value,
733
+ env,
734
+ `linkDirectives.${key}`
735
+ );
704
736
  }
705
737
  return resolved;
706
738
  };
@@ -725,12 +757,13 @@ const resolveSourcesFromPatterns = async (patterns, env, srcDir, label) => {
725
757
  const buildDefineFlags = (defines) => Object.entries(defines).flatMap(
726
758
  ([key, value]) => value === null || value === void 0 ? [`-D${key}`] : [`-D${key}=${String(value)}`]
727
759
  );
760
+ const serializeLinkDirectiveValue = (value) => Array.isArray(value) ? JSON.stringify(value) : String(value);
728
761
  const buildLinkDirectiveFlags = (directives) => {
729
762
  if (Object.keys(directives).length === 0) {
730
763
  return [];
731
764
  }
732
765
  return Object.entries(directives).flatMap(
733
- ([key, value]) => value === null || value === void 0 ? ["-s", key] : ["-s", `${key}=${String(value)}`]
766
+ ([key, value]) => value === null || value === void 0 ? ["-s", key] : ["-s", `${key}=${serializeLinkDirectiveValue(value)}`]
734
767
  );
735
768
  };
736
769
  const buildExportFlags = (exports$1) => {
@@ -785,6 +818,248 @@ const dedupeValues = (values) => {
785
818
  }
786
819
  return deduped;
787
820
  };
821
+ const isSubPath = (parentDir, targetPath) => {
822
+ const rel = relative(parentDir, targetPath);
823
+ if (rel === "") {
824
+ return true;
825
+ }
826
+ return !rel.startsWith("..") && !isAbsolute(rel);
827
+ };
828
+ const readTextIfExists = async (filePath) => {
829
+ try {
830
+ return await readFile(filePath, "utf8");
831
+ } catch (error) {
832
+ const nodeError = error;
833
+ if (nodeError.code === "ENOENT") {
834
+ return void 0;
835
+ }
836
+ throw error;
837
+ }
838
+ };
839
+ const writeTextIfChanged = async (filePath, content) => {
840
+ const existing = await readTextIfExists(filePath);
841
+ if (existing === content) {
842
+ return false;
843
+ }
844
+ await ensureDirectory(dirname(filePath));
845
+ await writeFile(filePath, content, "utf8");
846
+ return true;
847
+ };
848
+ const toPascalCaseIdentifier = (value) => {
849
+ const tokens = value.split(/[^A-Za-z0-9]+/).map((token) => token.trim()).filter((token) => token.length > 0);
850
+ if (tokens.length === 0) {
851
+ throw new Error(`Cannot derive loader function name from target: ${value}`);
852
+ }
853
+ const pascal = tokens.map((token) => token.charAt(0).toUpperCase() + token.slice(1)).join("");
854
+ return /^[0-9]/.test(pascal) ? `Target${pascal}` : pascal;
855
+ };
856
+ const toRelativeImportSpecifier = (fromFile, toFile) => {
857
+ const rel = relative(dirname(fromFile), toFile).replace(/\\/g, "/");
858
+ return rel.startsWith(".") ? rel : `./${rel}`;
859
+ };
860
+ const buildGeneratedLoaderContent = (generatedLoaderFile, targets) => {
861
+ const targetBlocks = targets.map((target) => {
862
+ const specifier = JSON.stringify(
863
+ toRelativeImportSpecifier(generatedLoaderFile, target.outFile)
864
+ );
865
+ return `export const ${target.functionName} = async <T extends object>(
866
+ options?: TargetWasmLoadOptions
867
+ ): Promise<WasmInstance<T>> => {
868
+ const source = options?.url ?? new URL(${specifier}, import.meta.url);
869
+ return await loadWasm<T>(source, {
870
+ imports: options?.imports,
871
+ });
872
+ };`;
873
+ }).join("\n\n");
874
+ return `// Generated by emsdk-env. DO NOT EDIT.
875
+
876
+ export type WasmSource =
877
+ | string
878
+ | URL
879
+ | ArrayBuffer
880
+ | ArrayBufferView
881
+ | Response;
882
+
883
+ export interface WasmLoadOptions {
884
+ readonly imports?: WebAssembly.Imports;
885
+ }
886
+
887
+ export interface TargetWasmLoadOptions extends WasmLoadOptions {
888
+ readonly url?: string | URL;
889
+ }
890
+
891
+ export interface WasmInstance<T extends object> {
892
+ readonly exports: T;
893
+ readonly memory: WebAssembly.Memory;
894
+ readonly table: WebAssembly.Table | undefined;
895
+ readonly rawExports: WebAssembly.Exports;
896
+ readonly module: WebAssembly.Module;
897
+ readonly instance: WebAssembly.Instance;
898
+ readonly initialize: (() => unknown) | undefined;
899
+ readonly start: (() => unknown) | undefined;
900
+ }
901
+
902
+ const resolveWasmBytes = async (source: WasmSource): Promise<ArrayBuffer> => {
903
+ if (typeof Response !== 'undefined' && source instanceof Response) {
904
+ return await source.arrayBuffer();
905
+ }
906
+ if (source instanceof URL || typeof source === 'string') {
907
+ const response = await fetch(source);
908
+ if (!response.ok) {
909
+ throw new Error(\`Failed to fetch wasm: \${response.url}\`);
910
+ }
911
+ return await response.arrayBuffer();
912
+ }
913
+ if (ArrayBuffer.isView(source)) {
914
+ const view = new Uint8Array(
915
+ source.buffer,
916
+ source.byteOffset,
917
+ source.byteLength
918
+ );
919
+ return view.slice().buffer;
920
+ }
921
+ return source;
922
+ };
923
+
924
+ const getImportValue = (
925
+ imports: WebAssembly.Imports | undefined,
926
+ moduleName: string,
927
+ name: string
928
+ ) => {
929
+ const moduleImports = imports?.[moduleName];
930
+ if (!moduleImports || typeof moduleImports !== 'object') {
931
+ return undefined;
932
+ }
933
+ return (moduleImports as Record<string, unknown>)[name];
934
+ };
935
+
936
+ const resolveMemory = (
937
+ module: WebAssembly.Module,
938
+ instance: WebAssembly.Instance,
939
+ imports: WebAssembly.Imports | undefined
940
+ ) => {
941
+ let memory: WebAssembly.Memory | undefined;
942
+ for (const entry of WebAssembly.Module.exports(module)) {
943
+ if (entry.kind !== 'memory') {
944
+ continue;
945
+ }
946
+ if (memory) {
947
+ throw new Error('Multiple wasm memories are not supported.');
948
+ }
949
+ const value = instance.exports[entry.name];
950
+ if (!(value instanceof WebAssembly.Memory)) {
951
+ throw new Error(\`Export is not a WebAssembly.Memory: \${entry.name}\`);
952
+ }
953
+ memory = value;
954
+ }
955
+ if (memory) {
956
+ return memory;
957
+ }
958
+ for (const entry of WebAssembly.Module.imports(module)) {
959
+ if (entry.kind !== 'memory') {
960
+ continue;
961
+ }
962
+ if (memory) {
963
+ throw new Error('Multiple wasm memories are not supported.');
964
+ }
965
+ const value = getImportValue(imports, entry.module, entry.name);
966
+ if (!(value instanceof WebAssembly.Memory)) {
967
+ throw new Error(
968
+ \`Imported value is not a WebAssembly.Memory: \${entry.module}.\${entry.name}\`
969
+ );
970
+ }
971
+ memory = value;
972
+ }
973
+ if (!memory) {
974
+ throw new Error('WASM memory export/import was not resolved.');
975
+ }
976
+ return memory;
977
+ };
978
+
979
+ const resolveTable = (
980
+ module: WebAssembly.Module,
981
+ instance: WebAssembly.Instance,
982
+ imports: WebAssembly.Imports | undefined
983
+ ) => {
984
+ let table: WebAssembly.Table | undefined;
985
+ for (const entry of WebAssembly.Module.exports(module)) {
986
+ if (entry.kind !== 'table') {
987
+ continue;
988
+ }
989
+ if (table) {
990
+ throw new Error('Multiple wasm tables are not supported.');
991
+ }
992
+ const value = instance.exports[entry.name];
993
+ if (!(value instanceof WebAssembly.Table)) {
994
+ throw new Error(\`Export is not a WebAssembly.Table: \${entry.name}\`);
995
+ }
996
+ table = value;
997
+ }
998
+ if (table) {
999
+ return table;
1000
+ }
1001
+ for (const entry of WebAssembly.Module.imports(module)) {
1002
+ if (entry.kind !== 'table') {
1003
+ continue;
1004
+ }
1005
+ if (table) {
1006
+ throw new Error('Multiple wasm tables are not supported.');
1007
+ }
1008
+ const value = getImportValue(imports, entry.module, entry.name);
1009
+ if (!(value instanceof WebAssembly.Table)) {
1010
+ throw new Error(
1011
+ \`Imported value is not a WebAssembly.Table: \${entry.module}.\${entry.name}\`
1012
+ );
1013
+ }
1014
+ table = value;
1015
+ }
1016
+ return table;
1017
+ };
1018
+
1019
+ export const loadWasm = async <T extends object>(
1020
+ source: WasmSource,
1021
+ options?: WasmLoadOptions
1022
+ ): Promise<WasmInstance<T>> => {
1023
+ const bytes = await resolveWasmBytes(source);
1024
+ const module = await WebAssembly.compile(bytes);
1025
+ const instance = await WebAssembly.instantiate(module, options?.imports ?? {});
1026
+
1027
+ const functionExports: Record<string, (...args: unknown[]) => unknown> = {};
1028
+ for (const entry of WebAssembly.Module.exports(module)) {
1029
+ if (entry.kind !== 'function') {
1030
+ continue;
1031
+ }
1032
+ const value = instance.exports[entry.name];
1033
+ if (typeof value !== 'function') {
1034
+ throw new Error(\`Export is not a function: \${entry.name}\`);
1035
+ }
1036
+ functionExports[entry.name] = value as (...args: unknown[]) => unknown;
1037
+ }
1038
+
1039
+ const initialize =
1040
+ typeof instance.exports._initialize === 'function'
1041
+ ? (instance.exports._initialize as () => unknown)
1042
+ : undefined;
1043
+ const start =
1044
+ typeof instance.exports._start === 'function'
1045
+ ? (instance.exports._start as () => unknown)
1046
+ : undefined;
1047
+
1048
+ return {
1049
+ exports: functionExports as T,
1050
+ memory: resolveMemory(module, instance, options?.imports),
1051
+ table: resolveTable(module, instance, options?.imports),
1052
+ rawExports: instance.exports,
1053
+ module,
1054
+ instance,
1055
+ initialize,
1056
+ start,
1057
+ };
1058
+ };
1059
+
1060
+ ${targetBlocks}
1061
+ `;
1062
+ };
788
1063
  const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
789
1064
  const resolvePackageJsonPath = async (startPath, packageName) => {
790
1065
  let current = dirname(startPath);
@@ -885,6 +1160,42 @@ const resolveImportDirectories = async (rootDir, imports) => {
885
1160
  libDirs: dedupeValues(libDirs)
886
1161
  };
887
1162
  };
1163
+ const resolveGeneratedLoaderOutFile = (rootDir, env, generatedLoader) => {
1164
+ var _a;
1165
+ if (!(generatedLoader == null ? void 0 : generatedLoader.enable)) {
1166
+ return void 0;
1167
+ }
1168
+ const rawOutFile = expandPlaceholders(
1169
+ (_a = generatedLoader.outFile) != null ? _a : DEFAULT_GENERATED_LOADER_OUT_FILE,
1170
+ env,
1171
+ "generatedLoader.outFile"
1172
+ );
1173
+ return resolvePath(rootDir, rawOutFile);
1174
+ };
1175
+ const resolveGeneratedLoaderWatchDirs = (rootDir, srcDir, commonIncludeDirs, env, targetEntries, importIncludeDirs) => {
1176
+ const dirs = [srcDir, ...resolveIncludeDirs(commonIncludeDirs, env, rootDir)];
1177
+ for (const [targetName, target] of targetEntries) {
1178
+ if (!target.includeDirs || target.includeDirs.length === 0) {
1179
+ continue;
1180
+ }
1181
+ const targetEnv = {
1182
+ ...env,
1183
+ TARGET_NAME: targetName
1184
+ };
1185
+ dirs.push(...resolveIncludeDirs(target.includeDirs, targetEnv, rootDir));
1186
+ }
1187
+ dirs.push(...importIncludeDirs);
1188
+ return dedupeValues(dirs);
1189
+ };
1190
+ const validateGeneratedLoaderOutFile = (generatedLoaderFile, watchDirs) => {
1191
+ for (const dir of watchDirs) {
1192
+ if (isSubPath(dir, generatedLoaderFile)) {
1193
+ throw new Error(
1194
+ `generatedLoader.outFile must not be placed under watched directory: ${generatedLoaderFile}`
1195
+ );
1196
+ }
1197
+ }
1198
+ };
888
1199
  const buildCompileArgs = (options, includeDirs, defines, env, rootDir) => {
889
1200
  const resolvedOptions = expandArray(options, env, "options");
890
1201
  const includeArgs = resolveIncludeDirs(includeDirs, env, rootDir).map(
@@ -967,6 +1278,22 @@ const buildWasm = async (options) => {
967
1278
  const importIncludeDirs = importDirectories.includeDirs;
968
1279
  const importLibDirs = importDirectories.libDirs;
969
1280
  const linkLibDirs = dedupeValues([libDir, ...importLibDirs]);
1281
+ const generatedLoaderFile = resolveGeneratedLoaderOutFile(
1282
+ rootDir,
1283
+ envWithDirs,
1284
+ options.generatedLoader
1285
+ );
1286
+ if (generatedLoaderFile) {
1287
+ const watchDirs = resolveGeneratedLoaderWatchDirs(
1288
+ rootDir,
1289
+ srcDir,
1290
+ commonIncludeDirs,
1291
+ envWithDirs,
1292
+ targetEntries,
1293
+ importIncludeDirs
1294
+ );
1295
+ validateGeneratedLoaderOutFile(generatedLoaderFile, watchDirs);
1296
+ }
970
1297
  logger.debug(`Detected rootDir: '${rootDir}'`);
971
1298
  logger.debug(`Detected srcDir: '${srcDir}'`);
972
1299
  logger.debug(`Detected outDir: '${outDir}'`);
@@ -983,6 +1310,9 @@ const buildWasm = async (options) => {
983
1310
  logger.debug(
984
1311
  `Detected importLibDirs: [${importLibDirs.map((p) => `'${p}'`).join(",")}]`
985
1312
  );
1313
+ if (generatedLoaderFile) {
1314
+ logger.debug(`Detected generatedLoaderFile: '${generatedLoaderFile}'`);
1315
+ }
986
1316
  await ensureDirectory(outDir);
987
1317
  await ensureDirectory(libDir);
988
1318
  await ensureDirectory(buildDir);
@@ -1005,6 +1335,7 @@ const buildWasm = async (options) => {
1005
1335
  return wasmOptCommand;
1006
1336
  };
1007
1337
  const outFiles = {};
1338
+ let resultGeneratedLoaderFile;
1008
1339
  const buildTargets = async (expectedType) => {
1009
1340
  var _a2;
1010
1341
  for (const [targetName, target] of targetEntries) {
@@ -1276,6 +1607,40 @@ const buildWasm = async (options) => {
1276
1607
  try {
1277
1608
  await buildTargets("archive");
1278
1609
  await buildTargets("wasm");
1610
+ if (generatedLoaderFile) {
1611
+ const generatedTargets = [];
1612
+ const functionNames = /* @__PURE__ */ new Set();
1613
+ for (const [targetName, target] of targetEntries) {
1614
+ if (resolveTargetType(target.type) !== "wasm") {
1615
+ continue;
1616
+ }
1617
+ const outFile = outFiles[targetName];
1618
+ if (!outFile) {
1619
+ continue;
1620
+ }
1621
+ const functionName = `load${toPascalCaseIdentifier(targetName)}Wasm`;
1622
+ if (functionNames.has(functionName)) {
1623
+ throw new Error(
1624
+ `Generated loader function name collision: ${functionName}`
1625
+ );
1626
+ }
1627
+ functionNames.add(functionName);
1628
+ generatedTargets.push({
1629
+ targetName,
1630
+ functionName,
1631
+ outFile
1632
+ });
1633
+ }
1634
+ const content = buildGeneratedLoaderContent(
1635
+ generatedLoaderFile,
1636
+ generatedTargets
1637
+ );
1638
+ const didWrite = await writeTextIfChanged(generatedLoaderFile, content);
1639
+ logger.info(
1640
+ didWrite ? `Generated loader: ${relative(rootDir, generatedLoaderFile)}` : `Generated loader unchanged: ${relative(rootDir, generatedLoaderFile)}`
1641
+ );
1642
+ resultGeneratedLoaderFile = generatedLoaderFile;
1643
+ }
1279
1644
  } finally {
1280
1645
  if (cleanupBuildDir) {
1281
1646
  await rm(buildRunDir, { recursive: true, force: true });
@@ -1283,7 +1648,8 @@ const buildWasm = async (options) => {
1283
1648
  }
1284
1649
  return {
1285
1650
  emsdkRoot,
1286
- outFiles
1651
+ outFiles,
1652
+ ...resultGeneratedLoaderFile ? { generatedLoaderFile: resultGeneratedLoaderFile } : {}
1287
1653
  };
1288
1654
  };
1289
1655
  export {
@@ -1291,4 +1657,4 @@ export {
1291
1657
  createConsoleLogger as c,
1292
1658
  prepareEmsdk as p
1293
1659
  };
1294
- //# sourceMappingURL=build-DzrgEC4A.js.map
1660
+ //# sourceMappingURL=build-BOZTStIM.js.map