wesl-plugin 0.6.46 → 0.6.49

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
@@ -1,109 +1,135 @@
1
- # **WESL Plugin**
1
+ # WESL Plugin
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/wesl-plugin)](https://www.npmjs.com/package/wesl-plugin)
4
4
  [![Static Badge](https://img.shields.io/badge/Read%20the%20-Docs-blue)](https://wesl-lang.dev/)
5
5
 
6
- > The wesl-plugin handles `.wesl` and `.wgsl` files
7
- in JavaScript bundlers to make using webgpu shaders more convenient.
6
+ Bundler plugin for importing `.wesl` and `.wgsl` shader files in JavaScript/TypeScript.
8
7
 
9
- Plugin features are accessed from JavaScript and TypeScript with `import` statements:
8
+ ## Install
10
9
 
11
- ```ts
12
- import linkConfig from "./shaders/app.wesl?link";
13
10
  ```
14
-
15
- ```ts
16
- import linkConfig from "./shaders/app.wesl?static";
11
+ npm install wesl wesl-plugin
17
12
  ```
18
13
 
19
- Each plugin ?feature is enabled by its own wesl-plugin [extension](#extensions).
14
+ ## Quick Start
20
15
 
21
- ## Install
16
+ ### Build-time linking (?static)
17
+
18
+ Link shaders at build time for the smallest application bundle size.
19
+
20
+ ```ts
21
+ // vite.config.ts
22
+ import { staticBuildExtension } from "wesl-plugin";
23
+ import viteWesl from "wesl-plugin/vite";
22
24
 
25
+ export default {
26
+ plugins: [viteWesl({ extensions: [staticBuildExtension] })]
27
+ };
23
28
  ```
24
- npm install wesl
25
- npm install wesl-plugin
29
+
30
+ ```ts
31
+ // app.ts
32
+ import wgsl from "./shaders/main.wesl?static";
33
+
34
+ const module = device.createShaderModule({ code: wgsl });
26
35
  ```
27
36
 
28
- ### Vite Configuration
37
+ ### Runtime linking (?link)
29
38
 
30
- Add the wesl-plugin along with any selected extensions to `vite.config.ts`:
39
+ Link shaders at runtime when you need dynamic conditions or constants:
31
40
 
32
41
  ```ts
33
- import { UserConfig } from "vite";
34
- import weslPlugin from "wesl-plugin/vite";
42
+ // vite.config.ts
35
43
  import { linkBuildExtension } from "wesl-plugin";
44
+ import viteWesl from "wesl-plugin/vite";
36
45
 
37
- const config: UserConfig = {
38
- plugins: [ weslPlugin({ extensions: [linkBuildExtension] }) ],
46
+ export default {
47
+ plugins: [viteWesl({ extensions: [linkBuildExtension] })]
39
48
  };
49
+ ```
50
+
51
+ ```ts
52
+ // app.ts
53
+ import { link } from "wesl";
54
+ import shaderConfig from "./shaders/main.wesl?link";
55
+
56
+ const linked = await link({
57
+ ...shaderConfig,
58
+ conditions: { MOBILE: isMobileGPU },
59
+ constants: { num_lights: 4 }
60
+ });
40
61
 
41
- export default config;
62
+ const module = linked.createShaderModule(device, {});
42
63
  ```
43
64
 
44
- In your JavaScript or TypeScript program you can then import
45
- wesl or wgsl shaders with a `?link` suffix and link them into WGSL at runtime.
65
+ ## Other Bundlers
46
66
 
47
67
  ```ts
48
- import linkConfig from "./shaders/app.wesl?link";
49
-
50
- function makeShaders() {
51
- const vertShader = await link({
52
- ...linkConfig,
53
- rootModuleName: "myVerts.wesl",
54
- conditions: {mobileGPU: true}
55
- });
56
- const computeShader = await link({
57
- ...linkConfig,
58
- rootModuleName: "myCompute.wesl",
59
- constants: {num_lights: 1}
60
- });
61
- }
62
-
68
+ import viteWesl from "wesl-plugin/vite";
69
+ import esbuildWesl from "wesl-plugin/esbuild";
70
+ import rollupWesl from "wesl-plugin/rollup";
71
+ import webpackWesl from "wesl-plugin/webpack";
72
+ // Also: nuxt, farm, rspack, astro
63
73
  ```
64
74
 
65
- ### Other Bundlers
75
+ ## Extensions
76
+
77
+ Extensions enable different import suffixes:
78
+
79
+ | Extension | Suffix | Output | Use Case |
80
+ |-----------|--------|--------|----------|
81
+ | `staticBuildExtension` | `?static` | WGSL string | Build-time linking, simplest |
82
+ | `linkBuildExtension` | `?link` | LinkParams object | Runtime conditions/constants |
66
83
 
67
- The wesl-plugin is available for many popular bundlers:
84
+ ### Combining Extensions
68
85
 
69
- ``` ts
70
- import weslPlugin from "wesl-plugin/esbuild";
71
- import weslPlugin from "wesl-plugin/rollup";
72
- import weslPlugin from "wesl-plugin/webpack";
73
- import weslPlugin from "wesl-plugin/nuxt";
74
- import weslPlugin from "wesl-plugin/farm";
75
- import weslPlugin from "wesl-plugin/rpack";
76
- // etc.
86
+ ```ts
87
+ import { staticBuildExtension, linkBuildExtension } from "wesl-plugin";
88
+ import viteWesl from "wesl-plugin/vite";
89
+
90
+ export default {
91
+ plugins: [viteWesl({
92
+ extensions: [staticBuildExtension, linkBuildExtension]
93
+ })]
94
+ };
77
95
  ```
78
96
 
79
- ## Extensions
97
+ ### Conditions in Import Path
80
98
 
81
- - **LinkExtension** - import `?link` in JavaScript/TypeScript programs to conveniently assemble shader files and libraries for linking at runtime.
82
- Reads the `wesl.toml` file to find local shader files and libraries,
83
- Returns a `LinkParams` object ready to use for runtime linking.
99
+ For `?static`, you can specify conditions directly in the import:
84
100
 
85
- - **StaticExtension** - import `?static` in JavaScript/TypeScript programs to
86
- link shader files at build time.
87
- Reads the `wesl.toml` file to find local shader files and libraries,
88
- Returns a wgsl string ready to use for `createShaderModule`.
101
+ ```ts
102
+ import wgsl from "./app.wesl MOBILE=true DEBUG=false ?static";
103
+ ```
104
+
105
+ ## Configuration (wesl.toml)
106
+
107
+ The plugin reads `wesl.toml` to find shader files and dependencies:
89
108
 
90
- ### Prototype Extensions
109
+ ```toml
110
+ weslFiles = ["shaders/**/*.wesl"]
111
+ weslRoot = "shaders"
112
+ dependencies = ["auto"] # Auto-detect from package.json
113
+ ```
91
114
 
92
- - **SimpleReReflectExtension** - (_demo for extension writers_) import `?simple_reflect` to
93
- translate some wgsl `struct` elements into JavaScript and TypeScript.
94
- Demonstrates to wesl-plugin extension authors how to connect
95
- to the wesl-plugin, how to produce JavaScript, and how to produce TypeScript.
96
- - **BindingLayoutExtension** - (_prototype_) import `?bindingLayout` to collect JavaScript
97
- `BindingGroupLayout` objects.
98
- Works in concert with the `bindingStructsPlugin` to translate a proposed new WGSL
99
- feature for defining binding group layouts in shaders [#4957](https://github.com/gpuweb/gpuweb/issues/4957).
115
+ ## Prototype Extensions
100
116
 
101
- ## Developing a wesl-plugin extension
117
+ - **SimpleReflectExtension** - Demo for extension authors showing how to generate JS/TS from shader structs
118
+ - **BindingLayoutExtension** - Prototype for generating `BindGroupLayout` objects from shaders
102
119
 
103
- To add a new extension to the wesl-plugin:
120
+ ## Writing Custom Extensions
104
121
 
105
- - Pick an import suffix (e.g. `?myExtension`).
106
- - Implement a function that returns a JavaScript string.
107
- - Extensions have access to wgsl/wesl sources, a parsed abstract syntax tree for the sources, etc.
122
+ ```ts
123
+ import type { PluginExtension } from "wesl-plugin";
124
+
125
+ const myExtension: PluginExtension = {
126
+ extensionName: "myfeature", // enables ?myfeature imports
127
+ emitFn: async (shaderPath, api, conditions) => {
128
+ const sources = await api.weslSrc();
129
+ // Return JavaScript code as a string
130
+ return `export default ${JSON.stringify(sources)};`;
131
+ }
132
+ };
133
+ ```
108
134
 
109
- See [PluginExtension.ts](https://github.com/wgsl-tooling-wg/wesl-js/blob/master/tools/packages/wesl-plugin/src/PluginExtension.ts) for details.
135
+ See [PluginExtension.ts](https://github.com/wgsl-tooling-wg/wesl-js/blob/main/tools/packages/wesl-plugin/src/PluginExtension.ts) for the full API.
@@ -1,6 +1,6 @@
1
1
  import { t as resolve } from "./import-meta-resolve-CUFqnZwT.js";
2
2
  import path, { posix, win32 } from "node:path";
3
- import { RecordResolver, WeslParseError, bindIdentsRecursive, filterMap, findValidRootDecls, minimalMangle } from "wesl";
3
+ import { RecordResolver, WeslParseError, filterMap, findUnboundIdents, npmNameVariations } from "wesl";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import * as actualFS from "node:fs";
6
6
  import { createUnplugin } from "unplugin";
@@ -5841,41 +5841,6 @@ const glob = Object.assign(glob_, {
5841
5841
  });
5842
5842
  glob.glob = glob;
5843
5843
 
5844
- //#endregion
5845
- //#region ../wesl-tooling/src/FindUnboundIdents.ts
5846
- /**
5847
- * Find unbound package references in library sources.
5848
- *
5849
- * Binds local references without following cross-package imports, revealing
5850
- * which external packages are referenced but not resolved.
5851
- *
5852
- * @param resolver - Module resolver that supports batch operations
5853
- * @returns Array of unbound module paths, each as an array of path segments
5854
- * (e.g., [['foo', 'bar', 'baz'], ['other', 'pkg']])
5855
- */
5856
- function findUnboundIdents(resolver) {
5857
- const bindContext = {
5858
- resolver,
5859
- conditions: {},
5860
- knownDecls: /* @__PURE__ */ new Set(),
5861
- foundScopes: /* @__PURE__ */ new Set(),
5862
- globalNames: /* @__PURE__ */ new Set(),
5863
- globalStatements: /* @__PURE__ */ new Map(),
5864
- mangler: minimalMangle,
5865
- unbound: [],
5866
- dontFollowDecls: true
5867
- };
5868
- for (const [_modulePath, ast] of resolver.allModules()) {
5869
- const declEntries = findValidRootDecls(ast.rootScope, {}).map((d) => [d.originalName, d]);
5870
- const liveDecls = {
5871
- decls: new Map(declEntries),
5872
- parent: null
5873
- };
5874
- bindIdentsRecursive(ast.rootScope, bindContext, liveDecls, true);
5875
- }
5876
- return bindContext.unbound;
5877
- }
5878
-
5879
5844
  //#endregion
5880
5845
  //#region ../../node_modules/.pnpm/toml@3.0.0/node_modules/toml/lib/parser.js
5881
5846
  var require_parser = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/toml@3.0.0/node_modules/toml/lib/parser.js": ((exports, module) => {
@@ -9712,38 +9677,6 @@ async function findWeslToml(projectDir, specifiedToml) {
9712
9677
  };
9713
9678
  }
9714
9679
 
9715
- //#endregion
9716
- //#region ../wesl-tooling/src/PackageNameUtils.ts
9717
- /** Generate npm package name variations from sanitized WESL identifier.
9718
- *
9719
- * Uses double-underscore encoding to distinguish scoped vs unscoped packages:
9720
- * - Has __ → scoped package (try @scope/pkg variants)
9721
- * - No __ → unscoped package (try pkg variants)
9722
- *
9723
- * Examples:
9724
- * "lygia__shader_utils" → ["@lygia/shader_utils", "@lygia/shader-utils"]
9725
- * "random_wgsl" → ["random_wgsl", "random-wgsl"]
9726
- */
9727
- function* npmNameVariations(sanitizedPath) {
9728
- const [pkg, sub] = breakAt(sanitizedPath, "/");
9729
- let pkgName = pkg;
9730
- let scopePrefix = "";
9731
- if (pkg.includes("__")) {
9732
- const [scope, ...rest] = pkg.split("__");
9733
- pkgName = rest.join("__");
9734
- scopePrefix = `@${scope}/`;
9735
- }
9736
- yield `${scopePrefix}${pkgName}${sub}`;
9737
- yield `${scopePrefix}${pkgName.replaceAll("_", "-")}${sub}`;
9738
- }
9739
- /** Break string at first occurrence of delimiter.
9740
- * @returns [before, after] where after includes the delimiter */
9741
- function breakAt(str, delimiter) {
9742
- const index = str.indexOf(delimiter);
9743
- if (index === -1) return [str, ""];
9744
- return [str.slice(0, index), str.slice(index)];
9745
- }
9746
-
9747
9680
  //#endregion
9748
9681
  //#region ../wesl-tooling/src/NpmResolver.ts
9749
9682
  /** Find longest resolvable npm subpath from WESL module path segments.
@@ -9947,11 +9880,14 @@ function weslPlugin(options, meta) {
9947
9880
  meta,
9948
9881
  options
9949
9882
  };
9883
+ const log = options.debug ? debugLog : noopLog;
9884
+ log("init", { extensions: options.extensions?.map((e) => e.extensionName) });
9950
9885
  return {
9951
9886
  name: "wesl-plugin",
9952
- resolveId: buildResolver(options, context),
9953
- load: buildLoader(context),
9887
+ resolveId: buildResolver(options, context, log),
9888
+ load: buildLoader(context, log),
9954
9889
  watchChange(id, _change) {
9890
+ log("watchChange", { id });
9955
9891
  if (id.endsWith("wesl.toml")) {
9956
9892
  cache.weslToml = void 0;
9957
9893
  cache.registry = void 0;
@@ -9972,15 +9908,18 @@ function pluginsByName(options) {
9972
9908
  * or
9973
9909
  * foo/bar.wesl COND=false ?static
9974
9910
  *
9911
+ * Bundlers may add extra query params (e.g. Vite adds ?import for dynamic imports,
9912
+ * ?t=123 for cache busting), so we capture the full query and search within it.
9913
+ *
9975
9914
  * someday it'd be nice to support import attributes like:
9976
9915
  * import "foo.bar.wesl?static" with { COND: false};
9977
9916
  * (but that doesn't seem supported to be supported in the the bundler plugins yet)
9978
9917
  */
9979
- const pluginMatch = /(^^)?(?<baseId>.*\.w[eg]sl)(?<cond>(\s*\w+(=\w+)?\s*)*)\?(?<pluginName>[\w_-]+)$/;
9918
+ const pluginMatch = /(^^)?(?<baseId>.*\.w[eg]sl)(?<cond>(\s*\w+(=\w+)?\s*)*)\?(?<query>.+)$/;
9980
9919
  const resolvedPrefix = "^^";
9981
9920
  /** build plugin entry for 'resolverId'
9982
9921
  * to validate our javascript virtual module imports (with e.g. ?static or ?link suffixes) */
9983
- function buildResolver(options, context) {
9922
+ function buildResolver(options, context, log) {
9984
9923
  const suffixes = pluginNames(options);
9985
9924
  return resolver;
9986
9925
  /**
@@ -9994,39 +9933,58 @@ function buildResolver(options, context) {
9994
9933
  if (id.startsWith(resolvedPrefix)) return id;
9995
9934
  if (id === context.weslToml) return id;
9996
9935
  const matched = pluginSuffixMatch(id, suffixes);
9936
+ log("resolveId", {
9937
+ id,
9938
+ matched: !!matched,
9939
+ suffixes
9940
+ });
9997
9941
  if (matched) {
9998
9942
  const { importParams, baseId, pluginName } = matched;
9999
9943
  const importerDir = path.dirname(importer);
10000
- return resolvedPrefix + path.join(importerDir, baseId) + importParams + "?" + pluginName;
9944
+ const result = resolvedPrefix + path.join(importerDir, baseId) + importParams + "?" + pluginName;
9945
+ log("resolveId resolved", { result });
9946
+ return result;
10001
9947
  }
10002
9948
  return matched ? id : null;
10003
9949
  }
10004
9950
  }
9951
+ /** Find matching plugin suffix in query string (handles ?import&static, ?t=123&static, etc.) */
10005
9952
  function pluginSuffixMatch(id, suffixes) {
10006
- const suffixMatch = id.match(pluginMatch);
10007
- const pluginName = suffixMatch?.groups?.pluginName;
10008
- if (!pluginName || !suffixes.includes(pluginName)) return null;
9953
+ const match$1 = id.match(pluginMatch);
9954
+ const query = match$1?.groups?.query;
9955
+ if (!query) return null;
9956
+ const segments = query.split("&");
9957
+ const pluginName = suffixes.find((s) => segments.includes(s));
9958
+ if (!pluginName) return null;
10009
9959
  return {
10010
9960
  pluginName,
10011
- baseId: suffixMatch.groups.baseId,
10012
- importParams: suffixMatch.groups?.cond
9961
+ baseId: match$1.groups.baseId,
9962
+ importParams: match$1.groups?.cond
10013
9963
  };
10014
9964
  }
10015
9965
  /** build plugin function for serving a javascript module in response to
10016
9966
  * an import of of our virtual import modules. */
10017
- function buildLoader(context) {
9967
+ function buildLoader(context, log) {
10018
9968
  const { options } = context;
10019
9969
  const suffixes = pluginNames(options);
10020
9970
  const pluginsMap = pluginsByName(options);
10021
9971
  return loader;
10022
9972
  async function loader(id) {
10023
9973
  const matched = pluginSuffixMatch(id, suffixes);
9974
+ log("load", {
9975
+ id,
9976
+ matched: matched?.pluginName ?? null
9977
+ });
10024
9978
  if (matched) {
10025
9979
  const buildPluginApi = buildApi(context, this);
10026
9980
  const plugin = pluginsMap[matched.pluginName];
10027
9981
  const { baseId, importParams } = matched;
10028
9982
  const conditions = importParamsToConditions(importParams);
10029
9983
  const shaderPath = baseId.startsWith(resolvedPrefix) ? baseId.slice(2) : baseId;
9984
+ log("load emitting", {
9985
+ shaderPath,
9986
+ conditions
9987
+ });
10030
9988
  return await plugin.emitFn(shaderPath, buildPluginApi, conditions);
10031
9989
  }
10032
9990
  return null;
@@ -10048,6 +10006,13 @@ function importParamsToConditions(importParams) {
10048
10006
  });
10049
10007
  return Object.fromEntries(condEntries);
10050
10008
  }
10009
+ function fmtDebugData(data) {
10010
+ return data ? " " + JSON.stringify(data) : "";
10011
+ }
10012
+ function debugLog(msg, data) {
10013
+ console.error(`[wesl-plugin] ${msg}${fmtDebugData(data)}`);
10014
+ }
10015
+ function noopLog() {}
10051
10016
  const unplugin = createUnplugin((options, meta) => {
10052
10017
  return weslPlugin(options, meta);
10053
10018
  });
@@ -4,6 +4,8 @@ import { n as PluginExtension } from "./PluginExtension-DlhUTOLC.js";
4
4
  interface WeslPluginOptions {
5
5
  weslToml?: string;
6
6
  extensions?: PluginExtension[];
7
+ /** Log plugin activity to stderr for debugging */
8
+ debug?: boolean;
7
9
  }
8
10
  //#endregion
9
11
  export { WeslPluginOptions as t };
@@ -17,7 +17,7 @@ async function emitLinkJs(baseId, api) {
17
17
  const autoDeps = await api.weslDependencies();
18
18
  const sanitizedDeps = autoDeps.map((dep) => dep.replaceAll("/", "_"));
19
19
  const bundleImports = autoDeps.map((p, i) => `import ${sanitizedDeps[i]} from "${p}";`).join("\n");
20
- const paramsName = `link${path.basename(rootModuleName)}Config`;
20
+ const paramsName = `link${path.basename(rootModuleName).replace(/\W/g, "_")}Config`;
21
21
  const linkParams = {
22
22
  rootModuleName,
23
23
  weslSrc,
@@ -1,5 +1,5 @@
1
1
  import "../PluginExtension-DlhUTOLC.js";
2
- import { t as WeslPluginOptions } from "../WeslPluginOptions-BXvD7dWh.js";
2
+ import { t as WeslPluginOptions } from "../WeslPluginOptions-BljqDSO5.js";
3
3
 
4
4
  //#region src/plugins/astro.d.ts
5
5
  declare const _default: (options: WeslPluginOptions) => any;
@@ -1,4 +1,4 @@
1
- import { t as WeslPlugin_default } from "../WeslPlugin-D0IEnDmK.js";
1
+ import { t as WeslPlugin_default } from "../WeslPlugin-JYxf2uLM.js";
2
2
  import "../import-meta-resolve-CUFqnZwT.js";
3
3
 
4
4
  //#region src/plugins/astro.ts
@@ -1,5 +1,5 @@
1
1
  import "../PluginExtension-DlhUTOLC.js";
2
- import { t as WeslPluginOptions } from "../WeslPluginOptions-BXvD7dWh.js";
2
+ import { t as WeslPluginOptions } from "../WeslPluginOptions-BljqDSO5.js";
3
3
  import * as esbuild0 from "esbuild";
4
4
 
5
5
  //#region src/plugins/esbuild.d.ts
@@ -1,4 +1,4 @@
1
- import { n as weslPlugin } from "../WeslPlugin-D0IEnDmK.js";
1
+ import { n as weslPlugin } from "../WeslPlugin-JYxf2uLM.js";
2
2
  import "../import-meta-resolve-CUFqnZwT.js";
3
3
  import { createEsbuildPlugin } from "unplugin";
4
4
 
@@ -1,5 +1,5 @@
1
1
  import "../PluginExtension-DlhUTOLC.js";
2
- import { t as WeslPluginOptions } from "../WeslPluginOptions-BXvD7dWh.js";
2
+ import { t as WeslPluginOptions } from "../WeslPluginOptions-BljqDSO5.js";
3
3
  import * as _farmfe_core0 from "@farmfe/core";
4
4
 
5
5
  //#region src/plugins/farm.d.ts
@@ -1,4 +1,4 @@
1
- import { n as weslPlugin } from "../WeslPlugin-D0IEnDmK.js";
1
+ import { n as weslPlugin } from "../WeslPlugin-JYxf2uLM.js";
2
2
  import "../import-meta-resolve-CUFqnZwT.js";
3
3
  import { createFarmPlugin } from "unplugin";
4
4
 
@@ -1,5 +1,5 @@
1
1
  import "../PluginExtension-DlhUTOLC.js";
2
- import { t as WeslPluginOptions } from "../WeslPluginOptions-BXvD7dWh.js";
2
+ import { t as WeslPluginOptions } from "../WeslPluginOptions-BljqDSO5.js";
3
3
  import * as _nuxt_schema0 from "@nuxt/schema";
4
4
 
5
5
  //#region src/plugins/nuxt.d.ts
@@ -1,7 +1,7 @@
1
- import "../WeslPlugin-D0IEnDmK.js";
1
+ import "../WeslPlugin-JYxf2uLM.js";
2
2
  import "../import-meta-resolve-CUFqnZwT.js";
3
- import { t as vite_default } from "../vite-CccajS5p.js";
4
- import { t as webpack_default } from "../webpack-Duvcn8o1.js";
3
+ import { t as vite_default } from "../vite-CE8Yhwk-.js";
4
+ import { t as webpack_default } from "../webpack-C7YoLSyP.js";
5
5
  import { addVitePlugin, addWebpackPlugin, defineNuxtModule } from "@nuxt/kit";
6
6
  import "@nuxt/schema";
7
7
 
@@ -1,5 +1,5 @@
1
1
  import "../PluginExtension-DlhUTOLC.js";
2
- import { t as WeslPluginOptions } from "../WeslPluginOptions-BXvD7dWh.js";
2
+ import { t as WeslPluginOptions } from "../WeslPluginOptions-BljqDSO5.js";
3
3
  import * as rollup0 from "rollup";
4
4
 
5
5
  //#region src/plugins/rollup.d.ts
@@ -1,4 +1,4 @@
1
- import { n as weslPlugin } from "../WeslPlugin-D0IEnDmK.js";
1
+ import { n as weslPlugin } from "../WeslPlugin-JYxf2uLM.js";
2
2
  import "../import-meta-resolve-CUFqnZwT.js";
3
3
  import { createRollupPlugin } from "unplugin";
4
4
 
@@ -1,5 +1,5 @@
1
1
  import "../PluginExtension-DlhUTOLC.js";
2
- import { t as WeslPluginOptions } from "../WeslPluginOptions-BXvD7dWh.js";
2
+ import { t as WeslPluginOptions } from "../WeslPluginOptions-BljqDSO5.js";
3
3
 
4
4
  //#region src/plugins/rspack.d.ts
5
5
  declare const _default: (options: WeslPluginOptions) => RspackPluginInstance;
@@ -1,4 +1,4 @@
1
- import { n as weslPlugin } from "../WeslPlugin-D0IEnDmK.js";
1
+ import { n as weslPlugin } from "../WeslPlugin-JYxf2uLM.js";
2
2
  import "../import-meta-resolve-CUFqnZwT.js";
3
3
  import { createRspackPlugin } from "unplugin";
4
4
 
@@ -1,5 +1,5 @@
1
1
  import "../PluginExtension-DlhUTOLC.js";
2
- import { t as WeslPluginOptions } from "../WeslPluginOptions-BXvD7dWh.js";
2
+ import { t as WeslPluginOptions } from "../WeslPluginOptions-BljqDSO5.js";
3
3
  import * as vite0 from "vite";
4
4
 
5
5
  //#region src/plugins/vite.d.ts
@@ -1,5 +1,5 @@
1
- import "../WeslPlugin-D0IEnDmK.js";
1
+ import "../WeslPlugin-JYxf2uLM.js";
2
2
  import "../import-meta-resolve-CUFqnZwT.js";
3
- import { t as vite_default } from "../vite-CccajS5p.js";
3
+ import { t as vite_default } from "../vite-CE8Yhwk-.js";
4
4
 
5
5
  export { vite_default as default };
@@ -1,5 +1,5 @@
1
1
  import "../PluginExtension-DlhUTOLC.js";
2
- import { t as WeslPluginOptions } from "../WeslPluginOptions-BXvD7dWh.js";
2
+ import { t as WeslPluginOptions } from "../WeslPluginOptions-BljqDSO5.js";
3
3
  import * as webpack0 from "webpack";
4
4
 
5
5
  //#region src/plugins/webpack.d.ts
@@ -1,5 +1,5 @@
1
- import "../WeslPlugin-D0IEnDmK.js";
1
+ import "../WeslPlugin-JYxf2uLM.js";
2
2
  import "../import-meta-resolve-CUFqnZwT.js";
3
- import { t as webpack_default } from "../webpack-Duvcn8o1.js";
3
+ import { t as webpack_default } from "../webpack-C7YoLSyP.js";
4
4
 
5
5
  export { webpack_default as default };
@@ -1,4 +1,4 @@
1
- import { n as weslPlugin } from "./WeslPlugin-D0IEnDmK.js";
1
+ import { n as weslPlugin } from "./WeslPlugin-JYxf2uLM.js";
2
2
  import { createVitePlugin } from "unplugin";
3
3
 
4
4
  //#region src/plugins/vite.ts
@@ -1,4 +1,4 @@
1
- import { n as weslPlugin } from "./WeslPlugin-D0IEnDmK.js";
1
+ import { n as weslPlugin } from "./WeslPlugin-JYxf2uLM.js";
2
2
  import { createWebpackPlugin } from "unplugin";
3
3
 
4
4
  //#region src/plugins/webpack.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wesl-plugin",
3
3
  "description": "",
4
- "version": "0.6.46",
4
+ "version": "0.6.49",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "src",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "unplugin": "^2.3.5",
28
- "wesl": "0.6.46"
28
+ "wesl": "0.6.49"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@nuxt/kit": "^3.17.6",
package/src/WeslPlugin.ts CHANGED
@@ -50,6 +50,8 @@ export interface PluginContext {
50
50
  weslToml?: string;
51
51
  }
52
52
 
53
+ type DebugLog = (msg: string, data?: Record<string, unknown>) => void;
54
+
53
55
  /**
54
56
  * A bundler plugin for processing WESL files.
55
57
  *
@@ -66,12 +68,16 @@ export function weslPlugin(
66
68
  ): UnpluginOptions {
67
69
  const cache: PluginCache = {};
68
70
  const context: PluginContext = { cache, meta, options };
71
+ const log = options.debug ? debugLog : noopLog;
72
+
73
+ log("init", { extensions: options.extensions?.map(e => e.extensionName) });
69
74
 
70
75
  return {
71
76
  name: "wesl-plugin",
72
- resolveId: buildResolver(options, context),
73
- load: buildLoader(context),
77
+ resolveId: buildResolver(options, context, log),
78
+ load: buildLoader(context, log),
74
79
  watchChange(id, _change) {
80
+ log("watchChange", { id });
75
81
  if (id.endsWith("wesl.toml")) {
76
82
  // The cache is shared for multiple imports
77
83
  cache.weslToml = undefined;
@@ -100,12 +106,15 @@ function pluginsByName(
100
106
  * or
101
107
  * foo/bar.wesl COND=false ?static
102
108
  *
109
+ * Bundlers may add extra query params (e.g. Vite adds ?import for dynamic imports,
110
+ * ?t=123 for cache busting), so we capture the full query and search within it.
111
+ *
103
112
  * someday it'd be nice to support import attributes like:
104
113
  * import "foo.bar.wesl?static" with { COND: false};
105
114
  * (but that doesn't seem supported to be supported in the the bundler plugins yet)
106
115
  */
107
116
  const pluginMatch =
108
- /(^^)?(?<baseId>.*\.w[eg]sl)(?<cond>(\s*\w+(=\w+)?\s*)*)\?(?<pluginName>[\w_-]+)$/;
117
+ /(^^)?(?<baseId>.*\.w[eg]sl)(?<cond>(\s*\w+(=\w+)?\s*)*)\?(?<query>.+)$/;
109
118
 
110
119
  const resolvedPrefix = "^^";
111
120
 
@@ -114,6 +123,7 @@ const resolvedPrefix = "^^";
114
123
  function buildResolver(
115
124
  options: WeslPluginOptions,
116
125
  context: PluginContext,
126
+ log: DebugLog,
117
127
  ): Resolver {
118
128
  const suffixes = pluginNames(options);
119
129
  return resolver;
@@ -141,6 +151,7 @@ function buildResolver(
141
151
  return id;
142
152
  }
143
153
  const matched = pluginSuffixMatch(id, suffixes);
154
+ log("resolveId", { id, matched: !!matched, suffixes });
144
155
  if (matched) {
145
156
  const { importParams, baseId, pluginName } = matched;
146
157
 
@@ -149,6 +160,7 @@ function buildResolver(
149
160
  const pathToShader = path.join(importerDir, baseId);
150
161
  const result =
151
162
  resolvedPrefix + pathToShader + importParams + "?" + pluginName;
163
+ log("resolveId resolved", { result });
152
164
  return result;
153
165
  }
154
166
  return matched ? id : null; // this case doesn't happen AFAIK
@@ -161,20 +173,27 @@ interface PluginMatch {
161
173
  pluginName: string;
162
174
  }
163
175
 
176
+ /** Find matching plugin suffix in query string (handles ?import&static, ?t=123&static, etc.) */
164
177
  function pluginSuffixMatch(id: string, suffixes: string[]): PluginMatch | null {
165
- const suffixMatch = id.match(pluginMatch);
166
- const pluginName = suffixMatch?.groups?.pluginName;
167
- if (!pluginName || !suffixes.includes(pluginName)) return null;
178
+ const match = id.match(pluginMatch);
179
+ const query = match?.groups?.query;
180
+ if (!query) return null;
181
+
182
+ // Query params are &-separated; find one that matches a configured suffix
183
+ const segments = query.split("&");
184
+ const pluginName = suffixes.find(s => segments.includes(s));
185
+ if (!pluginName) return null;
186
+
168
187
  return {
169
188
  pluginName,
170
- baseId: suffixMatch.groups!.baseId,
171
- importParams: suffixMatch.groups?.cond,
189
+ baseId: match.groups!.baseId,
190
+ importParams: match.groups?.cond,
172
191
  };
173
192
  }
174
193
 
175
194
  /** build plugin function for serving a javascript module in response to
176
195
  * an import of of our virtual import modules. */
177
- function buildLoader(context: PluginContext): Loader {
196
+ function buildLoader(context: PluginContext, log: DebugLog): Loader {
178
197
  const { options } = context;
179
198
  const suffixes = pluginNames(options);
180
199
  const pluginsMap = pluginsByName(options);
@@ -185,6 +204,7 @@ function buildLoader(context: PluginContext): Loader {
185
204
  id: string,
186
205
  ) {
187
206
  const matched = pluginSuffixMatch(id, suffixes);
207
+ log("load", { id, matched: matched?.pluginName ?? null });
188
208
  if (matched) {
189
209
  const buildPluginApi = buildApi(context, this);
190
210
  const plugin = pluginsMap[matched.pluginName];
@@ -194,6 +214,7 @@ function buildLoader(context: PluginContext): Loader {
194
214
  ? baseId.slice(resolvedPrefix.length)
195
215
  : baseId;
196
216
 
217
+ log("load emitting", { shaderPath, conditions });
197
218
  return await plugin.emitFn(shaderPath, buildPluginApi, conditions);
198
219
  }
199
220
 
@@ -226,6 +247,16 @@ function importParamsToConditions(
226
247
  return conditions;
227
248
  }
228
249
 
250
+ function fmtDebugData(data?: Record<string, unknown>): string {
251
+ return data ? " " + JSON.stringify(data) : "";
252
+ }
253
+
254
+ function debugLog(msg: string, data?: Record<string, unknown>): void {
255
+ console.error(`[wesl-plugin] ${msg}${fmtDebugData(data)}`);
256
+ }
257
+
258
+ function noopLog(): void {}
259
+
229
260
  export const unplugin = createUnplugin(
230
261
  (options: WeslPluginOptions, meta: UnpluginContextMeta) => {
231
262
  return weslPlugin(options, meta);
@@ -3,4 +3,7 @@ import type { PluginExtension } from "./PluginExtension.ts";
3
3
  export interface WeslPluginOptions {
4
4
  weslToml?: string;
5
5
  extensions?: PluginExtension[];
6
+
7
+ /** Log plugin activity to stderr for debugging */
8
+ debug?: boolean;
6
9
  }
@@ -32,7 +32,7 @@ async function emitLinkJs(
32
32
  .map((p, i) => `import ${sanitizedDeps[i]} from "${p}";`)
33
33
  .join("\n");
34
34
 
35
- const rootName = path.basename(rootModuleName);
35
+ const rootName = path.basename(rootModuleName).replace(/\W/g, "_");
36
36
  const paramsName = `link${rootName}Config`;
37
37
 
38
38
  const linkParams: LinkParams = {