LoaderPlugin.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const NormalModule = require("../NormalModule");
  7. const LazySet = require("../util/LazySet");
  8. const LoaderDependency = require("./LoaderDependency");
  9. const LoaderImportDependency = require("./LoaderImportDependency");
  10. /** @typedef {import("webpack-sources").RawSourceMap} RawSourceMap */
  11. /** @typedef {import("../Compilation").DependencyConstructor} DependencyConstructor */
  12. /** @typedef {import("../Compilation").ExecuteModuleExports} ExecuteModuleExports */
  13. /** @typedef {import("../Compilation").ExecuteModuleResult} ExecuteModuleResult */
  14. /** @typedef {import("../Compiler")} Compiler */
  15. /** @typedef {import("../Module").BuildInfo} BuildInfo */
  16. /** @typedef {import("../Module").FileSystemDependencies} FileSystemDependencies */
  17. /**
  18. * Defines the import module callback callback.
  19. * @callback ImportModuleCallback
  20. * @param {(Error | null)=} err error object
  21. * @param {ExecuteModuleExports=} exports exports of the evaluated module
  22. * @returns {void}
  23. */
  24. /**
  25. * Defines the import module options type used by this module.
  26. * @typedef {object} ImportModuleOptions
  27. * @property {string=} layer the target layer
  28. * @property {string=} publicPath the target public path
  29. * @property {string=} baseUri target base uri
  30. */
  31. const PLUGIN_NAME = "LoaderPlugin";
  32. class LoaderPlugin {
  33. /**
  34. * Applies the plugin by registering its hooks on the compiler.
  35. * @param {Compiler} compiler the compiler instance
  36. * @returns {void}
  37. */
  38. apply(compiler) {
  39. compiler.hooks.compilation.tap(
  40. PLUGIN_NAME,
  41. (compilation, { normalModuleFactory }) => {
  42. compilation.dependencyFactories.set(
  43. LoaderDependency,
  44. normalModuleFactory
  45. );
  46. compilation.dependencyFactories.set(
  47. LoaderImportDependency,
  48. normalModuleFactory
  49. );
  50. }
  51. );
  52. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  53. const moduleGraph = compilation.moduleGraph;
  54. NormalModule.getCompilationHooks(compilation).loader.tap(
  55. PLUGIN_NAME,
  56. (loaderContext) => {
  57. loaderContext.loadModule = (request, callback) => {
  58. const dep = new LoaderDependency(request);
  59. dep.loc = {
  60. name: request
  61. };
  62. const factory = compilation.dependencyFactories.get(
  63. /** @type {DependencyConstructor} */
  64. (dep.constructor)
  65. );
  66. if (factory === undefined) {
  67. return callback(
  68. new Error(
  69. `No module factory available for dependency type: ${dep.constructor.name}`
  70. )
  71. );
  72. }
  73. const oldFactorizeQueueContext =
  74. compilation.factorizeQueue.getContext();
  75. compilation.factorizeQueue.setContext("load-module");
  76. const oldAddModuleQueueContext =
  77. compilation.addModuleQueue.getContext();
  78. compilation.addModuleQueue.setContext("load-module");
  79. compilation.buildQueue.increaseParallelism();
  80. compilation.handleModuleCreation(
  81. {
  82. factory,
  83. dependencies: [dep],
  84. originModule:
  85. /** @type {NormalModule} */
  86. (loaderContext._module),
  87. context: loaderContext.context,
  88. recursive: false
  89. },
  90. (err) => {
  91. compilation.factorizeQueue.setContext(oldFactorizeQueueContext);
  92. compilation.addModuleQueue.setContext(oldAddModuleQueueContext);
  93. compilation.buildQueue.decreaseParallelism();
  94. if (err) {
  95. return callback(err);
  96. }
  97. const referencedModule = moduleGraph.getModule(dep);
  98. if (!referencedModule) {
  99. return callback(new Error("Cannot load the module"));
  100. }
  101. if (referencedModule.getNumberOfErrors() > 0) {
  102. return callback(
  103. new Error("The loaded module contains errors")
  104. );
  105. }
  106. const moduleSource = referencedModule.originalSource();
  107. if (!moduleSource) {
  108. return callback(
  109. new Error(
  110. "The module created for a LoaderDependency must have an original source"
  111. )
  112. );
  113. }
  114. /** @type {null | RawSourceMap} */
  115. let map;
  116. /** @type {string | Buffer | undefined} */
  117. let source;
  118. if (moduleSource.sourceAndMap) {
  119. const sourceAndMap = moduleSource.sourceAndMap();
  120. map = sourceAndMap.map;
  121. source = sourceAndMap.source;
  122. } else {
  123. map = moduleSource.map();
  124. source = moduleSource.source();
  125. }
  126. /** @type {FileSystemDependencies} */
  127. const fileDependencies = new LazySet();
  128. /** @type {FileSystemDependencies} */
  129. const contextDependencies = new LazySet();
  130. /** @type {FileSystemDependencies} */
  131. const missingDependencies = new LazySet();
  132. /** @type {FileSystemDependencies} */
  133. const buildDependencies = new LazySet();
  134. referencedModule.addCacheDependencies(
  135. fileDependencies,
  136. contextDependencies,
  137. missingDependencies,
  138. buildDependencies
  139. );
  140. for (const d of fileDependencies) {
  141. loaderContext.addDependency(d);
  142. }
  143. for (const d of contextDependencies) {
  144. loaderContext.addContextDependency(d);
  145. }
  146. for (const d of missingDependencies) {
  147. loaderContext.addMissingDependency(d);
  148. }
  149. for (const d of buildDependencies) {
  150. loaderContext.addBuildDependency(d);
  151. }
  152. return callback(null, source, map, referencedModule);
  153. }
  154. );
  155. };
  156. /**
  157. * Processes the provided request.
  158. * @param {string} request the request string to load the module from
  159. * @param {ImportModuleOptions} options options
  160. * @param {ImportModuleCallback} callback callback returning the exports
  161. * @returns {void}
  162. */
  163. const importModule = (request, options, callback) => {
  164. const dep = new LoaderImportDependency(request);
  165. dep.loc = {
  166. name: request
  167. };
  168. const factory = compilation.dependencyFactories.get(
  169. /** @type {DependencyConstructor} */
  170. (dep.constructor)
  171. );
  172. if (factory === undefined) {
  173. return callback(
  174. new Error(
  175. `No module factory available for dependency type: ${dep.constructor.name}`
  176. )
  177. );
  178. }
  179. const oldFactorizeQueueContext =
  180. compilation.factorizeQueue.getContext();
  181. compilation.factorizeQueue.setContext("import-module");
  182. const oldAddModuleQueueContext =
  183. compilation.addModuleQueue.getContext();
  184. compilation.addModuleQueue.setContext("import-module");
  185. compilation.buildQueue.increaseParallelism();
  186. compilation.handleModuleCreation(
  187. {
  188. factory,
  189. dependencies: [dep],
  190. originModule:
  191. /** @type {NormalModule} */
  192. (loaderContext._module),
  193. contextInfo: {
  194. issuerLayer: options.layer
  195. },
  196. context: loaderContext.context,
  197. connectOrigin: false,
  198. checkCycle: true
  199. },
  200. (err) => {
  201. compilation.factorizeQueue.setContext(oldFactorizeQueueContext);
  202. compilation.addModuleQueue.setContext(oldAddModuleQueueContext);
  203. compilation.buildQueue.decreaseParallelism();
  204. if (err) {
  205. return callback(err);
  206. }
  207. const referencedModule = moduleGraph.getModule(dep);
  208. if (!referencedModule) {
  209. return callback(new Error("Cannot load the module"));
  210. }
  211. compilation.buildQueue.increaseParallelism();
  212. compilation.executeModule(
  213. referencedModule,
  214. {
  215. entryOptions: {
  216. baseUri: options.baseUri,
  217. publicPath: options.publicPath
  218. }
  219. },
  220. (err, result) => {
  221. compilation.buildQueue.decreaseParallelism();
  222. if (err) return callback(err);
  223. const {
  224. fileDependencies,
  225. contextDependencies,
  226. missingDependencies,
  227. buildDependencies,
  228. cacheable,
  229. assets,
  230. exports
  231. } = /** @type {ExecuteModuleResult} */ (result);
  232. for (const d of fileDependencies) {
  233. loaderContext.addDependency(d);
  234. }
  235. for (const d of contextDependencies) {
  236. loaderContext.addContextDependency(d);
  237. }
  238. for (const d of missingDependencies) {
  239. loaderContext.addMissingDependency(d);
  240. }
  241. for (const d of buildDependencies) {
  242. loaderContext.addBuildDependency(d);
  243. }
  244. if (cacheable === false) loaderContext.cacheable(false);
  245. for (const [name, { source, info }] of assets) {
  246. const buildInfo =
  247. /** @type {BuildInfo} */
  248. (
  249. /** @type {NormalModule} */ (loaderContext._module)
  250. .buildInfo
  251. );
  252. if (!buildInfo.assets) {
  253. buildInfo.assets = Object.create(null);
  254. buildInfo.assetsInfo = new Map();
  255. }
  256. /** @type {NonNullable<BuildInfo["assets"]>} */
  257. (buildInfo.assets)[name] = source;
  258. /** @type {NonNullable<BuildInfo["assetsInfo"]>} */
  259. (buildInfo.assetsInfo).set(name, info);
  260. }
  261. callback(null, exports);
  262. }
  263. );
  264. }
  265. );
  266. };
  267. // @ts-expect-error overloading doesn't work
  268. loaderContext.importModule = (request, options, callback) => {
  269. if (!callback) {
  270. return new Promise((resolve, reject) => {
  271. importModule(request, options || {}, (err, result) => {
  272. if (err) reject(err);
  273. else resolve(result);
  274. });
  275. });
  276. }
  277. return importModule(request, options || {}, callback);
  278. };
  279. }
  280. );
  281. });
  282. }
  283. }
  284. module.exports = LoaderPlugin;