ReadFileCompileAsyncWasmPlugin.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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 Template = require("../Template");
  9. const { usesFullHashDigest } = require("../TemplatedPathPlugin");
  10. const { needsRuntimeFullHash } = require("../wasm/wasmModuleFilename");
  11. const AsyncWasmCompileRuntimeModule = require("../wasm-async/AsyncWasmCompileRuntimeModule");
  12. const AsyncWasmLoadingRuntimeModule = require("../wasm-async/AsyncWasmLoadingRuntimeModule");
  13. /** @import Chunk from "../Chunk" */
  14. /** @import Compiler from "../Compiler" */
  15. /** @import { RuntimeSpec } from "../util/runtime" */
  16. /**
  17. * Defines the read file compile async wasm plugin options type used by this module.
  18. * @typedef {object} ReadFileCompileAsyncWasmPluginOptions
  19. * @property {boolean=} import use import?
  20. */
  21. const PLUGIN_NAME = "ReadFileCompileAsyncWasmPlugin";
  22. class ReadFileCompileAsyncWasmPlugin {
  23. /**
  24. * Creates an instance of ReadFileCompileAsyncWasmPlugin.
  25. * @param {ReadFileCompileAsyncWasmPluginOptions=} options options object
  26. */
  27. constructor({ import: useImport = false } = {}) {
  28. /** @type {boolean} */
  29. this._import = useImport;
  30. }
  31. /**
  32. * Applies the plugin by registering its hooks on the compiler.
  33. * @param {Compiler} compiler the compiler instance
  34. * @returns {void}
  35. */
  36. apply(compiler) {
  37. compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
  38. const globalWasmLoading = compilation.outputOptions.wasmLoading;
  39. /**
  40. * Checks whether this read file compile async wasm plugin is enabled for chunk.
  41. * @param {Chunk} chunk chunk
  42. * @returns {boolean} true, if wasm loading is enabled for the chunk
  43. */
  44. const isEnabledForChunk = (chunk) => {
  45. const options = chunk.getEntryOptions();
  46. const wasmLoading =
  47. options && options.wasmLoading !== undefined
  48. ? options.wasmLoading
  49. : globalWasmLoading;
  50. return wasmLoading === "async-node";
  51. };
  52. // The call site already passes a complete `URL` when it bakes one, and `readFile`
  53. // takes one as-is. Read that per call: it depends on parse results, so it is not
  54. // settled while the plugin is being applied.
  55. /**
  56. * @param {string} path rendered path to the wasm file
  57. * @param {RuntimeSpec} runtime the runtime this loader serves
  58. * @returns {string} the argument to hand the reader
  59. */
  60. const wasmUrlArg = (path, runtime) =>
  61. compilation.runtimeTemplate.supportsAnalyzable(
  62. "wasm",
  63. undefined,
  64. undefined,
  65. runtime
  66. )
  67. ? path
  68. : compilation.runtimeTemplate.importMetaUrl(path);
  69. /**
  70. * @type {(path: string, runtime: RuntimeSpec) => string} callback to generate code to load the wasm file
  71. */
  72. const generateLoadBinaryCode = this._import
  73. ? (path, runtime) =>
  74. Template.asString([
  75. "Promise.all([import('fs'), import('url')]).then(([{ readFile }, { URL }]) => new Promise((resolve, reject) => {",
  76. Template.indent([
  77. `readFile(${wasmUrlArg(path, runtime)}, (err, buffer) => {`,
  78. Template.indent([
  79. "if (err) return reject(err);",
  80. "",
  81. "// Fake fetch response",
  82. "resolve({",
  83. Template.indent([
  84. // Return a real ArrayBuffer: some runtimes (e.g. Deno)
  85. // reject a Node Buffer view here as "not a buffer source".
  86. "arrayBuffer() { return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); }"
  87. ]),
  88. "});"
  89. ]),
  90. "});"
  91. ]),
  92. "}))"
  93. ])
  94. : (path) =>
  95. Template.asString([
  96. "new Promise(function (resolve, reject) {",
  97. Template.indent([
  98. "try {",
  99. Template.indent([
  100. compilation.runtimeTemplate.destructureObject(
  101. ["readFile"],
  102. `require(${compilation.runtimeTemplate.renderNodePrefixForCoreModule("fs")})`
  103. ),
  104. compilation.runtimeTemplate.destructureObject(
  105. ["join"],
  106. `require(${compilation.runtimeTemplate.renderNodePrefixForCoreModule("path")})`
  107. ),
  108. "",
  109. `readFile(join(__dirname, ${path}), function(err, buffer){`,
  110. Template.indent([
  111. "if (err) return reject(err);",
  112. "",
  113. "// Fake fetch response",
  114. "resolve({",
  115. Template.indent([
  116. // Return a real ArrayBuffer: some runtimes (e.g. Deno)
  117. // reject a Node Buffer view here as "not a buffer source".
  118. compilation.runtimeTemplate.method(
  119. "arrayBuffer",
  120. "",
  121. "return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);"
  122. )
  123. ]),
  124. "});"
  125. ]),
  126. "});"
  127. ]),
  128. "} catch (err) { reject(err); }"
  129. ]),
  130. "})"
  131. ]);
  132. compilation.hooks.runtimeRequirementInTree
  133. .for(RuntimeGlobals.instantiateWasm)
  134. .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
  135. if (!isEnabledForChunk(chunk)) return;
  136. if (
  137. !chunkGraph.hasModuleInGraph(
  138. chunk,
  139. (m) => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC
  140. )
  141. ) {
  142. return;
  143. }
  144. // A baked URL is already a literal, so nothing interpolates the name.
  145. if (
  146. !compilation.runtimeTemplate.supportsAnalyzable(
  147. "wasm",
  148. undefined,
  149. undefined,
  150. chunk.runtime
  151. ) &&
  152. needsRuntimeFullHash(
  153. /** @type {string} */ (
  154. compilation.outputOptions.webassemblyModuleFilename
  155. )
  156. )
  157. ) {
  158. set.add(RuntimeGlobals.getFullHash);
  159. }
  160. compilation.addRuntimeModule(
  161. chunk,
  162. new AsyncWasmLoadingRuntimeModule({
  163. fullHashDigest: usesFullHashDigest(
  164. /** @type {string} */ (
  165. compilation.outputOptions.webassemblyModuleFilename
  166. )
  167. ),
  168. generateLoadBinaryCode,
  169. supportsStreaming: false
  170. })
  171. );
  172. });
  173. compilation.hooks.runtimeRequirementInTree
  174. .for(RuntimeGlobals.compileWasm)
  175. .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => {
  176. if (!isEnabledForChunk(chunk)) return;
  177. if (
  178. !chunkGraph.hasModuleInGraph(
  179. chunk,
  180. (m) => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC
  181. )
  182. ) {
  183. return;
  184. }
  185. // A baked URL is already a literal, so nothing interpolates the name.
  186. if (
  187. !compilation.runtimeTemplate.supportsAnalyzable(
  188. "wasm",
  189. undefined,
  190. undefined,
  191. chunk.runtime
  192. ) &&
  193. needsRuntimeFullHash(
  194. /** @type {string} */ (
  195. compilation.outputOptions.webassemblyModuleFilename
  196. )
  197. )
  198. ) {
  199. set.add(RuntimeGlobals.getFullHash);
  200. }
  201. compilation.addRuntimeModule(
  202. chunk,
  203. new AsyncWasmCompileRuntimeModule({
  204. fullHashDigest: usesFullHashDigest(
  205. /** @type {string} */ (
  206. compilation.outputOptions.webassemblyModuleFilename
  207. )
  208. ),
  209. generateLoadBinaryCode,
  210. supportsStreaming: false
  211. })
  212. );
  213. });
  214. });
  215. }
  216. }
  217. module.exports = ReadFileCompileAsyncWasmPlugin;