diff --git a/Cargo.toml b/Cargo.toml
index f4bcfcb353..5d388196b5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -172,7 +172,7 @@ hkdf = "0.12.3"
 
 # webgpu
 raw-window-handle = "0.5.0"
-wgpu-core = "=0.18"
+wgpu-core = { version = "=0.18", features = ["raw-window-handle"] }
 wgpu-types = "=0.18"
 wgpu-hal = "=0.18"
 
diff --git a/ext/webgpu/00_init.js b/ext/webgpu/00_init.js
new file mode 100644
index 0000000000..b559fe3db0
--- /dev/null
+++ b/ext/webgpu/00_init.js
@@ -0,0 +1,39 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+import { core } from "ext:core/mod.js";
+const ops = core.ops;
+
+let webgpu;
+
+function webGPUNonEnumerable(getter) {
+  let valueIsSet = false;
+  let value;
+
+  return {
+    get() {
+      loadWebGPU();
+
+      if (valueIsSet) {
+        return value;
+      } else {
+        return getter();
+      }
+    },
+    set(v) {
+      loadWebGPU();
+
+      valueIsSet = true;
+      value = v;
+    },
+    enumerable: false,
+    configurable: true,
+  };
+}
+
+function loadWebGPU() {
+  if (!webgpu) {
+    webgpu = ops.op_lazy_load_esm("ext:deno_webgpu/01_webgpu.js");
+  }
+}
+
+export { loadWebGPU, webgpu, webGPUNonEnumerable };
diff --git a/ext/webgpu/01_webgpu.js b/ext/webgpu/01_webgpu.js
index c8258621f8..0dd660d428 100644
--- a/ext/webgpu/01_webgpu.js
+++ b/ext/webgpu/01_webgpu.js
@@ -7036,6 +7036,78 @@ webidl.converters["GPUSignedOffset32"] = (V, opts) =>
 // TYPEDEF: GPUFlagsConstant
 webidl.converters["GPUFlagsConstant"] = webidl.converters["unsigned long"];
 
+// ENUM: GPUCanvasAlphaMode
+webidl.converters["GPUCanvasAlphaMode"] = webidl.createEnumConverter(
+  "GPUCanvasAlphaMode",
+  [
+    "opaque",
+    "premultiplied",
+  ],
+);
+
+// NON-SPEC: ENUM: GPUPresentMode
+webidl.converters["GPUPresentMode"] = webidl.createEnumConverter(
+  "GPUPresentMode",
+  [
+    "autoVsync",
+    "autoNoVsync",
+    "fifo",
+    "fifoRelaxed",
+    "immediate",
+    "mailbox",
+  ],
+);
+
+// DICT: GPUCanvasConfiguration
+const dictMembersGPUCanvasConfiguration = [
+  { key: "device", converter: webidl.converters.GPUDevice, required: true },
+  {
+    key: "format",
+    converter: webidl.converters.GPUTextureFormat,
+    required: true,
+  },
+  {
+    key: "usage",
+    converter: webidl.converters["GPUTextureUsageFlags"],
+    defaultValue: GPUTextureUsage.RENDER_ATTACHMENT,
+  },
+  {
+    key: "alphaMode",
+    converter: webidl.converters["GPUCanvasAlphaMode"],
+    defaultValue: "opaque",
+  },
+
+  // Extended from spec
+  {
+    key: "presentMode",
+    converter: webidl.converters["GPUPresentMode"],
+  },
+  {
+    key: "width",
+    converter: webidl.converters["long"],
+    required: true,
+  },
+  {
+    key: "height",
+    converter: webidl.converters["long"],
+    required: true,
+  },
+  {
+    key: "viewFormats",
+    converter: webidl.createSequenceConverter(
+      webidl.converters["GPUTextureFormat"],
+    ),
+    get defaultValue() {
+      return [];
+    },
+  },
+];
+webidl.converters["GPUCanvasConfiguration"] = webidl
+  .createDictionaryConverter(
+    "GPUCanvasConfiguration",
+    dictMembersGPUCanvasConfiguration,
+  );
+
 const gpu = webidl.createBranded(GPU);
 export {
   _device,
diff --git a/ext/webgpu/02_surface.js b/ext/webgpu/02_surface.js
index fb767e52a4..9ae7fb54d6 100644
--- a/ext/webgpu/02_surface.js
+++ b/ext/webgpu/02_surface.js
@@ -11,22 +11,16 @@ const ops = core.ops;
 import * as webidl from "ext:deno_webidl/00_webidl.js";
 import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
 const { Symbol, SymbolFor, ObjectPrototypeIsPrototypeOf } = primordials;
-import {
-  _device,
-  assertDevice,
-  createGPUTexture,
-  GPUTextureUsage,
-} from "ext:deno_webgpu/01_webgpu.js";
+import { loadWebGPU, webgpu } from "ext:deno_webgpu/00_init.js";
 
 const _surfaceRid = Symbol("[[surfaceRid]]");
 const _configuration = Symbol("[[configuration]]");
 const _canvas = Symbol("[[canvas]]");
 const _currentTexture = Symbol("[[currentTexture]]");
+const _present = Symbol("[[present]]");
 class GPUCanvasContext {
   /** @type {number} */
   [_surfaceRid];
-  /** @type {InnerGPUDevice} */
-  [_device];
   [_configuration];
   [_canvas];
   /** @type {GPUTexture | undefined} */
@@ -50,6 +44,7 @@ class GPUCanvasContext {
       context: "Argument 1",
     });
 
+    const { _device, assertDevice } = webgpu;
     this[_device] = configuration.device[_device];
     this[_configuration] = configuration;
     const device = assertDevice(this, {
@@ -72,6 +67,8 @@ class GPUCanvasContext {
   }
 
   unconfigure() {
+    const { _device } = webgpu;
+
     webidl.assertBranded(this, GPUCanvasContextPrototype);
 
     this[_configuration] = null;
@@ -86,6 +83,7 @@ class GPUCanvasContext {
     if (this[_configuration] === null) {
       throw new DOMException("context is not configured.", "InvalidStateError");
     }
+    const { createGPUTexture, assertDevice } = webgpu;
 
     const device = assertDevice(this, { prefix, context: "this" });
 
@@ -119,8 +117,10 @@ class GPUCanvasContext {
     return texture;
   }
 
-  // Extended from spec. Required to present the texture; browser don't need this.
-  present() {
+  // Required to present the texture; browser don't need this.
+  [_present]() {
+    const { assertDevice } = webgpu;
+
     webidl.assertBranded(this, GPUCanvasContextPrototype);
     const prefix = "Failed to execute 'present' on 'GPUCanvasContext'";
     const device = assertDevice(this[_currentTexture], {
@@ -148,88 +148,17 @@ class GPUCanvasContext {
 const GPUCanvasContextPrototype = GPUCanvasContext.prototype;
 
 function createCanvasContext(options) {
+  // lazy load webgpu if needed
+  loadWebGPU();
+
   const canvasContext = webidl.createBranded(GPUCanvasContext);
   canvasContext[_surfaceRid] = options.surfaceRid;
   canvasContext[_canvas] = options.canvas;
   return canvasContext;
 }
 
-// Converters
+function presentGPUCanvasContext(ctx) {
+  ctx[_present]();
+}
 
-// ENUM: GPUCanvasAlphaMode
-webidl.converters["GPUCanvasAlphaMode"] = webidl.createEnumConverter(
-  "GPUCanvasAlphaMode",
-  [
-    "opaque",
-    "premultiplied",
-  ],
-);
-
-// NON-SPEC: ENUM: GPUPresentMode
-webidl.converters["GPUPresentMode"] = webidl.createEnumConverter(
-  "GPUPresentMode",
-  [
-    "autoVsync",
-    "autoNoVsync",
-    "fifo",
-    "fifoRelaxed",
-    "immediate",
-    "mailbox",
-  ],
-);
-
-// DICT: GPUCanvasConfiguration
-const dictMembersGPUCanvasConfiguration = [
-  { key: "device", converter: webidl.converters.GPUDevice, required: true },
-  {
-    key: "format",
-    converter: webidl.converters.GPUTextureFormat,
-    required: true,
-  },
-  {
-    key: "usage",
-    converter: webidl.converters["GPUTextureUsageFlags"],
-    defaultValue: GPUTextureUsage.RENDER_ATTACHMENT,
-  },
-  {
-    key: "alphaMode",
-    converter: webidl.converters["GPUCanvasAlphaMode"],
-    defaultValue: "opaque",
-  },
-
-  // Extended from spec
-  {
-    key: "presentMode",
-    converter: webidl.converters["GPUPresentMode"],
-  },
-  {
-    key: "width",
-    converter: webidl.converters["long"],
-    required: true,
-  },
-  {
-    key: "height",
-    converter: webidl.converters["long"],
-    required: true,
-  },
-  {
-    key: "viewFormats",
-    converter: webidl.createSequenceConverter(
-      webidl.converters["GPUTextureFormat"],
-    ),
-    get defaultValue() {
-      return [];
-    },
-  },
-];
-webidl.converters["GPUCanvasConfiguration"] = webidl
-  .createDictionaryConverter(
-    "GPUCanvasConfiguration",
-    dictMembersGPUCanvasConfiguration,
-  );
-
-window.__bootstrap.webgpu = {
-  ...window.__bootstrap.webgpu,
-  GPUCanvasContext,
-  createCanvasContext,
-};
+export { createCanvasContext, GPUCanvasContext, presentGPUCanvasContext };
diff --git a/ext/webgpu/Cargo.toml b/ext/webgpu/Cargo.toml
index accee5ed98..461eddf5e8 100644
--- a/ext/webgpu/Cargo.toml
+++ b/ext/webgpu/Cargo.toml
@@ -13,9 +13,6 @@ description = "WebGPU implementation for Deno"
 [lib]
 path = "lib.rs"
 
-[features]
-surface = ["wgpu-core/raw-window-handle", "dep:raw-window-handle"]
-
 # We make all dependencies conditional on not being wasm,
 # so the whole workspace can built as wasm.
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
diff --git a/ext/webgpu/error.rs b/ext/webgpu/error.rs
index be8021f87e..5d8abb4a3c 100644
--- a/ext/webgpu/error.rs
+++ b/ext/webgpu/error.rs
@@ -23,7 +23,6 @@ use wgpu_core::device::DeviceError;
 use wgpu_core::pipeline::CreateComputePipelineError;
 use wgpu_core::pipeline::CreateRenderPipelineError;
 use wgpu_core::pipeline::CreateShaderModuleError;
-#[cfg(feature = "surface")]
 use wgpu_core::present::ConfigureSurfaceError;
 use wgpu_core::resource::BufferAccessError;
 use wgpu_core::resource::CreateBufferError;
@@ -282,7 +281,6 @@ impl From<ClearError> for WebGpuError {
   }
 }
 
-#[cfg(feature = "surface")]
 impl From<ConfigureSurfaceError> for WebGpuError {
   fn from(err: ConfigureSurfaceError) -> Self {
     WebGpuError::Validation(fmt_err(&err))
diff --git a/ext/webgpu/lib.rs b/ext/webgpu/lib.rs
index 834ac6bcc7..abb36fb8dd 100644
--- a/ext/webgpu/lib.rs
+++ b/ext/webgpu/lib.rs
@@ -75,7 +75,6 @@ pub mod queue;
 pub mod render_pass;
 pub mod sampler;
 pub mod shader;
-#[cfg(feature = "surface")]
 pub mod surface;
 pub mod texture;
 
@@ -212,7 +211,12 @@ deno_core::extension!(
     queue::op_webgpu_write_texture,
     // shader
     shader::op_webgpu_create_shader_module,
+    // surface
+    surface::op_webgpu_surface_configure,
+    surface::op_webgpu_surface_get_current_texture,
+    surface::op_webgpu_surface_present
   ],
+  esm = ["00_init.js", "02_surface.js"],
   lazy_loaded_esm = ["01_webgpu.js"],
 );
 
diff --git a/ext/webgpu/surface.rs b/ext/webgpu/surface.rs
index 4f243fbb1d..1371c1fa48 100644
--- a/ext/webgpu/surface.rs
+++ b/ext/webgpu/surface.rs
@@ -11,17 +11,6 @@ use std::borrow::Cow;
 use std::rc::Rc;
 use wgpu_types::SurfaceStatus;
 
-deno_core::extension!(
-  deno_webgpu_surface,
-  deps = [deno_webidl, deno_web, deno_webgpu],
-  ops = [
-    op_webgpu_surface_configure,
-    op_webgpu_surface_get_current_texture,
-    op_webgpu_surface_present,
-  ],
-  esm = ["02_surface.js"],
-);
-
 pub struct WebGpuSurface(pub crate::Instance, pub wgpu_core::id::SurfaceId);
 impl Resource for WebGpuSurface {
   fn name(&self) -> Cow<str> {
diff --git a/runtime/js/98_global_scope.js b/runtime/js/98_global_scope.js
index 14b11cbde4..de83195282 100644
--- a/runtime/js/98_global_scope.js
+++ b/runtime/js/98_global_scope.js
@@ -42,6 +42,12 @@ import * as globalInterfaces from "ext:deno_web/04_global_interfaces.js";
 import * as webStorage from "ext:deno_webstorage/01_webstorage.js";
 import * as prompt from "ext:runtime/41_prompt.js";
 import * as imageData from "ext:deno_web/16_image_data.js";
+import {
+  loadWebGPU,
+  webgpu,
+  webGPUNonEnumerable,
+} from "ext:deno_webgpu/00_init.js";
+import * as webgpuSurface from "ext:deno_webgpu/02_surface.js";
 import { unstableIds } from "ext:runtime/90_deno_ns.js";
 
 // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
@@ -189,6 +195,7 @@ unstableForWindowOrWorkerGlobalScope[unstableIds.webgpu] = {
   GPUError: webGPUNonEnumerable(() => webgpu.GPUError),
   GPUValidationError: webGPUNonEnumerable(() => webgpu.GPUValidationError),
   GPUOutOfMemoryError: webGPUNonEnumerable(() => webgpu.GPUOutOfMemoryError),
+  GPUCanvasContext: webGPUNonEnumerable(() => webgpuSurface.GPUCanvasContext),
 };
 
 class Navigator {
@@ -229,39 +236,6 @@ const numCpus = memoizeLazy(() => ops.op_bootstrap_numcpus());
 const userAgent = memoizeLazy(() => ops.op_bootstrap_user_agent());
 const language = memoizeLazy(() => ops.op_bootstrap_language());
 
-let webgpu;
-
-function webGPUNonEnumerable(getter) {
-  let valueIsSet = false;
-  let value;
-
-  return {
-    get() {
-      loadWebGPU();
-
-      if (valueIsSet) {
-        return value;
-      } else {
-        return getter();
-      }
-    },
-    set(v) {
-      loadWebGPU();
-
-      valueIsSet = true;
-      value = v;
-    },
-    enumerable: false,
-    configurable: true,
-  };
-}
-
-function loadWebGPU() {
-  if (!webgpu) {
-    webgpu = ops.op_lazy_load_esm("ext:deno_webgpu/01_webgpu.js");
-  }
-}
-
 ObjectDefineProperties(Navigator.prototype, {
   gpu: {
     configurable: true,