dawn_node: Add template files
Templates used to generate the NodeJS interop classes for the WebGPU IDL.
Also includes a stub `Browser.idl` file that provides stub definitions for browser IDL declarations referenced by the WebGPU IDL.
Bug: dawn:1123
Change-Id: I4067cb186f63436a502c3516a879ed1c5cd30731
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/64902
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_node/interop/Browser.idl b/src/dawn_node/interop/Browser.idl
new file mode 100644
index 0000000..8208058
--- /dev/null
+++ b/src/dawn_node/interop/Browser.idl
@@ -0,0 +1,84 @@
+// Copyright 2021 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// An IDL file that provides stub definitions for dictionaries and interfaces
+// used by the webgpu.idl file
+
+dictionary EventInit {
+ boolean bubbles = false;
+ boolean cancelable = false;
+ boolean composed = false;
+};
+
+interface Navigator {
+ readonly attribute DOMString vendorSub;
+ readonly attribute DOMString productSub;
+ readonly attribute DOMString vendor;
+};
+
+interface Event {
+ readonly attribute boolean bubbles;
+ readonly attribute boolean cancelable;
+ attribute boolean returnValue;
+};
+
+interface WorkerNavigator{};
+
+interface EventListener {
+ undefined handleEvent(Event event);
+};
+
+interface EventTarget {
+ undefined addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options);
+ undefined removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options);
+ boolean dispatchEvent(Event event);
+};
+
+dictionary EventListenerOptions { boolean capture = false; };
+
+dictionary AddEventListenerOptions : EventListenerOptions {
+ boolean passive = false;
+ boolean once = false;
+};
+
+interface HTMLVideoElement {
+ attribute unsigned long width;
+ attribute unsigned long height;
+ readonly attribute unsigned long videoWidth;
+ readonly attribute unsigned long videoHeight;
+ attribute DOMString poster;
+};
+
+typedef(Int8Array or Int16Array or Int32Array or Uint8Array or Uint16Array or
+ Uint32Array or Float32Array or Float64Array or
+ DataView) ArrayBufferView;
+
+typedef(ArrayBufferView or ArrayBuffer) BufferSource;
+
+interface ImageBitmap {
+ readonly attribute unsigned long width;
+ readonly attribute unsigned long height;
+};
+
+interface HTMLCanvasElement {
+ attribute unsigned long width;
+ attribute unsigned long height;
+};
+
+interface OffscreenCanvas {
+ attribute unsigned long width;
+ attribute unsigned long height;
+};
+
+interface EventHandler{};
diff --git a/src/dawn_node/interop/WebGPU.cpp.tmpl b/src/dawn_node/interop/WebGPU.cpp.tmpl
new file mode 100644
index 0000000..d52a991
--- /dev/null
+++ b/src/dawn_node/interop/WebGPU.cpp.tmpl
@@ -0,0 +1,341 @@
+{{/*
+ Copyright 2021 The Dawn Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/}}
+
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with src/dawn_node/tools/cmd/idlgen/main.go to generate
+the WebGPU.cpp source file.
+
+See:
+* https://github.com/ben-clayton/webidlparser/blob/main/ast/ast.go for the AST
+ types used by this template
+* src/dawn_node/tools/cmd/idlgen/main.go for additional structures and functions
+ used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Include "WebGPUCommon.tmpl" -}}
+
+#include "src/dawn_node/interop/WebGPU.h"
+
+#include <unordered_map>
+
+#include "src/dawn_node/utils/Debug.h"
+
+namespace wgpu {
+namespace interop {
+
+namespace {
+
+{{template "Wrappers" $}}
+
+} // namespace
+
+{{ range $ := .Declarations}}
+{{- if IsDictionary $}}{{template "Dictionary" $}}
+{{- else if IsInterface $}}{{template "Interface" $}}
+{{- else if IsEnum $}}{{template "Enum" $}}
+{{- end}}
+{{- end}}
+
+
+void Initialize(Napi::Env env) {
+ auto* wrapper = Wrappers::Init(env);
+ auto global = env.Global();
+{{ range $ := .Declarations}}
+{{- if IsInterfaceOrNamespace $}}
+ global.Set(Napi::String::New(env, "{{$.Name}}"), wrapper->{{$.Name}}_ctor.Value());
+{{- end}}
+{{- end}}
+}
+
+} // namespace interop
+} // namespace wgpu
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Wrappers emits the C++ 'Wrappers' class, which holds all the interface and
+-- namespace interop wrapper classes.
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Wrappers"}}
+// Wrappers holds all the Napi class constructors, and Napi::ObjectWrap type
+// declarations, for each of the WebIDL interface and namespace types.
+class Wrappers {
+ Wrappers(Napi::Env env) {
+{{- range $ := .Declarations}}
+{{- if IsInterfaceOrNamespace $}}
+ {{$.Name}}_ctor = Napi::Persistent(W{{$.Name}}::Class(env));
+{{- end}}
+{{- end}}
+ }
+
+ static Wrappers* instance;
+
+public:
+{{- range $ := .Declarations}}
+{{- if IsInterfaceOrNamespace $}}{{template "Wrapper" $}}
+{{- end}}
+{{- end}}
+
+ // Allocates and constructs the Wrappers instance
+ static Wrappers* Init(Napi::Env env) {
+ instance = new Wrappers(env);
+ return instance;
+ }
+
+ // Destructs and frees the Wrappers instance
+ static void Term(Napi::Env env) {
+ delete instance;
+ instance = nullptr;
+ }
+
+ static Wrappers* For(Napi::Env env) {
+ // Currently Napi only actually supports a single Env, so there's no point
+ // maintaining a map of Env to Wrapper. Note: This might not always be true.
+ return instance;
+ }
+
+{{ range $ := .Declarations}}
+{{- if IsInterfaceOrNamespace $}}
+ Napi::FunctionReference {{$.Name}}_ctor;
+{{- end}}
+{{- end}}
+};
+
+Wrappers* Wrappers::instance = nullptr;
+{{- end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Wrapper emits the C++ wrapper class for the given ast.Interface or
+-- ast.Namespace.
+-- This wrapper class inherits from Napi::ObjectWrap, which binds the lifetime
+-- of the JavaScript object to the lifetime of the wrapper class instance.
+-- If the wrapper is for an interface, the wrapper object holds a unique_ptr to
+-- the interface implementation, and delegates all exposed method calls on to
+-- the implementation.
+-- See: https://github.com/nodejs/node-addon-api/blob/main/doc/object_wrap.md
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Wrapper"}}
+ struct W{{$.Name}} : public Napi::ObjectWrap<W{{$.Name}}> {
+{{- if IsInterface $}}
+ std::unique_ptr<{{$.Name}}> impl;
+{{- end}}
+ static Napi::Function Class(Napi::Env env) {
+ return DefineClass(env, "{{$.Name}}", {
+{{- range $m := MethodsOf $}}
+ InstanceMethod("{{$m.Name}}", &W{{$.Name}}::{{$m.Name}}),
+{{- end}}
+{{- range $a := AttributesOf $}}
+ InstanceAccessor("{{$a.Name}}", &W{{$.Name}}::get{{Title $a.Name}},
+{{- if $a.Readonly}} nullptr{{else}} &W{{$.Name}}::set{{Title $a.Name}}{{end -}}
+ ),
+{{- end}}
+{{- range $c := ConstantsOf $}}
+ StaticValue("{{$c.Name}}", ToJS(env, {{$.Name}}::{{$c.Name}})),
+{{- end}}
+ });
+ }
+
+ W{{$.Name}}(const Napi::CallbackInfo& info) : ObjectWrap(info) {}
+{{- range $m := MethodsOf $}}
+ Napi::Value {{$m.Name}}(const Napi::CallbackInfo& info) {
+{{- range $overload_idx, $o := $m.Overloads}}
+ { // Overload {{$overload_idx}}
+ std::tuple<
+{{- range $i, $p := $o.Parameters}}
+{{- if $i}}, {{end}}
+{{- if $p.Optional}}std::optional<{{template "Type" $p.Type}}>
+{{- else }}{{template "Type" $p.Type}}
+{{- end}}
+{{- end}}> args;
+ if (FromJS(info, args)) {
+ {{/* indent */}}INTEROP_LOG(
+{{- range $i, $p := $o.Parameters}}
+{{- if $i}}, ", {{$p.Name}}: "{{else}}"{{$p.Name}}: "{{end}}, std::get<{{$i}}>(args)
+{{- end}});
+ {{/* indent */}}
+{{- if not (IsUndefinedType $o.Type) }}auto result = {{end -}}
+ impl->{{$o.Name}}(info.Env(){{range $i, $_ := $o.Parameters}}, std::get<{{$i}}>(args){{end}});
+ {{/* indent */ -}}
+{{- if IsUndefinedType $o.Type}}return info.Env().Null();
+{{- else }}return ToJS(info.Env(), result);
+{{- end }}
+ }
+ }
+{{- end}}
+ Napi::Error::New(info.Env(), "invalid arguments to {{$m.Name}}").ThrowAsJavaScriptException();
+ return {};
+ }
+{{- end}}
+
+{{- range $a := AttributesOf $}}
+ Napi::Value get{{Title $a.Name}}(const Napi::CallbackInfo& info) {
+ return ToJS(info.Env(), impl->get{{Title $a.Name}}(info.Env()));
+ }
+{{- if not $a.Readonly}}
+ void set{{Title $a.Name}}(const Napi::CallbackInfo& info, const Napi::Value& value) {
+ {{template "Type" $a.Type}} v{};
+ if (FromJS(info.Env(), value, v)) {
+ impl->set{{Title $a.Name}}(info.Env(), std::move(v));
+ } else {
+ Napi::Error::New(info.Env(), "invalid value to {{$a.Name}}").ThrowAsJavaScriptException();
+ }
+ }
+{{- end }}
+{{- end}}
+ };
+{{end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Dictionary emits the C++ method implementations and associated functions of
+-- the interop type that defines the given ast.Dictionary
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Dictionary"}}
+bool Converter<{{$.Name}}>::FromJS(Napi::Env env, Napi::Value value, {{$.Name}}& out) {
+ auto object = value.ToObject();
+ return true{{template "DictionaryMembersFromJS" $}};
+}
+
+Napi::Value Converter<{{$.Name}}>::ToJS(Napi::Env env, {{$.Name}} value) {
+ auto object = Napi::Object::New(env);
+{{- template "DictionaryMembersToJS" $}}
+ return object;
+}
+
+std::ostream& operator<<(std::ostream& o, const {{$.Name}}& dict) {
+ o << "{{$.Name}} {";
+{{- range $i, $m := $.Members}}
+ o << {{if $i}}", "{{else}}" "{{end}} << "{{$m.Name}}: ";
+ utils::Write(o, dict.{{$m.Name}});
+{{- end }}
+ o << "}" << std::endl;
+ return o;
+}
+{{ end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- DictionaryMembersFromJS emits the C++ logic to convert each of the
+-- dictionary ast.Member fields from JavaScript to C++. Each call to FromJS() is
+-- prefixed with '&&' so that the combined expression is true iff all members
+-- are converted succesfully
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "DictionaryMembersFromJS"}}
+{{- if $.Inherits}}{{template "DictionaryMembersFromJS" (Lookup $.Inherits)}}{{end}}
+{{- range $i, $m := $.Members}} &&
+ {{/* indent */}}
+{{- if $m.Init }}interop::FromJSOptional(env, object.Get("{{$m.Name}}"), out.{{$m.Name}})
+{{- else }}interop::FromJS(env, object.Get("{{$m.Name}}"), out.{{$m.Name}})
+{{- end }}
+{{- end}}
+{{- end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- DictionaryMembersToJS emits the C++ logic to convert each of the
+-- dictionary ast.Member fields to JavaScript from C++. Each call to ToJS() is
+-- emitted as a separate statement
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "DictionaryMembersToJS"}}
+{{- if $.Inherits}}{{template "DictionaryMembersToJS" (Lookup $.Inherits)}}{{end}}
+{{- range $m := $.Members}}
+ object.Set(Napi::String::New(env, "{{$m.Name}}"), interop::ToJS(env, value.{{$m.Name}}));
+{{- end}}
+{{- end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Interface emits the C++ method implementations that define the given
+-- ast.Interface.
+-- Note: Most of the actual binding logic lives in the interface wrapper class.
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Interface"}}
+{{$.Name}}::{{$.Name}}() = default;
+
+{{$.Name}}* {{$.Name}}::Unwrap(Napi::Object object) {
+ auto* wrappers = Wrappers::For(object.Env());
+ if (!object.InstanceOf(wrappers->{{$.Name}}_ctor.Value())) {
+ return nullptr;
+ }
+ return Wrappers::W{{$.Name}}::Unwrap(object)->impl.get();
+}
+
+Interface<{{$.Name}}> {{$.Name}}::Bind(Napi::Env env, std::unique_ptr<{{$.Name}}>&& impl) {
+ auto* wrappers = Wrappers::For(env);
+ auto object = wrappers->{{$.Name}}_ctor.New({});
+ auto* wrapper = Wrappers::W{{$.Name}}::Unwrap(object);
+ wrapper->impl = std::move(impl);
+ return Interface<{{$.Name}}>(object);
+}
+
+{{$.Name}}::~{{$.Name}}() = default;
+{{ end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Enum emits the C++ associated functions of the interop type that defines the
+-- given ast.Enum
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Enum"}}
+bool Converter<{{$.Name}}>::FromJS(Napi::Env env, Napi::Value value, {{$.Name}}& out) {
+ std::string str = value.ToString();
+{{- range $e := $.Values}}
+ if (str == {{$e.Value}}) {
+ out = {{$.Name}}::{{EnumEntryName $e.Value}};
+ return true;
+ }
+{{- end}}
+ return false;
+}
+Napi::Value Converter<{{$.Name}}>::ToJS(Napi::Env env, {{$.Name}} value) {
+ switch (value) {
+{{- range $e := $.Values}}
+ case {{$.Name}}::{{EnumEntryName $e.Value}}:
+ return Napi::String::New(env, {{$e.Value}});
+ break;
+{{- end}}
+ }
+ return env.Undefined();
+}
+
+std::ostream& operator<<(std::ostream& o, {{$.Name}} value) {
+ switch (value) {
+{{- range $e := $.Values}}
+ case {{$.Name}}::{{EnumEntryName $e.Value}}:
+ return o << {{$e.Value}};
+{{- end}}
+ }
+ return o << "undefined<{{$.Name}}>";
+}
+
+{{end}}
diff --git a/src/dawn_node/interop/WebGPU.h.tmpl b/src/dawn_node/interop/WebGPU.h.tmpl
new file mode 100644
index 0000000..7c791c6
--- /dev/null
+++ b/src/dawn_node/interop/WebGPU.h.tmpl
@@ -0,0 +1,263 @@
+{{/*
+ Copyright 2021 The Dawn Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/}}
+
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with src/dawn_node/tools/cmd/idlgen/main.go to generate
+the WebGPU.h header file.
+
+See:
+* https://github.com/ben-clayton/webidlparser/blob/main/ast/ast.go for the AST
+ types used by this template
+* src/dawn_node/tools/cmd/idlgen/main.go for additional structures and functions
+ used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- Include "WebGPUCommon.tmpl" -}}
+
+#ifndef DAWN_NODE_GEN_INTEROP_WEBGPU_H_
+#define DAWN_NODE_GEN_INTEROP_WEBGPU_H_
+
+#include "src/dawn_node/interop/Core.h"
+
+namespace wgpu {
+namespace interop {
+
+// Initialize() registers the WebGPU types with the Napi environment.
+void Initialize(Napi::Env env);
+
+{{ range $ := .Declarations}}
+{{- if IsDictionary $}}{{template "Dictionary" $}}
+{{- else if IsNamespace $}}{{template "Namespace" $}}
+{{- else if IsInterface $}}{{template "Interface" $}}
+{{- else if IsEnum $}}{{template "Enum" $}}
+{{- else if IsTypedef $}}{{template "Typedef" $}}
+{{- end}}
+{{- end}}
+
+} // namespace interop
+} // namespace wgpu
+
+#endif // DAWN_NODE_GEN_INTEROP_WEBGPU_H_
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Dictionary emits the C++ header declaration that defines the interop type for
+-- the given ast.Dictionary
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Dictionary"}}
+// dictionary {{$.Name}}
+class {{$.Name}} {{- if $.Inherits }} : public {{$.Inherits}}{{end}} {
+public:
+{{ range $m := $.Members}}
+{{- if IsConstructor $m}} {{$.Name}}();
+{{ else if IsMember $m}} {{template "DictionaryMember" $m}}
+{{ end}}
+{{- end -}}
+};
+
+template<>
+class Converter<{{$.Name}}> {
+public:
+ static bool FromJS(Napi::Env, Napi::Value, {{$.Name}}&);
+ static Napi::Value ToJS(Napi::Env, {{$.Name}});
+};
+
+std::ostream& operator<<(std::ostream& o, const {{$.Name}}& desc);
+{{end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Namespace emits the C++ header declaration that defines the interop type for
+-- the given ast.Namespace
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Namespace"}}
+// namespace {{$.Name}}
+class {{$.Name}} {
+public:
+ virtual ~{{$.Name}}();
+ {{$.Name}}();
+{{- range $c := ConstantsOf $}}
+{{- template "Constant" $c}}
+{{- end}}
+};
+{{end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Interface emits the C++ header declaration that defines the interop type for
+-- the given ast.Interface
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Interface"}}
+// interface {{$.Name}}
+class {{$.Name}} {{- if $.Inherits }} : public {{$.Inherits}}{{end}} {
+public:
+ static Interface<{{$.Name}}> Bind(Napi::Env, std::unique_ptr<{{$.Name}}>&&);
+ static {{$.Name}}* Unwrap(Napi::Object);
+
+ template<typename T, typename ... ARGS>
+ static inline Interface<{{$.Name}}> Create(Napi::Env env, ARGS&& ... args) {
+ return Bind(env, std::make_unique<T>(std::forward<ARGS>(args)...));
+ }
+
+ virtual ~{{$.Name}}();
+ {{$.Name}}();
+{{- range $m := MethodsOf $}}
+{{- template "InterfaceMethod" $m}}
+{{- end}}
+{{- range $a := AttributesOf $}}
+{{- template "InterfaceAttribute" $a}}
+{{- end}}
+{{- range $c := ConstantsOf $}}
+{{- template "Constant" $c}}
+{{- end}}
+};
+{{end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Typedef emits the C++ header declaration that defines the interop type for
+-- the given ast.Interface
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Typedef"}}
+using {{$.Name}} = {{template "Type" $.Type}};
+{{end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Enum emits the C++ header declaration that defines the interop type for
+-- the given ast.Enum
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Enum"}}
+enum class {{$.Name}} {
+{{- range $ := $.Values}}
+ {{EnumEntryName $.Value}},
+{{- end}}
+};
+
+template<>
+class Converter<{{$.Name}}> {
+public:
+ static bool FromJS(Napi::Env, Napi::Value, {{$.Name}}&);
+ static Napi::Value ToJS(Napi::Env, {{$.Name}});
+};
+
+std::ostream& operator<<(std::ostream& o, {{$.Name}});
+{{end}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- DictionaryMember emits the C++ declaration for a single dictionary ast.Member
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "DictionaryMember"}}
+{{- if $.Attribute}}{{template "AttributeType" $}} {{$.Name}}
+{{- if $.Init}} = {{Eval "Literal" "Value" $.Init "Type" $.Type}}{{end}};
+{{- else }}{{template "Type" $.Type}} {{$.Name}}({{template "Parameters" $.Parameters}});
+{{- end }}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- InterfaceMethod emits the C++ declaration for a single interface ast.Member
+-- method
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "InterfaceMethod"}}
+{{- range $o := $.Overloads}}
+ virtual {{template "Type" $o.Type}} {{$.Name}}(Napi::Env{{template "ParametersWithLeadingComma" $o.Parameters}}) = 0;
+{{- end }}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- InterfaceAttribute emits the C++ declaration for a single interface
+-- ast.Member attribute
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "InterfaceAttribute"}}
+ virtual {{template "Type" $.Type}} get{{Title $.Name}}(Napi::Env) = 0;
+{{- if not $.Readonly}}
+ virtual void set{{Title $.Name}}(Napi::Env, {{template "Type" $.Type}} value) = 0;
+{{- end }}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Constant emits the C++ declaration for a single ast.Member constant
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Constant"}}
+ static constexpr {{template "Type" $.Type}} {{$.Name}} = {{Eval "Literal" "Value" $.Init "Type" $.Type}};
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Parameters emits the C++ comma separated list of parameter declarations for
+-- the given []ast.Parameter
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Parameters"}}
+{{- range $i, $param := $ }}
+{{- if $i }}, {{end}}
+{{- template "Parameter" $param}}
+{{- end }}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- ParametersWithLeadingComma emits the C++ comma separated list of parameter
+-- declarations for the given []ast.Parameter, starting with a leading comma
+-- for the first parameter
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "ParametersWithLeadingComma"}}
+{{- range $i, $param := $ }}, {{/* */}}
+{{- template "Parameter" $param}}
+{{- end }}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Parameter emits the C++ parameter type and name for the given ast.Parameter
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Parameter" -}}
+{{- if $.Optional -}}
+std::optional<{{template "Type" $.Type}}> {{$.Name}}
+{{- else}}
+{{- template "Type" $.Type}} {{$.Name}}
+{{- end}}
+{{- end}}
diff --git a/src/dawn_node/interop/WebGPUCommon.tmpl b/src/dawn_node/interop/WebGPUCommon.tmpl
new file mode 100644
index 0000000..8630773
--- /dev/null
+++ b/src/dawn_node/interop/WebGPUCommon.tmpl
@@ -0,0 +1,126 @@
+{{/*
+ Copyright 2021 The Dawn Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/}}
+
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with src/dawn_node/tools/cmd/idlgen/main.go.
+This file provides common template definitions and is included by WebGPU.h.tmpl
+and WebGPU.cpp.tmpl.
+
+See:
+* https://github.com/ben-clayton/webidlparser/blob/main/ast/ast.go for the AST
+ types used by this template
+* src/dawn_node/tools/cmd/idlgen/main.go for additional structures and functions
+ used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Type generates the C++ type for the given ast.Type
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Type" -}}
+{{- if IsUndefinedType $}}void
+{{- else if IsTypeName $}}
+{{- if eq $.Name "boolean" }}bool
+{{- else if eq $.Name "long" }}int32_t
+{{- else if eq $.Name "unsigned long" }}uint32_t
+{{- else if eq $.Name "long long" }}int64_t
+{{- else if eq $.Name "unsigned long long" }}uint64_t
+{{- else if eq $.Name "object" }}Object
+{{- else if eq $.Name "DOMString" }}std::string
+{{- else if eq $.Name "USVString" }}std::string
+{{- else if eq $.Name "ArrayBuffer" }}ArrayBuffer
+{{- else if IsInterface (Lookup $.Name) }}Interface<{{$.Name}}>
+{{- else }}{{$.Name}}
+{{- end }}
+{{- else if IsParametrizedType $}}{{$.Name}}<{{template "TypeList" $.Elems}}>
+{{- else if IsNullableType $}}std::optional<{{template "Type" $.Type}}>
+{{- else if IsUnionType $}}std::variant<{{template "VariantTypeList" $.Types}}>
+{{- else if IsSequenceType $}}std::vector<{{template "Type" $.Elem}}>
+{{- else if IsRecordType $}}std::unordered_map<{{template "Type" $.Key}}, {{template "Type" $.Elem}}>
+{{- else }} /* Unhandled Type {{printf "%T" $}} */
+{{- end -}}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- AttributeType generates the C++ type for the given ast.Member
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "AttributeType" -}}
+{{- if $.Required }}{{template "Type" $.Type}}
+{{- else if $.Init }}{{template "Type" $.Type}}
+{{- else }}std::optional<{{template "Type" $.Type}}>
+{{- end}}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- Literal generates a C++ literal value using the following arguments:
+-- Value - the ast.Literal
+-- Type - the ast.Type of the literal
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "Literal" -}}
+{{- if IsDefaultDictionaryLiteral $.Value}}{{template "Type" $.Type}}{}
+{{- else if IsTypeName $.Type }}
+{{- $ty := Lookup $.Type.Name}}
+{{- if IsEnum $ty }}{{$.Type.Name}}::{{EnumEntryName $.Value.Value}}
+{{- else if IsBasicLiteral $.Value }}{{$.Value.Value}}
+{{- else }}/* Unhandled Type {{printf "ty: %v $.Type.Name: %T $.Value: %T" $ty $.Type.Name $.Value}} */
+{{- end }}
+{{- else if IsSequenceType $.Type }}{{template "Type" $.Type}}{} {{- /* TODO: Assumes the initialiser is empty */}}
+{{- else if IsBasicLiteral $.Value }}{{$.Value.Value}}
+{{- else }} /* Unhandled Type {{printf "%T %T" $.Type $.Value}} */
+{{- end}}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- TypeList generates a C++ comma separated list of types from the given
+-- []ast.Type
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "TypeList" -}}
+{{- range $i, $ty := $}}
+{{- if $i }}, {{end}}
+{{- template "Type" $ty}}
+{{- end}}
+{{- end }}
+
+
+{{- /*
+--------------------------------------------------------------------------------
+-- VariantTypeList generates a C++ comma separated list of types from the given
+-- []ast.Type, skipping any 'undefined' types
+--------------------------------------------------------------------------------
+*/ -}}
+{{- define "VariantTypeList" -}}
+{{- range $i, $ty := $}}
+{{- if not (IsUndefinedType $ty)}}
+{{- if $i }}, {{end}}
+{{- template "Type" $ty}}
+{{- end}}
+{{- end}}
+{{- end }}
+