LoaderPlugin.js 9.1 KB

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