LoaderPlugin.js 9.0 KB

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