UniversalCompileAsyncWasmPlugin.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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 AsyncWasmCompileRuntimeModule = require("../wasm-async/AsyncWasmCompileRuntimeModule");
  10. const AsyncWasmLoadingRuntimeModule = require("../wasm-async/AsyncWasmLoadingRuntimeModule");
  11. /** @typedef {import("../Chunk")} Chunk */
  12. /** @typedef {import("../Compiler")} Compiler */
  13. const PLUGIN_NAME = "UniversalCompileAsyncWasmPlugin";
  14. /**
  15. * Enables async WebAssembly loading that works in both browser-like and Node.js
  16. * environments by selecting the appropriate binary-loading strategy at runtime.
  17. */
  18. class UniversalCompileAsyncWasmPlugin {
  19. /**
  20. * Registers compilation hooks that attach the universal async wasm runtime
  21. * to chunks using `wasmLoading: "universal"`.
  22. * @param {Compiler} compiler the compiler instance
  23. * @returns {void}
  24. */
  25. apply(compiler) {
  26. compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
  27. const globalWasmLoading = compilation.outputOptions.wasmLoading;
  28. /**
  29. * Determines whether the chunk should use the universal async wasm
  30. * loading backend.
  31. * @param {Chunk} chunk chunk
  32. * @returns {boolean} true, if wasm loading is enabled for the chunk
  33. */
  34. const isEnabledForChunk = (chunk) => {
  35. const options = chunk.getEntryOptions();
  36. const wasmLoading =
  37. options && options.wasmLoading !== undefined
  38. ? options.wasmLoading
  39. : globalWasmLoading;
  40. return wasmLoading === "universal";
  41. };
  42. const generateBeforeStreaming = () =>
  43. Template.asString([
  44. "if (!useFetch) {",
  45. Template.indent(["return fallback();"]),
  46. "}"
  47. ]);
  48. /**
  49. * Generates setup code that decides whether the current environment can
  50. * use `fetch` and captures the wasm module URL.
  51. * @param {string} path path
  52. * @returns {string} code
  53. */
  54. const generateBeforeLoadBinaryCode = (path) =>
  55. Template.asString([
  56. "var useFetch = typeof document !== 'undefined' || typeof self !== 'undefined';",
  57. `var wasmUrl = ${path};`
  58. ]);
  59. /**
  60. * Generates the runtime expression that fetches the binary in browsers
  61. * or reads it from the filesystem in Node.js.
  62. * @type {(path: string) => string}
  63. */
  64. const generateLoadBinaryCode = () =>
  65. Template.asString([
  66. "(useFetch",
  67. Template.indent([
  68. `? fetch(new URL(wasmUrl, ${compilation.outputOptions.importMetaName}.url))`
  69. ]),
  70. Template.indent([
  71. ": Promise.all([import('fs'), import('url')]).then(([{ readFile }, { URL }]) => new Promise((resolve, reject) => {",
  72. Template.indent([
  73. `readFile(new URL(wasmUrl, ${compilation.outputOptions.importMetaName}.url), (err, buffer) => {`,
  74. Template.indent([
  75. "if (err) return reject(err);",
  76. "",
  77. "// Fake fetch response",
  78. "resolve({",
  79. Template.indent(["arrayBuffer() { return buffer; }"]),
  80. "});"
  81. ]),
  82. "});"
  83. ]),
  84. "})))"
  85. ])
  86. ]);
  87. compilation.hooks.runtimeRequirementInTree
  88. .for(RuntimeGlobals.instantiateWasm)
  89. .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
  90. if (!isEnabledForChunk(chunk)) return;
  91. if (
  92. !chunkGraph.hasModuleInGraph(
  93. chunk,
  94. (m) => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC
  95. )
  96. ) {
  97. return;
  98. }
  99. compilation.addRuntimeModule(
  100. chunk,
  101. new AsyncWasmLoadingRuntimeModule({
  102. generateBeforeLoadBinaryCode,
  103. generateLoadBinaryCode,
  104. generateBeforeInstantiateStreaming: generateBeforeStreaming,
  105. supportsStreaming: true
  106. })
  107. );
  108. });
  109. compilation.hooks.runtimeRequirementInTree
  110. .for(RuntimeGlobals.compileWasm)
  111. .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
  112. if (!isEnabledForChunk(chunk)) return;
  113. if (
  114. !chunkGraph.hasModuleInGraph(
  115. chunk,
  116. (m) => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC
  117. )
  118. ) {
  119. return;
  120. }
  121. compilation.addRuntimeModule(
  122. chunk,
  123. new AsyncWasmCompileRuntimeModule({
  124. generateBeforeLoadBinaryCode,
  125. generateLoadBinaryCode,
  126. generateBeforeCompileStreaming: generateBeforeStreaming,
  127. supportsStreaming: true
  128. })
  129. );
  130. });
  131. });
  132. }
  133. }
  134. module.exports = UniversalCompileAsyncWasmPlugin;