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