LoaderPlugin.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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("../Compilation").DepConstructor} DepConstructor */
  11. /** @typedef {import("../Compiler")} Compiler */
  12. /** @typedef {import("../Module")} Module */
  13. /**
  14. * @callback LoadModuleCallback
  15. * @param {(Error | null)=} err error object
  16. * @param {string | Buffer=} source source code
  17. * @param {object=} map source map
  18. * @param {Module=} module loaded module if successful
  19. */
  20. /**
  21. * @callback ImportModuleCallback
  22. * @param {(Error | null)=} err error object
  23. * @param {any=} exports exports of the evaluated module
  24. */
  25. /**
  26. * @typedef {Object} ImportModuleOptions
  27. * @property {string=} layer the target layer
  28. * @property {string=} publicPath the target public path
  29. */
  30. class LoaderPlugin {
  31. /**
  32. * @param {Object} options options
  33. */
  34. constructor(options = {}) {}
  35. /**
  36. * Apply the plugin
  37. * @param {Compiler} compiler the compiler instance
  38. * @returns {void}
  39. */
  40. apply(compiler) {
  41. compiler.hooks.compilation.tap(
  42. "LoaderPlugin",
  43. (compilation, { normalModuleFactory }) => {
  44. compilation.dependencyFactories.set(
  45. LoaderDependency,
  46. normalModuleFactory
  47. );
  48. compilation.dependencyFactories.set(
  49. LoaderImportDependency,
  50. normalModuleFactory
  51. );
  52. }
  53. );
  54. compiler.hooks.compilation.tap("LoaderPlugin", compilation => {
  55. const moduleGraph = compilation.moduleGraph;
  56. NormalModule.getCompilationHooks(compilation).loader.tap(
  57. "LoaderPlugin",
  58. loaderContext => {
  59. /**
  60. * @param {string} request the request string to load the module from
  61. * @param {LoadModuleCallback} callback callback returning the loaded module or error
  62. * @returns {void}
  63. */
  64. loaderContext.loadModule = (request, callback) => {
  65. const dep = new LoaderDependency(request);
  66. dep.loc = {
  67. name: request
  68. };
  69. const factory = compilation.dependencyFactories.get(
  70. /** @type {DepConstructor} */ (dep.constructor)
  71. );
  72. if (factory === undefined) {
  73. return callback(
  74. new Error(
  75. `No module factory available for dependency type: ${dep.constructor.name}`
  76. )
  77. );
  78. }
  79. compilation.buildQueue.increaseParallelism();
  80. compilation.handleModuleCreation(
  81. {
  82. factory,
  83. dependencies: [dep],
  84. originModule: loaderContext._module,
  85. context: loaderContext.context,
  86. recursive: false
  87. },
  88. err => {
  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 source, map;
  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. compilation.buildQueue.increaseParallelism();
  167. compilation.handleModuleCreation(
  168. {
  169. factory,
  170. dependencies: [dep],
  171. originModule: loaderContext._module,
  172. contextInfo: {
  173. issuerLayer: options.layer
  174. },
  175. context: loaderContext.context,
  176. connectOrigin: false
  177. },
  178. err => {
  179. compilation.buildQueue.decreaseParallelism();
  180. if (err) {
  181. return callback(err);
  182. }
  183. const referencedModule = moduleGraph.getModule(dep);
  184. if (!referencedModule) {
  185. return callback(new Error("Cannot load the module"));
  186. }
  187. compilation.executeModule(
  188. referencedModule,
  189. {
  190. entryOptions: {
  191. publicPath: options.publicPath
  192. }
  193. },
  194. (err, result) => {
  195. if (err) return callback(err);
  196. for (const d of result.fileDependencies) {
  197. loaderContext.addDependency(d);
  198. }
  199. for (const d of result.contextDependencies) {
  200. loaderContext.addContextDependency(d);
  201. }
  202. for (const d of result.missingDependencies) {
  203. loaderContext.addMissingDependency(d);
  204. }
  205. for (const d of result.buildDependencies) {
  206. loaderContext.addBuildDependency(d);
  207. }
  208. if (result.cacheable === false)
  209. loaderContext.cacheable(false);
  210. for (const [name, { source, info }] of result.assets) {
  211. const { buildInfo } = loaderContext._module;
  212. if (!buildInfo.assets) {
  213. buildInfo.assets = Object.create(null);
  214. buildInfo.assetsInfo = new Map();
  215. }
  216. buildInfo.assets[name] = source;
  217. buildInfo.assetsInfo.set(name, info);
  218. }
  219. callback(null, result.exports);
  220. }
  221. );
  222. }
  223. );
  224. };
  225. /**
  226. * @param {string} request the request string to load the module from
  227. * @param {ImportModuleOptions} options options
  228. * @param {ImportModuleCallback=} callback callback returning the exports
  229. * @returns {Promise<any> | void} exports
  230. */
  231. loaderContext.importModule = (request, options, callback) => {
  232. if (!callback) {
  233. return new Promise((resolve, reject) => {
  234. importModule(request, options || {}, (err, result) => {
  235. if (err) reject(err);
  236. else resolve(result);
  237. });
  238. });
  239. }
  240. return importModule(request, options || {}, callback);
  241. };
  242. }
  243. );
  244. });
  245. }
  246. }
  247. module.exports = LoaderPlugin;