AsyncWasmLoadingRuntimeModule.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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. /** @typedef {import("../Chunk")} Chunk */
  10. /** @typedef {import("../Compilation")} Compilation */
  11. /** @typedef {(wasmModuleSrcPath: string) => string} GenerateBeforeLoadBinaryCode */
  12. /** @typedef {(wasmModuleSrcPath: string) => string} GenerateLoadBinaryCode */
  13. /** @typedef {() => string} GenerateBeforeInstantiateStreaming */
  14. /**
  15. * @typedef {object} AsyncWasmLoadingRuntimeModuleOptions
  16. * @property {GenerateLoadBinaryCode} generateLoadBinaryCode
  17. * @property {GenerateBeforeLoadBinaryCode=} generateBeforeLoadBinaryCode
  18. * @property {GenerateBeforeInstantiateStreaming=} generateBeforeInstantiateStreaming
  19. * @property {boolean} supportsStreaming
  20. */
  21. class AsyncWasmLoadingRuntimeModule extends RuntimeModule {
  22. /**
  23. * @param {AsyncWasmLoadingRuntimeModuleOptions} options options
  24. */
  25. constructor({
  26. generateLoadBinaryCode,
  27. generateBeforeLoadBinaryCode,
  28. generateBeforeInstantiateStreaming,
  29. supportsStreaming
  30. }) {
  31. super("wasm loading", RuntimeModule.STAGE_NORMAL);
  32. /** @type {GenerateLoadBinaryCode} */
  33. this.generateLoadBinaryCode = generateLoadBinaryCode;
  34. /** @type {generateBeforeLoadBinaryCode | undefined} */
  35. this.generateBeforeLoadBinaryCode = generateBeforeLoadBinaryCode;
  36. /** @type {generateBeforeInstantiateStreaming | undefined} */
  37. this.generateBeforeInstantiateStreaming =
  38. generateBeforeInstantiateStreaming;
  39. /** @type {boolean} */
  40. this.supportsStreaming = supportsStreaming;
  41. }
  42. /**
  43. * Generates runtime code for this runtime module.
  44. * @returns {string | null} runtime code
  45. */
  46. generate() {
  47. const compilation = /** @type {Compilation} */ (this.compilation);
  48. const chunk = /** @type {Chunk} */ (this.chunk);
  49. const { outputOptions, runtimeTemplate } = compilation;
  50. const fn = RuntimeGlobals.instantiateWasm;
  51. const wasmModuleSrcPath = compilation.getPath(
  52. JSON.stringify(outputOptions.webassemblyModuleFilename),
  53. {
  54. hash: `" + ${RuntimeGlobals.getFullHash}() + "`,
  55. hashWithLength: (length) =>
  56. `" + ${RuntimeGlobals.getFullHash}}().slice(0, ${length}) + "`,
  57. module: {
  58. id: '" + wasmModuleId + "',
  59. hash: '" + wasmModuleHash + "',
  60. hashWithLength(length) {
  61. return `" + wasmModuleHash.slice(0, ${length}) + "`;
  62. }
  63. },
  64. runtime: chunk.runtime
  65. }
  66. );
  67. const loader = this.generateLoadBinaryCode(wasmModuleSrcPath);
  68. const fallback = [
  69. `.then(${runtimeTemplate.returningFunction("x.arrayBuffer()", "x")})`,
  70. `.then(${runtimeTemplate.returningFunction(
  71. "WebAssembly.instantiate(bytes, importsObj)",
  72. "bytes"
  73. )})`,
  74. `.then(${runtimeTemplate.returningFunction(
  75. "Object.assign(exports, res.instance.exports)",
  76. "res"
  77. )})`
  78. ];
  79. const getStreaming = () => {
  80. /**
  81. * @param {string[]} text text
  82. * @returns {string} merged text
  83. */
  84. const concat = (...text) => text.join("");
  85. return [
  86. this.generateBeforeLoadBinaryCode
  87. ? this.generateBeforeLoadBinaryCode(wasmModuleSrcPath)
  88. : "",
  89. `var req = ${loader};`,
  90. `var fallback = ${runtimeTemplate.returningFunction(
  91. Template.asString(["req", Template.indent(fallback)])
  92. )};`,
  93. concat(
  94. "return req.then(",
  95. runtimeTemplate.basicFunction("res", [
  96. 'if (typeof WebAssembly.instantiateStreaming === "function") {',
  97. Template.indent(
  98. this.generateBeforeInstantiateStreaming
  99. ? this.generateBeforeInstantiateStreaming()
  100. : ""
  101. ),
  102. Template.indent([
  103. "return WebAssembly.instantiateStreaming(res, importsObj)",
  104. Template.indent([
  105. ".then(",
  106. Template.indent([
  107. `${runtimeTemplate.returningFunction(
  108. "Object.assign(exports, res.instance.exports)",
  109. "res"
  110. )},`,
  111. runtimeTemplate.basicFunction("e", [
  112. 'if(res.headers.get("Content-Type") !== "application/wasm") {',
  113. Template.indent([
  114. '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);',
  115. "return fallback();"
  116. ]),
  117. "}",
  118. "throw e;"
  119. ])
  120. ]),
  121. ");"
  122. ])
  123. ]),
  124. "}",
  125. "return fallback();"
  126. ]),
  127. ");"
  128. )
  129. ];
  130. };
  131. return `${fn} = ${runtimeTemplate.basicFunction(
  132. "exports, wasmModuleId, wasmModuleHash, importsObj",
  133. this.supportsStreaming
  134. ? getStreaming()
  135. : [
  136. this.generateBeforeLoadBinaryCode
  137. ? this.generateBeforeLoadBinaryCode(wasmModuleSrcPath)
  138. : "",
  139. `return ${loader}`,
  140. `${Template.indent(fallback)};`
  141. ]
  142. )};`;
  143. }
  144. }
  145. module.exports = AsyncWasmLoadingRuntimeModule;