UniversalCompileAsyncWasmPlugin.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. const { WEBASSEMBLY_MODULE_TYPE_ASYNC } = require("../ModuleTypeConstants");
  7. const RuntimeGlobals = require("../RuntimeGlobals");
  8. const Template = require("../Template");
  9. const { usesFullHashDigest } = require("../TemplatedPathPlugin");
  10. const AsyncWasmCompileRuntimeModule = require("../wasm-async/AsyncWasmCompileRuntimeModule");
  11. const AsyncWasmLoadingRuntimeModule = require("../wasm-async/AsyncWasmLoadingRuntimeModule");
  12. /** @import Chunk from "../Chunk" */
  13. /** @import Compiler from "../Compiler" */
  14. /** @import { RuntimeSpec } from "../util/runtime" */
  15. const PLUGIN_NAME = "UniversalCompileAsyncWasmPlugin";
  16. /**
  17. * Enables async WebAssembly loading that works in both browser-like and Node.js
  18. * environments by selecting the appropriate binary-loading strategy at runtime.
  19. */
  20. class UniversalCompileAsyncWasmPlugin {
  21. /**
  22. * Registers compilation hooks that attach the universal async wasm runtime
  23. * to chunks using `wasmLoading: "universal"`.
  24. * @param {Compiler} compiler the compiler instance
  25. * @returns {void}
  26. */
  27. apply(compiler) {
  28. compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
  29. const globalWasmLoading = compilation.outputOptions.wasmLoading;
  30. /**
  31. * Determines whether the chunk should use the universal async wasm
  32. * loading backend.
  33. * @param {Chunk} chunk chunk
  34. * @returns {boolean} true, if wasm loading is enabled for the chunk
  35. */
  36. const isEnabledForChunk = (chunk) => {
  37. const options = chunk.getEntryOptions();
  38. const wasmLoading =
  39. options && options.wasmLoading !== undefined
  40. ? options.wasmLoading
  41. : globalWasmLoading;
  42. return wasmLoading === "universal";
  43. };
  44. const generateBeforeStreaming = () =>
  45. Template.asString([
  46. "if (!useFetch) {",
  47. Template.indent(["return fallback();"]),
  48. "}"
  49. ]);
  50. /**
  51. * Generates setup code that decides whether the current environment can
  52. * use `fetch` and captures the wasm module URL.
  53. * @param {string} path path
  54. * @returns {string} code
  55. */
  56. const generateBeforeLoadBinaryCode = (path) =>
  57. Template.asString([
  58. `var useFetch = ${compilation.runtimeTemplate.isWebLikePlatformExpression()};`,
  59. `var wasmUrl = ${path};`
  60. ]);
  61. // `wasmUrl` already holds a complete `URL` when the call site bakes it. Read that
  62. // per call: it depends on parse results, so it is not settled while the plugin
  63. // is being applied.
  64. /**
  65. * @param {RuntimeSpec} runtime the runtime this loader serves
  66. * @returns {string} the argument to hand the reader
  67. */
  68. const wasmUrlArg = (runtime) =>
  69. compilation.runtimeTemplate.supportsAnalyzable(
  70. "wasm",
  71. undefined,
  72. undefined,
  73. runtime
  74. )
  75. ? "wasmUrl"
  76. : compilation.runtimeTemplate.importMetaUrl("wasmUrl");
  77. /**
  78. * Generates the runtime expression that fetches the binary in browsers
  79. * or reads it from the filesystem in Node.js.
  80. * @type {(path: string, runtime: RuntimeSpec) => string}
  81. */
  82. const generateLoadBinaryCode = (path, runtime) =>
  83. Template.asString([
  84. "(useFetch",
  85. Template.indent([`? fetch(${wasmUrlArg(runtime)})`]),
  86. Template.indent([
  87. ": Promise.all([import('fs'), import('url')]).then(([{ readFile }, { URL }]) => new Promise((resolve, reject) => {",
  88. Template.indent([
  89. `readFile(${wasmUrlArg(runtime)}, (err, buffer) => {`,
  90. Template.indent([
  91. "if (err) return reject(err);",
  92. "",
  93. "// Fake fetch response",
  94. "resolve({",
  95. Template.indent(["arrayBuffer() { return buffer; }"]),
  96. "});"
  97. ]),
  98. "});"
  99. ]),
  100. "})))"
  101. ])
  102. ]);
  103. compilation.hooks.runtimeRequirementInTree
  104. .for(RuntimeGlobals.instantiateWasm)
  105. .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
  106. if (!isEnabledForChunk(chunk)) return;
  107. if (
  108. !chunkGraph.hasModuleInGraph(
  109. chunk,
  110. (m) => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC
  111. )
  112. ) {
  113. return;
  114. }
  115. compilation.addRuntimeModule(
  116. chunk,
  117. new AsyncWasmLoadingRuntimeModule({
  118. fullHashDigest: usesFullHashDigest(
  119. /** @type {string} */ (
  120. compilation.outputOptions.webassemblyModuleFilename
  121. )
  122. ),
  123. generateBeforeLoadBinaryCode,
  124. generateLoadBinaryCode,
  125. generateBeforeInstantiateStreaming: generateBeforeStreaming,
  126. supportsStreaming: true
  127. })
  128. );
  129. });
  130. compilation.hooks.runtimeRequirementInTree
  131. .for(RuntimeGlobals.compileWasm)
  132. .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
  133. if (!isEnabledForChunk(chunk)) return;
  134. if (
  135. !chunkGraph.hasModuleInGraph(
  136. chunk,
  137. (m) => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC
  138. )
  139. ) {
  140. return;
  141. }
  142. compilation.addRuntimeModule(
  143. chunk,
  144. new AsyncWasmCompileRuntimeModule({
  145. fullHashDigest: usesFullHashDigest(
  146. /** @type {string} */ (
  147. compilation.outputOptions.webassemblyModuleFilename
  148. )
  149. ),
  150. generateBeforeLoadBinaryCode,
  151. generateLoadBinaryCode,
  152. generateBeforeCompileStreaming: generateBeforeStreaming,
  153. supportsStreaming: true
  154. })
  155. );
  156. });
  157. });
  158. }
  159. }
  160. module.exports = UniversalCompileAsyncWasmPlugin;