AsyncWasmCompileRuntimeModule.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const RuntimeGlobals = require("../RuntimeGlobals");
  7. const RuntimeModule = require("../RuntimeModule");
  8. const Template = require("../Template");
  9. const { fullHashPathData } = require("../wasm/wasmModuleFilename");
  10. /** @import Chunk from "../Chunk" */
  11. /** @import Compilation from "../Compilation" */
  12. /** @import { RuntimeSpec } from "../util/runtime" */
  13. /** @typedef {(wasmModuleSrcPath: string) => string} GenerateBeforeLoadBinaryCode */
  14. /** @typedef {(wasmModuleSrcPath: string, runtime: RuntimeSpec) => string} GenerateLoadBinaryCode */
  15. /** @typedef {() => string} GenerateBeforeCompileStreaming */
  16. /**
  17. * @typedef {object} AsyncWasmCompileRuntimeModuleOptions
  18. * @property {GenerateLoadBinaryCode} generateLoadBinaryCode
  19. * @property {GenerateBeforeLoadBinaryCode=} generateBeforeLoadBinaryCode
  20. * @property {GenerateBeforeCompileStreaming=} generateBeforeCompileStreaming
  21. * @property {boolean} supportsStreaming
  22. * @property {boolean=} fullHashDigest the binary's name inlines a re-encoded compilation hash
  23. */
  24. class AsyncWasmCompileRuntimeModule extends RuntimeModule {
  25. /**
  26. * @param {AsyncWasmCompileRuntimeModuleOptions} options options
  27. */
  28. constructor({
  29. generateLoadBinaryCode,
  30. generateBeforeLoadBinaryCode,
  31. generateBeforeCompileStreaming,
  32. supportsStreaming,
  33. fullHashDigest
  34. }) {
  35. super("wasm compile", RuntimeModule.STAGE_NORMAL);
  36. // A re-encoded digest is inlined from the settled hash, so this has to render
  37. // again once there is one.
  38. if (fullHashDigest) {
  39. /** @type {boolean} */
  40. this.fullHash = true;
  41. }
  42. /** @type {GenerateLoadBinaryCode} */
  43. this.generateLoadBinaryCode = generateLoadBinaryCode;
  44. /** @type {GenerateBeforeLoadBinaryCode | undefined} */
  45. this.generateBeforeLoadBinaryCode = generateBeforeLoadBinaryCode;
  46. /** @type {GenerateBeforeCompileStreaming | undefined} */
  47. this.generateBeforeCompileStreaming = generateBeforeCompileStreaming;
  48. /** @type {boolean} */
  49. this.supportsStreaming = supportsStreaming;
  50. }
  51. /**
  52. * Generates runtime code for this runtime module.
  53. * @returns {string | null} runtime code
  54. */
  55. generate() {
  56. const compilation = /** @type {Compilation} */ (this.compilation);
  57. const chunk = /** @type {Chunk} */ (this.chunk);
  58. const { outputOptions, runtimeTemplate } = compilation;
  59. // Opt-out fallback to non-streaming when the server serves wasm with a wrong MIME type.
  60. const streamingFallback = outputOptions.wasmStreamingFallback;
  61. const fn = RuntimeGlobals.compileWasm;
  62. // Analyzable output bakes the binary's URL into the call site instead.
  63. const analyzable = runtimeTemplate.supportsAnalyzable(
  64. "wasm",
  65. undefined,
  66. undefined,
  67. chunk.runtime
  68. );
  69. const wasmModuleSrcPath = analyzable
  70. ? "wasmModuleUrl"
  71. : compilation.getPath(
  72. JSON.stringify(outputOptions.webassemblyModuleFilename),
  73. {
  74. ...fullHashPathData(compilation),
  75. module: {
  76. id: '" + wasmModuleId + "',
  77. hash: '" + wasmModuleHash + "',
  78. hashWithLength(length) {
  79. return `" + wasmModuleHash.slice(0, ${length}) + "`;
  80. }
  81. },
  82. runtime: chunk.runtime
  83. }
  84. );
  85. const loader = this.generateLoadBinaryCode(
  86. wasmModuleSrcPath,
  87. chunk.runtime
  88. );
  89. // Fallback path: fetch -> arrayBuffer -> WebAssembly.compile
  90. const fallback = [
  91. `.then(${runtimeTemplate.returningFunction("x.arrayBuffer()", "x")})`,
  92. `.then(${runtimeTemplate.returningFunction(
  93. "WebAssembly.compile(bytes)",
  94. "bytes"
  95. )})`
  96. ];
  97. const getStreaming = () => {
  98. /**
  99. * @param {string[]} text text
  100. * @returns {string} merged text
  101. */
  102. const concat = (...text) => text.join("");
  103. return [
  104. this.generateBeforeLoadBinaryCode
  105. ? this.generateBeforeLoadBinaryCode(wasmModuleSrcPath)
  106. : "",
  107. `var req = ${loader};`,
  108. `var fallback = ${runtimeTemplate.returningFunction(
  109. Template.asString(["req", Template.indent(fallback)])
  110. )};`,
  111. concat(
  112. "return req.then(",
  113. runtimeTemplate.basicFunction("res", [
  114. 'if (typeof WebAssembly.compileStreaming === "function") {',
  115. Template.indent(
  116. this.generateBeforeCompileStreaming
  117. ? this.generateBeforeCompileStreaming()
  118. : ""
  119. ),
  120. Template.indent(
  121. streamingFallback
  122. ? [
  123. "return WebAssembly.compileStreaming(res)",
  124. Template.indent([
  125. ".catch(",
  126. Template.indent([
  127. runtimeTemplate.basicFunction("e", [
  128. 'if(res.headers.get("Content-Type") !== "application/wasm") {',
  129. Template.indent([
  130. 'console.warn("`WebAssembly.compileStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.compile` which is slower. Original error:\\n", e);',
  131. "return fallback();"
  132. ]),
  133. "}",
  134. "throw e;"
  135. ])
  136. ]),
  137. ");"
  138. ])
  139. ]
  140. : ["return WebAssembly.compileStreaming(res);"]
  141. ),
  142. "}",
  143. "return fallback();"
  144. ]),
  145. ");"
  146. )
  147. ];
  148. };
  149. return `${fn} = ${runtimeTemplate.basicFunction(
  150. analyzable ? "wasmModuleUrl" : "wasmModuleId, wasmModuleHash",
  151. this.supportsStreaming
  152. ? getStreaming()
  153. : [
  154. this.generateBeforeLoadBinaryCode
  155. ? this.generateBeforeLoadBinaryCode(wasmModuleSrcPath)
  156. : "",
  157. `return ${loader}`,
  158. `${Template.indent(fallback)};`
  159. ]
  160. )};`;
  161. }
  162. }
  163. module.exports = AsyncWasmCompileRuntimeModule;