diff --git a/web_generator/bin/update_idl_bindings.dart b/web_generator/bin/update_idl_bindings.dart
index 019b422..1da5464 100644
--- a/web_generator/bin/update_idl_bindings.dart
+++ b/web_generator/bin/update_idl_bindings.dart
@@ -67,12 +67,17 @@
 
   // Run app with `node`.
   final generateAll = argResult['generate-all'] as bool;
+  final inputFiles = argResult['input'] as List<String>;
   await runProc(
     'node',
     [
       'main.mjs',
       '--idl',
-      '--output=${p.join(_webPackagePath, 'lib', 'src')}',
+      for (String inputFile in inputFiles) '--input=$inputFile',
+      if (inputFiles.isEmpty)
+        '--output=${p.join(_webPackagePath, 'lib', 'src')}'
+      else
+        '--output=${argResult['output'] as String? ?? p.current}',
       if (generateAll) '--generate-all',
     ],
     workingDirectory: bindingsGeneratorPath,
@@ -89,16 +94,17 @@
   // delete context file
   await contextFile.delete();
 
-  // Update readme.
-  final readmeFile =
-      File(p.normalize(p.fromUri(Platform.script.resolve('../README.md'))));
+  if (inputFiles.isEmpty) {
+    // Update readme.
+    final readmeFile =
+        File(p.normalize(p.fromUri(Platform.script.resolve('../README.md'))));
 
-  final sourceContent = readmeFile.readAsStringSync();
+    final sourceContent = readmeFile.readAsStringSync();
 
-  final cssVersion = _packageLockVersion(_webRefCss);
-  final elementsVersion = _packageLockVersion(_webRefElements);
-  final idlVersion = _packageLockVersion(_webRefIdl);
-  final versions = '''
+    final cssVersion = _packageLockVersion(_webRefCss);
+    final elementsVersion = _packageLockVersion(_webRefElements);
+    final idlVersion = _packageLockVersion(_webRefIdl);
+    final versions = '''
 $_startComment
 | Item | Version |
 | --- | --: |
@@ -107,15 +113,16 @@
 | `$_webRefIdl` | [$idlVersion](https://www.npmjs.com/package/$_webRefIdl/v/$idlVersion) |
 ''';
 
-  final newContent =
-      sourceContent.substring(0, sourceContent.indexOf(_startComment)) +
-          versions +
-          sourceContent.substring(sourceContent.indexOf(_endComment));
-  if (newContent == sourceContent) {
-    print(ansi.styleBold.wrap('No update for readme.'));
-  } else {
-    print(ansi.styleBold.wrap('Updating readme for IDL version $idlVersion'));
-    readmeFile.writeAsStringSync(newContent, mode: FileMode.writeOnly);
+    final newContent =
+        sourceContent.substring(0, sourceContent.indexOf(_startComment)) +
+            versions +
+            sourceContent.substring(sourceContent.indexOf(_endComment));
+    if (newContent == sourceContent) {
+      print(ansi.styleBold.wrap('No update for readme.'));
+    } else {
+      print(ansi.styleBold.wrap('Updating readme for IDL version $idlVersion'));
+      readmeFile.writeAsStringSync(newContent, mode: FileMode.writeOnly);
+    }
   }
 }
 
@@ -161,23 +168,26 @@
     '<!-- END updated by $_scriptPOSIXPath. Do not modify by hand -->';
 
 final _usage = '''
-Global Options:
-${_parser.usage}
+${ansi.styleBold.wrap('WebIDL Gen')}:
+$_thisScript [options]
 
-${ansi.styleBold.wrap('IDL Command')}: $_thisScript idl [options]
+If no IDL file is provided, defaults to the WebIDL definitions needed for package:web
 
 Usage:
-${_parser.commands['idl']?.usage}
-
-${ansi.styleBold.wrap('Typescript Gen Command')}: $_thisScript dts <.d.ts file> [options]
-
-Usage:
-${_parser.commands['dts']?.usage}''';
+${_parser.usage}''';
 
 final _parser = ArgParser()
   ..addFlag('help', negatable: false, help: 'Show help information')
   ..addFlag('update', abbr: 'u', help: 'Update npm dependencies')
   ..addFlag('compile', defaultsTo: true)
+  ..addOption('output',
+      abbr: 'o',
+      help: 'Output directory where bindings will be generated to '
+          '(defaults to `lib/src` in the web package when no IDL file is provided)')
+  ..addMultiOption('input',
+      abbr: 'i',
+      help: 'The input IDL file(s) to read and generate bindings for. '
+          'If not provided, the default WebIDL definitions will be used.')
   ..addFlag('generate-all',
       negatable: false,
       help: 'Generate bindings for all IDL definitions, including experimental '
diff --git a/web_generator/lib/src/ast.dart b/web_generator/lib/src/ast.dart
deleted file mode 100644
index 14a43a6..0000000
--- a/web_generator/lib/src/ast.dart
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'package:code_builder/code_builder.dart';
-
-import 'interop_gen/generate.dart';
-import 'interop_gen/namer.dart';
-
-sealed class Node {
-  abstract final String? name;
-  abstract final ID id;
-  final String? dartName;
-
-  Node() : dartName = null;
-}
-
-abstract class Declaration extends Node {
-  @override
-  abstract final String name;
-
-  Spec emit();
-}
-
-abstract class NamedDeclaration extends Declaration {
-  ReferredType asReferredType([List<Type>? typeArgs]) =>
-      ReferredType(name: name, declaration: this, typeParams: typeArgs ?? []);
-}
-
-abstract interface class ExportableDeclaration extends Declaration {
-  /// Whether this declaration is exported.
-  bool get exported;
-}
-
-abstract class Type extends Node {
-  Reference emit();
-}
-
-enum PrimitiveType implements Type {
-  string('string'),
-  any('any'),
-  object('object'),
-  number('number'),
-  boolean('boolean'),
-  undefined('undefined'),
-  unknown('unknown');
-
-  const PrimitiveType(this.name);
-
-  @override
-  final String name;
-
-  @override
-  ID get id => ID(type: 'type', name: name);
-
-  // TODO(https://github.com/dart-lang/web/pull/386): Configuration options: double and num
-  @override
-  Reference emit() {
-    return switch (this) {
-      PrimitiveType.string => refer('String'),
-      PrimitiveType.any => refer('JSAny', 'dart:js_interop'),
-      PrimitiveType.object => refer('JSObject', 'dart:js_interop'),
-      PrimitiveType.number => refer('int'),
-      PrimitiveType.boolean => refer('bool'),
-      PrimitiveType.undefined => TypeReference((t) => t
-        ..symbol = 'JSAny'
-        ..url = 'dart:js_interop'
-        ..isNullable = true),
-      PrimitiveType.unknown => TypeReference((t) => t
-        ..symbol = 'JSAny'
-        ..url = 'dart:js_interop'
-        ..isNullable = true)
-    };
-  }
-
-  @override
-  String? get dartName => null;
-}
-
-// TODO(): Refactor name - not all types can be referred to
-//  (only specific types) Instead change this
-//  to represent `typeof` declarations.
-// TODO(): Create a shared type for such types that
-//  can be referred to (i.e namespace, interface, class)
-//  as a type `ReferrableDeclaration`.
-class ReferredType<T extends Declaration> extends Type {
-  @override
-  String name;
-
-  @override
-  ID get id => ID(type: 'type', name: name);
-
-  T declaration;
-
-  List<Type> typeParams;
-
-  ReferredType(
-      {required this.name,
-      required this.declaration,
-      this.typeParams = const []});
-
-  @override
-  Reference emit() {
-    // TODO: implement emit
-    throw UnimplementedError();
-  }
-}
-
-// TODO(https://github.com/dart-lang/web/issues/385): Implement Support for UnionType (including implementing `emit`)
-class UnionType extends Type {
-  List<Type> types;
-
-  UnionType({required this.types});
-
-  @override
-  ID get id => ID(type: 'type', name: types.map((t) => t.id).join('|'));
-
-  @override
-  Reference emit() {
-    throw UnimplementedError();
-  }
-
-  @override
-  String? get name => null;
-}
-
-class VariableDeclaration extends NamedDeclaration
-    implements ExportableDeclaration {
-  /// The variable modifier, as represented in TypeScript
-  VariableModifier modifier;
-
-  @override
-  String name;
-
-  Type type;
-
-  @override
-  bool exported;
-
-  VariableDeclaration(
-      {required this.name,
-      required this.type,
-      required this.modifier,
-      required this.exported});
-
-  @override
-  ID get id => ID(type: 'var', name: name);
-
-  @override
-  Spec emit() {
-    if (modifier == VariableModifier.$const) {
-      return Method((m) => m
-        ..name = name
-        ..type = MethodType.getter
-        ..annotations.add(generateJSAnnotation())
-        ..external = true
-        ..returns = type.emit());
-    } else {
-      // getter and setter -> single variable
-      return Field((f) => f
-        ..external = true
-        ..name = name
-        ..type = type.emit()
-        ..annotations.add(generateJSAnnotation()));
-    }
-  }
-}
-
-enum VariableModifier { let, $const, $var }
diff --git a/web_generator/lib/src/ast/base.dart b/web_generator/lib/src/ast/base.dart
new file mode 100644
index 0000000..6ebc13c
--- /dev/null
+++ b/web_generator/lib/src/ast/base.dart
@@ -0,0 +1,76 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:code_builder/code_builder.dart';
+
+import '../interop_gen/namer.dart';
+import 'types.dart';
+
+class GlobalOptions {
+  static int variardicArgsCount = 4;
+  static bool shouldEmitJsTypes = false;
+}
+
+class Options {}
+
+// TODO(nikeokoronkwo): Remove this once we address isNullable
+class DeclarationOptions extends Options {
+  DeclarationOptions();
+
+  TypeOptions toTypeOptions({bool nullable = false}) =>
+      TypeOptions(nullable: nullable);
+}
+
+class TypeOptions extends Options {
+  bool nullable;
+
+  TypeOptions({this.nullable = false});
+}
+
+class ASTOptions {
+  bool parameter;
+  bool emitJSTypes;
+  int variardicArgsCount;
+
+  ASTOptions(
+      {this.parameter = false,
+      this.variardicArgsCount = 4,
+      this.emitJSTypes = false});
+}
+
+sealed class Node {
+  abstract final String? name;
+  abstract final ID id;
+  String? get dartName;
+
+  Spec emit([Options? options]);
+
+  Node();
+}
+
+abstract class Declaration extends Node {
+  @override
+  abstract final String name;
+
+  @override
+  Spec emit([covariant DeclarationOptions? options]);
+}
+
+abstract class NamedDeclaration extends Declaration {
+  ReferredType asReferredType([List<Type>? typeArgs]) =>
+      ReferredType(name: name, declaration: this, typeParams: typeArgs ?? []);
+}
+
+abstract interface class ExportableDeclaration extends Declaration {
+  /// Whether this declaration is exported.
+  bool get exported;
+}
+
+abstract class Type extends Node {
+  @override
+  String? dartName;
+
+  @override
+  Reference emit([covariant TypeOptions? options]);
+}
diff --git a/web_generator/lib/src/ast/builtin.dart b/web_generator/lib/src/ast/builtin.dart
new file mode 100644
index 0000000..563572c
--- /dev/null
+++ b/web_generator/lib/src/ast/builtin.dart
@@ -0,0 +1,116 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ignore_for_file: non_constant_identifier_names
+
+import 'package:code_builder/code_builder.dart';
+
+import '../interop_gen/namer.dart';
+import 'base.dart';
+
+/// A built in type supported by `dart:js_interop` or by this library
+/// (with generated declarations)
+class BuiltinType extends Type {
+  @override
+  final String name;
+
+  final List<Type> typeParams;
+
+  /// Whether the given type is present in "dart:js_interop"
+  final bool fromDartJSInterop;
+
+  // TODO(nikeokoronkwo): Types in general should have an `isNullable`
+  //  property on them to indicate nullability for Dart generated code.
+  final bool? isNullable;
+
+  BuiltinType(
+      {required this.name,
+      this.typeParams = const [],
+      this.fromDartJSInterop = false,
+      this.isNullable});
+
+  @override
+  ID get id => ID(type: 'type', name: name);
+
+  @override
+  String? get dartName => null;
+
+  @override
+  Reference emit([TypeOptions? options]) {
+    options ??= TypeOptions();
+
+    return TypeReference((t) => t
+      ..symbol = name
+      ..types.addAll(typeParams
+          // if there is only one type param, and it is void, ignore
+          .where((p) => typeParams.length != 1 || p != $voidType)
+          .map((p) => p.emit(TypeOptions())))
+      ..url = fromDartJSInterop ? 'dart:js_interop' : null
+      ..isNullable = isNullable ?? options!.nullable);
+  }
+
+  static final BuiltinType $voidType = BuiltinType(name: 'void');
+  static final BuiltinType anyType =
+      BuiltinType(name: 'JSAny', fromDartJSInterop: true, isNullable: true);
+
+  static BuiltinType primitiveType(PrimitiveType typeIdentifier,
+      {bool? shouldEmitJsType,
+      bool? isNullable,
+      List<Type> typeParams = const []}) {
+    shouldEmitJsType ??= GlobalOptions.shouldEmitJsTypes;
+    return switch (typeIdentifier) {
+      PrimitiveType.int ||
+      PrimitiveType.num ||
+      PrimitiveType.double when shouldEmitJsType =>
+        BuiltinType(
+            name: 'JSNumber', fromDartJSInterop: true, isNullable: isNullable),
+      PrimitiveType.int => BuiltinType(name: 'int', isNullable: isNullable),
+      PrimitiveType.num => BuiltinType(name: 'num', isNullable: isNullable),
+      PrimitiveType.double =>
+        BuiltinType(name: 'double', isNullable: isNullable),
+      PrimitiveType.boolean => shouldEmitJsType
+          ? BuiltinType(
+              name: 'JSBoolean',
+              fromDartJSInterop: true,
+              isNullable: isNullable)
+          : BuiltinType(name: 'bool', isNullable: isNullable),
+      PrimitiveType.string => shouldEmitJsType
+          ? BuiltinType(
+              name: 'JSString', fromDartJSInterop: true, isNullable: isNullable)
+          : BuiltinType(name: 'String', isNullable: isNullable),
+      PrimitiveType.$void || PrimitiveType.undefined => $voidType,
+      PrimitiveType.any || PrimitiveType.unknown => anyType,
+      PrimitiveType.object => BuiltinType(
+          name: 'JSObject', fromDartJSInterop: true, isNullable: isNullable),
+      PrimitiveType.array => BuiltinType(
+          name: 'JSArray',
+          typeParams: [typeParams.single],
+          fromDartJSInterop: true,
+          isNullable: isNullable),
+      PrimitiveType.promise => BuiltinType(
+          name: 'JSPromise',
+          typeParams: [typeParams.single],
+          fromDartJSInterop: true,
+          isNullable: isNullable),
+      PrimitiveType.function => BuiltinType(
+          name: 'JSFunction', fromDartJSInterop: true, isNullable: isNullable),
+    };
+  }
+}
+
+enum PrimitiveType {
+  int,
+  num,
+  double,
+  boolean,
+  string,
+  $void,
+  any,
+  object,
+  unknown,
+  undefined,
+  array,
+  promise,
+  function
+}
diff --git a/web_generator/lib/src/ast/declarations.dart b/web_generator/lib/src/ast/declarations.dart
new file mode 100644
index 0000000..1b02774
--- /dev/null
+++ b/web_generator/lib/src/ast/declarations.dart
@@ -0,0 +1,140 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:code_builder/code_builder.dart';
+
+import '../interop_gen/namer.dart';
+import 'base.dart';
+import 'helpers.dart';
+import 'types.dart';
+
+class VariableDeclaration extends NamedDeclaration
+    implements ExportableDeclaration {
+  /// The variable modifier, as represented in TypeScript
+  VariableModifier modifier;
+
+  @override
+  String name;
+
+  Type type;
+
+  @override
+  bool exported;
+
+  VariableDeclaration(
+      {required this.name,
+      required this.type,
+      required this.modifier,
+      required this.exported});
+
+  @override
+  ID get id => ID(type: 'var', name: name);
+
+  @override
+  Spec emit([DeclarationOptions? options]) {
+    if (modifier == VariableModifier.$const) {
+      return Method((m) => m
+        ..name = name
+        ..type = MethodType.getter
+        ..annotations.add(generateJSAnnotation())
+        ..external = true
+        ..returns = type.emit());
+    } else {
+      // getter and setter -> single variable
+      return Field((f) => f
+        ..external = true
+        ..name = name
+        ..type = type.emit()
+        ..annotations.add(generateJSAnnotation()));
+    }
+  }
+
+  @override
+  String? get dartName => null;
+}
+
+enum VariableModifier { let, $const, $var }
+
+class FunctionDeclaration extends NamedDeclaration
+    implements ExportableDeclaration {
+  @override
+  final String name;
+
+  @override
+  final String? dartName;
+
+  final List<ParameterDeclaration> parameters;
+
+  final List<GenericType> typeParameters;
+
+  final Type returnType;
+
+  @override
+  bool exported;
+
+  @override
+  ID id;
+
+  FunctionDeclaration(
+      {required this.name,
+      required this.id,
+      this.dartName,
+      this.parameters = const [],
+      this.typeParameters = const [],
+      required this.exported,
+      required this.returnType});
+
+  @override
+  Spec emit([DeclarationOptions? options]) {
+    options ??= DeclarationOptions();
+
+    final requiredParams = <Parameter>[];
+    final optionalParams = <Parameter>[];
+    for (final p in parameters) {
+      if (p.variardic) {
+        optionalParams.addAll(spreadParam(p, GlobalOptions.variardicArgsCount));
+        requiredParams.add(p.emit(options));
+      } else {
+        if (p.optional) {
+          optionalParams.add(p.emit(options));
+        } else {
+          requiredParams.add(p.emit(options));
+        }
+      }
+    }
+
+    return Method((m) => m
+      ..external = true
+      ..name = dartName ?? name
+      ..annotations.add(generateJSAnnotation(
+          dartName == null || dartName == name ? null : name))
+      ..types
+          .addAll(typeParameters.map((t) => t.emit(options?.toTypeOptions())))
+      ..returns = returnType.emit()
+      ..requiredParameters.addAll(requiredParams)
+      ..optionalParameters.addAll(optionalParams));
+  }
+}
+
+class ParameterDeclaration {
+  final String name;
+
+  final bool optional;
+
+  final Type type;
+
+  final bool variardic;
+
+  ParameterDeclaration(
+      {required this.name,
+      this.optional = false,
+      required this.type,
+      this.variardic = false});
+
+  Parameter emit([DeclarationOptions? options]) {
+    return Parameter((p) => p
+      ..name = name
+      ..type = type.emit(TypeOptions(nullable: optional)));
+  }
+}
diff --git a/web_generator/lib/src/ast/helpers.dart b/web_generator/lib/src/ast/helpers.dart
new file mode 100644
index 0000000..e987ccd
--- /dev/null
+++ b/web_generator/lib/src/ast/helpers.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:code_builder/code_builder.dart';
+
+import 'base.dart';
+import 'builtin.dart';
+import 'declarations.dart';
+
+BuiltinType? getSupportedType(String name, [List<Type> typeParams = const []]) {
+  final type = switch (name) {
+    'Array' => PrimitiveType.array,
+    'Promise' => PrimitiveType.promise,
+    _ => null
+  };
+
+  if (type == null) return null;
+
+  return BuiltinType.primitiveType(type, typeParams: [
+    getJSTypeAlternative(typeParams.singleOrNull ?? BuiltinType.anyType)
+  ]);
+}
+
+Type getJSTypeAlternative(Type type) {
+  if (type is BuiltinType) {
+    if (type.fromDartJSInterop) return type;
+
+    final primitiveType = switch (type.name) {
+      'num' => PrimitiveType.num,
+      'int' => PrimitiveType.int,
+      'double' => PrimitiveType.double,
+      'String' => PrimitiveType.string,
+      'bool' => PrimitiveType.boolean,
+      _ => null
+    };
+
+    if (primitiveType == null) return BuiltinType.anyType;
+
+    return BuiltinType.primitiveType(primitiveType, shouldEmitJsType: true);
+  }
+  return type;
+}
+
+Expression generateJSAnnotation([String? name]) {
+  return refer('JS', 'dart:js_interop')
+      .call([if (name != null) literalString(name)]);
+}
+
+List<Parameter> spreadParam(ParameterDeclaration p, int count) {
+  return List.generate(count - 1, (i) {
+    final paramNumber = i + 2;
+    final paramName = '${p.name}$paramNumber';
+    return ParameterDeclaration(name: paramName, type: p.type).emit();
+  });
+}
diff --git a/web_generator/lib/src/ast/types.dart b/web_generator/lib/src/ast/types.dart
new file mode 100644
index 0000000..e3d789b
--- /dev/null
+++ b/web_generator/lib/src/ast/types.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:code_builder/code_builder.dart';
+import '../interop_gen/namer.dart';
+import 'base.dart';
+
+class ReferredType<T extends Declaration> extends Type {
+  @override
+  String name;
+
+  @override
+  ID get id => ID(type: 'type', name: name);
+
+  T declaration;
+
+  List<Type> typeParams;
+
+  ReferredType(
+      {required this.name,
+      required this.declaration,
+      this.typeParams = const []});
+
+  @override
+  Reference emit([TypeOptions? options]) {
+    // TODO: implement emit
+    throw UnimplementedError();
+  }
+}
+
+// TODO(https://github.com/dart-lang/web/issues/385): Implement Support for UnionType (including implementing `emit`)
+class UnionType extends Type {
+  List<Type> types;
+
+  UnionType({required this.types});
+
+  @override
+  ID get id => ID(type: 'type', name: types.map((t) => t.id).join('|'));
+
+  @override
+  Reference emit([TypeOptions? options]) {
+    throw UnimplementedError('TODO: Implement UnionType.emit');
+  }
+
+  @override
+  String? get name => null;
+}
+
+/// The base class for a type generic (like 'T')
+class GenericType extends Type {
+  @override
+  final String name;
+
+  final Type? constraint;
+
+  final Declaration? parent;
+
+  GenericType({required this.name, this.constraint, this.parent});
+
+  @override
+  Reference emit([TypeOptions? options]) => TypeReference((t) => t
+    ..symbol = name
+    ..bound = constraint?.emit()
+    ..isNullable = options?.nullable);
+
+  @override
+  ID get id =>
+      ID(type: 'generic-type', name: '$name@${parent?.id ?? "(anonymous)"}');
+}
diff --git a/web_generator/lib/src/dart_main.dart b/web_generator/lib/src/dart_main.dart
index d5de405..f6943a4 100644
--- a/web_generator/lib/src/dart_main.dart
+++ b/web_generator/lib/src/dart_main.dart
@@ -32,7 +32,10 @@
 
   if (argResult.wasParsed('idl')) {
     await generateIDLBindings(
-      outputDirectory: argResult['output'] as String,
+      input: (argResult['input'] as List<String>).isEmpty
+          ? null
+          : argResult['input'] as Iterable<String>,
+      output: argResult['output'] as String,
       generateAll: argResult['generate-all'] as bool,
       languageVersion: Version.parse(languageVersionString),
     );
@@ -72,22 +75,46 @@
 }
 
 Future<void> generateIDLBindings({
-  required String outputDirectory,
+  Iterable<String>? input,
+  required String output,
   required bool generateAll,
   required Version languageVersion,
 }) async {
-  const librarySubDir = 'dom';
+  if (input == null) {
+    // parse dom library as normal
+    const librarySubDir = 'dom';
 
-  ensureDirectoryExists('$outputDirectory/$librarySubDir');
+    ensureDirectoryExists('$output/$librarySubDir');
 
-  final bindings = await generateBindings(packageRoot, librarySubDir,
-      generateAll: generateAll);
-  for (var entry in bindings.entries) {
-    final libraryPath = entry.key;
-    final library = entry.value;
+    final bindings = await generateBindings(packageRoot, librarySubDir,
+        generateAll: generateAll);
 
-    final contents = _emitLibrary(library, languageVersion).toJS;
-    fs.writeFileSync('$outputDirectory/$libraryPath'.toJS, contents);
+    for (var entry in bindings.entries) {
+      final libraryPath = entry.key;
+      final library = entry.value;
+
+      final contents = _emitLibrary(library, languageVersion).toJS;
+      fs.writeFileSync('$output/$libraryPath'.toJS, contents);
+    }
+  } else {
+    // parse individual files
+    ensureDirectoryExists(output);
+
+    final bindings = await generateBindingsForFiles({
+      for (final file in input)
+        file: (fs.readFileSync(
+                    file.toJS, JSReadFileOptions(encoding: 'utf-8'.toJS))
+                as JSString)
+            .toDart
+    }, output);
+
+    for (var entry in bindings.entries) {
+      final libraryPath = entry.key;
+      final library = entry.value;
+
+      final contents = _emitLibrary(library, languageVersion).toJS;
+      fs.writeFileSync('$output/$libraryPath'.toJS, contents);
+    }
   }
 }
 
diff --git a/web_generator/lib/src/generate_bindings.dart b/web_generator/lib/src/generate_bindings.dart
index 36324f7..33f9361 100644
--- a/web_generator/lib/src/generate_bindings.dart
+++ b/web_generator/lib/src/generate_bindings.dart
@@ -4,6 +4,9 @@
 
 import 'dart:js_interop';
 
+import 'package:path/path.dart' as p;
+
+import 'js/webidl2.dart' as webidl2;
 import 'js/webidl_api.dart' as webidl;
 import 'js/webref_css_api.dart';
 import 'js/webref_elements_api.dart';
@@ -74,8 +77,8 @@
   final cssStyleDeclarations = await _generateCSSStyleDeclarations();
   final elementHTMLMap = await _generateElementTagMap();
   final translator = Translator(
-      packageRoot, librarySubDir, cssStyleDeclarations, elementHTMLMap,
-      generateAll: generateAll);
+      librarySubDir, cssStyleDeclarations, elementHTMLMap,
+      generateAll: generateAll, packageRoot: packageRoot);
   final array = objectEntries(await idl.parseAll().toDart);
   for (var i = 0; i < array.length; i++) {
     final entry = array[i] as JSArray<JSAny?>;
@@ -86,3 +89,21 @@
   translator.addInterfacesAndNamespaces();
   return translator.translate();
 }
+
+Future<TranslationResult> generateBindingsForFiles(
+    Map<String, String> fileContents, String output) async {
+  // generate CSS style declarations and element tag map incase they are
+  // needed for the input files.
+  final cssStyleDeclarations = await _generateCSSStyleDeclarations();
+  final elementHTMLMap = await _generateElementTagMap();
+  final translator = Translator(output, cssStyleDeclarations, elementHTMLMap,
+      generateAll: true, generateForWeb: false);
+
+  for (final file in fileContents.entries) {
+    final ast = webidl2.parse(file.value);
+    translator.collect(p.basenameWithoutExtension(file.key), ast);
+  }
+
+  translator.addInterfacesAndNamespaces();
+  return translator.translate();
+}
diff --git a/web_generator/lib/src/interop_gen/generate.dart b/web_generator/lib/src/interop_gen/generate.dart
deleted file mode 100644
index 54ce2d3..0000000
--- a/web_generator/lib/src/interop_gen/generate.dart
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'package:code_builder/code_builder.dart';
-
-Expression generateJSAnnotation([String? name]) {
-  return refer('JS', 'dart:js_interop')
-      .call([if (name != null) literalString(name)]);
-}
diff --git a/web_generator/lib/src/interop_gen/namer.dart b/web_generator/lib/src/interop_gen/namer.dart
index 3725c11..dd07115 100644
--- a/web_generator/lib/src/interop_gen/namer.dart
+++ b/web_generator/lib/src/interop_gen/namer.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import '../banned_names.dart';
+
 class ID {
   final String type;
   final String name;
@@ -21,10 +23,44 @@
   UniqueNamer([Iterable<String> used = const <String>[]])
       : _usedNames = used.toSet();
 
+  /// Creates a unique name and ID for a given declaration to prevent
+  /// name collisions in Dart applications
+  ///
+  /// (Dart does not support operator overloading)
+  ({ID id, String name}) makeUnique(String name, String type) {
+    // nested structures (and anonymous structures) may not have a name
+    if (name.isEmpty) {
+      name = 'unnamed';
+    }
+
+    var newName = name;
+    if (keywords.contains(newName)) {
+      newName = '$newName\$';
+    }
+
+    var i = 0;
+    while (_usedNames.contains(newName)) {
+      ++i;
+      newName = '$name\$$i';
+    }
+
+    markUsed(newName);
+    return (
+      id: ID(type: type, name: name, index: i == 0 ? null : i),
+      name: newName
+    );
+  }
+
   static ID parse(String id) {
     String? index;
-    final [type, name, ...ids] = id.split('#');
-    if (ids.isEmpty) index = ids.single;
+    String name;
+    final [type, ...parts] = id.split('#');
+    if (parts.isEmpty) {
+      throw Exception('Invalid ID: $id');
+    } else {
+      name = parts[0];
+      if (parts.length > 1) index = parts[1];
+    }
 
     return ID(
         type: type, name: name, index: index == null ? null : int.parse(index));
diff --git a/web_generator/lib/src/interop_gen/transform.dart b/web_generator/lib/src/interop_gen/transform.dart
index f6c0a78..663786b 100644
--- a/web_generator/lib/src/interop_gen/transform.dart
+++ b/web_generator/lib/src/interop_gen/transform.dart
@@ -7,7 +7,7 @@
 import 'package:code_builder/code_builder.dart';
 import 'package:dart_style/dart_style.dart';
 
-import '../ast.dart';
+import '../ast/base.dart';
 import '../js/typescript.dart' as ts;
 import '../js/typescript.types.dart';
 import 'namer.dart';
@@ -29,9 +29,9 @@
       final specs = declMap.decls.values.map((d) {
         return switch (d) {
           final Declaration n => n.emit(),
-          final Type t => t.emit(),
+          final Type _ => null,
         };
-      });
+      }).whereType<Spec>();
       final lib = Library((l) => l..body.addAll(specs));
       return MapEntry(file, formatter.format('${lib.accept(emitter)}'));
     });
diff --git a/web_generator/lib/src/interop_gen/transform/transformer.dart b/web_generator/lib/src/interop_gen/transform/transformer.dart
index 0da0e79..404c299 100644
--- a/web_generator/lib/src/interop_gen/transform/transformer.dart
+++ b/web_generator/lib/src/interop_gen/transform/transformer.dart
@@ -3,7 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:js_interop';
-import '../../ast.dart';
+import '../../ast/base.dart';
+import '../../ast/builtin.dart';
+import '../../ast/declarations.dart';
+import '../../ast/helpers.dart';
+import '../../ast/types.dart';
 import '../../js/typescript.dart' as ts;
 import '../../js/typescript.types.dart';
 import '../namer.dart';
@@ -41,6 +45,8 @@
         nodeMap.addAll({for (final d in decs) d.id.toString(): d});
       default:
         final Declaration decl = switch (node.kind) {
+          TSSyntaxKind.FunctionDeclaration =>
+            _transformFunction(node as TSFunctionDeclaration),
           _ => throw Exception('Unsupported Declaration Kind: ${node.kind}')
         };
         // ignore: dead_code This line will not be dead in future decl additions
@@ -50,6 +56,180 @@
     nodes.add(node);
   }
 
+  List<Declaration> _transformVariable(TSVariableStatement variable) {
+    // get the modifier of the declaration
+    final modifiers = variable.modifiers.toDart;
+    final isExported = modifiers.any((m) {
+      return m.kind == TSSyntaxKind.ExportKeyword;
+    });
+
+    var modifier = VariableModifier.$var;
+
+    if ((variable.declarationList.flags & TSNodeFlags.Const) != 0) {
+      modifier = VariableModifier.$const;
+    } else if ((variable.declarationList.flags & TSNodeFlags.Let) != 0) {
+      modifier = VariableModifier.let;
+    }
+
+    return variable.declarationList.declarations.toDart.map((d) {
+      namer.markUsed(d.name.text);
+      return VariableDeclaration(
+          name: d.name.text,
+          type: d.type == null ? BuiltinType.anyType : _transformType(d.type!),
+          modifier: modifier,
+          exported: isExported);
+    }).toList();
+  }
+
+  TSNode? _getDeclarationByName(TSIdentifier name) {
+    final symbol = typeChecker.getSymbolAtLocation(name);
+
+    final declarations = symbol?.getDeclarations();
+    // TODO(https://github.com/dart-lang/web/issues/387): Some declarations may not be defined on file,
+    //  and may be from an import statement
+    //  We should be able to handle these
+    return declarations?.toDart.first;
+  }
+
+  FunctionDeclaration _transformFunction(TSFunctionDeclaration function) {
+    final name = function.name.text;
+
+    final modifiers = function.modifiers.toDart;
+    final isExported = modifiers.any((m) {
+      return m.kind == TSSyntaxKind.ExportKeyword;
+    });
+
+    final params = function.parameters.toDart;
+
+    final typeParams = function.typeParameters?.toDart;
+
+    final (id: id, name: uniqueName) = namer.makeUnique(name, 'fun');
+
+    return FunctionDeclaration(
+        name: name,
+        id: id,
+        dartName: uniqueName,
+        exported: isExported,
+        parameters: params.map(_transformParameter).toList(),
+        typeParameters:
+            typeParams?.map(_transformTypeParamDeclaration).toList() ?? [],
+        returnType: function.type != null
+            ? _transformType(function.type!)
+            : BuiltinType.anyType);
+  }
+
+  ParameterDeclaration _transformParameter(TSParameterDeclaration parameter) {
+    final type = parameter.type != null
+        ? _transformType(parameter.type!, parameter: true)
+        : BuiltinType.anyType;
+    final isOptional = parameter.questionToken != null;
+    final isVariardic = parameter.dotDotDotToken != null;
+
+    // what kind of parameter is this
+    switch (parameter.name.kind) {
+      case TSSyntaxKind.Identifier:
+        return ParameterDeclaration(
+            name: (parameter.name as TSIdentifier).text,
+            type: type,
+            variardic: isVariardic,
+            optional: isOptional);
+      default:
+        // TODO: Support Destructured Object Parameters
+        //  and Destructured Array Parameters
+        throw Exception('Unsupported Parameter Name kind ${parameter.kind}');
+    }
+  }
+
+  GenericType _transformTypeParamDeclaration(
+      TSTypeParameterDeclaration typeParam) {
+    return GenericType(
+        name: typeParam.name.text,
+        constraint: typeParam.constraint == null
+            ? BuiltinType.anyType
+            : _transformType(typeParam.constraint!));
+  }
+
+  /// Parses the type
+  ///
+  /// TODO(https://github.com/dart-lang/web/issues/384): Add support for literals (i.e individual booleans and `null`)
+  /// TODO(https://github.com/dart-lang/web/issues/383): Add support for `typeof` types
+  Type _transformType(TSTypeNode type, {bool parameter = false}) {
+    if (type.kind == TSSyntaxKind.UnionType) {
+      final unionType = type as TSUnionTypeNode;
+      return UnionType(
+          types: unionType.types.toDart.map<Type>(_transformType).toList());
+    }
+
+    if (type.kind == TSSyntaxKind.TypeReference) {
+      final refType = type as TSTypeReferenceNode;
+
+      final name = refType.typeName.text;
+      final typeArguments = refType.typeArguments?.toDart;
+
+      var declarationsMatching = nodeMap.findByName(name);
+
+      if (declarationsMatching.isEmpty) {
+        // check if builtin
+        // TODO(https://github.com/dart-lang/web/issues/380): A better name
+        //  for this, and adding support for "supported declarations"
+        //  (also a better name for that)
+        final supportedType = getSupportedType(
+            name, (typeArguments ?? []).map(_transformType).toList());
+        if (supportedType != null) {
+          return supportedType;
+        }
+
+        // TODO: In the case of overloading, should/shouldn't we handle more than one declaration?
+        final declaration = _getDeclarationByName(refType.typeName);
+
+        if (declaration == null) {
+          throw Exception('Found no declaration matching $name');
+        }
+
+        if (declaration.kind == TSSyntaxKind.TypeParameter) {
+          return GenericType(name: name);
+        }
+
+        transform(declaration);
+
+        declarationsMatching = nodeMap.findByName(name);
+      }
+
+      // TODO: In the case of overloading, should/shouldn't we handle more than one declaration?
+      final firstNode =
+          declarationsMatching.whereType<NamedDeclaration>().first;
+
+      return firstNode.asReferredType(
+        (typeArguments ?? []).map(_transformType).toList(),
+      );
+    }
+
+    if (type.kind == TSSyntaxKind.ArrayType) {
+      return BuiltinType.primitiveType(PrimitiveType.array, typeParams: [
+        getJSTypeAlternative(
+            _transformType((type as TSArrayTypeNode).elementType))
+      ]);
+    }
+
+    // check for primitive type via its kind
+    final primitiveType = switch (type.kind) {
+      TSSyntaxKind.ArrayType => PrimitiveType.array,
+      TSSyntaxKind.StringKeyword => PrimitiveType.string,
+      TSSyntaxKind.AnyKeyword => PrimitiveType.any,
+      TSSyntaxKind.ObjectKeyword => PrimitiveType.object,
+      TSSyntaxKind.NumberKeyword =>
+        (parameter ? PrimitiveType.num : PrimitiveType.double),
+      TSSyntaxKind.UndefinedKeyword => PrimitiveType.undefined,
+      TSSyntaxKind.UnknownKeyword => PrimitiveType.unknown,
+      TSSyntaxKind.BooleanKeyword => PrimitiveType.boolean,
+      TSSyntaxKind.VoidKeyword => PrimitiveType.$void,
+      _ => throw UnsupportedError(
+          'The given type with kind ${type.kind} is not supported yet')
+    };
+
+    return BuiltinType.primitiveType(primitiveType);
+  }
+
   NodeMap filter() {
     final filteredDeclarations = NodeMap();
 
@@ -66,7 +246,7 @@
             filteredDeclarations.add(e);
           }
           break;
-        case final PrimitiveType _:
+        case final BuiltinType _:
           // primitive types are generated by default
           break;
         case Type():
@@ -94,15 +274,29 @@
 
     switch (decl) {
       case final VariableDeclaration v:
-        if (v.type is! PrimitiveType) filteredDeclarations.add(v.type);
+        if (v.type is! BuiltinType) filteredDeclarations.add(v.type);
+        break;
+      case final FunctionDeclaration f:
+        if (f.returnType is! BuiltinType) {
+          filteredDeclarations.add(f.returnType);
+        }
+        filteredDeclarations.addAll({
+          for (final node in f.parameters.map((p) => p.type))
+            node.id.toString(): node
+        });
+        filteredDeclarations.addAll({
+          for (final node
+              in f.typeParameters.map((p) => p.constraint).whereType<Type>())
+            node.id.toString(): node
+        });
         break;
       case final UnionType u:
         filteredDeclarations.addAll({
-          for (final t in u.types.where((t) => t is! PrimitiveType))
+          for (final t in u.types.where((t) => t is! BuiltinType))
             t.id.toString(): t
         });
         break;
-      case final PrimitiveType _:
+      case final BuiltinType _:
         // primitive types are generated by default
         break;
       default:
@@ -121,95 +315,4 @@
 
     return filteredDeclarations;
   }
-
-  List<Declaration> _transformVariable(TSVariableStatement variable) {
-    // get the modifier of the declaration
-    final modifiers = variable.modifiers.toDart;
-    final isExported = modifiers.any((m) {
-      return m.kind == TSSyntaxKind.ExportKeyword;
-    });
-
-    var modifier = VariableModifier.$var;
-
-    if ((variable.declarationList.flags & TSNodeFlags.Const) != 0) {
-      modifier = VariableModifier.$const;
-    } else if ((variable.declarationList.flags & TSNodeFlags.Let) != 0) {
-      modifier = VariableModifier.let;
-    }
-
-    return variable.declarationList.declarations.toDart.map((d) {
-      namer.markUsed(d.name.text);
-      return VariableDeclaration(
-          name: d.name.text,
-          type: d.type == null ? PrimitiveType.any : _transformType(d.type!),
-          modifier: modifier,
-          exported: isExported);
-    }).toList();
-  }
-
-  TSNode? _getDeclarationByName(TSIdentifier name) {
-    final symbol = typeChecker.getSymbolAtLocation(name);
-
-    final declarations = symbol?.getDeclarations();
-    // TODO(https://github.com/dart-lang/web/issues/387): Some declarations may not be defined on file,
-    //  and may be from an import statement
-    //  We should be able to handle these
-    return declarations?.toDart.first;
-  }
-
-  /// Parses the type
-  ///
-  /// TODO(https://github.com/dart-lang/web/issues/384): Add support for literals (i.e individual booleans and `null`)
-  /// TODO(https://github.com/dart-lang/web/issues/383): Add support for `typeof` types
-  Type _transformType(TSTypeNode type) {
-    if (type.kind == TSSyntaxKind.UnionType) {
-      final unionType = type as TSUnionTypeNode;
-      // parse union type
-      return UnionType(
-          types: unionType.types.toDart.map<Type>(_transformType).toList());
-    }
-
-    if (type.kind == TSSyntaxKind.TypeReference) {
-      // reference type
-      final refType = type as TSTypeReferenceNode;
-
-      final name = refType.typeName.text;
-      final typeArguments = refType.typeArguments?.toDart;
-
-      var declarationsMatching = nodeMap.findByName(name);
-      if (declarationsMatching.isEmpty) {
-        // TODO: In the case of overloading, should/shouldn't we handle more than one declaration?
-        final declaration = _getDeclarationByName(refType.typeName);
-
-        if (declaration == null) {
-          throw Exception('Found no declaration matching $name');
-        }
-
-        transform(declaration);
-
-        declarationsMatching = nodeMap.findByName(name);
-      }
-
-      // TODO: In the case of overloading, should/shouldn't we handle more than one declaration?
-      final firstNode =
-          declarationsMatching.whereType<NamedDeclaration>().first;
-
-      return firstNode.asReferredType(
-        (typeArguments ?? []).map(_transformType).toList(),
-      );
-    }
-
-    // check for its kind
-    return switch (type.kind) {
-      TSSyntaxKind.StringKeyword => PrimitiveType.string,
-      TSSyntaxKind.AnyKeyword => PrimitiveType.any,
-      TSSyntaxKind.ObjectKeyword => PrimitiveType.object,
-      TSSyntaxKind.NumberKeyword => PrimitiveType.number,
-      TSSyntaxKind.UndefinedKeyword => PrimitiveType.undefined,
-      TSSyntaxKind.UnknownKeyword => PrimitiveType.unknown,
-      TSSyntaxKind.BooleanKeyword => PrimitiveType.boolean,
-      _ => throw UnsupportedError(
-          'The given type with kind ${type.kind} is not supported yet')
-    };
-  }
 }
diff --git a/web_generator/lib/src/js/typescript.dart b/web_generator/lib/src/js/typescript.dart
index bc9a73d..f098057 100644
--- a/web_generator/lib/src/js/typescript.dart
+++ b/web_generator/lib/src/js/typescript.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 @JS('ts')
 library;
 
diff --git a/web_generator/lib/src/js/typescript.types.dart b/web_generator/lib/src/js/typescript.types.dart
index e6d31d3..ed8855d 100644
--- a/web_generator/lib/src/js/typescript.types.dart
+++ b/web_generator/lib/src/js/typescript.types.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 // ignore_for_file: constant_identifier_names
 
 @JS('ts')
@@ -7,6 +11,8 @@
 
 import 'package:meta/meta.dart';
 
+import 'typescript.dart';
+
 extension type const TSSyntaxKind._(num _) {
   /// To be ignored
   static const TSSyntaxKind EndOfFileToken = TSSyntaxKind._(1);
@@ -18,6 +24,7 @@
   static const TSSyntaxKind InterfaceDeclaration = TSSyntaxKind._(264);
   static const TSSyntaxKind FunctionDeclaration = TSSyntaxKind._(262);
   static const TSSyntaxKind ExportDeclaration = TSSyntaxKind._(278);
+  static const TSSyntaxKind Parameter = TSSyntaxKind._(169);
 
   /// keywords
   static const TSSyntaxKind ExportKeyword = TSSyntaxKind._(95);
@@ -34,13 +41,17 @@
   static const TSSyntaxKind UndefinedKeyword = TSSyntaxKind._(157);
   static const TSSyntaxKind SetKeyword = TSSyntaxKind._(153);
   static const TSSyntaxKind UnknownKeyword = TSSyntaxKind._(159);
+  static const TSSyntaxKind VoidKeyword = TSSyntaxKind._(116);
 
   // types
   static const TSSyntaxKind UnionType = TSSyntaxKind._(192);
   static const TSSyntaxKind TypeReference = TSSyntaxKind._(183);
+  static const TSSyntaxKind ArrayType = TSSyntaxKind._(188);
 
   /// Other
   static const TSSyntaxKind Identifier = TSSyntaxKind._(80);
+  static const TSSyntaxKind ObjectBindingPattern = TSSyntaxKind._(206);
+  static const TSSyntaxKind ArrayBindingPattern = TSSyntaxKind._(207);
   static const TSSyntaxKind TypeParameter = TSSyntaxKind._(168);
   static const TSSyntaxKind HeritageClause = TSSyntaxKind._(298);
   static const TSSyntaxKind ExpressionWithTypeArguments = TSSyntaxKind._(233);
@@ -57,11 +68,20 @@
   external TSSyntaxKind get kind;
   external TSNode get parent;
   external TSNodeFlags get flags;
+  external String getText([TSSourceFile? sourceFile]);
+  external String getFullText([TSSourceFile? sourceFile]);
 }
 
 @JS('TypeNode')
 extension type TSTypeNode._(JSObject _) implements TSNode {}
 
+@JS('ArrayTypeNode')
+extension type TSArrayTypeNode._(JSObject _) implements TSTypeNode {
+  @redeclare
+  TSSyntaxKind get kind => TSSyntaxKind.ArrayType;
+  external TSTypeNode get elementType;
+}
+
 @JS('UnionTypeNode')
 extension type TSUnionTypeNode._(JSObject _) implements TSTypeNode {
   @redeclare
@@ -89,21 +109,47 @@
   external String get text;
 }
 
-@JS('VariableDeclaration')
+@JS('VariableStatement')
 extension type TSVariableStatement._(JSObject _) implements TSStatement {
   external TSVariableDeclarationList get declarationList;
   external TSNodeArray<TSNode> get modifiers;
 }
 
+@JS('VariableDeclarationList')
+extension type TSVariableDeclarationList._(JSObject _) implements TSNode {
+  external TSNodeArray<TSVariableDeclaration> get declarations;
+}
+
 @JS('VariableDeclaration')
 extension type TSVariableDeclaration._(JSObject _) implements TSDeclaration {
   external TSIdentifier get name;
   external TSTypeNode? get type;
 }
 
-@JS('VariableDeclarationList')
-extension type TSVariableDeclarationList._(JSObject _) implements TSNode {
-  external TSNodeArray<TSVariableDeclaration> get declarations;
+@JS('FunctionDeclaration')
+extension type TSFunctionDeclaration._(JSObject _) implements TSDeclaration {
+  external TSIdentifier get name;
+  external TSTypeNode? get type;
+  external TSNode? get asteriskToken;
+  external TSNodeArray<TSParameterDeclaration> get parameters;
+  external TSNodeArray<TSTypeParameterDeclaration>? get typeParameters;
+  external TSNodeArray<TSNode> get modifiers;
+}
+
+@JS('ParameterDeclaration')
+extension type TSParameterDeclaration._(JSObject _) implements TSDeclaration {
+  external TSNode get name;
+  external TSTypeNode? get type;
+  external TSNodeArray<TSNode>? get modifiers;
+  external TSNode? get questionToken;
+  external TSNode? get dotDotDotToken;
+}
+
+@JS('TypeParameterDeclaration')
+extension type TSTypeParameterDeclaration._(JSObject _)
+    implements TSDeclaration {
+  external TSIdentifier get name;
+  external TSTypeNode? get constraint;
 }
 
 @JS('NodeArray')
diff --git a/web_generator/lib/src/js/typescript_extensions.dart b/web_generator/lib/src/js/typescript_extensions.dart
deleted file mode 100644
index 603d62d..0000000
--- a/web_generator/lib/src/js/typescript_extensions.dart
+++ /dev/null
@@ -1,14 +0,0 @@
-import 'typescript.types.dart';
-
-extension Names on TSSyntaxKind {
-  String get name {
-    return switch (this) {
-      TSSyntaxKind.DeclareKeyword => 'declare',
-      TSSyntaxKind.ExportKeyword => 'export',
-      TSSyntaxKind.ExtendsKeyword => 'extends',
-      TSSyntaxKind.ImplementsKeyword => 'implements',
-      TSSyntaxKind.VariableDeclaration => 'variable',
-      _ => throw UnsupportedError('The keyword is not supported at the moment')
-    };
-  }
-}
diff --git a/web_generator/lib/src/js/webidl2.dart b/web_generator/lib/src/js/webidl2.dart
new file mode 100644
index 0000000..78e97de
--- /dev/null
+++ b/web_generator/lib/src/js/webidl2.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+@JS('webidl2')
+library;
+
+import 'dart:js_interop';
+
+import 'webidl_api.dart' as idl;
+
+@JS()
+external JSArray<idl.Node> parse(String contents);
diff --git a/web_generator/lib/src/main.mjs b/web_generator/lib/src/main.mjs
index 2355045..c1b7383 100644
--- a/web_generator/lib/src/main.mjs
+++ b/web_generator/lib/src/main.mjs
@@ -8,6 +8,7 @@
 import * as css from '@webref/css';
 import * as elements from '@webref/elements';
 import * as idl from '@webref/idl';
+import * as webidl2 from "webidl2";
 import * as ts from 'typescript';
 
 const require = createRequire(import.meta.url);
@@ -19,6 +20,7 @@
 globalThis.elements = elements;
 globalThis.fs = fs;
 globalThis.idl = idl;
+globalThis.webidl2 = webidl2;
 globalThis.ts = ts;
 globalThis.location = { href: `file://${process.cwd()}/` }
 
diff --git a/web_generator/lib/src/package-lock.json b/web_generator/lib/src/package-lock.json
index e6d9b3d..a83802b 100644
--- a/web_generator/lib/src/package-lock.json
+++ b/web_generator/lib/src/package-lock.json
@@ -13,10 +13,8 @@
         "@webref/css": "^6.11.0",
         "@webref/elements": "^2.2.2",
         "@webref/idl": "^3.43.1",
-        "typescript": "^5.8.3"
-      },
-      "devDependencies": {
-        "webidl2": "^24.2.2"
+        "typescript": "^5.8.3",
+        "webidl2": "^24.4.1"
       }
     },
     "node_modules/@mdn/browser-compat-data": {
diff --git a/web_generator/lib/src/package.json b/web_generator/lib/src/package.json
index 58ac69e..2b77220 100644
--- a/web_generator/lib/src/package.json
+++ b/web_generator/lib/src/package.json
@@ -13,9 +13,7 @@
     "@webref/css": "^6.11.0",
     "@webref/elements": "^2.2.2",
     "@webref/idl": "^3.43.1",
-    "typescript": "^5.8.3"
-  },
-  "devDependencies": {
-    "webidl2": "^24.2.2"
+    "typescript": "^5.8.3",
+    "webidl2": "^24.4.1"
   }
 }
diff --git a/web_generator/lib/src/translator.dart b/web_generator/lib/src/translator.dart
index a47c588..2337a5f 100644
--- a/web_generator/lib/src/translator.dart
+++ b/web_generator/lib/src/translator.dart
@@ -648,10 +648,11 @@
 }
 
 class Translator {
-  final String packageRoot;
+  final String? packageRoot;
   final String _librarySubDir;
   final List<String> _cssStyleDeclarations;
   final Map<String, Set<String>> _elementTagMap;
+  final bool _generateForWeb;
 
   final _libraries = <String, _Library>{};
   final _typeToDeclaration = <String, idl.Node>{};
@@ -668,9 +669,10 @@
   /// Singleton so that various helper methods can access info about the AST.
   static Translator? instance;
 
-  Translator(this.packageRoot, this._librarySubDir, this._cssStyleDeclarations,
-      this._elementTagMap,
-      {required bool generateAll}) {
+  Translator(
+      this._librarySubDir, this._cssStyleDeclarations, this._elementTagMap,
+      {this.packageRoot, required bool generateAll, bool generateForWeb = true})
+      : _generateForWeb = generateForWeb {
     instance = this;
     docProvider = DocProvider.create();
     browserCompatData = BrowserCompatData.read(generateAll: generateAll);
@@ -815,7 +817,7 @@
     final libraryPath = '$_librarySubDir/${shortName.kebabToSnake}.dart';
     assert(!_libraries.containsKey(libraryPath));
 
-    final library = _Library(shortName, '$packageRoot/$libraryPath');
+    final library = _Library(shortName, '${packageRoot ?? '.'}/$libraryPath');
 
     for (var i = 0; i < ast.length; i++) {
       library.add(ast[i]);
@@ -1117,8 +1119,7 @@
     // from: https://github.com/w3c/webidl2.js/blob/main/README.md#default-and-const-values
     final body = switch (constant.valueType) {
       'string' => code.literalString((constant.value as JSString).toDart),
-      'boolean' => code.literalBool(
-          (constant.value as JSString).toDart.toLowerCase() == 'true'),
+      'boolean' => code.literalBool((constant.value as JSBoolean).toDart),
       'number' =>
         code.literalNum(num.parse((constant.value as JSString).toDart)),
       'null' => code.literalNull,
@@ -1375,39 +1376,51 @@
     ];
   }
 
-  code.Library _library(_Library library) => code.Library((b) => b
-    ..comments.addAll([
-      ...licenseHeader,
-      '',
-      ...mozLicenseHeader,
-    ])
-    // TODO(https://github.com/dart-lang/sdk/issues/56450): Remove this once
-    // this bug has been resolved.
-    ..ignoreForFile.addAll([
-      'unintended_html_in_doc_comment',
-    ])
-    ..generatedByComment = generatedFileDisclaimer
-    // TODO(srujzs): This is to address the issue around extension type object
-    // literal constructors in https://github.com/dart-lang/sdk/issues/54801.
-    // Once this package moves to an SDK version that contains a fix for that,
-    // this can be removed.
-    ..annotations.addAll(_jsOverride('', alwaysEmit: true))
-    ..body.addAll([
-      for (final typedef in library.typedefs.where(_usedTypes.contains))
-        _typedef(typedef.name, _desugarTypedef(_RawType(typedef.name, false))!),
-      for (final callback in library.callbacks.where(_usedTypes.contains))
-        _typedef(
-            callback.name, _desugarTypedef(_RawType(callback.name, false))!),
-      for (final callbackInterface
-          in library.callbackInterfaces.where(_usedTypes.contains))
-        _typedef(callbackInterface.name,
-            _desugarTypedef(_RawType(callbackInterface.name, false))!),
-      for (final enum_ in library.enums.where(_usedTypes.contains))
-        _typedef(enum_.name, _desugarTypedef(_RawType(enum_.name, false))!),
-      for (final interfacelike
-          in library.interfacelikes.where(_usedTypes.contains))
-        ..._interfacelike(interfacelike),
-    ]));
+  code.Library _library(_Library library) => code.Library((b) {
+        if (_generateForWeb) {
+          b.comments.addAll([
+            ...licenseHeader,
+            '',
+            ...mozLicenseHeader,
+          ]);
+        }
+        // TODO(https://github.com/dart-lang/sdk/issues/56450): Remove
+        //  this once this bug has been resolved.
+        b
+          ..ignoreForFile.addAll([
+            'unintended_html_in_doc_comment',
+            'constant_identifier_names',
+            if (library.interfacelikes
+                .where((i) => i.type == 'namespace')
+                .isNotEmpty)
+              'non_constant_identifier_names'
+          ])
+          ..generatedByComment = generatedFileDisclaimer
+          // TODO(srujzs): This is to address the issue around extension type
+          // object literal constructors in
+          // https://github.com/dart-lang/sdk/issues/54801.
+          // Once this package moves to an SDK version that contains a fix
+          // for that, this can be removed.
+          ..annotations.addAll(_jsOverride('', alwaysEmit: true))
+          ..body.addAll([
+            for (final typedef in library.typedefs.where(_usedTypes.contains))
+              _typedef(typedef.name,
+                  _desugarTypedef(_RawType(typedef.name, false))!),
+            for (final callback in library.callbacks.where(_usedTypes.contains))
+              _typedef(callback.name,
+                  _desugarTypedef(_RawType(callback.name, false))!),
+            for (final callbackInterface
+                in library.callbackInterfaces.where(_usedTypes.contains))
+              _typedef(callbackInterface.name,
+                  _desugarTypedef(_RawType(callbackInterface.name, false))!),
+            for (final enum_ in library.enums.where(_usedTypes.contains))
+              _typedef(
+                  enum_.name, _desugarTypedef(_RawType(enum_.name, false))!),
+            for (final interfacelike
+                in library.interfacelikes.where(_usedTypes.contains))
+              ..._interfacelike(interfacelike),
+          ]);
+      });
 
   code.Library generateRootImport(Iterable<String> files) =>
       code.Library((b) => b
@@ -1430,7 +1443,9 @@
       }
     }
 
-    dartLibraries['dom.dart'] = generateRootImport(dartLibraries.keys);
+    if (_generateForWeb) {
+      dartLibraries['dom.dart'] = generateRootImport(dartLibraries.keys);
+    }
 
     return dartLibraries;
   }
diff --git a/web_generator/lib/src/type_aliases.dart b/web_generator/lib/src/type_aliases.dart
index a2bf791..eaa5f33 100644
--- a/web_generator/lib/src/type_aliases.dart
+++ b/web_generator/lib/src/type_aliases.dart
@@ -12,6 +12,7 @@
   // Note that this is a special sentinel that doesn't actually exist in the set
   // of JS types today (although this might in the future).
   'undefined': 'JSUndefined',
+  'void': 'JSVoid',
   'Function': 'JSFunction',
   'SharedArrayBuffer': 'JSObject',
 
@@ -69,5 +70,6 @@
   // `Translator._typeReference` for more details.
   'JSDouble': 'num',
   'JSNumber': 'num',
+  'JSVoid': 'void',
   'JSUndefined': 'void',
 };
diff --git a/web_generator/test/integration/idl/callbacks_expected.dart b/web_generator/test/integration/idl/callbacks_expected.dart
new file mode 100644
index 0000000..0021fa2
--- /dev/null
+++ b/web_generator/test/integration/idl/callbacks_expected.dart
@@ -0,0 +1,23 @@
+// Generated from Web IDL definitions.
+
+// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment
+
+@JS()
+library;
+
+import 'dart:js_interop';
+
+typedef VoidCallback = JSFunction;
+typedef StringCallback = JSFunction;
+typedef Comparator = JSFunction;
+typedef Transformer = JSFunction;
+typedef AsyncOperationCallback = JSFunction;
+extension type AsyncOperations._(JSObject _) implements JSObject {
+  external void performOperation(AsyncOperationCallback whenFinished);
+}
+extension type Processor._(JSObject _) implements JSObject {
+  external void run(VoidCallback onComplete);
+  external void compare(Comparator cmp);
+  external void stringManipulate(String string, StringCallback callback);
+  external void transform(JSAny? data, Transformer transformer);
+}
diff --git a/web_generator/test/integration/idl/callbacks_input.idl b/web_generator/test/integration/idl/callbacks_input.idl
new file mode 100644
index 0000000..b58be8a
--- /dev/null
+++ b/web_generator/test/integration/idl/callbacks_input.idl
@@ -0,0 +1,17 @@
+callback VoidCallback = void ();
+callback StringCallback = void (DOMString result);
+callback Comparator = long (DOMString a, DOMString b);
+callback Transformer = any (any input);
+callback AsyncOperationCallback = undefined (DOMString status);
+
+[Exposed=Window]
+interface AsyncOperations {
+  undefined performOperation(AsyncOperationCallback whenFinished);
+};
+
+interface Processor {
+  void run(VoidCallback onComplete);
+  void compare(Comparator cmp);
+  void stringManipulate(DOMString string, StringCallback callback);
+  void transform(any data, Transformer transformer);
+};
diff --git a/web_generator/test/integration/idl/constructors_expected.dart b/web_generator/test/integration/idl/constructors_expected.dart
new file mode 100644
index 0000000..1c95d0c
--- /dev/null
+++ b/web_generator/test/integration/idl/constructors_expected.dart
@@ -0,0 +1,38 @@
+// Generated from Web IDL definitions.
+
+// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment
+
+@JS()
+library;
+
+import 'dart:js_interop';
+
+extension type Shape._(JSObject _) implements JSObject {
+  external factory Shape();
+}
+extension type Done._(JSObject _) implements JSObject {}
+extension type Coordinate._(JSObject _) implements JSObject {
+  external int get x;
+  external set x(int value);
+  external int get y;
+  external set y(int value);
+}
+extension type DoneList._(JSObject _) implements JSObject {
+  external factory DoneList(int length);
+
+  external Done item(int index);
+  external int get length;
+}
+extension type Circle._(JSObject _) implements Shape, JSObject {
+  external factory Circle(num radius);
+
+  external static Coordinate triangulate(Circle c1, Circle c2, Circle c3);
+  external static int get triangulationCount;
+  external double get r;
+  external set r(num value);
+  external double get cx;
+  external set cx(num value);
+  external double get cy;
+  external set cy(num value);
+  external double get circumference;
+}
diff --git a/web_generator/test/integration/idl/constructors_input.idl b/web_generator/test/integration/idl/constructors_input.idl
new file mode 100644
index 0000000..dc91471
--- /dev/null
+++ b/web_generator/test/integration/idl/constructors_input.idl
@@ -0,0 +1,32 @@
+interface Shape {
+  constructor();
+};
+
+/// Wordplay around "Node"
+interface Done {
+  
+};
+
+interface Coordinate {
+  attribute long x;
+  attribute long y;
+};
+
+[Exposed=Window]
+interface DoneList {
+  constructor(unsigned long length);
+  Done item(unsigned long index);
+  readonly attribute unsigned long length;
+};
+
+[Exposed=Window]
+interface Circle : Shape {
+  constructor(double radius);
+  attribute double r;
+  attribute double cx;
+  attribute double cy;
+  readonly attribute double circumference;
+
+  static readonly attribute long triangulationCount;
+  static Coordinate triangulate(Circle c1, Circle c2, Circle c3);
+};
diff --git a/web_generator/test/integration/idl/dictionaries_expected.dart b/web_generator/test/integration/idl/dictionaries_expected.dart
new file mode 100644
index 0000000..dd9b314
--- /dev/null
+++ b/web_generator/test/integration/idl/dictionaries_expected.dart
@@ -0,0 +1,45 @@
+// Generated from Web IDL definitions.
+
+// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment
+
+@JS()
+library;
+
+import 'dart:js_interop';
+
+extension type ConfigOptions._(JSObject _) implements JSObject {
+  external factory ConfigOptions({
+    required String endpoint,
+    bool useCache,
+    int timeout,
+    JSArray<JSString>? tags,
+  });
+
+  external String get endpoint;
+  external set endpoint(String value);
+  external bool get useCache;
+  external set useCache(bool value);
+  external int get timeout;
+  external set timeout(int value);
+  external JSArray<JSString>? get tags;
+  external set tags(JSArray<JSString>? value);
+}
+extension type ExtendedOptions._(JSObject _)
+    implements ConfigOptions, JSObject {
+  external factory ExtendedOptions({
+    required String endpoint,
+    bool useCache,
+    int timeout,
+    JSArray<JSString>? tags,
+    String? userToken,
+    String mode,
+  });
+
+  external String? get userToken;
+  external set userToken(String? value);
+  external String get mode;
+  external set mode(String value);
+}
+extension type Configurable._(JSObject _) implements JSObject {
+  external void applySettings([ExtendedOptions options]);
+}
diff --git a/web_generator/test/integration/idl/dictionaries_input.idl b/web_generator/test/integration/idl/dictionaries_input.idl
new file mode 100644
index 0000000..ed73f60
--- /dev/null
+++ b/web_generator/test/integration/idl/dictionaries_input.idl
@@ -0,0 +1,15 @@
+dictionary ConfigOptions {
+  required DOMString endpoint;
+  boolean useCache = true;
+  long timeout = 5000;
+  sequence<DOMString>? tags;
+};
+
+dictionary ExtendedOptions : ConfigOptions {
+  DOMString? userToken;
+  DOMString mode = "default";
+};
+
+interface Configurable {
+  void applySettings(optional ExtendedOptions options);
+};
diff --git a/web_generator/test/integration/idl/enum_expected.dart b/web_generator/test/integration/idl/enum_expected.dart
new file mode 100644
index 0000000..f0dff91
--- /dev/null
+++ b/web_generator/test/integration/idl/enum_expected.dart
@@ -0,0 +1,21 @@
+// Generated from Web IDL definitions.
+
+// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment
+
+@JS()
+library;
+
+import 'dart:js_interop';
+
+typedef LogLevel = String;
+typedef Direction = String;
+typedef DisplayMode = String;
+extension type Logger._(JSObject _) implements JSObject {
+  external void log(String message, [LogLevel level]);
+  external void logWithDirection(
+    String message, [
+    LogLevel level,
+    Direction dir,
+  ]);
+  external void logWithDisplayMode(String message, DisplayMode display);
+}
diff --git a/web_generator/test/integration/idl/enum_input.idl b/web_generator/test/integration/idl/enum_input.idl
new file mode 100644
index 0000000..5a5e291
--- /dev/null
+++ b/web_generator/test/integration/idl/enum_input.idl
@@ -0,0 +1,26 @@
+enum LogLevel {
+  "debug",
+  "info",
+  "warn",
+  "error"
+};
+
+enum Direction {
+  "up",
+  "down",
+  "left",
+  "right"
+};
+
+enum DisplayMode {
+  "fullscreen",
+  "standalone",
+  "minimal-ui",
+  "browser"
+};
+
+interface Logger {
+  void log(DOMString message, optional LogLevel level = "info");
+  void logWithDirection(DOMString message, optional LogLevel level = "info", optional Direction dir = "up");
+  void logWithDisplayMode(DOMString message, DisplayMode display);
+};
diff --git a/web_generator/test/integration/idl/indexers_expected.dart b/web_generator/test/integration/idl/indexers_expected.dart
new file mode 100644
index 0000000..4ee2c64
--- /dev/null
+++ b/web_generator/test/integration/idl/indexers_expected.dart
@@ -0,0 +1,16 @@
+// Generated from Web IDL definitions.
+
+// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment
+
+@JS()
+library;
+
+import 'dart:js_interop';
+
+extension type OrderedMap._(JSObject _) implements JSObject {
+  external JSAny? operator [](int index);
+  external void operator []=(int index, JSAny? value);
+  external JSAny? get(String name);
+  external void set(String name, JSAny? value);
+  external int get size;
+}
diff --git a/web_generator/test/integration/idl/indexers_input.idl b/web_generator/test/integration/idl/indexers_input.idl
new file mode 100644
index 0000000..a0145b5
--- /dev/null
+++ b/web_generator/test/integration/idl/indexers_input.idl
@@ -0,0 +1,9 @@
+interface OrderedMap {
+  readonly attribute unsigned long size;
+
+  getter any (unsigned long index);
+  setter undefined (unsigned long index, any value);
+
+  getter any get(DOMString name);
+  setter undefined set(DOMString name, any value);
+};
diff --git a/web_generator/test/integration/idl/interfaces_expected.dart b/web_generator/test/integration/idl/interfaces_expected.dart
new file mode 100644
index 0000000..07d9ec2
--- /dev/null
+++ b/web_generator/test/integration/idl/interfaces_expected.dart
@@ -0,0 +1,61 @@
+// Generated from Web IDL definitions.
+
+// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment
+
+@JS()
+library;
+
+import 'dart:js_interop';
+
+typedef LogLevel = String;
+extension type ConfigOptions._(JSObject _) implements JSObject {}
+extension type EmptyInterface._(JSObject _) implements JSObject {}
+extension type BaseInterface._(JSObject _) implements JSObject {
+  external void f();
+  external void g();
+}
+extension type Paint._(JSObject _) implements JSObject {}
+extension type SolidColor._(JSObject _) implements Paint, JSObject {
+  external double get red;
+  external set red(num value);
+  external double get green;
+  external set green(num value);
+  external double get blue;
+  external set blue(num value);
+}
+extension type MyInterface._(JSObject _) implements JSObject {
+  external void doSomething();
+  external bool isReady();
+  external JSPromise<JSString> saveData([ConfigOptions config]);
+  external void log(String message, [LogLevel level]);
+  external String operator [](int index);
+  external String get name;
+  external set name(String value);
+  external int get id;
+}
+extension type MySubInterface._(JSObject _) implements JSObject {
+  external void reset();
+  external String get info;
+  external set info(String value);
+}
+extension type MyException._(JSObject _) implements JSObject {
+  external factory MyException([String message, String name]);
+
+  external String get name;
+  external String get message;
+  external int get code;
+}
+extension type ProtocolXError._(JSObject _) implements MyException, JSObject {
+  external factory ProtocolXError(
+    ProtocolXErrorOptions options, [
+    String message,
+  ]);
+
+  external int get errorCode;
+}
+extension type ProtocolXErrorOptions._(JSObject _) implements JSObject {
+  external factory ProtocolXErrorOptions({required int errorCode});
+
+  external int get errorCode;
+  external set errorCode(int value);
+}
diff --git a/web_generator/test/integration/idl/interfaces_input.idl b/web_generator/test/integration/idl/interfaces_input.idl
new file mode 100644
index 0000000..16864a9
--- /dev/null
+++ b/web_generator/test/integration/idl/interfaces_input.idl
@@ -0,0 +1,66 @@
+enum LogLevel {
+  "debug",
+  "info",
+  "warn",
+  "error"
+};
+
+
+interface ConfigOptions {
+
+};
+
+interface EmptyInterface { };
+
+interface BaseInterface {
+  undefined f();
+  undefined g();
+};
+
+interface Paint { };
+
+interface SolidColor : Paint {
+  attribute double red;
+  attribute double green;
+  attribute double blue;
+};
+
+interface MyInterface {
+  attribute DOMString name;
+  readonly attribute unsigned long id;
+
+  void doSomething();
+  boolean isReady();
+
+  Promise<DOMString> saveData(optional ConfigOptions config);
+
+  void log(DOMString message);
+  void log(DOMString message, LogLevel level);
+
+  getter DOMString (unsigned long index);
+};
+
+interface MySubInterface {
+  attribute DOMString info;
+  void reset();
+};
+
+[Exposed=*,
+ Serializable]
+interface MyException { // but see below note about JavaScript binding
+  constructor(optional DOMString message = "", optional DOMString name = "Error");
+  readonly attribute DOMString name;
+  readonly attribute DOMString message;
+  readonly attribute unsigned short code;
+};
+
+[Exposed=Window, Serializable]
+interface ProtocolXError : MyException {
+  constructor(optional DOMString message = "", ProtocolXErrorOptions options);
+
+  readonly attribute unsigned long long errorCode;
+};
+
+dictionary ProtocolXErrorOptions {
+    required [EnforceRange] unsigned long long errorCode;
+};
diff --git a/web_generator/test/integration/idl/methods_expected.dart b/web_generator/test/integration/idl/methods_expected.dart
new file mode 100644
index 0000000..699b495
--- /dev/null
+++ b/web_generator/test/integration/idl/methods_expected.dart
@@ -0,0 +1,34 @@
+// Generated from Web IDL definitions.
+
+// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment
+
+@JS()
+library;
+
+import 'dart:js_interop';
+
+extension type MyMethodExamples._(JSObject _) implements JSObject {
+  external static bool isValid(String candidate);
+  external void reset();
+  external void configure([bool force, String? label]);
+  external void log(
+    String message, [
+    JSAny? extra1,
+    JSAny? extra2,
+    JSAny? extra3,
+    JSAny? extra4,
+  ]);
+  external void update(JSAny keyOrKeys, [String value]);
+  external JSPromise<JSString> fetchRemoteValue([String? endpoint]);
+  external void init();
+}
+extension type Dimensions._(JSObject _) implements JSObject {
+  external int get width;
+  external set width(int value);
+  external int get height;
+  external set height(int value);
+}
+extension type Button._(JSObject _) implements JSObject {
+  external bool isMouseOver();
+  external void setDimensions(JSAny sizeOrWidth, [int height]);
+}
diff --git a/web_generator/test/integration/idl/methods_input.idl b/web_generator/test/integration/idl/methods_input.idl
new file mode 100644
index 0000000..6e97649
--- /dev/null
+++ b/web_generator/test/integration/idl/methods_input.idl
@@ -0,0 +1,34 @@
+interface MyMethodExamples {
+  void reset();
+
+  void configure(optional boolean force = false, optional DOMString? label = null);
+
+  void log(DOMString message, any... extras);
+
+  void update(DOMString key, DOMString value);
+  void update(sequence<DOMString> keys);
+
+  Promise<DOMString> fetchRemoteValue(optional DOMString? endpoint);
+
+  static boolean isValid(DOMString candidate);
+
+  [Deprecated="Use configure() instead."]
+  void init();
+};
+
+[Exposed=Window]
+interface Dimensions {
+  attribute unsigned long width;
+  attribute unsigned long height;
+};
+
+[Exposed=Window]
+interface Button {
+
+  // An operation that takes no arguments and returns a boolean.
+  boolean isMouseOver();
+
+  // Overloaded operations.
+  undefined setDimensions(Dimensions size);
+  undefined setDimensions(unsigned long width, unsigned long height);
+};
diff --git a/web_generator/test/integration/idl/mixin_interfaces_expected.dart b/web_generator/test/integration/idl/mixin_interfaces_expected.dart
new file mode 100644
index 0000000..fcc2315
--- /dev/null
+++ b/web_generator/test/integration/idl/mixin_interfaces_expected.dart
@@ -0,0 +1,17 @@
+// Generated from Web IDL definitions.
+
+// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment
+
+@JS()
+library;
+
+import 'dart:js_interop';
+
+extension type Position._(JSObject _) implements JSObject {
+  external void somethingMixedIn();
+  external void moveTo(num newX, num newY);
+  external double get x;
+  external set x(num value);
+  external double get y;
+  external set y(num value);
+}
diff --git a/web_generator/test/integration/idl/mixin_interfaces_input.idl b/web_generator/test/integration/idl/mixin_interfaces_input.idl
new file mode 100644
index 0000000..cf4d22d
--- /dev/null
+++ b/web_generator/test/integration/idl/mixin_interfaces_input.idl
@@ -0,0 +1,23 @@
+interface mixin EventTargetMixin {
+  void addEventListener(DOMString type, EventListener callback, optional boolean capture = false);
+  void removeEventListener(DOMString type, EventListener callback, optional boolean capture = false);
+  boolean dispatchEvent(Event event);
+};
+
+interface mixin PositionMixin {
+  attribute double x;
+  attribute double y;
+  void moveTo(double newX, double newY);
+};
+
+interface Position {};
+
+Position includes PositionMixin;
+
+interface mixin MyMixin {
+  void somethingMixedIn();
+};
+
+partial interface mixin Position {
+  void somethingMixedIn();
+};
diff --git a/web_generator/test/integration/idl/namespaces_expected.dart b/web_generator/test/integration/idl/namespaces_expected.dart
new file mode 100644
index 0000000..520b157
--- /dev/null
+++ b/web_generator/test/integration/idl/namespaces_expected.dart
@@ -0,0 +1,28 @@
+// Generated from Web IDL definitions.
+
+// ignore_for_file: constant_identifier_names, non_constant_identifier_names
+// ignore_for_file: unintended_html_in_doc_comment
+
+@JS()
+library;
+
+import 'dart:js_interop';
+
+typedef Comparator = JSFunction;
+typedef LogLevel = String;
+extension type ConfigOptions._(JSObject _) implements JSObject {}
+@JS()
+external $MyLibrary get MyLibrary;
+@JS('MyLibrary')
+extension type $MyLibrary._(JSObject _) implements JSObject {
+  static const int VERSION_MAJOR = 1;
+
+  static const int VERSION_MINOR = 4;
+
+  external void initialize();
+  external String stringify(JSObject input);
+  external JSPromise<JSAny?> fetchResource(String url);
+  external void log(String message, [LogLevel level]);
+  external ConfigOptions getDefaultConfig();
+  external void forEach(JSArray<JSString> items, Comparator compareFn);
+}
diff --git a/web_generator/test/integration/idl/namespaces_input.idl b/web_generator/test/integration/idl/namespaces_input.idl
new file mode 100644
index 0000000..d8f1929
--- /dev/null
+++ b/web_generator/test/integration/idl/namespaces_input.idl
@@ -0,0 +1,27 @@
+enum LogLevel {
+  "debug",
+  "info",
+  "warn",
+  "error"
+};
+
+callback Comparator = long (DOMString a, DOMString b);
+
+interface ConfigOptions { };
+
+[Exposed=Window]
+namespace MyLibrary {
+  const unsigned short VERSION_MAJOR = 1;
+  const unsigned short VERSION_MINOR = 4;
+
+  void initialize();
+  DOMString stringify(object input);
+  Promise<any> fetchResource(DOMString url);
+
+  void log(DOMString message);
+  void log(DOMString message, LogLevel level);
+
+  ConfigOptions getDefaultConfig();
+
+  void forEach(sequence<DOMString> items, Comparator compareFn);
+};
diff --git a/web_generator/test/integration/idl/properties_expected.dart b/web_generator/test/integration/idl/properties_expected.dart
new file mode 100644
index 0000000..d2f5dca
--- /dev/null
+++ b/web_generator/test/integration/idl/properties_expected.dart
@@ -0,0 +1,29 @@
+// Generated from Web IDL definitions.
+
+// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment
+
+@JS()
+library;
+
+import 'dart:js_interop';
+
+extension type PropertyTest._(JSObject _) implements JSObject {
+  static const bool DEBUG = false;
+
+  external static int get testCount;
+  external String operator [](JSAny indexOrName);
+  external void operator []=(int index, String value);
+  external String get title;
+  external set title(String value);
+  external int get createdAt;
+  external String? get optionalName;
+  external set optionalName(String? value);
+  external JSArray<JSString> get tags;
+  external set tags(JSArray<JSString> value);
+  external JSUint8Array get binaryData;
+  external set binaryData(JSUint8Array value);
+  external JSAny? get flexibleValue;
+  external set flexibleValue(JSAny? value);
+  external String get testInfo;
+  external set testInfo(String value);
+}
diff --git a/web_generator/test/integration/idl/properties_input.idl b/web_generator/test/integration/idl/properties_input.idl
new file mode 100644
index 0000000..64ebe3a
--- /dev/null
+++ b/web_generator/test/integration/idl/properties_input.idl
@@ -0,0 +1,25 @@
+interface PropertyTest {
+  const boolean DEBUG = false;
+
+  attribute DOMString title;
+
+  static readonly attribute long testCount;
+
+  readonly attribute unsigned long createdAt;
+
+  attribute DOMString? optionalName;
+
+  attribute sequence<DOMString> tags;
+  attribute Uint8Array binaryData;
+
+  attribute (DOMString or long)? flexibleValue;
+
+  getter DOMString (unsigned long index);
+  setter void (unsigned long index, DOMString value);
+
+  getter DOMString (DOMString name);
+
+  deleter boolean (DOMString name);
+
+  stringifier attribute DOMString testInfo;
+};
diff --git a/web_generator/test/integration/idl/typedefs_expected.dart b/web_generator/test/integration/idl/typedefs_expected.dart
new file mode 100644
index 0000000..c3a7e27
--- /dev/null
+++ b/web_generator/test/integration/idl/typedefs_expected.dart
@@ -0,0 +1,16 @@
+// Generated from Web IDL definitions.
+
+// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment
+
+@JS()
+library;
+
+import 'dart:js_interop';
+
+typedef ArrayBufferView = JSObject;
+typedef BufferSource = JSObject;
+typedef Timestamp = int;
+extension type DataHandler._(JSObject _) implements JSObject {
+  external void setInput(BufferSource data);
+  external Timestamp getLastUpdated();
+}
diff --git a/web_generator/test/integration/idl/typedefs_input.idl b/web_generator/test/integration/idl/typedefs_input.idl
new file mode 100644
index 0000000..11d13f4
--- /dev/null
+++ b/web_generator/test/integration/idl/typedefs_input.idl
@@ -0,0 +1,15 @@
+typedef (Int8Array or Int16Array or Int32Array or
+         Uint8Array or Uint16Array or Uint32Array or Uint8ClampedArray or
+         BigInt64Array or BigUint64Array or
+         Float16Array or Float32Array or Float64Array or DataView) ArrayBufferView;
+         
+typedef (ArrayBuffer or ArrayBufferView) BufferSource;
+typedef (DOMString or USVString) StringInput;
+typedef sequence<DOMString> StringList;
+typedef unsigned long long Timestamp;
+
+
+interface DataHandler {
+  void setInput(BufferSource data);
+  Timestamp getLastUpdated();
+};
diff --git a/web_generator/test/integration/idl/types_expected.dart b/web_generator/test/integration/idl/types_expected.dart
new file mode 100644
index 0000000..c582396
--- /dev/null
+++ b/web_generator/test/integration/idl/types_expected.dart
@@ -0,0 +1,18 @@
+// Generated from Web IDL definitions.
+
+// ignore_for_file: constant_identifier_names, unintended_html_in_doc_comment
+
+@JS()
+library;
+
+import 'dart:js_interop';
+
+extension type Global._(JSObject _) implements JSObject {
+  static const bool DEBUG = false;
+
+  static const int LF = 10;
+
+  static const int BIT_MASK = 64512;
+
+  static const double AVOGADRO = 6.022e+23;
+}
diff --git a/web_generator/test/integration/idl/types_input.idl b/web_generator/test/integration/idl/types_input.idl
new file mode 100644
index 0000000..e5cd371
--- /dev/null
+++ b/web_generator/test/integration/idl/types_input.idl
@@ -0,0 +1,6 @@
+interface Global {
+  const boolean DEBUG = false;
+  const octet LF = 10;
+  const unsigned long BIT_MASK = 0x0000fc00;
+  const double AVOGADRO = 6.022e23;
+};
diff --git a/web_generator/test/integration/idl_test.dart b/web_generator/test/integration/idl_test.dart
new file mode 100644
index 0000000..7aa6b9b
--- /dev/null
+++ b/web_generator/test/integration/idl_test.dart
@@ -0,0 +1,72 @@
+@TestOn('vm')
+library;
+
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:web_generator/src/cli.dart';
+
+/// Actual test output can be found in `.dart_tool/idl`
+void main() {
+  final bindingsGenPath = p.join('lib', 'src');
+  group('IDL Integration Test', () {
+    final testGenFolder = p.join('test', 'integration', 'idl');
+    final inputDir = Directory(testGenFolder);
+
+    setUpAll(() async {
+      // set up npm
+      await runProc('npm', ['install'], workingDirectory: bindingsGenPath);
+
+      // compile file
+      await compileDartMain(dir: bindingsGenPath);
+    });
+
+    for (final inputFile in inputDir
+        .listSync()
+        .whereType<File>()
+        .where((f) => p.basenameWithoutExtension(f.path).endsWith('_input'))) {
+      final inputFileName = p.basenameWithoutExtension(inputFile.path);
+      final inputName = inputFileName.replaceFirst('_input', '');
+
+      final outputActualPath =
+          p.join('.dart_tool', 'idl', '${inputName}_actual.dart');
+      final outputExpectedPath =
+          p.join(testGenFolder, '${inputName}_expected.dart');
+
+      test(inputName, () async {
+        final inputFilePath = p.relative(inputFile.path, from: bindingsGenPath);
+        final outFilePath = p.relative(outputActualPath, from: bindingsGenPath);
+        // run the entrypoint
+        await runProc(
+            'node',
+            [
+              'main.mjs',
+              '--input=$inputFilePath',
+              '--output=${p.dirname(outFilePath)}',
+              '--idl'
+            ],
+            workingDirectory: bindingsGenPath);
+
+        await File(
+                p.join(p.dirname(outputActualPath), '${inputName}_input.dart'))
+            .rename(outputActualPath);
+
+        // read files
+        final expectedOutput = await File(outputExpectedPath).readAsString();
+        final actualOutput = await File(outputActualPath).readAsString();
+
+        expect(actualOutput, expectedOutput);
+      });
+
+      tearDownAll(() {
+        inputDir
+            .listSync()
+            .whereType<File>()
+            .where(
+                (f) => p.basenameWithoutExtension(f.path).endsWith('_actual'))
+            .forEach((f) => f.deleteSync());
+      });
+    }
+  });
+}
diff --git a/web_generator/test/integration/interop_gen/functions_expected.dart b/web_generator/test/integration/interop_gen/functions_expected.dart
new file mode 100644
index 0000000..4c8c2f8
--- /dev/null
+++ b/web_generator/test/integration/interop_gen/functions_expected.dart
@@ -0,0 +1,47 @@
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:js_interop' as _i1;
+
+@_i1.JS()
+external String greetUser(String name);
+@_i1.JS()
+external void logMessages(
+  _i1.JSArray<_i1.JSString> messages, [
+  _i1.JSArray<_i1.JSString> messages2,
+  _i1.JSArray<_i1.JSString> messages3,
+  _i1.JSArray<_i1.JSString> messages4,
+]);
+@_i1.JS()
+external _i1.JSPromise<U> delay<U extends _i1.JSAny?>(num ms, [U? returnValue]);
+@_i1.JS()
+external _i1.JSArray<_i1.JSNumber> toArray(num a);
+@_i1.JS()
+external double square(num a);
+@_i1.JS()
+external double pow(num a);
+@_i1.JS('pow')
+external double pow$1(num a, num power);
+@_i1.JS('toArray')
+external _i1.JSArray<_i1.JSString> toArray$1(String a);
+@_i1.JS()
+external _i1.JSObject createUser(String name, [num? age, String? role]);
+@_i1.JS()
+external T firstElement<T extends _i1.JSAny?>(_i1.JSArray<T> arr);
+@_i1.JS()
+external void throwError(String msg);
+@_i1.JS()
+external _i1.JSArray<T> wrapInArray<T extends _i1.JSAny?>(T value);
+@_i1.JS()
+external T identity<T extends _i1.JSAny?>(T value);
+@_i1.JS()
+external void someFunction<A extends _i1.JSAny?>(_i1.JSArray<A> arr);
+@_i1.JS('someFunction')
+external B someFunction$1<A extends _i1.JSAny?, B extends _i1.JSAny?>(
+  _i1.JSArray<A> arr,
+);
+@_i1.JS()
+external T logTuple<T extends _i1.JSArray<_i1.JSAny?>>(
+  T args, [
+  T args2,
+  T args3,
+  T args4,
+]);
diff --git a/web_generator/test/integration/interop_gen/functions_input.d.ts b/web_generator/test/integration/interop_gen/functions_input.d.ts
new file mode 100644
index 0000000..6fc8665
--- /dev/null
+++ b/web_generator/test/integration/interop_gen/functions_input.d.ts
@@ -0,0 +1,16 @@
+export declare function greetUser(name: string): string;
+export declare function logMessages(...messages: string[]): void;
+export declare function delay<U>(ms: number, returnValue?: U): Promise<U>;
+export declare function toArray(a: number): number[];
+export declare function square(a: number): number;
+export declare function pow(a: number): number;
+export declare function pow(a: number, power: number): number;
+export declare function toArray(a: string): string[];
+export declare function createUser(name: string, age?: number, role?: string): object;
+export declare function firstElement<T>(arr: T[]): T;
+export declare function throwError(msg: string): void;
+export declare function wrapInArray<T>(value: T): T[];
+export declare function identity<T = string>(value: T): T;
+export declare function someFunction<A>(arr: A[]): undefined;
+export declare function someFunction<A, B>(arr: A[]): B;
+export declare function logTuple<T extends any[]>(...args: T): T;
diff --git a/web_generator/test/integration/interop_gen/variables_expected.dart b/web_generator/test/integration/interop_gen/variables_expected.dart
index 2609c67..1df172a 100644
--- a/web_generator/test/integration/interop_gen/variables_expected.dart
+++ b/web_generator/test/integration/interop_gen/variables_expected.dart
@@ -2,11 +2,11 @@
 import 'dart:js_interop' as _i1;
 
 @_i1.JS()
-external int counter;
+external double counter;
 @_i1.JS()
 external String get appName;
 @_i1.JS()
-external int globalCounter;
+external double globalCounter;
 @_i1.JS()
 external _i1.JSObject globalObject;
 @_i1.JS()
@@ -14,18 +14,22 @@
 @_i1.JS()
 external String username;
 @_i1.JS()
-external int get foo;
+external double get foo;
 @_i1.JS()
-external int get bar;
+external double get bar;
 @_i1.JS()
-external int free;
+external double free;
 @_i1.JS()
-external int dom;
+external double dom;
 @_i1.JS()
 external String fred;
 @_i1.JS()
 external String doctor;
 @_i1.JS()
-external _i1.JSAny something;
+external _i1.JSAny? something;
 @_i1.JS()
 external _i1.JSAny? get maybeValue;
+@_i1.JS()
+external _i1.JSArray<_i1.JSString> get names;
+@_i1.JS()
+external _i1.JSArray<_i1.JSString> get newNames;
diff --git a/web_generator/test/integration/interop_gen/variables_input.d.ts b/web_generator/test/integration/interop_gen/variables_input.d.ts
index 933449b..1bc7d05 100644
--- a/web_generator/test/integration/interop_gen/variables_input.d.ts
+++ b/web_generator/test/integration/interop_gen/variables_input.d.ts
@@ -9,3 +9,5 @@
 export declare let fred: string, doctor: string;
 export declare let something: any;
 export declare const maybeValue: unknown;
+export declare const names: string[];
+export declare const newNames: Array<string>;