diff --git a/source/npm/qsharp/package.json b/source/npm/qsharp/package.json index 494d6dc003..316e28d749 100644 --- a/source/npm/qsharp/package.json +++ b/source/npm/qsharp/package.json @@ -12,7 +12,11 @@ "directory": "npm" }, "exports": { - ".": "./dist/main.js", + ".": { + "browser": "./dist/main.js", + "node": "./dist/node.js", + "default": "./dist/main.js" + }, "./compiler-worker": "./dist/compiler/worker.js", "./language-service-worker": "./dist/language-service/worker.js", "./debug-service-worker": "./dist/debug-service/worker.js", diff --git a/source/npm/qsharp/src/main.ts b/source/npm/qsharp/src/main.ts index a829b72082..eb852efa0c 100644 --- a/source/npm/qsharp/src/main.ts +++ b/source/npm/qsharp/src/main.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// This module is the single entry point for both browser and Node.js environments. +// Main entrypoint. Browsers use this directly; Node.js uses it through the node.ts wrapper. import * as wasm from "../lib/web/qsc_wasm.js"; import initWasm, { @@ -32,6 +32,12 @@ import { log } from "./log.js"; import { ProjectLoader } from "./project.js"; import { createProxy } from "./workers/main.js"; +let workerType: "classic" | "module" = "classic"; + +export function setWorkerType(type: "classic" | "module") { + workerType = type; +} + // Create once. A module is stateless and can be efficiently passed to WebWorkers. let wasmModule: WebAssembly.Module | null = null; let wasmModulePromise: Promise | null = null; @@ -126,7 +132,7 @@ export function getDebugServiceWorker( worker: string | Worker, ): IDebugServiceWorker { if (!wasmModule) throw "Wasm module must be loaded first"; - return createProxy(worker, wasmModule, debugServiceProtocol); + return createProxy(worker, wasmModule, debugServiceProtocol, workerType); } export async function getCompiler(): Promise { @@ -139,7 +145,7 @@ export async function getCompiler(): Promise { // messages, then the worker may be passed in and it will be initialized. export function getCompilerWorker(worker: string | Worker): ICompilerWorker { if (!wasmModule) throw "Wasm module must be loaded first"; - return createProxy(worker, wasmModule, compilerProtocol); + return createProxy(worker, wasmModule, compilerProtocol, workerType); } export async function getLanguageService( @@ -156,7 +162,7 @@ export function getLanguageServiceWorker( worker: string | Worker, ): ILanguageServiceWorker { if (!wasmModule) throw "Wasm module must be loaded first"; - return createProxy(worker, wasmModule, languageServiceProtocol); + return createProxy(worker, wasmModule, languageServiceProtocol, workerType); } /// Extracts the target profile from a Q# source file's entry point. diff --git a/source/npm/qsharp/src/node.ts b/source/npm/qsharp/src/node.ts new file mode 100644 index 0000000000..f7abcae45c --- /dev/null +++ b/source/npm/qsharp/src/node.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Node.js entrypoint. Polyfills the Worker global before loading the main module. + +import worker from "web-worker"; +import { setWorkerType } from "./main.js"; + +if (typeof globalThis.Worker === "undefined") { + globalThis.Worker = worker; +} +setWorkerType("module"); + +export * from "./main.js"; diff --git a/source/npm/qsharp/src/workers/main.ts b/source/npm/qsharp/src/workers/main.ts index 6f40011ac0..fa65c052c2 100644 --- a/source/npm/qsharp/src/workers/main.ts +++ b/source/npm/qsharp/src/workers/main.ts @@ -19,25 +19,6 @@ import type { ServiceState, } from "./types.js"; -export const isBrowser = typeof Worker !== "undefined"; - -if (!isBrowser) { - // In CJS (esbuild bundle), require is available directly. - // In ESM (e.g. node --test), we use dynamic import. - if (typeof require === "function") { - // eslint-disable-next-line @typescript-eslint/no-require-imports - globalThis.Worker = require("web-worker"); - } else { - // Dynamic import for ESM - this is lazy, Worker will be available - // by the time it's actually needed. - import("web-worker").then((mod) => { - globalThis.Worker = mod.default; - }); - } - log.debug( - "Running in Node.js environment, using web-worker package for Worker support.", - ); -} /** * Creates and initializes a service in a web worker, and returns a proxy for the service * to be used from the main thread. @@ -45,6 +26,7 @@ if (!isBrowser) { * @param workerArg The service web worker or the URL of the web worker script. * @param wasmModule The wasm module to initialize the service with * @param serviceProtocol An object that describes the service: its constructor, methods and events + * @param workerType The type of worker to create: "classic" for browsers, "module" for Node.js * @returns A proxy object that implements the service interface. * This interface can now be used as if calling into the real service, * and the calls will be proxied to the web worker. @@ -56,11 +38,10 @@ export function createProxy< workerArg: string | Worker, wasmModule: WebAssembly.Module, serviceProtocol: ServiceProtocol, + workerType: "classic" | "module", ): TService & IServiceProxy { // Create or use the WebWorker - const useModuleWorker: WorkerOptions = isBrowser - ? { type: "classic" } - : { type: "module" }; + const useModuleWorker: WorkerOptions = { type: workerType }; const worker = typeof workerArg === "string" diff --git a/source/npm/qsharp/test/basics.js b/source/npm/qsharp/test/basics.js index 0f29c54156..a779f3cb1a 100644 --- a/source/npm/qsharp/test/basics.js +++ b/source/npm/qsharp/test/basics.js @@ -14,7 +14,7 @@ import { getDebugServiceWorker, loadWasmModule, utils, -} from "../dist/main.js"; +} from "../dist/node.js"; import { QscEventTarget } from "../dist/compiler/events.js"; import { getAllKatas, getExerciseSources, getKata } from "../dist/katas.js"; diff --git a/source/npm/qsharp/test/circuits.js b/source/npm/qsharp/test/circuits.js index 737a2e66b5..290ed802e8 100644 --- a/source/npm/qsharp/test/circuits.js +++ b/source/npm/qsharp/test/circuits.js @@ -15,7 +15,7 @@ import { afterEach, beforeEach, test } from "node:test"; import { fileURLToPath } from "node:url"; import prettier from "prettier"; import { log } from "../dist/log.js"; -import { getCompiler, loadWasmModule } from "../dist/main.js"; +import { getCompiler, loadWasmModule } from "../dist/node.js"; import { draw } from "../dist/ux/circuit-vis/index.js"; // Load the wasm module before running any tests diff --git a/source/npm/qsharp/test/diagnostics.js b/source/npm/qsharp/test/diagnostics.js index 1037234d01..f31c4db47a 100644 --- a/source/npm/qsharp/test/diagnostics.js +++ b/source/npm/qsharp/test/diagnostics.js @@ -13,7 +13,7 @@ import { getCompilerWorker, getProjectLoader, loadWasmModule, -} from "../dist/main.js"; +} from "../dist/node.js"; const distDir = new URL("../dist/", import.meta.url); const compilerWorkerPath = new URL("compiler/worker.js", distDir).href; diff --git a/source/npm/qsharp/test/languageService.js b/source/npm/qsharp/test/languageService.js index 60e0988d29..a5dfbecf26 100644 --- a/source/npm/qsharp/test/languageService.js +++ b/source/npm/qsharp/test/languageService.js @@ -7,7 +7,7 @@ import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { test } from "node:test"; import { log } from "../dist/log.js"; -import { getLanguageService, loadWasmModule } from "../dist/main.js"; +import { getLanguageService, loadWasmModule } from "../dist/node.js"; // Load the wasm module before running any tests const wasmPath = new URL("../lib/web/qsc_wasm_bg.wasm", import.meta.url);