Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.py
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now that we're just using the web target, should I simplify the code to account for that, or do I leave the structure as is in case we want to add the nodejs target back?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would simplify.

Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
npm_cmd = "npm.cmd" if platform.system() == "Windows" else "npm"

build_type = "debug" if args.debug else "release"
wasm_targets = ["web", "nodejs"] if not args.web_only else ["web"]
wasm_targets = ["web"] if not args.web_only else ["web"]
run_tests = args.test

root_dir = os.path.dirname(os.path.abspath(__file__))
Expand Down
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 5 additions & 22 deletions source/npm/qsharp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,11 @@ to it when calling the `loadWasmModule` method so it may be located and loaded.

## Node and browser support

wasm-bindgen generates different files for the browser and Node.js environments. The wasm is slightly
different, and the loader code is quite different. This can be seen in `./lib/web/qsc_wasm.cjs`
and `./lib/nodejs/qsc_wasm.js` files respectively. Specifically, the web environment loads the wasm
file using async web APIs such as `fetch` with a URI, and Node.js uses `require` to load the `fs` module
and calls to `readFileSync`. Once the wasm module is loaded however, the exported APIs are used
in a similar manner.

To support using this npm package from both environments, the package uses "conditional exports"
<https://nodejs.org/dist/latest-v18.x/docs/api/packages.html#conditional-exports> to expose one
entry point for Node.js, and another for browsers. The distinct entry points uses their respective
loader to load the wasm module for the platform, and then expose functionality that uses the
loaded module via common code.

When bundling for the web, bundlers such as esbuild will automatically use the default entry point,
whereas when loaded as a Node.js module, it will use the "node" entry point.

Note that TypeScript seems to add the ['import', 'types', 'node'] conditions by default when
searching the Node.js `exports`, and so will always find the 'node' export before the 'default'
export. To resolve this, a 'browser' condition was added (which is same as 'default' but earlier
than 'node') and the tsconfig compiler option `"customConditions": ["browser"]` should be added
(requires TypeScript 5.0 or later). esbuild also adds the 'browser' condition when bundling for
the browser (see <https://esbuild.github.io/api/#how-conditions-work>).
This package is platform-agnostic, using a single entry point (`main.ts`) for both browser
and Node.js environments. The wasm module and JavaScript glue code can be found in `./lib/web/`.
Consumers must bundle the package for their target platform so that external
dependencies (such as the `web-worker` package) are resolved correctly for the
runtime environment.

## Design

Expand Down
9 changes: 7 additions & 2 deletions source/npm/qsharp/generate_docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

// @ts-check

import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

import { generate_docs } from "./lib/nodejs/qsc_wasm.cjs";
import initWasm, { generate_docs } from "./lib/web/qsc_wasm.js";

const scriptDirPath = dirname(fileURLToPath(import.meta.url));
const docsDirPath = join(scriptDirPath, "docs");
Expand All @@ -16,6 +16,11 @@ if (!existsSync(docsDirPath)) {
mkdirSync(docsDirPath);
}

// Initialize wasm before calling any exported functions
const wasmPath = join(scriptDirPath, "lib", "web", "qsc_wasm_bg.wasm");
const wasmBytes = readFileSync(wasmPath);
await initWasm({ module_or_path: wasmBytes });

// 'filename' will be of the format 'namespace/api.md' (except for 'toc.yaml')
// 'metadata' will be the metadata that will appear at the top of the file
// 'contents' will contain the non-metadata markdown expected
Expand Down
17 changes: 8 additions & 9 deletions source/npm/qsharp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,10 @@
"directory": "npm"
},
"exports": {
".": {
"browser": "./dist/browser.js",
"node": "./dist/main.js",
"default": "./dist/browser.js"
},
"./compiler-worker": "./dist/compiler/worker-browser.js",
"./language-service-worker": "./dist/language-service/worker-browser.js",
"./debug-service-worker": "./dist/debug-service/worker-browser.js",
".": "./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",
"./katas": "./dist/katas.js",
"./katas-md": "./dist/katas-md.js",
"./state-viz": "./ux/circuit-vis/state-viz/worker/index.ts",
Expand All @@ -44,5 +40,8 @@
"docs",
"lib",
"ux"
]
],
"dependencies": {
"web-worker": "^1.5.0"
}
}
220 changes: 0 additions & 220 deletions source/npm/qsharp/src/browser.ts

This file was deleted.

29 changes: 29 additions & 0 deletions source/npm/qsharp/src/common-exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export type { IVariable, IVariableChild } from "../lib/web/qsc_wasm.js";
export * as utils from "./utils.js";
export { log } from "./log.js";
export { QscEventTarget } from "./compiler/events.js";
export { QdkDiagnostics } from "./diagnostics.js";
export { default as samples } from "./samples.generated.js";
export { default as openqasm_samples } from "./openqasm-samples.generated.js";
export {
qsharpGithubUriScheme,
qsharpLibraryUriScheme,
} from "./language-service/language-service.js";
export type { Dump, ShotResult } from "./compiler/common.js";
export type { CompilerState, ProgramConfig } from "./compiler/compiler.js";
export type { ICompiler, ICompilerWorker } from "./compiler/compiler.js";
export type {
IDebugService,
IDebugServiceWorker,
} from "./debug-service/debug-service.js";
export type {
ILanguageService,
ILanguageServiceWorker,
LanguageServiceDiagnosticEvent,
LanguageServiceEvent,
LanguageServiceTestCallablesEvent,
} from "./language-service/language-service.js";
export type { ProjectLoader } from "./project.js";
export type { CircuitGroup as CircuitData } from "./data-structures/circuit.js";
export type { LogLevel } from "./log.js";
export { StepResultId } from "../lib/web/qsc_wasm.js";
2 changes: 0 additions & 2 deletions source/npm/qsharp/src/compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ import {
} from "./events.js";
import { callAndTransformExceptions } from "../diagnostics.js";

// The wasm types generated for the node.js bundle are just the exported APIs,
// so use those as the set used by the shared compiler
type Wasm = typeof import("../../lib/web/qsc_wasm.js");

// These need to be async/promise results for when communicating across a WebWorker, however
Expand Down
7 changes: 0 additions & 7 deletions source/npm/qsharp/src/compiler/worker-node.ts

This file was deleted.

Loading
Loading