/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const asyncLib = require("async"); const path = require("path"); const Tapable = require("tapable").Tapable; const AsyncSeriesWaterfallHook = require("tapable").AsyncSeriesWaterfallHook; const SyncWaterfallHook = require("tapable").SyncWaterfallHook; const ContextModule = require("./ContextModule"); const ContextElementDependency = require("./dependencies/ContextElementDependency"); const EMPTY_RESOLVE_OPTIONS = {}; module.exports = class ContextModuleFactory extends Tapable { constructor(resolverFactory) { super(); this.hooks = { beforeResolve: new AsyncSeriesWaterfallHook(["data"]), afterResolve: new AsyncSeriesWaterfallHook(["data"]), contextModuleFiles: new SyncWaterfallHook(["files"]), alternatives: new AsyncSeriesWaterfallHook(["modules"]) }; this._pluginCompat.tap("ContextModuleFactory", options => { switch(options.name) { case "before-resolve": case "after-resolve": case "alternatives": options.async = true; break; } }); this.resolverFactory = resolverFactory; } create(data, callback) { const context = data.context; const dependencies = data.dependencies; const resolveOptions = data.resolveOptions; const dependency = dependencies[0]; this.hooks.beforeResolve.callAsync(Object.assign({ context: context, dependencies: dependencies, resolveOptions }, dependency.options), (err, beforeResolveResult) => { if(err) return callback(err); // Ignored if(!beforeResolveResult) return callback(); const context = beforeResolveResult.context; const request = beforeResolveResult.request; const resolveOptions = beforeResolveResult.resolveOptions; let loaders, resource, loadersPrefix = ""; const idx = request.lastIndexOf("!"); if(idx >= 0) { loaders = request.substr(0, idx + 1); let i; for(i = 0; i < loaders.length && loaders[i] === "!"; i++) { loadersPrefix += "!"; } loaders = loaders.substr(i).replace(/!+$/, "").replace(/!!+/g, "!"); if(loaders === "") loaders = []; else loaders = loaders.split("!"); resource = request.substr(idx + 1); } else { loaders = []; resource = request; } const contextResolver = this.resolverFactory.get("context", resolveOptions || EMPTY_RESOLVE_OPTIONS); const loaderResolver = this.resolverFactory.get("loader", EMPTY_RESOLVE_OPTIONS); asyncLib.parallel([ callback => { contextResolver.resolve({}, context, resource, {}, (err, result) => { if(err) return callback(err); callback(null, result); }); }, callback => { asyncLib.map(loaders, (loader, callback) => { loaderResolver.resolve({}, context, loader, {}, (err, result) => { if(err) return callback(err); callback(null, result); }); }, callback); } ], (err, result) => { if(err) return callback(err); this.hooks.afterResolve.callAsync(Object.assign({ addon: loadersPrefix + result[1].join("!") + (result[1].length > 0 ? "!" : ""), resource: result[0], resolveDependencies: this.resolveDependencies.bind(this) }, beforeResolveResult), (err, result) => { if(err) return callback(err); // Ignored if(!result) return callback(); return callback(null, new ContextModule(result.resolveDependencies, result)); }); }); }); } resolveDependencies(fs, options, callback) { const cmf = this; let resource = options.resource; let resourceQuery = options.resourceQuery; let recursive = options.recursive; let regExp = options.regExp; let include = options.include; let exclude = options.exclude; if(!regExp || !resource) return callback(null, []); const addDirectory = (directory, callback) => { fs.readdir(directory, (err, files) => { if(err) return callback(err); files = cmf.hooks.contextModuleFiles.call(files); if(!files || files.length === 0) return callback(null, []); asyncLib.map(files.filter(p => p.indexOf(".") !== 0), (seqment, callback) => { const subResource = path.join(directory, seqment); if(!exclude || !subResource.match(exclude)) { fs.stat(subResource, (err, stat) => { if(err) { if(err.code === "ENOENT") { // ENOENT is ok here because the file may have been deleted between // the readdir and stat calls. return callback(); } else { return callback(err); } } if(stat.isDirectory()) { if(!recursive) return callback(); addDirectory.call(this, subResource, callback); } else if(stat.isFile() && (!include || subResource.match(include))) { const obj = { context: resource, request: "." + subResource.substr(resource.length).replace(/\\/g, "/") }; this.hooks.alternatives.callAsync([obj], (err, alternatives) => { if(err) return callback(err); alternatives = alternatives.filter(obj => regExp.test(obj.request)).map(obj => { const dep = new ContextElementDependency(obj.request + resourceQuery, obj.request); dep.optional = true; return dep; }); callback(null, alternatives); }); } else callback(); }); } else callback(); }, (err, result) => { if(err) return callback(err); if(!result) return callback(null, []); callback(null, result.filter(Boolean).reduce((a, i) => a.concat(i), [])); }); }); }; addDirectory(resource, callback); } };