Skip to content

fix: copy raw binary to dist/index.wasm during standalone builds, not base64#7237

Draft
alfonso-noriega wants to merge 1 commit intomainfrom
04-09-fix_copy_raw_binary_to_dist_index.wasm_during_standalone_builds_not_base64
Draft

fix: copy raw binary to dist/index.wasm during standalone builds, not base64#7237
alfonso-noriega wants to merge 1 commit intomainfrom
04-09-fix_copy_raw_binary_to_dist_index.wasm_during_standalone_builds_not_base64

Conversation

@alfonso-noriega
Copy link
Copy Markdown
Contributor

@alfonso-noriega alfonso-noriega commented Apr 9, 2026

WHY are these changes introduced?

Community regression (CLI 3.93.0): after shopify app build, dist/index.wasm contains base64-encoded text instead of raw binary, causing vitest to fail immediately when loading the wasm.


How the bug was introduced — the full chain

There are two independent concerns that outputPath has been confusingly conflating:

Concern Meaning
Local build output path Where the compiler writes the wasm on disk (configured via build.path in TOML, e.g. target/wasm32-wasi/release/my_func.wasm)
Bundle destination path Where the wasm lands inside the deploy artifact (always dist/index.wasm — a hard server-side contract)

1. Commit 4446834 (bad refactor): changed getOutputRelativePath to return build.path ?? dist/index.wasm. This caused extension.outputPath at construction time to point at the compiler output path, which meant the wasm ended up under the wrong key in the deploy bundle — breaking all Function deploys.

2. PR #7085 (fix, Mar 24): restored getOutputRelativePath to always return dist/index.wasm. This fixed deploys. However, it also means extension.outputPath starts as <project>/dist/index.wasm again. Inside buildFunctionExtension:

bundlePath = extension.outputPath          // ← <project>/dist/index.wasm
extension.outputPath = <project>/<build.path>  // mutated to compiler output
// build runs, raw wasm at <project>/<build.path>
if bundlePath !== extension.outputPath && dirname differs:
    bundleFunctionExtension(...)           // ← writes BASE64 to <project>/dist/index.wasm ✗

When called from shopify app build (i.e. ext.build() directly), bundlePath is the project's own dist/index.wasm, so base64 is written straight into the developer's project.

3. PR #7161 (partial fix, Apr 2): added a dirname check to skip bundling when build.path is in the same directory as dist/ (e.g. dist/custom.wasm). This helps only that narrow case. Typical Rust functions have build.path = "target/wasm32-wasi/release/func.wasm" — a completely different directory — so the base64 encoding still runs.

4. Community report (Apr 7): users on CLI 3.93.0 see vitest fail because dist/index.wasm has base64 text, not raw WASM bytes.


Why the same code works correctly for shopify app deploy

When called from buildForBundle (the deploy path), buildForBundle pre-sets extension.outputPath to the temp bundle directory before calling build():

// in buildForBundle:
this.outputPath = this.getOutputPathForDirectory(bundleDirectory, outputId)
//   = <appDir>/.shopify/deploy-bundle/<id>/dist/index.wasm  ← bundle dir, not project
await this.build(options)

So inside buildFunctionExtension, bundlePath = <bundleDir>/dist/index.wasm (outside the project), and the base64 encoding goes into the bundle directory — exactly right for the zip upload. The project's dist/index.wasm is never touched.

The problem is that shopify app build skips buildForBundle and calls ext.build() directly, so bundlePath is the project's own dist/index.wasm.


WHAT is this pull request doing?

Adds a check in buildFunctionExtension to distinguish between the two call sites using a simple, unambiguous condition:

const projectOutputPath = joinPath(extension.directory, extension.outputRelativePath)
// projectOutputPath is always <project>/dist/index.wasm

if (bundlePath === projectOutputPath) {
  // Standalone build — copy raw binary so vitest and local tooling can load the wasm
  await copyFile(extension.outputPath, bundlePath)
} else if (dirname(bundlePath) !== dirname(extension.outputPath)) {
  // Deploy bundle build — base64-encode for the server contract
  await bundleFunctionExtension(extension.outputPath, bundlePath)
}
Scenario bundlePath Behaviour
shopify app build (standalone) <project>/dist/index.wasm Raw binary copied → vitest works ✓
shopify app deploy / buildForBundle <bundleDir>/<id>/dist/index.wasm Base64 encoded → server contract satisfied ✓
build.path = dist/custom.wasm (same dir, standalone) <project>/dist/index.wasm Raw binary copied from dist/custom.wasmdist/index.wasm
build.path = dist/custom.wasm (same dir, bundle) <bundleDir>/<id>/dist/index.wasm Base64 encoded → server contract satisfied ✓

How to test your changes?

  1. Create a Rust function extension with a custom build.path pointing to the Cargo output:
    [build]
    command = "cargo wasi build --release"
    path = "target/wasm32-wasi/release/my_func.wasm"
  2. Run shopify app build — verify dist/index.wasm is raw binary (e.g. starts with \0asm magic bytes), not base64 text.
  3. Run your vitest suite — verify function tests pass.
  4. Run shopify app deploy — verify the function deploys successfully (wasm uploaded under dist/index.wasm).
  5. Run shopify app dev — verify dev draft updates work correctly.

Measuring impact

  • n/a — bug fix for a confirmed community regression; no new behaviour surface

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@alfonso-noriega alfonso-noriega force-pushed the 04-09-fix_copy_raw_binary_to_dist_index.wasm_during_standalone_builds_not_base64 branch from 6f0ccbe to 4a8884f Compare April 9, 2026 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant