WorkletDependency.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author sheo13666q
  4. */
  5. "use strict";
  6. const Dependency = require("../Dependency");
  7. const RuntimeGlobals = require("../RuntimeGlobals");
  8. const Template = require("../Template");
  9. const makeSerializable = require("../util/makeSerializable");
  10. const ModuleDependency = require("./ModuleDependency");
  11. /** @import { ReplaceSource } from "webpack-sources" */
  12. /** @import AsyncDependenciesBlock from "../AsyncDependenciesBlock" */
  13. /** @import { ReferencedExports, UpdateHashContext } from "../Dependency" */
  14. /** @import { DependencyTemplateContext } from "../DependencyTemplate" */
  15. /** @import Entrypoint from "../Entrypoint" */
  16. /** @import ModuleGraph from "../ModuleGraph" */
  17. /** @import { Range } from "../javascript/JavascriptParser" */
  18. /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[WorkletDependencyOptions, Range]>} ObjectDeserializerContext */
  19. /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[WorkletDependencyOptions, Range]>} ObjectSerializerContext */
  20. /** @import Hash from "../util/Hash" */
  21. /** @import { RuntimeSpec } from "../util/runtime" */
  22. /**
  23. * @typedef {object} WorkletDependencyOptions
  24. * @property {string=} publicPath public path for the worklet
  25. */
  26. // The wrapper param forwarded as the module argument to the original `addModule`.
  27. const WORKLET_MODULE = "__webpack_worklet_module__";
  28. class WorkletDependency extends ModuleDependency {
  29. /**
  30. * Creates an instance of WorkletDependency.
  31. * @param {string} request request
  32. * @param {Range} range range of the zero-width insertion point (before the call)
  33. * @param {Range} callRange range of the call argument list `(<url>)`
  34. * @param {WorkletDependencyOptions} workletDependencyOptions options
  35. */
  36. constructor(request, range, callRange, workletDependencyOptions) {
  37. super(request);
  38. this.range = range;
  39. /** @type {Range} */
  40. this.callRange = callRange;
  41. // If options are updated, don't forget to update the hash and serialization functions
  42. /** @type {WorkletDependencyOptions} */
  43. this.options = workletDependencyOptions;
  44. /** Cache the hash */
  45. /** @type {undefined | string} */
  46. this._hashUpdate = undefined;
  47. }
  48. /**
  49. * Returns list of exports referenced by this dependency
  50. * @param {ModuleGraph} moduleGraph module graph
  51. * @param {RuntimeSpec} runtime the runtime for which the module is analysed
  52. * @returns {ReferencedExports} referenced exports
  53. */
  54. getReferencedExports(moduleGraph, runtime) {
  55. return Dependency.NO_EXPORTS_REFERENCED;
  56. }
  57. get type() {
  58. return "Worklet.addModule()";
  59. }
  60. get category() {
  61. return "worker";
  62. }
  63. /**
  64. * Updates the hash with the data contributed by this instance.
  65. * @param {Hash} hash hash to be updated
  66. * @param {UpdateHashContext} context context
  67. * @returns {void}
  68. */
  69. updateHash(hash, context) {
  70. if (this._hashUpdate === undefined) {
  71. this._hashUpdate = JSON.stringify(this.options);
  72. }
  73. hash.update(this._hashUpdate);
  74. }
  75. /**
  76. * Serializes this instance into the provided serializer context.
  77. * @param {ObjectSerializerContext} context context
  78. */
  79. serialize(context) {
  80. context.write(this.options).write(this.callRange);
  81. super.serialize(context);
  82. }
  83. /**
  84. * Restores this instance from the provided deserializer context.
  85. * @param {ObjectDeserializerContext} context context
  86. */
  87. deserialize(context) {
  88. this.options = context.read();
  89. const rest = context.rest;
  90. this.callRange = rest.read();
  91. super.deserialize(rest.rest);
  92. }
  93. }
  94. WorkletDependency.Template = class WorkletDependencyTemplate extends (
  95. ModuleDependency.Template
  96. ) {
  97. /**
  98. * Applies the plugin by registering its hooks on the compiler.
  99. * @param {Dependency} dependency the dependency for which the template should be applied
  100. * @param {ReplaceSource} source the current replace source which can be modified
  101. * @param {DependencyTemplateContext} templateContext the context object
  102. * @returns {void}
  103. */
  104. apply(dependency, source, templateContext) {
  105. const {
  106. module,
  107. chunkGraph,
  108. moduleGraph,
  109. runtimeRequirements,
  110. runtimeTemplate
  111. } = templateContext;
  112. const dep = /** @type {WorkletDependency} */ (dependency);
  113. const block = /** @type {AsyncDependenciesBlock} */ (
  114. moduleGraph.getParentBlock(dependency)
  115. );
  116. const entrypoint = /** @type {Entrypoint} */ (
  117. chunkGraph.getBlockChunkGroup(block)
  118. );
  119. const entryChunk = entrypoint.getEntrypointChunk();
  120. const runtimeChunk = entrypoint.getRuntimeChunk();
  121. const referencedChunks = entryChunk.getAllReferencedChunks();
  122. const runtimeChunkId =
  123. runtimeChunk && runtimeChunk.id !== null ? runtimeChunk.id : null;
  124. // For ESM module output emit the analyzable `new URL("./worklet.<name>.js",
  125. // import.meta.url)` form (literal specifier, no runtime helpers) so other bundlers
  126. // and webpack itself can statically follow the worklet. The worklet chunk links its
  127. // splits via native `import`, so only the entry chunk is referenced.
  128. const analyzableSpecifier =
  129. runtimeTemplate.supportsAnalyzable("url", chunkGraph, module) &&
  130. entryChunk.id !== null
  131. ? runtimeTemplate._getAnalyzableChunkSpecifier(
  132. dep.options.publicPath,
  133. entryChunk,
  134. module,
  135. chunkGraph,
  136. runtimeRequirements
  137. )
  138. : null;
  139. if (analyzableSpecifier !== null) {
  140. source.replace(
  141. dep.callRange[0],
  142. dep.callRange[1] - 1,
  143. `(new URL(/* worklet import */ ${analyzableSpecifier}, ${runtimeTemplate.outputOptions.importMetaName}.url))`
  144. );
  145. return;
  146. }
  147. const workletImportBaseUrl = dep.options.publicPath
  148. ? JSON.stringify(dep.options.publicPath)
  149. : RuntimeGlobals.publicPath;
  150. runtimeRequirements.add(RuntimeGlobals.publicPath);
  151. runtimeRequirements.add(RuntimeGlobals.baseURI);
  152. runtimeRequirements.add(RuntimeGlobals.getChunkScriptFilename);
  153. /**
  154. * @param {string | number} id chunk id
  155. * @returns {string} `new URL(...)` expression for the chunk file
  156. */
  157. const toUrl = (id) =>
  158. `new URL(${workletImportBaseUrl} + ${
  159. RuntimeGlobals.getChunkScriptFilename
  160. }(${JSON.stringify(id)}), ${RuntimeGlobals.baseURI})`;
  161. // Module output: the worklet chunk is an ES module that links its split
  162. // chunks via native `import`, which `addModule` resolves — so add just the
  163. // entry chunk, with no worker-scope shim and no chunk pre-loading.
  164. if (runtimeTemplate.isModule() && entryChunk.id !== null) {
  165. source.replace(
  166. dep.callRange[0],
  167. dep.callRange[1] - 1,
  168. `(${toUrl(entryChunk.id)})`
  169. );
  170. return;
  171. }
  172. // Fast path: a self-contained worklet (a single chunk, no splits or async
  173. // `import()`) needs no chunk-loading runtime and no worker-scope shim, so the
  174. // chunk is `addModule`-d directly with no bootstrap.
  175. if (referencedChunks.size === 1 && runtimeChunkId !== null) {
  176. source.replace(
  177. dep.callRange[0],
  178. dep.callRange[1] - 1,
  179. `(${toUrl(runtimeChunkId)})`
  180. );
  181. return;
  182. }
  183. // A worklet can't load chunks at runtime, so every reachable chunk (initial
  184. // splits and async `import()` targets alike) is pre-added via `addModule`
  185. // from the calling scope. The runtime chunk is added last so it installs the
  186. // chunks pushed before it and runs entry startup with every module present
  187. // (see ImportScriptsChunkLoadingRuntimeModule).
  188. /** @type {(string | number)[]} */
  189. const restChunkIds = [];
  190. for (const chunk of referencedChunks) {
  191. if (chunk !== runtimeChunk && chunk.id !== null) {
  192. restChunkIds.push(chunk.id);
  193. }
  194. }
  195. runtimeRequirements.add(RuntimeGlobals.getWorkletBootstrap);
  196. const add = "__webpack_worklet_add__";
  197. const boot = `${add}(${RuntimeGlobals.getWorkletBootstrap}())`;
  198. // Non-runtime chunks have no ordering constraint, so add them concurrently.
  199. const addOne = runtimeTemplate.returningFunction(`${add}(u)`, "u");
  200. const parallel =
  201. restChunkIds.length > 0
  202. ? `Promise.all([${restChunkIds.map(toUrl).join(", ")}].map(${addOne}))`
  203. : undefined;
  204. // The runtime chunk is added last: it runs entry startup with every module present.
  205. const runtimeAdd =
  206. runtimeChunkId !== null ? `${add}(${toUrl(runtimeChunkId)})` : undefined;
  207. // Emit `async`/`await` only where the target supports async functions;
  208. // otherwise fall back to an equivalent Promise chain (boot → rest → runtime).
  209. let bootstrap;
  210. if (runtimeTemplate.supportsAsyncFunction()) {
  211. bootstrap = Template.asString([
  212. `async function(${add}) {`,
  213. Template.indent(
  214. [
  215. `await ${boot};`,
  216. parallel ? `await ${parallel};` : "",
  217. runtimeAdd ? `await ${runtimeAdd};` : ""
  218. ].filter(Boolean)
  219. ),
  220. "}"
  221. ]);
  222. } else {
  223. let chain = boot;
  224. if (parallel) {
  225. chain += `.then(${runtimeTemplate.returningFunction(parallel)})`;
  226. }
  227. if (runtimeAdd) {
  228. chain += `.then(${runtimeTemplate.returningFunction(runtimeAdd)})`;
  229. }
  230. bootstrap = `function(${add}) { return ${chain}; }`;
  231. }
  232. // The wrapper forwards to the original `addModule` (its callee stays in the
  233. // source, so the receiver is preserved); a plain function keeps it valid on
  234. // ES targets without arrow functions.
  235. source.insert(
  236. dep.range[0],
  237. `(/* worklet bootstrap */ ${bootstrap})(function(${WORKLET_MODULE}) { return `
  238. );
  239. source.replace(
  240. dep.callRange[0],
  241. dep.callRange[1] - 1,
  242. `(${WORKLET_MODULE}); })`
  243. );
  244. }
  245. };
  246. makeSerializable(
  247. WorkletDependency,
  248. "webpack/lib/dependencies/WorkletDependency"
  249. );
  250. WorkletDependency.WORKLET_MODULE = WORKLET_MODULE;
  251. module.exports = WorkletDependency;