wesl-plugin 0.6.70 → 0.6.71
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 +3 -3
- package/dist/{PluginExtension-nH8vi0nm.d.mts → PluginExtension-CHxUB0-8.d.mts} +15 -3
- package/dist/{StaticExtension-Chl5KPpw.mjs → StaticExtension-Q8HMuFJa.mjs} +15 -24
- package/dist/{WeslPlugin-BKQJvQve.mjs → WeslPlugin-CajtfXHH.mjs} +145 -144
- package/dist/{WeslPluginOptions-DWIrd183.d.mts → WeslPluginOptions-kd-CXSl9.d.mts} +1 -1
- package/dist/pluginIndex.d.mts +11 -12
- package/dist/pluginIndex.mjs +31 -2
- package/dist/plugins/astro.d.mts +1 -1
- package/dist/plugins/astro.mjs +2 -2
- package/dist/plugins/esbuild.d.mts +1 -1
- package/dist/plugins/esbuild.mjs +2 -2
- package/dist/plugins/farm.d.mts +1 -1
- package/dist/plugins/farm.mjs +2 -2
- package/dist/plugins/nuxt.d.mts +1 -1
- package/dist/plugins/nuxt.mjs +2 -2
- package/dist/plugins/rollup.d.mts +1 -1
- package/dist/plugins/rollup.mjs +2 -2
- package/dist/plugins/rspack.d.mts +1 -1
- package/dist/plugins/rspack.mjs +2 -2
- package/dist/plugins/vite.d.mts +1 -1
- package/dist/plugins/vite.mjs +2 -2
- package/dist/plugins/webpack.d.mts +1 -1
- package/dist/plugins/webpack.mjs +2 -2
- package/package.json +3 -2
- package/src/PluginApi.ts +111 -53
- package/src/PluginExtension.ts +21 -2
- package/src/WeslPlugin.ts +96 -146
- package/src/defaultSuffixTypes.d.ts +6 -0
- package/src/extensions/LinkExtension.ts +9 -16
- package/src/extensions/ReflectExtension.ts +53 -0
- package/src/extensions/StaticExtension.ts +16 -30
- package/src/pluginIndex.ts +1 -0
package/README.md
CHANGED
|
@@ -96,10 +96,10 @@ export default {
|
|
|
96
96
|
|
|
97
97
|
### Conditions in Import Path
|
|
98
98
|
|
|
99
|
-
For `?static`, you can specify conditions
|
|
99
|
+
For `?static`, you can specify conditions as query parameters:
|
|
100
100
|
|
|
101
101
|
```ts
|
|
102
|
-
import wgsl from "./app.wesl
|
|
102
|
+
import wgsl from "./app.wesl?MOBILE=true&DEBUG=false&static";
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
## Configuration (wesl.toml)
|
|
@@ -124,7 +124,7 @@ import type { PluginExtension } from "wesl-plugin";
|
|
|
124
124
|
|
|
125
125
|
const myExtension: PluginExtension = {
|
|
126
126
|
extensionName: "myfeature", // enables ?myfeature imports
|
|
127
|
-
emitFn: async (shaderPath, api, conditions) => {
|
|
127
|
+
emitFn: async (shaderPath, api, conditions, options) => {
|
|
128
128
|
const sources = await api.weslSrc();
|
|
129
129
|
// Return JavaScript code as a string
|
|
130
130
|
return `export default ${JSON.stringify(sources)};`;
|
|
@@ -31,14 +31,16 @@ interface WeslTomlInfo {
|
|
|
31
31
|
}
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region src/PluginExtension.d.ts
|
|
34
|
-
/** function type required for
|
|
34
|
+
/** function type required for emit extensions */
|
|
35
35
|
type ExtensionEmitFn = (/** absolute path to the shader to which the extension is attached */
|
|
36
36
|
|
|
37
37
|
shaderPath: string, /** support functions available to plugin extensions */
|
|
38
38
|
|
|
39
39
|
pluginApi: PluginExtensionApi, /** static conditions specified on the js import */
|
|
40
40
|
|
|
41
|
-
conditions?: Record<string, boolean
|
|
41
|
+
conditions?: Record<string, boolean>, /** plugin-level options from query params (e.g., { include: "all" }) */
|
|
42
|
+
|
|
43
|
+
options?: Record<string, string>) => Promise<string>;
|
|
42
44
|
/** an extension that runs inside the wesl-js build plugin */
|
|
43
45
|
interface PluginExtension extends WeslJsPlugin {
|
|
44
46
|
/** javascript imports with this suffix will trigger the plugin */
|
|
@@ -47,6 +49,10 @@ interface PluginExtension extends WeslJsPlugin {
|
|
|
47
49
|
* e.g. import myPluginJs from "./foo.wesl?myPlugin"; */
|
|
48
50
|
emitFn: ExtensionEmitFn;
|
|
49
51
|
}
|
|
52
|
+
interface ProjectSources {
|
|
53
|
+
weslSrc: Record<string, string>;
|
|
54
|
+
dependencies: string[];
|
|
55
|
+
}
|
|
50
56
|
/** api supplied to plugin extensions */
|
|
51
57
|
interface PluginExtensionApi {
|
|
52
58
|
weslToml: () => Promise<WeslTomlInfo>;
|
|
@@ -54,6 +60,12 @@ interface PluginExtensionApi {
|
|
|
54
60
|
weslRegistry: () => Promise<BatchModuleResolver>;
|
|
55
61
|
weslMain: (baseId: string) => Promise<string>;
|
|
56
62
|
weslDependencies: () => Promise<string[]>;
|
|
63
|
+
/** weslRoot relative to tomlDir, with forward slashes. */
|
|
64
|
+
debugWeslRoot: () => Promise<string>;
|
|
65
|
+
/** Get weslSrc scoped to modules reachable from a root, plus their deps. */
|
|
66
|
+
scopedProject: (rootModuleName: string) => Promise<ProjectSources>;
|
|
67
|
+
/** Fetch project sources, either all or scoped to reachable modules. */
|
|
68
|
+
fetchProject: (rootModuleName: string, options?: Record<string, string>) => Promise<ProjectSources>;
|
|
57
69
|
}
|
|
58
70
|
//#endregion
|
|
59
|
-
export { PluginExtension as n, PluginExtensionApi as r, ExtensionEmitFn as t };
|
|
71
|
+
export { ProjectSources as i, PluginExtension as n, PluginExtensionApi as r, ExtensionEmitFn as t };
|
|
@@ -14,12 +14,9 @@ const linkBuildExtension = {
|
|
|
14
14
|
emitFn: emitLinkJs
|
|
15
15
|
};
|
|
16
16
|
/** Emit a JavaScript LinkParams structure, ready for linking at runtime. */
|
|
17
|
-
async function emitLinkJs(baseId, api) {
|
|
18
|
-
const { resolvedRoot, tomlDir } = await api.weslToml();
|
|
19
|
-
const weslSrc = await api.weslSrc();
|
|
17
|
+
async function emitLinkJs(baseId, api, _conditions, options) {
|
|
20
18
|
const rootModuleName = noSuffix(await api.weslMain(baseId));
|
|
21
|
-
const debugWeslRoot =
|
|
22
|
-
const autoDeps = await api.weslDependencies();
|
|
19
|
+
const [{ weslSrc, dependencies: autoDeps }, debugWeslRoot] = await Promise.all([api.fetchProject(rootModuleName, options), api.debugWeslRoot()]);
|
|
23
20
|
const sanitizedDeps = autoDeps.map((dep) => dep.replaceAll("/", "_"));
|
|
24
21
|
const bundleImports = autoDeps.map((p, i) => `import ${sanitizedDeps[i]} from "${p}";`).join("\n");
|
|
25
22
|
const paramsName = `link${path.basename(rootModuleName).replace(/\W/g, "_")}Config`;
|
|
@@ -1301,34 +1298,28 @@ function resolve(specifier, parent) {
|
|
|
1301
1298
|
|
|
1302
1299
|
//#endregion
|
|
1303
1300
|
//#region src/extensions/StaticExtension.ts
|
|
1304
|
-
/**
|
|
1305
|
-
* a wesl-js ?static build extension that statically links from the root file
|
|
1306
|
-
* and emits a JavaScript file containing the linked wgsl string.
|
|
1307
|
-
*
|
|
1308
|
-
* use it like this:
|
|
1309
|
-
* import wgsl from "./shaders/app.wesl?static";
|
|
1310
|
-
*
|
|
1311
|
-
* or with conditions, like this:
|
|
1312
|
-
* import wgsl from "../shaders/foo/app.wesl MOBILE=true FUN SAFE=false ?static";
|
|
1313
|
-
*/
|
|
1301
|
+
/** Build extension for ?static imports: links WESL at build time, emits WGSL string. */
|
|
1314
1302
|
const staticBuildExtension = {
|
|
1315
1303
|
extensionName: "static",
|
|
1316
1304
|
emitFn: emitStaticJs
|
|
1317
1305
|
};
|
|
1318
|
-
/** Emit a
|
|
1319
|
-
async function emitStaticJs(baseId, api, conditions) {
|
|
1306
|
+
/** Emit a JS module exporting the statically linked WGSL string. */
|
|
1307
|
+
async function emitStaticJs(baseId, api, conditions, _options) {
|
|
1320
1308
|
const { resolvedRoot, tomlDir } = await api.weslToml();
|
|
1321
1309
|
const parentModule = url.pathToFileURL(path.join(tomlDir, "wesl.toml")).toString();
|
|
1322
|
-
const
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1310
|
+
const rootModuleName = noSuffix(await api.weslMain(baseId));
|
|
1311
|
+
const [weslSrc, dependencies] = await Promise.all([api.weslSrc(), api.weslDependencies()]);
|
|
1312
|
+
const libFileUrls = dependencies.map((d) => resolve(d, parentModule));
|
|
1313
|
+
const libs = (await Promise.all(libFileUrls.map((f) => import(f)))).map((m) => m.default);
|
|
1314
|
+
const { dest: wgsl } = await link({
|
|
1315
|
+
weslSrc,
|
|
1316
|
+
rootModuleName,
|
|
1328
1317
|
debugWeslRoot: path.relative(tomlDir, resolvedRoot).replaceAll(path.sep, "/"),
|
|
1329
1318
|
libs,
|
|
1330
1319
|
conditions
|
|
1331
|
-
})
|
|
1320
|
+
});
|
|
1321
|
+
return `
|
|
1322
|
+
export const wgsl = \`${wgsl}\`;
|
|
1332
1323
|
export default wgsl;
|
|
1333
1324
|
`;
|
|
1334
1325
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { n as resolve, r as linkBuildExtension, t as staticBuildExtension } from "./StaticExtension-
|
|
1
|
+
import { n as resolve, r as linkBuildExtension, t as staticBuildExtension } from "./StaticExtension-Q8HMuFJa.mjs";
|
|
2
2
|
import path, { posix, win32 } from "node:path";
|
|
3
|
-
import { RecordResolver, WeslParseError, filterMap, findUnboundIdents, npmNameVariations } from "wesl";
|
|
3
|
+
import { RecordResolver, WeslParseError, discoverModules, fileToModulePath, filterMap, findUnboundIdents, npmNameVariations } from "wesl";
|
|
4
|
+
import fs, { lstat, readdir, readlink, realpath } from "node:fs/promises";
|
|
4
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
6
|
import * as xi from "node:fs";
|
|
6
7
|
import { createUnplugin } from "unplugin";
|
|
7
|
-
import fs$1, { lstat, readdir, readlink, realpath } from "node:fs/promises";
|
|
8
8
|
import { lstatSync, readdir as readdir$1, readdirSync, readlinkSync, realpathSync as realpathSync$1 } from "fs";
|
|
9
9
|
import { EventEmitter } from "node:events";
|
|
10
10
|
import Pe from "node:stream";
|
|
@@ -6947,7 +6947,7 @@ const defaultWeslToml = {
|
|
|
6947
6947
|
* Provide default values for any required WeslToml fields.
|
|
6948
6948
|
*/
|
|
6949
6949
|
async function loadWeslToml(tomlFile) {
|
|
6950
|
-
const tomlString = await fs
|
|
6950
|
+
const tomlString = await fs.readFile(tomlFile, "utf-8");
|
|
6951
6951
|
const parsed = import_toml.default.parse(tomlString);
|
|
6952
6952
|
return {
|
|
6953
6953
|
...defaultWeslToml,
|
|
@@ -6964,11 +6964,11 @@ async function loadWeslToml(tomlFile) {
|
|
|
6964
6964
|
async function findWeslToml(projectDir, specifiedToml) {
|
|
6965
6965
|
let tomlFile;
|
|
6966
6966
|
if (specifiedToml) {
|
|
6967
|
-
await fs
|
|
6967
|
+
await fs.access(specifiedToml);
|
|
6968
6968
|
tomlFile = specifiedToml;
|
|
6969
6969
|
} else {
|
|
6970
6970
|
const tomlPath = path.join(projectDir, "wesl.toml");
|
|
6971
|
-
tomlFile = await fs
|
|
6971
|
+
tomlFile = await fs.access(tomlPath).then(() => tomlPath).catch(() => {});
|
|
6972
6972
|
}
|
|
6973
6973
|
let parsedToml;
|
|
6974
6974
|
let tomlDir;
|
|
@@ -7047,7 +7047,7 @@ function* exportSubpaths(mPath) {
|
|
|
7047
7047
|
*
|
|
7048
7048
|
* @param weslSrc - Record of WESL source files by path
|
|
7049
7049
|
* @param projectDir - Project directory for resolving package imports
|
|
7050
|
-
* @param virtualLibNames - Virtual lib names to exclude (e.g., ['
|
|
7050
|
+
* @param virtualLibNames - Virtual lib names to exclude (e.g., ['env', 'constants'])
|
|
7051
7051
|
* @returns Dependency paths in npm format (e.g., 'foo/bar', 'foo')
|
|
7052
7052
|
*/
|
|
7053
7053
|
function parseDependencies(weslSrc, projectDir, virtualLibNames = []) {
|
|
@@ -7063,6 +7063,10 @@ function parseDependencies(weslSrc, projectDir, virtualLibNames = []) {
|
|
|
7063
7063
|
}
|
|
7064
7064
|
const unbound = findUnboundIdents(resolver);
|
|
7065
7065
|
if (!unbound) return [];
|
|
7066
|
+
return resolvePkgDeps(unbound, projectDir, virtualLibNames);
|
|
7067
|
+
}
|
|
7068
|
+
/** Resolve pre-computed unbound refs to npm dependency paths. */
|
|
7069
|
+
function resolvePkgDeps(unbound, projectDir, virtualLibNames = []) {
|
|
7066
7070
|
const excludeRoots = new Set(["constants", ...virtualLibNames]);
|
|
7067
7071
|
const pkgRefs = unbound.filter((modulePath) => modulePath.length > 1 && !excludeRoots.has(modulePath[0]));
|
|
7068
7072
|
if (pkgRefs.length === 0) return [];
|
|
@@ -7079,29 +7083,51 @@ function projectDirURL(projectDir) {
|
|
|
7079
7083
|
|
|
7080
7084
|
//#endregion
|
|
7081
7085
|
//#region src/PluginApi.ts
|
|
7086
|
+
/** Construct the API surface available to plugin extensions. */
|
|
7082
7087
|
function buildApi(context, unpluginCtx) {
|
|
7083
|
-
|
|
7088
|
+
const api = {
|
|
7084
7089
|
weslToml: async () => getWeslToml(context, unpluginCtx),
|
|
7085
7090
|
weslSrc: async () => loadWesl(context, unpluginCtx),
|
|
7086
7091
|
weslRegistry: async () => getRegistry(context, unpluginCtx),
|
|
7087
7092
|
weslMain: makeGetWeslMain(context, unpluginCtx),
|
|
7088
|
-
weslDependencies: async () => findDependencies(context, unpluginCtx)
|
|
7093
|
+
weslDependencies: async () => findDependencies(context, unpluginCtx),
|
|
7094
|
+
debugWeslRoot: async () => getDebugWeslRoot(context, unpluginCtx),
|
|
7095
|
+
scopedProject: (rootModuleName) => getScopedProject(rootModuleName, context, unpluginCtx),
|
|
7096
|
+
fetchProject: (rootModuleName, options) => fetchProject(api, rootModuleName, options)
|
|
7097
|
+
};
|
|
7098
|
+
return api;
|
|
7099
|
+
}
|
|
7100
|
+
/** Get weslSrc scoped to modules reachable from root, plus their deps. */
|
|
7101
|
+
async function getScopedProject(rootModuleName, context, unpluginCtx) {
|
|
7102
|
+
const fullSrc = await loadWesl(context, unpluginCtx);
|
|
7103
|
+
const { toml, tomlDir: projectDir } = await getWeslToml(context, unpluginCtx);
|
|
7104
|
+
const { weslSrc, unbound } = discoverModules(fullSrc, new RecordResolver(fullSrc), fileToModulePath(rootModuleName, "package", false));
|
|
7105
|
+
return {
|
|
7106
|
+
weslSrc,
|
|
7107
|
+
dependencies: resolveDepsFromUnbound(toml.dependencies, unbound, projectDir)
|
|
7089
7108
|
};
|
|
7090
7109
|
}
|
|
7091
|
-
/**
|
|
7110
|
+
/** Resolve dependencies using pre-computed unbound refs (avoids re-parsing). */
|
|
7111
|
+
function resolveDepsFromUnbound(dependencies, unbound, projectDir) {
|
|
7112
|
+
const depsArray = Array.isArray(dependencies) ? dependencies : [dependencies ?? "auto"];
|
|
7113
|
+
if (!depsArray.includes("auto")) return depsArray;
|
|
7114
|
+
const base = depsArray.filter((dep) => dep !== "auto");
|
|
7115
|
+
const discovered = resolvePkgDeps(unbound, projectDir);
|
|
7116
|
+
return [...new Set([...base, ...discovered])];
|
|
7117
|
+
}
|
|
7118
|
+
/** Load and cache the wesl.toml configuration. */
|
|
7092
7119
|
async function getWeslToml(context, unpluginCtx) {
|
|
7093
7120
|
const { cache } = context;
|
|
7094
7121
|
if (cache.weslToml) return cache.weslToml;
|
|
7095
|
-
const
|
|
7096
|
-
const tomlInfo = await findWeslToml(process.cwd(), specifiedToml);
|
|
7122
|
+
const tomlInfo = await findWeslToml(process.cwd(), context.options.weslToml);
|
|
7097
7123
|
if (tomlInfo.tomlFile) {
|
|
7098
7124
|
unpluginCtx.addWatchFile(tomlInfo.tomlFile);
|
|
7099
7125
|
context.weslToml = tomlInfo.tomlFile;
|
|
7100
7126
|
}
|
|
7101
7127
|
cache.weslToml = tomlInfo;
|
|
7102
|
-
return
|
|
7128
|
+
return tomlInfo;
|
|
7103
7129
|
}
|
|
7104
|
-
/**
|
|
7130
|
+
/** Load all wesl files and return a cached RecordResolver. */
|
|
7105
7131
|
async function getRegistry(context, unpluginCtx) {
|
|
7106
7132
|
const { cache } = context;
|
|
7107
7133
|
let { registry } = cache;
|
|
@@ -7109,26 +7135,42 @@ async function getRegistry(context, unpluginCtx) {
|
|
|
7109
7135
|
const loaded = await loadWesl(context, unpluginCtx);
|
|
7110
7136
|
const { resolvedRoot } = await getWeslToml(context, unpluginCtx);
|
|
7111
7137
|
registry = new RecordResolver(loaded);
|
|
7112
|
-
Object.keys(loaded).map((p) => path.resolve(resolvedRoot, p))
|
|
7113
|
-
|
|
7114
|
-
});
|
|
7138
|
+
const fullPaths = Object.keys(loaded).map((p) => path.resolve(resolvedRoot, p));
|
|
7139
|
+
for (const f of fullPaths) unpluginCtx.addWatchFile(f);
|
|
7115
7140
|
cache.registry = registry;
|
|
7116
7141
|
return registry;
|
|
7117
7142
|
}
|
|
7118
|
-
/**
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7143
|
+
/** Compute weslRoot relative to tomlDir, with forward slashes. */
|
|
7144
|
+
async function getDebugWeslRoot(context, unpluginCtx) {
|
|
7145
|
+
const { resolvedRoot, tomlDir } = await getWeslToml(context, unpluginCtx);
|
|
7146
|
+
return toUnixPath(path.relative(tomlDir, resolvedRoot));
|
|
7147
|
+
}
|
|
7148
|
+
/** Fetch project sources, either all or scoped to reachable modules. */
|
|
7149
|
+
async function fetchProject(api, rootModuleName, options) {
|
|
7150
|
+
if (options?.include === "all") {
|
|
7151
|
+
const [weslSrc, dependencies] = await Promise.all([api.weslSrc(), api.weslDependencies()]);
|
|
7152
|
+
return {
|
|
7153
|
+
weslSrc,
|
|
7154
|
+
dependencies
|
|
7155
|
+
};
|
|
7156
|
+
}
|
|
7157
|
+
return api.scopedProject(rootModuleName);
|
|
7158
|
+
}
|
|
7159
|
+
/** Find dependencies, resolving "auto" entries by parsing source files. */
|
|
7122
7160
|
async function findDependencies(context, unpluginCtx) {
|
|
7123
7161
|
const { toml, tomlDir: projectDir } = await getWeslToml(context, unpluginCtx);
|
|
7124
7162
|
const weslSrc = await loadWesl(context, unpluginCtx);
|
|
7125
|
-
|
|
7126
|
-
|
|
7163
|
+
return resolveDeps(toml.dependencies, weslSrc, projectDir);
|
|
7164
|
+
}
|
|
7165
|
+
/** Resolve the dependency list, replacing "auto" entries with discovered deps. */
|
|
7166
|
+
function resolveDeps(dependencies, weslSrc, projectDir) {
|
|
7167
|
+
const depsArray = Array.isArray(dependencies) ? dependencies : [dependencies ?? "auto"];
|
|
7127
7168
|
if (!depsArray.includes("auto")) return depsArray;
|
|
7128
7169
|
const base = depsArray.filter((dep) => dep !== "auto");
|
|
7129
|
-
const
|
|
7130
|
-
return [...new Set([...base, ...
|
|
7170
|
+
const discovered = parseDependencies(weslSrc, projectDir);
|
|
7171
|
+
return [...new Set([...base, ...discovered])];
|
|
7131
7172
|
}
|
|
7173
|
+
/** @return a function that resolves a shader path to a weslRoot-relative module path. */
|
|
7132
7174
|
function makeGetWeslMain(context, unpluginContext) {
|
|
7133
7175
|
return getWeslMain;
|
|
7134
7176
|
/**
|
|
@@ -7137,58 +7179,40 @@ function makeGetWeslMain(context, unpluginContext) {
|
|
|
7137
7179
|
*/
|
|
7138
7180
|
async function getWeslMain(shaderPath) {
|
|
7139
7181
|
const { resolvedRoot } = await getWeslToml(context, unpluginContext);
|
|
7140
|
-
await fs
|
|
7182
|
+
await fs.access(shaderPath);
|
|
7141
7183
|
const absRoot = path.join(process.cwd(), resolvedRoot);
|
|
7142
7184
|
return toUnixPath(path.relative(absRoot, shaderPath));
|
|
7143
7185
|
}
|
|
7144
7186
|
}
|
|
7145
|
-
/**
|
|
7146
|
-
* Load the wesl files referenced in the wesl.toml file
|
|
7147
|
-
*
|
|
7148
|
-
* @return a record of wesl files with
|
|
7149
|
-
* keys as wesl file paths, and
|
|
7150
|
-
* values as wesl file contents.
|
|
7151
|
-
*/
|
|
7187
|
+
/** Load wesl files referenced in wesl.toml as a path-to-contents record. */
|
|
7152
7188
|
async function loadWesl(context, unpluginCtx) {
|
|
7153
|
-
const
|
|
7189
|
+
const tomlInfo = await getWeslToml(context, unpluginCtx);
|
|
7190
|
+
const { resolvedRoot, tomlDir } = tomlInfo;
|
|
7191
|
+
const { include } = tomlInfo.toml;
|
|
7154
7192
|
const futureFiles = include.map((g) => Ze(g, {
|
|
7155
7193
|
cwd: tomlDir,
|
|
7156
7194
|
absolute: true
|
|
7157
7195
|
}));
|
|
7158
7196
|
const files = (await Promise.all(futureFiles)).flat();
|
|
7159
|
-
files.
|
|
7160
|
-
unpluginCtx.addWatchFile(f);
|
|
7161
|
-
});
|
|
7197
|
+
for (const f of files) unpluginCtx.addWatchFile(f);
|
|
7162
7198
|
return await loadFiles(files, resolvedRoot);
|
|
7163
7199
|
}
|
|
7164
|
-
/**
|
|
7200
|
+
/** Load shader files, returning paths relative to weslRoot. */
|
|
7165
7201
|
async function loadFiles(files, weslRoot) {
|
|
7166
|
-
const
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
}
|
|
7172
|
-
return Object.fromEntries(loaded);
|
|
7202
|
+
const entries = await Promise.all(files.map(async (fullPath) => {
|
|
7203
|
+
const normalized = (await fs.readFile(fullPath, "utf-8")).replace(/\r\n/g, "\n");
|
|
7204
|
+
return [toUnixPath(path.relative(weslRoot, fullPath)), normalized];
|
|
7205
|
+
}));
|
|
7206
|
+
return Object.fromEntries(entries);
|
|
7173
7207
|
}
|
|
7174
7208
|
function toUnixPath(p) {
|
|
7175
|
-
|
|
7176
|
-
else return p;
|
|
7209
|
+
return path.sep !== "/" ? p.replaceAll(path.sep, "/") : p;
|
|
7177
7210
|
}
|
|
7178
7211
|
|
|
7179
7212
|
//#endregion
|
|
7180
7213
|
//#region src/WeslPlugin.ts
|
|
7181
|
-
/**
|
|
7182
|
-
* A bundler plugin for processing WESL files.
|
|
7183
|
-
*
|
|
7184
|
-
* The plugin works by reading the wesl.toml file and possibly package.json
|
|
7185
|
-
*
|
|
7186
|
-
* The plugin is triggered by imports to special virtual module urls
|
|
7187
|
-
* two urls suffixes are supported:
|
|
7188
|
-
* 1. `import "./shaders/bar.wesl?reflect"` - produces a javascript file for binding struct reflection
|
|
7189
|
-
* 2. `import "./shaders/bar.wesl?link"` - produces a javascript file for preconstructed link functions
|
|
7190
|
-
*/
|
|
7191
7214
|
const builtinExtensions = [staticBuildExtension, linkBuildExtension];
|
|
7215
|
+
/** Bundler plugin for WESL files, triggered by ?link or ?static import suffixes. */
|
|
7192
7216
|
function weslPlugin(options, meta) {
|
|
7193
7217
|
const o = options ?? {};
|
|
7194
7218
|
const extensions = [...builtinExtensions, ...o.extensions ?? []];
|
|
@@ -7210,10 +7234,8 @@ function weslPlugin(options, meta) {
|
|
|
7210
7234
|
load: buildLoader(context, log),
|
|
7211
7235
|
watchChange(id, _change) {
|
|
7212
7236
|
log("watchChange", { id });
|
|
7213
|
-
if (id.endsWith("wesl.toml"))
|
|
7214
|
-
|
|
7215
|
-
cache.registry = void 0;
|
|
7216
|
-
} else cache.registry = void 0;
|
|
7237
|
+
if (id.endsWith("wesl.toml")) cache.weslToml = void 0;
|
|
7238
|
+
cache.registry = void 0;
|
|
7217
7239
|
}
|
|
7218
7240
|
};
|
|
7219
7241
|
}
|
|
@@ -7224,110 +7246,91 @@ function pluginsByName(options) {
|
|
|
7224
7246
|
const entries = options.extensions?.map((p) => [p.extensionName, p]) ?? [];
|
|
7225
7247
|
return Object.fromEntries(entries);
|
|
7226
7248
|
}
|
|
7227
|
-
/** wesl
|
|
7228
|
-
|
|
7229
|
-
* foo/bar.wesl?link
|
|
7230
|
-
* or
|
|
7231
|
-
* foo/bar.wesl COND=false ?static
|
|
7232
|
-
*
|
|
7233
|
-
* Bundlers may add extra query params (e.g. Vite adds ?import for dynamic imports,
|
|
7234
|
-
* ?t=123 for cache busting), so we capture the full query and search within it.
|
|
7235
|
-
*
|
|
7236
|
-
* someday it'd be nice to support import attributes like:
|
|
7237
|
-
* import "foo.bar.wesl?static" with { COND: false};
|
|
7238
|
-
* (but that doesn't seem supported to be supported in the the bundler plugins yet)
|
|
7239
|
-
*/
|
|
7240
|
-
const pluginMatch = /(^^)?(?<baseId>.*\.w[eg]sl)(?<cond>(\s*\w+(=\w+)?\s*)*)\?(?<query>.+)$/;
|
|
7249
|
+
/** Match .wesl/.wgsl imports with query params. Bundlers may append extra params. */
|
|
7250
|
+
const pluginMatch = /(^^)?(?<baseId>.*\.w[eg]sl)\?(?<query>.+)$/;
|
|
7241
7251
|
const resolvedPrefix = "^^";
|
|
7242
|
-
/**
|
|
7243
|
-
|
|
7252
|
+
/** Reserved query param names (not treated as conditions). */
|
|
7253
|
+
const reservedParams = new Set(["include"]);
|
|
7254
|
+
/** Parse query string into plugin name, conditions, and options. */
|
|
7255
|
+
function parsePluginQuery(query, suffixes) {
|
|
7256
|
+
const segments = query.split("&");
|
|
7257
|
+
const pluginName = suffixes.find((s) => segments.includes(s));
|
|
7258
|
+
if (!pluginName) return null;
|
|
7259
|
+
const isBundlerParam = (s) => s === "import" || /^t=\d+/.test(s);
|
|
7260
|
+
const userSegments = segments.filter((s) => s !== pluginName && !isBundlerParam(s));
|
|
7261
|
+
const conditions = {};
|
|
7262
|
+
const options = {};
|
|
7263
|
+
for (const seg of userSegments) {
|
|
7264
|
+
const eqIdx = seg.indexOf("=");
|
|
7265
|
+
if (eqIdx === -1) conditions[seg] = true;
|
|
7266
|
+
else {
|
|
7267
|
+
const key = seg.slice(0, eqIdx);
|
|
7268
|
+
const val = seg.slice(eqIdx + 1);
|
|
7269
|
+
if (reservedParams.has(key)) options[key] = val;
|
|
7270
|
+
else conditions[key] = val !== "false";
|
|
7271
|
+
}
|
|
7272
|
+
}
|
|
7273
|
+
const hasConds = Object.keys(conditions).length > 0;
|
|
7274
|
+
const hasOpts = Object.keys(options).length > 0;
|
|
7275
|
+
return {
|
|
7276
|
+
pluginName,
|
|
7277
|
+
conditions: hasConds ? conditions : void 0,
|
|
7278
|
+
options: hasOpts ? options : void 0
|
|
7279
|
+
};
|
|
7280
|
+
}
|
|
7281
|
+
/** Build the resolveId hook for virtual module imports (?static, ?link, etc). */
|
|
7244
7282
|
function buildResolver(options, context, log) {
|
|
7245
7283
|
const suffixes = pluginNames(options);
|
|
7246
7284
|
return resolver;
|
|
7247
|
-
/**
|
|
7248
|
-
* For imports with conditions, vite won't resolve the module-path part of the js import
|
|
7249
|
-
* so we do it here.
|
|
7250
|
-
*
|
|
7251
|
-
* To avoid recirculating on resolve(), we rewrite the resolution id to start with ^^
|
|
7252
|
-
* The loader will drop the prefix.
|
|
7253
|
-
*/
|
|
7254
7285
|
function resolver(id, importer) {
|
|
7255
7286
|
if (id.startsWith(resolvedPrefix)) return id;
|
|
7256
7287
|
if (id === context.weslToml) return id;
|
|
7257
|
-
const
|
|
7288
|
+
const match = id.match(pluginMatch);
|
|
7289
|
+
const query = match?.groups?.query;
|
|
7290
|
+
if (!query) return null;
|
|
7291
|
+
const parsed = parsePluginQuery(query, suffixes);
|
|
7258
7292
|
log("resolveId", {
|
|
7259
7293
|
id,
|
|
7260
|
-
matched: !!
|
|
7294
|
+
matched: !!parsed,
|
|
7261
7295
|
suffixes
|
|
7262
7296
|
});
|
|
7263
|
-
if (
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
}
|
|
7270
|
-
return matched ? id : null;
|
|
7297
|
+
if (!parsed) return null;
|
|
7298
|
+
const baseId = match.groups.baseId;
|
|
7299
|
+
const importerDir = path.dirname(importer);
|
|
7300
|
+
const result = resolvedPrefix + path.join(importerDir, baseId) + "?" + query;
|
|
7301
|
+
log("resolveId resolved", { result });
|
|
7302
|
+
return result;
|
|
7271
7303
|
}
|
|
7272
7304
|
}
|
|
7273
|
-
/**
|
|
7274
|
-
function pluginSuffixMatch(id, suffixes) {
|
|
7275
|
-
const match = id.match(pluginMatch);
|
|
7276
|
-
const query = match?.groups?.query;
|
|
7277
|
-
if (!query) return null;
|
|
7278
|
-
const segments = query.split("&");
|
|
7279
|
-
const pluginName = suffixes.find((s) => segments.includes(s));
|
|
7280
|
-
if (!pluginName) return null;
|
|
7281
|
-
return {
|
|
7282
|
-
pluginName,
|
|
7283
|
-
baseId: match.groups.baseId,
|
|
7284
|
-
importParams: match.groups?.cond
|
|
7285
|
-
};
|
|
7286
|
-
}
|
|
7287
|
-
/** build plugin function for serving a javascript module in response to
|
|
7288
|
-
* an import of of our virtual import modules. */
|
|
7305
|
+
/** Build the load hook that emits JS for virtual module imports. */
|
|
7289
7306
|
function buildLoader(context, log) {
|
|
7290
7307
|
const { options } = context;
|
|
7291
7308
|
const suffixes = pluginNames(options);
|
|
7292
7309
|
const pluginsMap = pluginsByName(options);
|
|
7293
7310
|
return loader;
|
|
7294
7311
|
async function loader(id) {
|
|
7295
|
-
const
|
|
7312
|
+
const match = id.match(pluginMatch);
|
|
7313
|
+
const query = match?.groups?.query;
|
|
7314
|
+
if (!query) return null;
|
|
7315
|
+
const parsed = parsePluginQuery(query, suffixes);
|
|
7296
7316
|
log("load", {
|
|
7297
7317
|
id,
|
|
7298
|
-
matched:
|
|
7318
|
+
matched: parsed?.pluginName ?? null
|
|
7299
7319
|
});
|
|
7300
|
-
if (
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
return null;
|
|
7320
|
+
if (!parsed) return null;
|
|
7321
|
+
const buildPluginApi = buildApi(context, this);
|
|
7322
|
+
const plugin = pluginsMap[parsed.pluginName];
|
|
7323
|
+
const rawPath = match.groups.baseId;
|
|
7324
|
+
const shaderPath = rawPath.startsWith(resolvedPrefix) ? rawPath.slice(2) : rawPath;
|
|
7325
|
+
const { conditions, options: opts } = parsed;
|
|
7326
|
+
log("load emitting", {
|
|
7327
|
+
shaderPath,
|
|
7328
|
+
conditions,
|
|
7329
|
+
options: opts
|
|
7330
|
+
});
|
|
7331
|
+
return await plugin.emitFn(shaderPath, buildPluginApi, conditions, opts);
|
|
7313
7332
|
}
|
|
7314
7333
|
}
|
|
7315
|
-
/**
|
|
7316
|
-
* Convert an import parameters string to a Conditions record.
|
|
7317
|
-
*
|
|
7318
|
-
* Import parameters are key=value pairs separated by spaces.
|
|
7319
|
-
* Values may be "true" or "false" or missing (default to true)
|
|
7320
|
-
* e.g. ' MOBILE=true FUN SAFE=false '
|
|
7321
|
-
*/
|
|
7322
|
-
function importParamsToConditions(importParams) {
|
|
7323
|
-
if (!importParams) return void 0;
|
|
7324
|
-
const condEntries = importParams.trim().split(/\s+/).map((p) => {
|
|
7325
|
-
const [cond, value] = p.trim().split("=");
|
|
7326
|
-
if (value === void 0 || value === "true") return [cond, true];
|
|
7327
|
-
else return [cond, false];
|
|
7328
|
-
});
|
|
7329
|
-
return Object.fromEntries(condEntries);
|
|
7330
|
-
}
|
|
7331
7334
|
function fmtDebugData(data) {
|
|
7332
7335
|
return data ? " " + JSON.stringify(data) : "";
|
|
7333
7336
|
}
|
|
@@ -7335,9 +7338,7 @@ function debugLog(msg, data) {
|
|
|
7335
7338
|
console.error(`[wesl-plugin] ${msg}${fmtDebugData(data)}`);
|
|
7336
7339
|
}
|
|
7337
7340
|
function noopLog() {}
|
|
7338
|
-
const unplugin = createUnplugin(
|
|
7339
|
-
return weslPlugin(options, meta);
|
|
7340
|
-
});
|
|
7341
|
+
const unplugin = createUnplugin(weslPlugin);
|
|
7341
7342
|
|
|
7342
7343
|
//#endregion
|
|
7343
7344
|
export { weslPlugin as n, unplugin as t };
|
package/dist/pluginIndex.d.mts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import { n as PluginExtension, r as PluginExtensionApi, t as ExtensionEmitFn } from "./PluginExtension-
|
|
1
|
+
import { i as ProjectSources, n as PluginExtension, r as PluginExtensionApi, t as ExtensionEmitFn } from "./PluginExtension-CHxUB0-8.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/extensions/LinkExtension.d.ts
|
|
4
4
|
declare const linkBuildExtension: PluginExtension;
|
|
5
5
|
//#endregion
|
|
6
|
+
//#region src/extensions/ReflectExtension.d.ts
|
|
7
|
+
interface SimpleReflectOptions {
|
|
8
|
+
/** directory to contain the .d.ts files or undefined to not write .d.ts files */
|
|
9
|
+
typesDir?: string;
|
|
10
|
+
}
|
|
11
|
+
/** wesl-js build extension to reflect wgsl structs into js and .d.ts files. */
|
|
12
|
+
declare function simpleReflect(options?: SimpleReflectOptions): PluginExtension;
|
|
13
|
+
//#endregion
|
|
6
14
|
//#region src/extensions/StaticExtension.d.ts
|
|
7
|
-
/**
|
|
8
|
-
* a wesl-js ?static build extension that statically links from the root file
|
|
9
|
-
* and emits a JavaScript file containing the linked wgsl string.
|
|
10
|
-
*
|
|
11
|
-
* use it like this:
|
|
12
|
-
* import wgsl from "./shaders/app.wesl?static";
|
|
13
|
-
*
|
|
14
|
-
* or with conditions, like this:
|
|
15
|
-
* import wgsl from "../shaders/foo/app.wesl MOBILE=true FUN SAFE=false ?static";
|
|
16
|
-
*/
|
|
15
|
+
/** Build extension for ?static imports: links WESL at build time, emits WGSL string. */
|
|
17
16
|
declare const staticBuildExtension: PluginExtension;
|
|
18
17
|
//#endregion
|
|
19
|
-
export { ExtensionEmitFn, PluginExtension, PluginExtensionApi, linkBuildExtension, staticBuildExtension };
|
|
18
|
+
export { ExtensionEmitFn, PluginExtension, PluginExtensionApi, ProjectSources, SimpleReflectOptions, linkBuildExtension, simpleReflect, staticBuildExtension };
|
package/dist/pluginIndex.mjs
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
-
import { r as linkBuildExtension, t as staticBuildExtension } from "./StaticExtension-
|
|
1
|
+
import { r as linkBuildExtension, t as staticBuildExtension } from "./StaticExtension-Q8HMuFJa.mjs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { originalTypeName, weslStructs, wgslTypeToTs } from "wesl-reflect";
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
//#region src/extensions/ReflectExtension.ts
|
|
6
|
+
/** wesl-js build extension to reflect wgsl structs into js and .d.ts files. */
|
|
7
|
+
function simpleReflect(options = {}) {
|
|
8
|
+
const { typesDir = "./src/types" } = options;
|
|
9
|
+
return {
|
|
10
|
+
extensionName: "simple_reflect",
|
|
11
|
+
emitFn: async (_baseId, api) => {
|
|
12
|
+
const astStructs = [...(await api.weslRegistry()).allModules()].flatMap(([, module]) => module.moduleElem.contents.filter((e) => e.kind === "struct"));
|
|
13
|
+
const jsStructs = weslStructs(astStructs);
|
|
14
|
+
if (typesDir) await writeTypes(astStructs, typesDir);
|
|
15
|
+
return `export const structs = ${JSON.stringify(jsStructs, null, 2)};`;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Write .d.ts file with TypeScript interfaces for reflected structs. */
|
|
20
|
+
async function writeTypes(structs, typesDir) {
|
|
21
|
+
const tsdInterfaces = structs.map((s) => {
|
|
22
|
+
return `interface ${s.name.ident.originalName} {\n${s.members.map((m) => {
|
|
23
|
+
const tsType = wgslTypeToTs(originalTypeName(m.typeRef));
|
|
24
|
+
return ` ${m.name.name}: ${tsType};`;
|
|
25
|
+
}).join("\n")}\n}`;
|
|
26
|
+
});
|
|
27
|
+
await fs.mkdir(typesDir, { recursive: true });
|
|
28
|
+
await fs.writeFile(`${typesDir}/reflectTypes.d.ts`, tsdInterfaces.join("\n"));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { linkBuildExtension, simpleReflect, staticBuildExtension };
|
package/dist/plugins/astro.d.mts
CHANGED
package/dist/plugins/astro.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as unplugin } from "../WeslPlugin-
|
|
2
|
-
import "../StaticExtension-
|
|
1
|
+
import { t as unplugin } from "../WeslPlugin-CajtfXHH.mjs";
|
|
2
|
+
import "../StaticExtension-Q8HMuFJa.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/plugins/astro.ts
|
|
5
5
|
var astro_default = (options) => ({
|