LoaderPlugin.js 9.0 KB

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