Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7fbc222
save initial progress
joao-boechat Mar 24, 2026
2050f1a
unify common exports in single file
joao-boechat Mar 30, 2026
adf2adf
first complete working version
joao-boechat Apr 3, 2026
c482e3e
remove redundant code from main.ts entrypoint
joao-boechat Apr 3, 2026
600f6ab
fix deprecated parameters
joao-boechat Apr 3, 2026
a074ed3
fix qsharp-lang tests
joao-boechat Apr 3, 2026
1db22da
make generate_docs depend on web version of wasm
joao-boechat Apr 8, 2026
65adfc7
use web-worker npm package to make worker code agnostic of platform (…
joao-boechat Apr 8, 2026
642a244
update vscode extension to match new worker api
joao-boechat Apr 8, 2026
59663b7
update playground to match new worker api
joao-boechat Apr 8, 2026
d1d3082
build node-workers separately, keep web-worker external for the node …
joao-boechat Apr 8, 2026
cb2566d
fix qsharp-lang tests
joao-boechat Apr 8, 2026
bf2bcf8
log platform, UI, and remoteName during extension activation
joao-boechat Apr 8, 2026
dbff9f7
centralize platform env
joao-boechat Apr 8, 2026
d4aa75a
export messageHandler for backwards compatibility
joao-boechat Apr 8, 2026
1de5b89
update comments and README for qsharp-lang package
joao-boechat Apr 8, 2026
77a4efd
wasm-bindgen only builds to web
joao-boechat Apr 9, 2026
c1053fe
fix minor typo in variable name
joao-boechat Apr 10, 2026
a91aa50
add strict type to __PLATFORM__ variable
joao-boechat Apr 10, 2026
699ecae
remove common-exports.ts
joao-boechat Apr 10, 2026
36e6360
make logs less noisy
joao-boechat Apr 10, 2026
c65585c
make workers of type module by default
joao-boechat Apr 13, 2026
09510d1
remove browser dependency on web-worker
joao-boechat Apr 13, 2026
e25b89a
simplify build code
joao-boechat Apr 13, 2026
40b20a2
make web-worker optional dependency for qsharp
joao-boechat Apr 13, 2026
167c673
simplify copying web-worker for building package
joao-boechat Apr 13, 2026
526f9a5
fix watch mode
joao-boechat Apr 13, 2026
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
111 changes: 47 additions & 64 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,6 @@
help="Build and run the integration tests (default is --no-integration-tests)",
)

parser.add_argument(
"--web-only",
action=argparse.BooleanOptionalAction,
default=False,
help="Build only the web version of the wasm package",
)

parser.add_argument(
"--ci-bench",
action=argparse.BooleanOptionalAction,
Expand Down Expand Up @@ -135,7 +128,6 @@
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"]
run_tests = args.test

root_dir = os.path.dirname(os.path.abspath(__file__))
Expand Down Expand Up @@ -576,66 +568,57 @@ def run_ci_historic_benchmark():
subprocess.run(cargo_args, check=True, text=True, cwd=wasm_src)

# Next, create the JavaScript glue code using wasm-bindgen with something like:
# wasm-bindgen --target <nodejs|web> [--debug] --out-dir ./target/wasm32/{release,debug}/{nodejs|web} <infile>
# wasm-bindgen --target web [--debug] --out-dir ./target/wasm32/{release,debug}/web <infile>
#
# The output will be written to {out-dir}/qsc_wasm_bg.wasm
for target_platform in wasm_targets:
out_dir = os.path.join(wasm_bld, target_platform)
in_file = os.path.join(
root_dir, "target", "wasm32-unknown-unknown", build_type, "qsc_wasm.wasm"
)
out_dir = os.path.join(wasm_bld, "web")
in_file = os.path.join(
root_dir, "target", "wasm32-unknown-unknown", build_type, "qsc_wasm.wasm"
)

wasm_bindgen_args = [
"wasm-bindgen",
"--target",
"web",
"--out-dir",
out_dir,
]
if build_type == "debug":
wasm_bindgen_args.append("--debug")
wasm_bindgen_args.append(in_file)

subprocess.run(wasm_bindgen_args, check=True, text=True, cwd=wasm_src)

wasm_bindgen_args = [
"wasm-bindgen",
"--target",
target_platform,
"--out-dir",
out_dir,
# Also run wasm-opt to optimize the wasm file for release builds with:
# wasm-opt -Oz --enable-{<as needed>} --output <outfile> <infile>
#
# -Oz does extra size optimizations, and features are enabled to match Rust defaults
# to avoid mismatch errors, as wasm-opt currently disables some of these by default.
# See <https://doc.rust-lang.org/rustc/platform-support/wasm32-unknown-unknown.html#enabled-webassembly-features>
#
# This updates the wasm file in place.
#
# Note: wasm-opt is not needed for debug builds, so we only run it for release builds.
if build_type == "release":
wasm_file = os.path.join(out_dir, "qsc_wasm_bg.wasm")
wasm_opt_args = [
"wasm-opt",
"-Oz",
"--enable-bulk-memory",
"--enable-nontrapping-float-to-int",
"--output",
wasm_file,
wasm_file,
]
if build_type == "debug":
wasm_bindgen_args.append("--debug")
wasm_bindgen_args.append(in_file)

subprocess.run(wasm_bindgen_args, check=True, text=True, cwd=wasm_src)

# Also run wasm-opt to optimize the wasm file for release builds with:
# wasm-opt -Oz --enable-{<as needed>} --output <outfile> <infile>
#
# -Oz does extra size optimizations, and features are enabled to match Rust defaults
# to avoid mismatch errors, as wasm-opt currently disables some of these by default.
# See <https://doc.rust-lang.org/rustc/platform-support/wasm32-unknown-unknown.html#enabled-webassembly-features>
#
# This updates the wasm file in place.
#
# Note: wasm-opt is not needed for debug builds, so we only run it for release builds.
if build_type == "release":
wasm_file = os.path.join(out_dir, "qsc_wasm_bg.wasm")
wasm_opt_args = [
"wasm-opt",
"-Oz",
"--enable-bulk-memory",
"--enable-nontrapping-float-to-int",
"--output",
wasm_file,
wasm_file,
]
subprocess.run(wasm_opt_args, check=True, text=True, cwd=wasm_src)

# After building, copy the artifacts to the npm location
lib_dir = os.path.join(npm_src, "lib", target_platform)
os.makedirs(lib_dir, exist_ok=True)

for filename in ["qsc_wasm_bg.wasm", "qsc_wasm.d.ts", "qsc_wasm.js"]:
fullpath = os.path.join(out_dir, filename)

# To make the node files CommonJS modules, the extension needs to change
# (This is because the package is set to ECMAScript modules by default)
if target_platform == "nodejs" and filename == "qsc_wasm.js":
filename = "qsc_wasm.cjs"
if target_platform == "nodejs" and filename == "qsc_wasm.d.ts":
filename = "qsc_wasm.d.cts"

shutil.copy2(fullpath, os.path.join(lib_dir, filename))
subprocess.run(wasm_opt_args, check=True, text=True, cwd=wasm_src)

# After building, copy the artifacts to the npm location
lib_dir = os.path.join(npm_src, "lib", "web")
os.makedirs(lib_dir, exist_ok=True)

for filename in ["qsc_wasm_bg.wasm", "qsc_wasm.d.ts", "qsc_wasm.js"]:
fullpath = os.path.join(out_dir, filename)
shutil.copy2(fullpath, os.path.join(lib_dir, filename))

step_end()

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"
}
}
Loading
Loading