AsyncWasmLoadingRuntimeModule.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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. * @returns {string | null} runtime code
  44. */
  45. generate() {
  46. const compilation = /** @type {Compilation} */ (this.compilation);
  47. const chunk = /** @type {Chunk} */ (this.chunk);
  48. const { outputOptions, runtimeTemplate } = compilation;
  49. const fn = RuntimeGlobals.instantiateWasm;
  50. const wasmModuleSrcPath = compilation.getPath(
  51. JSON.stringify(outputOptions.webassemblyModuleFilename),
  52. {
  53. hash: `" + ${RuntimeGlobals.getFullHash}() + "`,
  54. hashWithLength: (length) =>
  55. `" + ${RuntimeGlobals.getFullHash}}().slice(0, ${length}) + "`,
  56. module: {
  57. id: '" + wasmModuleId + "',
  58. hash: '" + wasmModuleHash + "',
  59. hashWithLength(length) {
  60. return `" + wasmModuleHash.slice(0, ${length}) + "`;
  61. }
  62. },
  63. runtime: chunk.runtime
  64. }
  65. );
  66. const loader = this.generateLoadBinaryCode(wasmModuleSrcPath);
  67. const fallback = [
  68. `.then(${runtimeTemplate.returningFunction("x.arrayBuffer()", "x")})`,
  69. `.then(${runtimeTemplate.returningFunction(
  70. "WebAssembly.instantiate(bytes, importsObj)",
  71. "bytes"
  72. )})`,
  73. `.then(${runtimeTemplate.returningFunction(
  74. "Object.assign(exports, res.instance.exports)",
  75. "res"
  76. )})`
  77. ];
  78. const getStreaming = () => {
  79. /**
  80. * @param {string[]} text text
  81. * @returns {string} merged text
  82. */
  83. const concat = (...text) => text.join("");
  84. return [
  85. this.generateBeforeLoadBinaryCode
  86. ? this.generateBeforeLoadBinaryCode(wasmModuleSrcPath)
  87. : "",
  88. `var req = ${loader};`,
  89. `var fallback = ${runtimeTemplate.returningFunction(
  90. Template.asString(["req", Template.indent(fallback)])
  91. )};`,
  92. concat(
  93. "return req.then(",
  94. runtimeTemplate.basicFunction("res", [
  95. 'if (typeof WebAssembly.instantiateStreaming === "function") {',
  96. Template.indent(
  97. this.generateBeforeInstantiateStreaming
  98. ? this.generateBeforeInstantiateStreaming()
  99. : ""
  100. ),
  101. Template.indent([
  102. "return WebAssembly.instantiateStreaming(res, importsObj)",
  103. Template.indent([
  104. ".then(",
  105. Template.indent([
  106. `${runtimeTemplate.returningFunction(
  107. "Object.assign(exports, res.instance.exports)",
  108. "res"
  109. )},`,
  110. runtimeTemplate.basicFunction("e", [
  111. 'if(res.headers.get("Content-Type") !== "application/wasm") {',
  112. Template.indent([
  113. '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);',
  114. "return fallback();"
  115. ]),
  116. "}",
  117. "throw e;"
  118. ])
  119. ]),
  120. ");"
  121. ])
  122. ]),
  123. "}",
  124. "return fallback();"
  125. ]),
  126. ");"
  127. )
  128. ];
  129. };
  130. return `${fn} = ${runtimeTemplate.basicFunction(
  131. "exports, wasmModuleId, wasmModuleHash, importsObj",
  132. this.supportsStreaming
  133. ? getStreaming()
  134. : [
  135. this.generateBeforeLoadBinaryCode
  136. ? this.generateBeforeLoadBinaryCode(wasmModuleSrcPath)
  137. : "",
  138. `return ${loader}`,
  139. `${Template.indent(fallback)};`
  140. ]
  141. )};`;
  142. }
  143. }
  144. module.exports = AsyncWasmLoadingRuntimeModule;