LoaderPlugin.js 9.3 KB

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