AsyncWebAssemblyModulesPlugin.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { SyncWaterfallHook } = require("tapable");
  7. const Compilation = require("../Compilation");
  8. const Generator = require("../Generator");
  9. const { tryRunOrWebpackError } = require("../HookWebpackError");
  10. const { WEBASSEMBLY_MODULE_TYPE_ASYNC } = require("../ModuleTypeConstants");
  11. const NormalModule = require("../NormalModule");
  12. const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");
  13. const { compareModulesByFullName } = require("../util/comparators");
  14. const makeSerializable = require("../util/makeSerializable");
  15. const memoize = require("../util/memoize");
  16. /** @typedef {import("webpack-sources").Source} Source */
  17. /** @typedef {import("../Chunk")} Chunk */
  18. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  19. /** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
  20. /** @typedef {import("../Compiler")} Compiler */
  21. /** @typedef {import("../DependencyTemplates")} DependencyTemplates */
  22. /** @typedef {import("../Module")} Module */
  23. /** @typedef {import("../dependencies/ImportPhase").ImportPhaseName} ImportPhaseName */
  24. /** @typedef {import("../NormalModule").NormalModuleCreateData} NormalModuleCreateData */
  25. /** @typedef {import("../ModuleGraph")} ModuleGraph */
  26. /** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */
  27. /** @typedef {import("../WebpackError")} WebpackError */
  28. /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
  29. /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
  30. const getAsyncWebAssemblyGenerator = memoize(() =>
  31. require("./AsyncWebAssemblyGenerator")
  32. );
  33. const getAsyncWebAssemblyJavascriptGenerator = memoize(() =>
  34. require("./AsyncWebAssemblyJavascriptGenerator")
  35. );
  36. const getAsyncWebAssemblyParser = memoize(() =>
  37. require("./AsyncWebAssemblyParser")
  38. );
  39. /** @typedef {NormalModule & { phase: ImportPhaseName | undefined }} AsyncWasmModuleClass */
  40. class AsyncWasmModule extends NormalModule {
  41. /**
  42. * @param {NormalModuleCreateData & { phase: ImportPhaseName | undefined }} options options object
  43. */
  44. constructor(options) {
  45. super(options);
  46. this.phase = options.phase;
  47. }
  48. /**
  49. * Returns the unique identifier used to reference this module.
  50. * @returns {string} a unique identifier of the module
  51. */
  52. identifier() {
  53. let str = super.identifier();
  54. if (this.phase) {
  55. str = `${str}|${this.phase}`;
  56. }
  57. return str;
  58. }
  59. /**
  60. * Assuming this module is in the cache. Update the (cached) module with
  61. * the fresh module from the factory. Usually updates internal references
  62. * and properties.
  63. * @param {Module} module fresh module
  64. * @returns {void}
  65. */
  66. updateCacheModule(module) {
  67. super.updateCacheModule(module);
  68. const m = /** @type {AsyncWasmModule} */ (module);
  69. this.phase = m.phase;
  70. }
  71. /**
  72. * Serializes this instance into the provided serializer context.
  73. * @param {ObjectSerializerContext} context context
  74. */
  75. serialize(context) {
  76. const { write } = context;
  77. write(this.phase);
  78. super.serialize(context);
  79. }
  80. /**
  81. * @param {ObjectDeserializerContext} context context
  82. * @returns {AsyncWasmModule} the deserialized object
  83. */
  84. static deserialize(context) {
  85. const obj = new AsyncWasmModule({
  86. // will be deserialized by Module
  87. layer: /** @type {EXPECTED_ANY} */ (null),
  88. type: "",
  89. // will be filled by updateCacheModule
  90. resource: "",
  91. context: "",
  92. request: /** @type {EXPECTED_ANY} */ (null),
  93. userRequest: /** @type {EXPECTED_ANY} */ (null),
  94. rawRequest: /** @type {EXPECTED_ANY} */ (null),
  95. loaders: /** @type {EXPECTED_ANY} */ (null),
  96. matchResource: /** @type {EXPECTED_ANY} */ (null),
  97. parser: /** @type {EXPECTED_ANY} */ (null),
  98. parserOptions: /** @type {EXPECTED_ANY} */ (null),
  99. generator: /** @type {EXPECTED_ANY} */ (null),
  100. generatorOptions: /** @type {EXPECTED_ANY} */ (null),
  101. resolveOptions: /** @type {EXPECTED_ANY} */ (null),
  102. extractSourceMap: /** @type {EXPECTED_ANY} */ (null),
  103. phase: /** @type {EXPECTED_ANY} */ (null)
  104. });
  105. obj.deserialize(context);
  106. return obj;
  107. }
  108. /**
  109. * Restores this instance from the provided deserializer context.
  110. * @param {ObjectDeserializerContext} context context
  111. */
  112. deserialize(context) {
  113. const { read } = context;
  114. this.phase = read();
  115. super.deserialize(context);
  116. }
  117. }
  118. makeSerializable(AsyncWasmModule, "webpack/lib/wasm-async/AsyncWasmModule");
  119. /**
  120. * Defines the web assembly render context type used by this module.
  121. * @typedef {object} WebAssemblyRenderContext
  122. * @property {Chunk} chunk the chunk
  123. * @property {DependencyTemplates} dependencyTemplates the dependency templates
  124. * @property {RuntimeTemplate} runtimeTemplate the runtime template
  125. * @property {ModuleGraph} moduleGraph the module graph
  126. * @property {ChunkGraph} chunkGraph the chunk graph
  127. * @property {CodeGenerationResults} codeGenerationResults results of code generation
  128. */
  129. /**
  130. * Defines the compilation hooks type used by this module.
  131. * @typedef {object} CompilationHooks
  132. * @property {SyncWaterfallHook<[Source, Module, WebAssemblyRenderContext]>} renderModuleContent
  133. */
  134. /**
  135. * Defines the async web assembly modules plugin options type used by this module.
  136. * @typedef {object} AsyncWebAssemblyModulesPluginOptions
  137. * @property {boolean=} mangleImports mangle imports
  138. */
  139. /** @type {WeakMap<Compilation, CompilationHooks>} */
  140. const compilationHooksMap = new WeakMap();
  141. const PLUGIN_NAME = "AsyncWebAssemblyModulesPlugin";
  142. class AsyncWebAssemblyModulesPlugin {
  143. /**
  144. * Returns the attached hooks.
  145. * @param {Compilation} compilation the compilation
  146. * @returns {CompilationHooks} the attached hooks
  147. */
  148. static getCompilationHooks(compilation) {
  149. if (!(compilation instanceof Compilation)) {
  150. throw new TypeError(
  151. "The 'compilation' argument must be an instance of Compilation"
  152. );
  153. }
  154. let hooks = compilationHooksMap.get(compilation);
  155. if (hooks === undefined) {
  156. hooks = {
  157. renderModuleContent: new SyncWaterfallHook([
  158. "source",
  159. "module",
  160. "renderContext"
  161. ])
  162. };
  163. compilationHooksMap.set(compilation, hooks);
  164. }
  165. return hooks;
  166. }
  167. /**
  168. * Creates an instance of AsyncWebAssemblyModulesPlugin.
  169. * @param {AsyncWebAssemblyModulesPluginOptions} options options
  170. */
  171. constructor(options) {
  172. /** @type {AsyncWebAssemblyModulesPluginOptions} */
  173. this.options = options;
  174. }
  175. /**
  176. * Applies the plugin by registering its hooks on the compiler.
  177. * @param {Compiler} compiler the compiler instance
  178. * @returns {void}
  179. */
  180. apply(compiler) {
  181. compiler.hooks.compilation.tap(
  182. PLUGIN_NAME,
  183. (compilation, { normalModuleFactory }) => {
  184. const hooks =
  185. AsyncWebAssemblyModulesPlugin.getCompilationHooks(compilation);
  186. compilation.dependencyFactories.set(
  187. WebAssemblyImportDependency,
  188. normalModuleFactory
  189. );
  190. normalModuleFactory.hooks.createModuleClass
  191. .for(WEBASSEMBLY_MODULE_TYPE_ASYNC)
  192. .tap(
  193. PLUGIN_NAME,
  194. (createData, resolveData) =>
  195. new AsyncWasmModule({
  196. .../** @type {NormalModuleCreateData & { type: string }} */
  197. (createData),
  198. phase: resolveData.phase
  199. })
  200. );
  201. normalModuleFactory.hooks.createParser
  202. .for(WEBASSEMBLY_MODULE_TYPE_ASYNC)
  203. .tap(PLUGIN_NAME, () => {
  204. const AsyncWebAssemblyParser = getAsyncWebAssemblyParser();
  205. return new AsyncWebAssemblyParser();
  206. });
  207. normalModuleFactory.hooks.createGenerator
  208. .for(WEBASSEMBLY_MODULE_TYPE_ASYNC)
  209. .tap(PLUGIN_NAME, () => {
  210. const AsyncWebAssemblyJavascriptGenerator =
  211. getAsyncWebAssemblyJavascriptGenerator();
  212. const AsyncWebAssemblyGenerator = getAsyncWebAssemblyGenerator();
  213. return Generator.byType({
  214. javascript: new AsyncWebAssemblyJavascriptGenerator(),
  215. webassembly: new AsyncWebAssemblyGenerator(this.options)
  216. });
  217. });
  218. compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => {
  219. const { moduleGraph, chunkGraph, runtimeTemplate } = compilation;
  220. const {
  221. chunk,
  222. outputOptions,
  223. dependencyTemplates,
  224. codeGenerationResults
  225. } = options;
  226. for (const module of chunkGraph.getOrderedChunkModulesIterable(
  227. chunk,
  228. compareModulesByFullName(compiler)
  229. )) {
  230. if (module.type === WEBASSEMBLY_MODULE_TYPE_ASYNC) {
  231. const filenameTemplate = outputOptions.webassemblyModuleFilename;
  232. result.push({
  233. render: () =>
  234. this.renderModule(
  235. module,
  236. {
  237. chunk,
  238. dependencyTemplates,
  239. runtimeTemplate,
  240. moduleGraph,
  241. chunkGraph,
  242. codeGenerationResults
  243. },
  244. hooks
  245. ),
  246. filenameTemplate,
  247. pathOptions: {
  248. module,
  249. runtime: chunk.runtime,
  250. chunkGraph
  251. },
  252. auxiliary: true,
  253. identifier: `webassemblyAsyncModule${chunkGraph.getModuleId(
  254. module
  255. )}`,
  256. hash: chunkGraph.getModuleHash(module, chunk.runtime)
  257. });
  258. }
  259. }
  260. return result;
  261. });
  262. }
  263. );
  264. }
  265. /**
  266. * Renders the newly generated source from rendering.
  267. * @param {Module} module the rendered module
  268. * @param {WebAssemblyRenderContext} renderContext options object
  269. * @param {CompilationHooks} hooks hooks
  270. * @returns {Source} the newly generated source from rendering
  271. */
  272. renderModule(module, renderContext, hooks) {
  273. const { codeGenerationResults, chunk } = renderContext;
  274. try {
  275. const moduleSource = codeGenerationResults.getSource(
  276. module,
  277. chunk.runtime,
  278. "webassembly"
  279. );
  280. return tryRunOrWebpackError(
  281. () =>
  282. hooks.renderModuleContent.call(moduleSource, module, renderContext),
  283. "AsyncWebAssemblyModulesPlugin.getCompilationHooks().renderModuleContent"
  284. );
  285. } catch (err) {
  286. /** @type {WebpackError} */ (err).module = module;
  287. throw err;
  288. }
  289. }
  290. }
  291. module.exports = AsyncWebAssemblyModulesPlugin;