LoaderPlugin.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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. * @property {string=} baseUri target base uri
  30. */
  31. class LoaderPlugin {
  32. /**
  33. * @param {object} options options
  34. */
  35. constructor(options = {}) {}
  36. /**
  37. * Apply the plugin
  38. * @param {Compiler} compiler the compiler instance
  39. * @returns {void}
  40. */
  41. apply(compiler) {
  42. compiler.hooks.compilation.tap(
  43. "LoaderPlugin",
  44. (compilation, { normalModuleFactory }) => {
  45. compilation.dependencyFactories.set(
  46. LoaderDependency,
  47. normalModuleFactory
  48. );
  49. compilation.dependencyFactories.set(
  50. LoaderImportDependency,
  51. normalModuleFactory
  52. );
  53. }
  54. );
  55. compiler.hooks.compilation.tap("LoaderPlugin", compilation => {
  56. const moduleGraph = compilation.moduleGraph;
  57. NormalModule.getCompilationHooks(compilation).loader.tap(
  58. "LoaderPlugin",
  59. loaderContext => {
  60. /**
  61. * @param {string} request the request string to load the module from
  62. * @param {LoadModuleCallback} callback callback returning the loaded module or error
  63. * @returns {void}
  64. */
  65. loaderContext.loadModule = (request, callback) => {
  66. const dep = new LoaderDependency(request);
  67. dep.loc = {
  68. name: request
  69. };
  70. const factory = compilation.dependencyFactories.get(
  71. /** @type {DepConstructor} */ (dep.constructor)
  72. );
  73. if (factory === undefined) {
  74. return callback(
  75. new Error(
  76. `No module factory available for dependency type: ${dep.constructor.name}`
  77. )
  78. );
  79. }
  80. compilation.buildQueue.increaseParallelism();
  81. compilation.handleModuleCreation(
  82. {
  83. factory,
  84. dependencies: [dep],
  85. originModule: loaderContext._module,
  86. context: loaderContext.context,
  87. recursive: false
  88. },
  89. err => {
  90. compilation.buildQueue.decreaseParallelism();
  91. if (err) {
  92. return callback(err);
  93. }
  94. const referencedModule = moduleGraph.getModule(dep);
  95. if (!referencedModule) {
  96. return callback(new Error("Cannot load the module"));
  97. }
  98. if (referencedModule.getNumberOfErrors() > 0) {
  99. return callback(
  100. new Error("The loaded module contains errors")
  101. );
  102. }
  103. const moduleSource = referencedModule.originalSource();
  104. if (!moduleSource) {
  105. return callback(
  106. new Error(
  107. "The module created for a LoaderDependency must have an original source"
  108. )
  109. );
  110. }
  111. let source, map;
  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. compilation.buildQueue.increaseParallelism();
  168. compilation.handleModuleCreation(
  169. {
  170. factory,
  171. dependencies: [dep],
  172. originModule: loaderContext._module,
  173. contextInfo: {
  174. issuerLayer: options.layer
  175. },
  176. context: loaderContext.context,
  177. connectOrigin: false,
  178. checkCycle: true
  179. },
  180. err => {
  181. compilation.buildQueue.decreaseParallelism();
  182. if (err) {
  183. return callback(err);
  184. }
  185. const referencedModule = moduleGraph.getModule(dep);
  186. if (!referencedModule) {
  187. return callback(new Error("Cannot load the module"));
  188. }
  189. compilation.executeModule(
  190. referencedModule,
  191. {
  192. entryOptions: {
  193. baseUri: options.baseUri,
  194. publicPath: options.publicPath
  195. }
  196. },
  197. (err, result) => {
  198. if (err) return callback(err);
  199. for (const d of result.fileDependencies) {
  200. loaderContext.addDependency(d);
  201. }
  202. for (const d of result.contextDependencies) {
  203. loaderContext.addContextDependency(d);
  204. }
  205. for (const d of result.missingDependencies) {
  206. loaderContext.addMissingDependency(d);
  207. }
  208. for (const d of result.buildDependencies) {
  209. loaderContext.addBuildDependency(d);
  210. }
  211. if (result.cacheable === false)
  212. loaderContext.cacheable(false);
  213. for (const [name, { source, info }] of result.assets) {
  214. const { buildInfo } = loaderContext._module;
  215. if (!buildInfo.assets) {
  216. buildInfo.assets = Object.create(null);
  217. buildInfo.assetsInfo = new Map();
  218. }
  219. buildInfo.assets[name] = source;
  220. buildInfo.assetsInfo.set(name, info);
  221. }
  222. callback(null, result.exports);
  223. }
  224. );
  225. }
  226. );
  227. };
  228. /**
  229. * @param {string} request the request string to load the module from
  230. * @param {ImportModuleOptions} options options
  231. * @param {ImportModuleCallback=} callback callback returning the exports
  232. * @returns {Promise<any> | void} exports
  233. */
  234. loaderContext.importModule = (request, options, callback) => {
  235. if (!callback) {
  236. return new Promise((resolve, reject) => {
  237. importModule(request, options || {}, (err, result) => {
  238. if (err) reject(err);
  239. else resolve(result);
  240. });
  241. });
  242. }
  243. return importModule(request, options || {}, callback);
  244. };
  245. }
  246. );
  247. });
  248. }
  249. }
  250. module.exports = LoaderPlugin;