FetchCompileAsyncWasmPlugin.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { WEBASSEMBLY_MODULE_TYPE_ASYNC } = require("../ModuleTypeConstants");
  7. const RuntimeGlobals = require("../RuntimeGlobals");
  8. const { usesFullHashDigest } = require("../TemplatedPathPlugin");
  9. const { needsRuntimeFullHash } = require("../wasm/wasmModuleFilename");
  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. const PLUGIN_NAME = "FetchCompileAsyncWasmPlugin";
  15. /**
  16. * Enables asynchronous WebAssembly loading through `fetch` for environments
  17. * that can instantiate fetched binaries at runtime.
  18. */
  19. class FetchCompileAsyncWasmPlugin {
  20. /**
  21. * Registers compilation hooks that attach the async fetch-based wasm runtime
  22. * to chunks containing async WebAssembly modules.
  23. * @param {Compiler} compiler the compiler instance
  24. * @returns {void}
  25. */
  26. apply(compiler) {
  27. compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
  28. const globalWasmLoading = compilation.outputOptions.wasmLoading;
  29. /**
  30. * Determines whether the chunk should load async WebAssembly binaries
  31. * through the `fetch` backend.
  32. * @param {Chunk} chunk chunk
  33. * @returns {boolean} true, if wasm loading is enabled for the chunk
  34. */
  35. const isEnabledForChunk = (chunk) => {
  36. const options = chunk.getEntryOptions();
  37. const wasmLoading =
  38. options && options.wasmLoading !== undefined
  39. ? options.wasmLoading
  40. : globalWasmLoading;
  41. return wasmLoading === "fetch";
  42. };
  43. /**
  44. * Downloads the emitted wasm binary through the runtime
  45. * `__webpack_require__.p` public path.
  46. * @param {string} path path to the wasm file
  47. * @returns {string} code to load the wasm file
  48. */
  49. const generateLoadBinaryCode = (path) =>
  50. `fetch(${RuntimeGlobals.publicPath} + ${path})`;
  51. /**
  52. * ESM-output variant: the analyzable `fetch(new URL(path, import.meta.url))`
  53. * form (relative to the chunk, no runtime public-path global) that other
  54. * bundlers and webpack itself can statically follow to the emitted wasm.
  55. * @param {string} path path to the wasm file
  56. * @returns {string} code to load the wasm file
  57. */
  58. const generateAnalyzableLoadBinaryCode = (path) =>
  59. `fetch(${compilation.runtimeTemplate.importMetaUrl(path)})`;
  60. /**
  61. * The call site already passes a complete `URL`, so no base is applied here.
  62. * @param {string} path the wasm binary's URL expression
  63. * @returns {string} code to load the wasm file
  64. */
  65. const generateBakedLoadBinaryCode = (path) => `fetch(${path})`;
  66. /**
  67. * A settled public path is the same string the global would hold, and `fetch`
  68. * reads both against the document, so the runtime module is not needed.
  69. * @param {string} constant the settled public path
  70. * @returns {(path: string) => string} code to load the wasm file
  71. */
  72. const generateConstantLoadBinaryCode = (constant) => (path) =>
  73. `fetch(${JSON.stringify(constant)} + ${path})`;
  74. compilation.hooks.runtimeRequirementInTree
  75. .for(RuntimeGlobals.instantiateWasm)
  76. .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
  77. if (!isEnabledForChunk(chunk)) return;
  78. if (
  79. !chunkGraph.hasModuleInGraph(
  80. chunk,
  81. (m) => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC
  82. )
  83. ) {
  84. return;
  85. }
  86. const baked = compilation.runtimeTemplate.supportsAnalyzable(
  87. "wasm",
  88. undefined,
  89. undefined,
  90. chunk.runtime
  91. );
  92. const analyzable =
  93. compilation.runtimeTemplate.supportsAnalyzable("wasm-relative");
  94. // A baked URL carries the public path already, and a settled one is written
  95. // out below; only a path nothing but the global knows asks for it.
  96. const constant =
  97. baked || analyzable
  98. ? undefined
  99. : compilation.runtimeTemplate.constantPublicPath();
  100. if (!baked && !analyzable && constant === undefined) {
  101. set.add(RuntimeGlobals.publicPath);
  102. }
  103. // A baked URL is already a literal, so nothing interpolates the name.
  104. if (
  105. !baked &&
  106. needsRuntimeFullHash(
  107. /** @type {string} */ (
  108. compilation.outputOptions.webassemblyModuleFilename
  109. )
  110. )
  111. ) {
  112. set.add(RuntimeGlobals.getFullHash);
  113. }
  114. compilation.addRuntimeModule(
  115. chunk,
  116. new AsyncWasmLoadingRuntimeModule({
  117. fullHashDigest: usesFullHashDigest(
  118. /** @type {string} */ (
  119. compilation.outputOptions.webassemblyModuleFilename
  120. )
  121. ),
  122. generateLoadBinaryCode: baked
  123. ? generateBakedLoadBinaryCode
  124. : analyzable
  125. ? generateAnalyzableLoadBinaryCode
  126. : constant === undefined
  127. ? generateLoadBinaryCode
  128. : generateConstantLoadBinaryCode(constant),
  129. supportsStreaming: true
  130. })
  131. );
  132. });
  133. compilation.hooks.runtimeRequirementInTree
  134. .for(RuntimeGlobals.compileWasm)
  135. .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
  136. if (!isEnabledForChunk(chunk)) return;
  137. if (
  138. !chunkGraph.hasModuleInGraph(
  139. chunk,
  140. (m) => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC
  141. )
  142. ) {
  143. return;
  144. }
  145. const baked = compilation.runtimeTemplate.supportsAnalyzable(
  146. "wasm",
  147. undefined,
  148. undefined,
  149. chunk.runtime
  150. );
  151. const analyzable =
  152. compilation.runtimeTemplate.supportsAnalyzable("wasm-relative");
  153. // A baked URL carries the public path already, and a settled one is written
  154. // out below; only a path nothing but the global knows asks for it.
  155. const constant =
  156. baked || analyzable
  157. ? undefined
  158. : compilation.runtimeTemplate.constantPublicPath();
  159. if (!baked && !analyzable && constant === undefined) {
  160. set.add(RuntimeGlobals.publicPath);
  161. }
  162. // A baked URL is already a literal, so nothing interpolates the name.
  163. if (
  164. !baked &&
  165. needsRuntimeFullHash(
  166. /** @type {string} */ (
  167. compilation.outputOptions.webassemblyModuleFilename
  168. )
  169. )
  170. ) {
  171. set.add(RuntimeGlobals.getFullHash);
  172. }
  173. compilation.addRuntimeModule(
  174. chunk,
  175. new AsyncWasmCompileRuntimeModule({
  176. fullHashDigest: usesFullHashDigest(
  177. /** @type {string} */ (
  178. compilation.outputOptions.webassemblyModuleFilename
  179. )
  180. ),
  181. generateLoadBinaryCode: baked
  182. ? generateBakedLoadBinaryCode
  183. : analyzable
  184. ? generateAnalyzableLoadBinaryCode
  185. : constant === undefined
  186. ? generateLoadBinaryCode
  187. : generateConstantLoadBinaryCode(constant),
  188. supportsStreaming: true
  189. })
  190. );
  191. });
  192. });
  193. }
  194. }
  195. module.exports = FetchCompileAsyncWasmPlugin;