wesl-tooling 0.6.21 → 0.6.22
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/dist/index.d.ts +186 -0
- package/dist/index.js +406 -0
- package/package.json +8 -4
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { ModuleResolver, WeslAST, WeslBundle } from "wesl";
|
|
2
|
+
|
|
3
|
+
//#region src/FileModuleResolver.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Loads WESL modules from the filesystem on demand with caching.
|
|
7
|
+
*
|
|
8
|
+
* Resolves module paths like `package::foo::bar` to filesystem paths
|
|
9
|
+
* like `baseDir/foo/bar.wesl` or `baseDir/foo/bar.wgsl`.
|
|
10
|
+
*/
|
|
11
|
+
declare class FileModuleResolver implements ModuleResolver {
|
|
12
|
+
/** Cached parsed ASTs to avoid re-parsing the same module */
|
|
13
|
+
readonly astCache: Map<string, WeslAST>;
|
|
14
|
+
/** Root directory containing shader source files */
|
|
15
|
+
readonly baseDir: string;
|
|
16
|
+
/** Package name that this resolver handles (in addition to generic "package") */
|
|
17
|
+
readonly packageName: string;
|
|
18
|
+
/** Optional root path for debug file paths (for browser-clickable errors) */
|
|
19
|
+
readonly debugWeslRoot?: string;
|
|
20
|
+
/**
|
|
21
|
+
* @param baseDir - Root directory containing shader source files
|
|
22
|
+
* @param packageName - Package name to resolve (defaults to "package")
|
|
23
|
+
* @param debugWeslRoot - Optional root path for debug file paths. If provided, error messages
|
|
24
|
+
* will show paths relative to this root (e.g., "shaders/foo.wesl") instead of absolute
|
|
25
|
+
* filesystem paths. This is needed for clickable errors in browser dev tools.
|
|
26
|
+
*/
|
|
27
|
+
constructor(baseDir: string, packageName?: string, debugWeslRoot?: string);
|
|
28
|
+
/**
|
|
29
|
+
* Resolves and parses a module by its import path.
|
|
30
|
+
*
|
|
31
|
+
* Returns cached AST if available, otherwise loads from filesystem,
|
|
32
|
+
* parses, caches, and returns the AST. Returns undefined if module
|
|
33
|
+
* cannot be found.
|
|
34
|
+
*
|
|
35
|
+
* @param modulePath - Module path like "package::foo::bar"
|
|
36
|
+
* @returns Parsed AST or undefined if module not found
|
|
37
|
+
*/
|
|
38
|
+
resolveModule(modulePath: string): WeslAST | undefined;
|
|
39
|
+
/** Try .wesl first, then .wgsl */
|
|
40
|
+
private tryExtensions;
|
|
41
|
+
private loadSource;
|
|
42
|
+
/** Convert module path (package::foo::bar) to filesystem path (baseDir/foo/bar) */
|
|
43
|
+
private moduleToFilePath;
|
|
44
|
+
/** Convert module path to debug path for error messages */
|
|
45
|
+
private modulePathToDebugPath;
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/LoadModules.d.ts
|
|
49
|
+
/**
|
|
50
|
+
* Load the wesl/wgsl shader sources.
|
|
51
|
+
*
|
|
52
|
+
* If baseDir or srcGlob are not provided, this function will attempt to read
|
|
53
|
+
* configuration from wesl.toml in the projectDir. If no wesl.toml exists,
|
|
54
|
+
* default values will be used.
|
|
55
|
+
*
|
|
56
|
+
* @param projectDir The project directory (typically cwd or directory containing package.json)
|
|
57
|
+
* @param baseDir Optional base directory for shaders (overrides wesl.toml if provided)
|
|
58
|
+
* @param srcGlob Optional glob pattern for shader files (overrides wesl.toml if provided)
|
|
59
|
+
*/
|
|
60
|
+
declare function loadModules(projectDir: string, baseDir?: string, srcGlob?: string): Promise<Record<string, string>>;
|
|
61
|
+
declare function zip<A, B>(as: A[], bs: B[]): [A, B][];
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/LoadProject.d.ts
|
|
64
|
+
interface ProjectInfo {
|
|
65
|
+
weslSrc: Record<string, string>;
|
|
66
|
+
rootModuleName: string;
|
|
67
|
+
packageName: string;
|
|
68
|
+
libs: WeslBundle[];
|
|
69
|
+
}
|
|
70
|
+
interface LoadProjectOptions {
|
|
71
|
+
/** Libraries provided at runtime, don't resolve from npm (e.g., ["test"]) */
|
|
72
|
+
virtualLibs?: string[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Load everything needed to link a shader file.
|
|
76
|
+
*
|
|
77
|
+
* Discovers the project root, loads wesl.toml config, reads all shader modules,
|
|
78
|
+
* and resolves external library dependencies from npm.
|
|
79
|
+
*
|
|
80
|
+
* @returns ProjectInfo with sources, libs, and module paths, or null if not in a project.
|
|
81
|
+
*/
|
|
82
|
+
declare function loadProject(filePath: string, opts?: LoadProjectOptions): Promise<ProjectInfo | null>;
|
|
83
|
+
/** Read package name from package.json, sanitized for WESL identifiers. */
|
|
84
|
+
declare function getPackageName(projectDir: string): Promise<string>;
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/LoadWeslToml.d.ts
|
|
87
|
+
/** Configuration from wesl.toml */
|
|
88
|
+
interface WeslToml {
|
|
89
|
+
/** WESL edition (e.g. "unstable_2025") */
|
|
90
|
+
edition: string;
|
|
91
|
+
/** glob patterns to find .wesl/.wgsl files. Relative to the toml directory. */
|
|
92
|
+
include: string[];
|
|
93
|
+
/** base directory for wesl files. Relative to the toml directory. */
|
|
94
|
+
root: string;
|
|
95
|
+
/** glob patterns to exclude directories. */
|
|
96
|
+
exclude?: string[];
|
|
97
|
+
/** package manager ("npm" or "cargo") */
|
|
98
|
+
"package-manager"?: string;
|
|
99
|
+
/** names of directly referenced wesl shader packages (e.g. npm package names), or "auto" for auto-detection */
|
|
100
|
+
dependencies?: string[] | string;
|
|
101
|
+
}
|
|
102
|
+
/** Information about the loaded wesl.toml file and its location */
|
|
103
|
+
interface WeslTomlInfo {
|
|
104
|
+
/** The path to the toml file, relative to the cwd, undefined if no toml file */
|
|
105
|
+
tomlFile: string | undefined;
|
|
106
|
+
/** The absolute path to the directory that contains the toml.
|
|
107
|
+
* Paths inside the toml are relative to this. */
|
|
108
|
+
tomlDir: string;
|
|
109
|
+
/** The wesl root, relative to the projectDir.
|
|
110
|
+
* This lets loadModules do `path.resolve(projectDir, resolvedRoot)` */
|
|
111
|
+
resolvedRoot: string;
|
|
112
|
+
/** The underlying toml file */
|
|
113
|
+
toml: WeslToml;
|
|
114
|
+
}
|
|
115
|
+
/** Default configuration when no wesl.toml is found */
|
|
116
|
+
declare const defaultWeslToml: WeslToml;
|
|
117
|
+
/**
|
|
118
|
+
* Load and parse a wesl.toml file from the fs.
|
|
119
|
+
* Provide default values for any required WeslToml fields.
|
|
120
|
+
*/
|
|
121
|
+
declare function loadWeslToml(tomlFile: string): Promise<WeslToml>;
|
|
122
|
+
/**
|
|
123
|
+
* Find and load the wesl.toml file, or use defaults if not found
|
|
124
|
+
*
|
|
125
|
+
* @param projectDir The directory to search for wesl.toml (typically cwd or project root)
|
|
126
|
+
* @param specifiedToml Optional explicit path to a toml file
|
|
127
|
+
* @returns Information about the loaded TOML configuration
|
|
128
|
+
*/
|
|
129
|
+
declare function findWeslToml(projectDir: string, specifiedToml?: string): Promise<WeslTomlInfo>;
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/ParseDependencies.d.ts
|
|
132
|
+
/**
|
|
133
|
+
* Find package dependencies in WESL source files.
|
|
134
|
+
*
|
|
135
|
+
* Parses sources and partially binds identifiers to reveal unresolved package
|
|
136
|
+
* references. Returns the longest resolvable npm subpath for each dependency.
|
|
137
|
+
*
|
|
138
|
+
* For example, 'foo::bar::baz' could resolve to:
|
|
139
|
+
* - 'foo/bar' (package foo, export './bar' bundle)
|
|
140
|
+
* - 'foo' (package foo, default export)
|
|
141
|
+
*
|
|
142
|
+
* @param weslSrc - Record of WESL source files by path
|
|
143
|
+
* @param projectDir - Project directory for resolving package imports
|
|
144
|
+
* @param virtualLibNames - Virtual lib names to exclude (e.g., ['test', 'constants'])
|
|
145
|
+
* @returns Dependency paths in npm format (e.g., 'foo/bar', 'foo')
|
|
146
|
+
*/
|
|
147
|
+
declare function parseDependencies(weslSrc: Record<string, string>, projectDir: string, virtualLibNames?: string[]): string[];
|
|
148
|
+
/**
|
|
149
|
+
* Load WeslBundle instances referenced by WESL sources.
|
|
150
|
+
*
|
|
151
|
+
* Parses sources to find external module references, then dynamically imports
|
|
152
|
+
* the corresponding weslBundle.js files.
|
|
153
|
+
*
|
|
154
|
+
* @param weslSrc - Record of WESL source files by path
|
|
155
|
+
* @param projectDir - Project directory for resolving imports
|
|
156
|
+
* @param packageName - Optional current package name
|
|
157
|
+
* @param includeCurrentPackage - Include current package in results (default: false)
|
|
158
|
+
* @param virtualLibNames - Virtual lib names to exclude from resolution
|
|
159
|
+
* @returns Loaded WeslBundle instances
|
|
160
|
+
*/
|
|
161
|
+
declare function dependencyBundles(weslSrc: Record<string, string>, projectDir: string, packageName?: string, includeCurrentPackage?: boolean, virtualLibNames?: string[]): Promise<WeslBundle[]>;
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/ResolveProjectDir.d.ts
|
|
164
|
+
/**
|
|
165
|
+
* Resolves a project directory by searching upward for package.json or wesl.toml.
|
|
166
|
+
*
|
|
167
|
+
* @param startPath - Optional starting path (file:// URL or filesystem path).
|
|
168
|
+
* If a file URL is provided, uses its directory.
|
|
169
|
+
* If omitted or falsy, defaults to process.cwd().
|
|
170
|
+
* @returns file:// URL string pointing to the project directory
|
|
171
|
+
* (the first ancestor containing package.json or wesl.toml, or the start directory)
|
|
172
|
+
*/
|
|
173
|
+
declare function resolveProjectDir(startPath?: string): Promise<string>;
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/Version.d.ts
|
|
176
|
+
/** Read package.json from a directory.
|
|
177
|
+
* @param projectDir - file:// URL string to directory containing package.json
|
|
178
|
+
* @returns the parsed package.json contents */
|
|
179
|
+
declare function readPackageJson(projectDir: string): Promise<Record<string, any>>;
|
|
180
|
+
/**
|
|
181
|
+
* @param projectDir - file:// URL string to directory containing package.json
|
|
182
|
+
* @returns the 'version' field from the package.json in the `projectDir`
|
|
183
|
+
*/
|
|
184
|
+
declare function versionFromPackageJson(projectDir: string): Promise<string>;
|
|
185
|
+
//#endregion
|
|
186
|
+
export { FileModuleResolver, LoadProjectOptions, ProjectInfo, WeslToml, WeslTomlInfo, defaultWeslToml, dependencyBundles, findWeslToml, getPackageName, loadModules, loadProject, loadWeslToml, parseDependencies, readPackageJson, resolveProjectDir, versionFromPackageJson, zip };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import * as fs$1 from "node:fs";
|
|
2
|
+
import { RecordResolver, WeslParseError, filterMap, findUnboundIdents, moduleToRelativePath, normalizeDebugRoot, npmNameVariations, parseSrcModule, sanitizePackageName } from "wesl";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import * as path$1 from "node:path";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { glob } from "glob";
|
|
7
|
+
import toml from "toml";
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
|
+
import { resolve } from "import-meta-resolve";
|
|
10
|
+
|
|
11
|
+
//#region src/FileModuleResolver.ts
|
|
12
|
+
/**
|
|
13
|
+
* Loads WESL modules from the filesystem on demand with caching.
|
|
14
|
+
*
|
|
15
|
+
* Resolves module paths like `package::foo::bar` to filesystem paths
|
|
16
|
+
* like `baseDir/foo/bar.wesl` or `baseDir/foo/bar.wgsl`.
|
|
17
|
+
*/
|
|
18
|
+
var FileModuleResolver = class {
|
|
19
|
+
/** Cached parsed ASTs to avoid re-parsing the same module */
|
|
20
|
+
astCache = /* @__PURE__ */ new Map();
|
|
21
|
+
/** Root directory containing shader source files */
|
|
22
|
+
baseDir;
|
|
23
|
+
/** Package name that this resolver handles (in addition to generic "package") */
|
|
24
|
+
packageName;
|
|
25
|
+
/** Optional root path for debug file paths (for browser-clickable errors) */
|
|
26
|
+
debugWeslRoot;
|
|
27
|
+
/**
|
|
28
|
+
* @param baseDir - Root directory containing shader source files
|
|
29
|
+
* @param packageName - Package name to resolve (defaults to "package")
|
|
30
|
+
* @param debugWeslRoot - Optional root path for debug file paths. If provided, error messages
|
|
31
|
+
* will show paths relative to this root (e.g., "shaders/foo.wesl") instead of absolute
|
|
32
|
+
* filesystem paths. This is needed for clickable errors in browser dev tools.
|
|
33
|
+
*/
|
|
34
|
+
constructor(baseDir, packageName = "package", debugWeslRoot) {
|
|
35
|
+
this.baseDir = baseDir;
|
|
36
|
+
this.packageName = packageName;
|
|
37
|
+
this.debugWeslRoot = debugWeslRoot;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolves and parses a module by its import path.
|
|
41
|
+
*
|
|
42
|
+
* Returns cached AST if available, otherwise loads from filesystem,
|
|
43
|
+
* parses, caches, and returns the AST. Returns undefined if module
|
|
44
|
+
* cannot be found.
|
|
45
|
+
*
|
|
46
|
+
* @param modulePath - Module path like "package::foo::bar"
|
|
47
|
+
* @returns Parsed AST or undefined if module not found
|
|
48
|
+
*/
|
|
49
|
+
resolveModule(modulePath) {
|
|
50
|
+
const cached = this.astCache.get(modulePath);
|
|
51
|
+
if (cached) return cached;
|
|
52
|
+
const sourceFile = this.tryExtensions(modulePath);
|
|
53
|
+
if (!sourceFile) return void 0;
|
|
54
|
+
const ast = parseSrcModule({
|
|
55
|
+
modulePath,
|
|
56
|
+
debugFilePath: this.debugWeslRoot ? this.modulePathToDebugPath(modulePath) : sourceFile.filePath,
|
|
57
|
+
src: sourceFile.source
|
|
58
|
+
});
|
|
59
|
+
this.astCache.set(modulePath, ast);
|
|
60
|
+
return ast;
|
|
61
|
+
}
|
|
62
|
+
/** Try .wesl first, then .wgsl */
|
|
63
|
+
tryExtensions(modulePath) {
|
|
64
|
+
const basePath = this.moduleToFilePath(modulePath);
|
|
65
|
+
if (!basePath) return void 0;
|
|
66
|
+
for (const ext of [".wesl", ".wgsl"]) {
|
|
67
|
+
const filePath = basePath + ext;
|
|
68
|
+
const source = this.loadSource(filePath);
|
|
69
|
+
if (source) return {
|
|
70
|
+
filePath,
|
|
71
|
+
source
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
loadSource(filePath) {
|
|
76
|
+
try {
|
|
77
|
+
return fs$1.readFileSync(filePath, "utf8");
|
|
78
|
+
} catch {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Convert module path (package::foo::bar) to filesystem path (baseDir/foo/bar) */
|
|
83
|
+
moduleToFilePath(modulePath) {
|
|
84
|
+
const relativePath = moduleToRelativePath(modulePath, this.packageName);
|
|
85
|
+
if (!relativePath) return void 0;
|
|
86
|
+
return `${this.baseDir}/${relativePath}`;
|
|
87
|
+
}
|
|
88
|
+
/** Convert module path to debug path for error messages */
|
|
89
|
+
modulePathToDebugPath(modulePath) {
|
|
90
|
+
const relative = moduleToRelativePath(modulePath, this.packageName) ?? "";
|
|
91
|
+
return normalizeDebugRoot(this.debugWeslRoot) + relative + ".wesl";
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/LoadWeslToml.ts
|
|
97
|
+
/** Default configuration when no wesl.toml is found */
|
|
98
|
+
const defaultWeslToml = {
|
|
99
|
+
edition: "unstable_2025",
|
|
100
|
+
include: ["shaders/**/*.w[eg]sl"],
|
|
101
|
+
root: "shaders",
|
|
102
|
+
dependencies: "auto"
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Load and parse a wesl.toml file from the fs.
|
|
106
|
+
* Provide default values for any required WeslToml fields.
|
|
107
|
+
*/
|
|
108
|
+
async function loadWeslToml(tomlFile) {
|
|
109
|
+
const tomlString = await fs.readFile(tomlFile, "utf-8");
|
|
110
|
+
const parsed = toml.parse(tomlString);
|
|
111
|
+
return {
|
|
112
|
+
...defaultWeslToml,
|
|
113
|
+
...parsed
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Find and load the wesl.toml file, or use defaults if not found
|
|
118
|
+
*
|
|
119
|
+
* @param projectDir The directory to search for wesl.toml (typically cwd or project root)
|
|
120
|
+
* @param specifiedToml Optional explicit path to a toml file
|
|
121
|
+
* @returns Information about the loaded TOML configuration
|
|
122
|
+
*/
|
|
123
|
+
async function findWeslToml(projectDir, specifiedToml) {
|
|
124
|
+
let tomlFile;
|
|
125
|
+
if (specifiedToml) {
|
|
126
|
+
await fs.access(specifiedToml);
|
|
127
|
+
tomlFile = specifiedToml;
|
|
128
|
+
} else {
|
|
129
|
+
const tomlPath = path.join(projectDir, "wesl.toml");
|
|
130
|
+
tomlFile = await fs.access(tomlPath).then(() => tomlPath).catch(() => {});
|
|
131
|
+
}
|
|
132
|
+
let parsedToml;
|
|
133
|
+
let tomlDir;
|
|
134
|
+
if (tomlFile) {
|
|
135
|
+
parsedToml = await loadWeslToml(tomlFile);
|
|
136
|
+
tomlDir = path.dirname(tomlFile);
|
|
137
|
+
} else {
|
|
138
|
+
parsedToml = defaultWeslToml;
|
|
139
|
+
tomlDir = projectDir;
|
|
140
|
+
}
|
|
141
|
+
const tomlToWeslRoot = path.resolve(tomlDir, parsedToml.root);
|
|
142
|
+
const projectDirAbs = path.resolve(projectDir);
|
|
143
|
+
const resolvedRoot = path.relative(projectDirAbs, tomlToWeslRoot);
|
|
144
|
+
return {
|
|
145
|
+
tomlFile,
|
|
146
|
+
tomlDir,
|
|
147
|
+
resolvedRoot,
|
|
148
|
+
toml: parsedToml
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/LoadModules.ts
|
|
154
|
+
/**
|
|
155
|
+
* Load the wesl/wgsl shader sources.
|
|
156
|
+
*
|
|
157
|
+
* If baseDir or srcGlob are not provided, this function will attempt to read
|
|
158
|
+
* configuration from wesl.toml in the projectDir. If no wesl.toml exists,
|
|
159
|
+
* default values will be used.
|
|
160
|
+
*
|
|
161
|
+
* @param projectDir The project directory (typically cwd or directory containing package.json)
|
|
162
|
+
* @param baseDir Optional base directory for shaders (overrides wesl.toml if provided)
|
|
163
|
+
* @param srcGlob Optional glob pattern for shader files (overrides wesl.toml if provided)
|
|
164
|
+
*/
|
|
165
|
+
async function loadModules(projectDir, baseDir, srcGlob) {
|
|
166
|
+
let resolvedBaseDir;
|
|
167
|
+
let resolvedSrcGlob;
|
|
168
|
+
if (!baseDir || !srcGlob) {
|
|
169
|
+
const tomlInfo = await findWeslToml(projectDir);
|
|
170
|
+
resolvedBaseDir = baseDir ?? tomlInfo.resolvedRoot;
|
|
171
|
+
resolvedSrcGlob = srcGlob ?? tomlInfo.toml.include[0];
|
|
172
|
+
} else {
|
|
173
|
+
resolvedBaseDir = baseDir;
|
|
174
|
+
resolvedSrcGlob = srcGlob;
|
|
175
|
+
}
|
|
176
|
+
const shaderFiles = (await glob(`${resolvedSrcGlob}`, {
|
|
177
|
+
cwd: projectDir,
|
|
178
|
+
ignore: "node_modules/**"
|
|
179
|
+
})).map((f) => path.resolve(projectDir, f));
|
|
180
|
+
const promisedSrcs = shaderFiles.map((f) => fs.readFile(f, { encoding: "utf8" }));
|
|
181
|
+
const src = await Promise.all(promisedSrcs);
|
|
182
|
+
if (src.length === 0) throw new Error(`no WGSL/WESL files found in ${resolvedSrcGlob}`);
|
|
183
|
+
const baseDirAbs = path.resolve(projectDir, resolvedBaseDir);
|
|
184
|
+
const moduleEntries = zip(shaderFiles.map((p) => path.relative(baseDirAbs, path.resolve(p))).map((p) => p.replace(/\\/g, "/")), src.map((s) => s.replace(/\r\n/g, "\n")));
|
|
185
|
+
return Object.fromEntries(moduleEntries);
|
|
186
|
+
}
|
|
187
|
+
function zip(as, bs) {
|
|
188
|
+
return as.map((a, i) => [a, bs[i]]);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/NpmResolver.ts
|
|
193
|
+
/** Find longest resolvable npm subpath from WESL module path segments.
|
|
194
|
+
*
|
|
195
|
+
* A WESL statement containing a WESL module path like 'import foo__bar::baz::elem;' references
|
|
196
|
+
* an npm package, an export within that package, a module within the WeslBundle,
|
|
197
|
+
* and an element within the WESL module.
|
|
198
|
+
* This function returns the npm package and export portion from the module path.
|
|
199
|
+
* The return value is usable to dynamically import the corresponding weslBundle.js file.
|
|
200
|
+
*
|
|
201
|
+
* Translation from a WESL module path to an npm package path involves:
|
|
202
|
+
* - Mapping WESL package names to their npm counterparts (e.g., 'foo__bar' -> '@foo/bar')
|
|
203
|
+
* - Probing to find the longest valid export subpath within the package
|
|
204
|
+
* - package.json allows export subpaths, so 'mypkg::gpu' could be 'mypkg/gpu' or just 'mypkg' in npm
|
|
205
|
+
* - Probing to handle variations in package naming
|
|
206
|
+
* - foo_bar could be foo-bar in npm
|
|
207
|
+
*
|
|
208
|
+
* Note that the resolution is based on package.json.
|
|
209
|
+
* The resolved file itself may not exist yet. (e.g. dist/weslBundle.js may not have been built yet)
|
|
210
|
+
*
|
|
211
|
+
* @param mPath - Module path segments
|
|
212
|
+
* @param importerURL - Base URL for resolution (e.g., 'file:///path/to/project/')
|
|
213
|
+
* @returns Longest resolvable subpath (e.g., 'foo/bar/baz' or 'foo')
|
|
214
|
+
*/
|
|
215
|
+
function npmResolveWESL(mPath, importerURL) {
|
|
216
|
+
for (const subPath of exportSubpaths(mPath)) for (const npmPath of npmNameVariations(subPath)) if (tryResolve(npmPath, importerURL)) return npmPath;
|
|
217
|
+
}
|
|
218
|
+
/** Try Node.js module resolution.
|
|
219
|
+
* @return undefined if unresolvable. */
|
|
220
|
+
function tryResolve(path$2, importerURL) {
|
|
221
|
+
try {
|
|
222
|
+
return resolve(path$2, importerURL);
|
|
223
|
+
} catch {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/** Yield possible export subpaths from module path, longest first.
|
|
228
|
+
* Drops the last segment (element name) and iterates down. */
|
|
229
|
+
function* exportSubpaths(mPath) {
|
|
230
|
+
const longest = mPath.length - 1;
|
|
231
|
+
for (let i = longest; i >= 0; i--) yield mPath.slice(0, i).join("/");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/ParseDependencies.ts
|
|
236
|
+
/**
|
|
237
|
+
* Find package dependencies in WESL source files.
|
|
238
|
+
*
|
|
239
|
+
* Parses sources and partially binds identifiers to reveal unresolved package
|
|
240
|
+
* references. Returns the longest resolvable npm subpath for each dependency.
|
|
241
|
+
*
|
|
242
|
+
* For example, 'foo::bar::baz' could resolve to:
|
|
243
|
+
* - 'foo/bar' (package foo, export './bar' bundle)
|
|
244
|
+
* - 'foo' (package foo, default export)
|
|
245
|
+
*
|
|
246
|
+
* @param weslSrc - Record of WESL source files by path
|
|
247
|
+
* @param projectDir - Project directory for resolving package imports
|
|
248
|
+
* @param virtualLibNames - Virtual lib names to exclude (e.g., ['test', 'constants'])
|
|
249
|
+
* @returns Dependency paths in npm format (e.g., 'foo/bar', 'foo')
|
|
250
|
+
*/
|
|
251
|
+
function parseDependencies(weslSrc, projectDir, virtualLibNames = []) {
|
|
252
|
+
let resolver;
|
|
253
|
+
try {
|
|
254
|
+
resolver = new RecordResolver(weslSrc);
|
|
255
|
+
} catch (e) {
|
|
256
|
+
if (e.cause instanceof WeslParseError) {
|
|
257
|
+
console.error(e.message, "\n");
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
throw e;
|
|
261
|
+
}
|
|
262
|
+
const unbound = findUnboundIdents(resolver);
|
|
263
|
+
if (!unbound) return [];
|
|
264
|
+
const excludeRoots = new Set(["constants", ...virtualLibNames]);
|
|
265
|
+
const pkgRefs = unbound.filter((modulePath) => modulePath.length > 1 && !excludeRoots.has(modulePath[0]));
|
|
266
|
+
if (pkgRefs.length === 0) return [];
|
|
267
|
+
const projectURL = projectDirURL(projectDir);
|
|
268
|
+
const deps = filterMap(pkgRefs, (mPath) => npmResolveWESL(mPath, projectURL));
|
|
269
|
+
return [...new Set(deps)];
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Load WeslBundle instances referenced by WESL sources.
|
|
273
|
+
*
|
|
274
|
+
* Parses sources to find external module references, then dynamically imports
|
|
275
|
+
* the corresponding weslBundle.js files.
|
|
276
|
+
*
|
|
277
|
+
* @param weslSrc - Record of WESL source files by path
|
|
278
|
+
* @param projectDir - Project directory for resolving imports
|
|
279
|
+
* @param packageName - Optional current package name
|
|
280
|
+
* @param includeCurrentPackage - Include current package in results (default: false)
|
|
281
|
+
* @param virtualLibNames - Virtual lib names to exclude from resolution
|
|
282
|
+
* @returns Loaded WeslBundle instances
|
|
283
|
+
*/
|
|
284
|
+
async function dependencyBundles(weslSrc, projectDir, packageName, includeCurrentPackage = false, virtualLibNames = []) {
|
|
285
|
+
const deps = parseDependencies(weslSrc, projectDir, virtualLibNames);
|
|
286
|
+
const filteredDeps = includeCurrentPackage ? deps : otherPackages(deps, packageName);
|
|
287
|
+
const projectURL = projectDirURL(projectDir);
|
|
288
|
+
const bundles = filteredDeps.map(async (dep) => {
|
|
289
|
+
return (await import(resolve(dep, projectURL))).default;
|
|
290
|
+
});
|
|
291
|
+
return await Promise.all(bundles);
|
|
292
|
+
}
|
|
293
|
+
/** Exclude current package from dependency list. */
|
|
294
|
+
function otherPackages(deps, packageName) {
|
|
295
|
+
if (!packageName) return deps;
|
|
296
|
+
return deps.filter((dep) => dep !== packageName && !dep.startsWith(`${packageName}/`));
|
|
297
|
+
}
|
|
298
|
+
/** Normalize project directory to file:// URL with trailing slash. */
|
|
299
|
+
function projectDirURL(projectDir) {
|
|
300
|
+
if (projectDir.startsWith("file://")) return projectDir.endsWith("/") ? projectDir : `${projectDir}/`;
|
|
301
|
+
const fileUrl = pathToFileURL(projectDir).href;
|
|
302
|
+
return fileUrl.endsWith("/") ? fileUrl : `${fileUrl}/`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/ResolveProjectDir.ts
|
|
307
|
+
/**
|
|
308
|
+
* Resolves a project directory by searching upward for package.json or wesl.toml.
|
|
309
|
+
*
|
|
310
|
+
* @param startPath - Optional starting path (file:// URL or filesystem path).
|
|
311
|
+
* If a file URL is provided, uses its directory.
|
|
312
|
+
* If omitted or falsy, defaults to process.cwd().
|
|
313
|
+
* @returns file:// URL string pointing to the project directory
|
|
314
|
+
* (the first ancestor containing package.json or wesl.toml, or the start directory)
|
|
315
|
+
*/
|
|
316
|
+
async function resolveProjectDir(startPath) {
|
|
317
|
+
let dir;
|
|
318
|
+
if (!startPath) dir = process.cwd();
|
|
319
|
+
else if (startPath.startsWith("file://")) {
|
|
320
|
+
const fsPath = fileURLToPath(startPath);
|
|
321
|
+
dir = await isFile(fsPath) ? path.dirname(fsPath) : fsPath;
|
|
322
|
+
} else dir = await isFile(startPath) ? path.dirname(startPath) : startPath;
|
|
323
|
+
let current = path.resolve(dir);
|
|
324
|
+
while (true) {
|
|
325
|
+
const hasPkgJson = await fileExists(path.join(current, "package.json"));
|
|
326
|
+
const hasWeslToml = await fileExists(path.join(current, "wesl.toml"));
|
|
327
|
+
if (hasPkgJson || hasWeslToml) return pathToFileURL(current).href;
|
|
328
|
+
const parent = path.dirname(current);
|
|
329
|
+
if (parent === current) return pathToFileURL(dir).href;
|
|
330
|
+
current = parent;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async function fileExists(filePath) {
|
|
334
|
+
try {
|
|
335
|
+
await fs.access(filePath);
|
|
336
|
+
return true;
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async function isFile(fsPath) {
|
|
342
|
+
try {
|
|
343
|
+
return (await fs.stat(fsPath)).isFile();
|
|
344
|
+
} catch {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region src/Version.ts
|
|
351
|
+
/** Read package.json from a directory.
|
|
352
|
+
* @param projectDir - file:// URL string to directory containing package.json
|
|
353
|
+
* @returns the parsed package.json contents */
|
|
354
|
+
async function readPackageJson(projectDir) {
|
|
355
|
+
const baseUrl = projectDir.endsWith("/") ? projectDir : `${projectDir}/`;
|
|
356
|
+
return (await import(new URL("package.json", baseUrl).href, { with: { type: "json" } })).default;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* @param projectDir - file:// URL string to directory containing package.json
|
|
360
|
+
* @returns the 'version' field from the package.json in the `projectDir`
|
|
361
|
+
*/
|
|
362
|
+
async function versionFromPackageJson(projectDir) {
|
|
363
|
+
return (await readPackageJson(projectDir)).version;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
//#endregion
|
|
367
|
+
//#region src/LoadProject.ts
|
|
368
|
+
/**
|
|
369
|
+
* Load everything needed to link a shader file.
|
|
370
|
+
*
|
|
371
|
+
* Discovers the project root, loads wesl.toml config, reads all shader modules,
|
|
372
|
+
* and resolves external library dependencies from npm.
|
|
373
|
+
*
|
|
374
|
+
* @returns ProjectInfo with sources, libs, and module paths, or null if not in a project.
|
|
375
|
+
*/
|
|
376
|
+
async function loadProject(filePath, opts = {}) {
|
|
377
|
+
const { virtualLibs = [] } = opts;
|
|
378
|
+
try {
|
|
379
|
+
const projectDir = fileURLToPath(await resolveProjectDir(filePath));
|
|
380
|
+
const tomlInfo = await findWeslToml(projectDir);
|
|
381
|
+
const packageName = await getPackageName(projectDir);
|
|
382
|
+
const weslSrc = await loadModules(projectDir);
|
|
383
|
+
const libs = (await dependencyBundles(weslSrc, projectDir, packageName, false, virtualLibs)).filter(Boolean);
|
|
384
|
+
const shaderRootAbs = path$1.resolve(projectDir, tomlInfo.resolvedRoot);
|
|
385
|
+
return {
|
|
386
|
+
weslSrc,
|
|
387
|
+
rootModuleName: path$1.relative(shaderRootAbs, filePath).replace(/\\/g, "/"),
|
|
388
|
+
packageName,
|
|
389
|
+
libs
|
|
390
|
+
};
|
|
391
|
+
} catch {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/** Read package name from package.json, sanitized for WESL identifiers. */
|
|
396
|
+
async function getPackageName(projectDir) {
|
|
397
|
+
try {
|
|
398
|
+
const projectUrl = pathToFileURL(projectDir).href;
|
|
399
|
+
return sanitizePackageName((await readPackageJson(projectUrl)).name);
|
|
400
|
+
} catch {
|
|
401
|
+
return path$1.basename(projectDir);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
//#endregion
|
|
406
|
+
export { FileModuleResolver, defaultWeslToml, dependencyBundles, findWeslToml, getPackageName, loadModules, loadProject, loadWeslToml, parseDependencies, readPackageJson, resolveProjectDir, versionFromPackageJson, zip };
|
package/package.json
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wesl-tooling",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
|
+
"dist",
|
|
6
7
|
"src"
|
|
7
8
|
],
|
|
8
9
|
"repository": "github:wgsl-tooling-wg/wesl-js",
|
|
9
10
|
"exports": {
|
|
10
11
|
".": {
|
|
11
|
-
"
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
12
14
|
}
|
|
13
15
|
},
|
|
14
16
|
"dependencies": {
|
|
@@ -20,11 +22,13 @@
|
|
|
20
22
|
"dependent_package": "x"
|
|
21
23
|
},
|
|
22
24
|
"peerDependencies": {
|
|
23
|
-
"wesl": "^0.7.
|
|
25
|
+
"wesl": "^0.7.9"
|
|
24
26
|
},
|
|
25
27
|
"scripts": {
|
|
28
|
+
"build": "tsdown",
|
|
26
29
|
"test": "cross-env FORCE_COLOR=1 vitest",
|
|
27
30
|
"test:once": "vitest run",
|
|
28
31
|
"typecheck": "tsgo"
|
|
29
|
-
}
|
|
32
|
+
},
|
|
33
|
+
"main": "dist/index.js"
|
|
30
34
|
}
|