AsyncWasmLoadingRuntimeModule.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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} GenerateBeforeInstantiateStreaming */
  16. /**
  17. * @typedef {object} AsyncWasmLoadingRuntimeModuleOptions
  18. * @property {GenerateLoadBinaryCode} generateLoadBinaryCode
  19. * @property {GenerateBeforeLoadBinaryCode=} generateBeforeLoadBinaryCode
  20. * @property {GenerateBeforeInstantiateStreaming=} generateBeforeInstantiateStreaming
  21. * @property {boolean} supportsStreaming
  22. * @property {boolean=} fullHashDigest the binary's name inlines a re-encoded compilation hash
  23. */
  24. class AsyncWasmLoadingRuntimeModule extends RuntimeModule {
  25. /**
  26. * @param {AsyncWasmLoadingRuntimeModuleOptions} options options
  27. */
  28. constructor({
  29. generateLoadBinaryCode,
  30. generateBeforeLoadBinaryCode,
  31. generateBeforeInstantiateStreaming,
  32. supportsStreaming,
  33. fullHashDigest
  34. }) {
  35. super("wasm loading", 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 {generateBeforeInstantiateStreaming | undefined} */
  47. this.generateBeforeInstantiateStreaming =
  48. generateBeforeInstantiateStreaming;
  49. /** @type {boolean} */
  50. this.supportsStreaming = supportsStreaming;
  51. }
  52. /**
  53. * Generates runtime code for this runtime module.
  54. * @returns {string | null} runtime code
  55. */
  56. generate() {
  57. const compilation = /** @type {Compilation} */ (this.compilation);
  58. const chunk = /** @type {Chunk} */ (this.chunk);
  59. const { outputOptions, runtimeTemplate } = compilation;
  60. // Opt-in fallback to non-streaming when the server serves wasm with a wrong MIME type.
  61. const streamingFallback = outputOptions.wasmStreamingFallback;
  62. const fn = RuntimeGlobals.instantiateWasm;
  63. // Analyzable output bakes the binary's URL into the call site instead.
  64. const analyzable = runtimeTemplate.supportsAnalyzable(
  65. "wasm",
  66. undefined,
  67. undefined,
  68. chunk.runtime
  69. );
  70. const wasmModuleSrcPath = analyzable
  71. ? "wasmModuleUrl"
  72. : compilation.getPath(
  73. JSON.stringify(outputOptions.webassemblyModuleFilename),
  74. {
  75. ...fullHashPathData(compilation),
  76. module: {
  77. id: '" + wasmModuleId + "',
  78. hash: '" + wasmModuleHash + "',
  79. hashWithLength(length) {
  80. return `" + wasmModuleHash.slice(0, ${length}) + "`;
  81. }
  82. },
  83. runtime: chunk.runtime
  84. }
  85. );
  86. const loader = this.generateLoadBinaryCode(
  87. wasmModuleSrcPath,
  88. chunk.runtime
  89. );
  90. const fallback = [
  91. `.then(${runtimeTemplate.returningFunction("x.arrayBuffer()", "x")})`,
  92. `.then(${runtimeTemplate.returningFunction(
  93. "WebAssembly.instantiate(bytes, importsObj)",
  94. "bytes"
  95. )})`,
  96. `.then(${runtimeTemplate.returningFunction(
  97. "Object.assign(exports, res.instance.exports)",
  98. "res"
  99. )})`
  100. ];
  101. const getStreaming = () => {
  102. /**
  103. * @param {string[]} text text
  104. * @returns {string} merged text
  105. */
  106. const concat = (...text) => text.join("");
  107. return [
  108. this.generateBeforeLoadBinaryCode
  109. ? this.generateBeforeLoadBinaryCode(wasmModuleSrcPath)
  110. : "",
  111. `var req = ${loader};`,
  112. `var fallback = ${runtimeTemplate.returningFunction(
  113. Template.asString(["req", Template.indent(fallback)])
  114. )};`,
  115. concat(
  116. "return req.then(",
  117. runtimeTemplate.basicFunction("res", [
  118. 'if (typeof WebAssembly.instantiateStreaming === "function") {',
  119. Template.indent(
  120. this.generateBeforeInstantiateStreaming
  121. ? this.generateBeforeInstantiateStreaming()
  122. : ""
  123. ),
  124. Template.indent(
  125. streamingFallback
  126. ? [
  127. "return WebAssembly.instantiateStreaming(res, importsObj)",
  128. Template.indent([
  129. ".then(",
  130. Template.indent([
  131. `${runtimeTemplate.returningFunction(
  132. "Object.assign(exports, res.instance.exports)",
  133. "res"
  134. )},`,
  135. runtimeTemplate.basicFunction("e", [
  136. 'if(res.headers.get("Content-Type") !== "application/wasm") {',
  137. Template.indent([
  138. 'console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n", e);',
  139. "return fallback();"
  140. ]),
  141. "}",
  142. "throw e;"
  143. ])
  144. ]),
  145. ");"
  146. ])
  147. ]
  148. : [
  149. `return WebAssembly.instantiateStreaming(res, importsObj).then(${runtimeTemplate.returningFunction(
  150. "Object.assign(exports, res.instance.exports)",
  151. "res"
  152. )});`
  153. ]
  154. ),
  155. "}",
  156. "return fallback();"
  157. ]),
  158. ");"
  159. )
  160. ];
  161. };
  162. return `${fn} = ${runtimeTemplate.basicFunction(
  163. analyzable
  164. ? "exports, wasmModuleUrl, importsObj"
  165. : "exports, wasmModuleId, wasmModuleHash, importsObj",
  166. this.supportsStreaming
  167. ? getStreaming()
  168. : [
  169. this.generateBeforeLoadBinaryCode
  170. ? this.generateBeforeLoadBinaryCode(wasmModuleSrcPath)
  171. : "",
  172. `return ${loader}`,
  173. `${Template.indent(fallback)};`
  174. ]
  175. )};`;
  176. }
  177. }
  178. module.exports = AsyncWasmLoadingRuntimeModule;