| /* |
| MIT License http://www.opensource.org/licenses/mit-license.php |
| Author Tobias Koppers @sokra |
| */ |
| "use strict"; |
| |
| const asyncLib = require("neo-async"); |
| const path = require("path"); |
| |
| const { |
| Tapable, |
| AsyncSeriesWaterfallHook, |
| SyncWaterfallHook |
| } = require("tapable"); |
| const ContextModule = require("./ContextModule"); |
| const ContextElementDependency = require("./dependencies/ContextElementDependency"); |
| |
| /** @typedef {import("./Module")} Module */ |
| |
| const EMPTY_RESOLVE_OPTIONS = {}; |
| |
| module.exports = class ContextModuleFactory extends Tapable { |
| constructor(resolverFactory) { |
| super(); |
| this.hooks = { |
| /** @type {AsyncSeriesWaterfallHook<TODO>} */ |
| beforeResolve: new AsyncSeriesWaterfallHook(["data"]), |
| /** @type {AsyncSeriesWaterfallHook<TODO>} */ |
| afterResolve: new AsyncSeriesWaterfallHook(["data"]), |
| /** @type {SyncWaterfallHook<string[]>} */ |
| contextModuleFiles: new SyncWaterfallHook(["files"]), |
| /** @type {SyncWaterfallHook<TODO[]>} */ |
| 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) { |
| let loadersRequest = request.substr(0, idx + 1); |
| let i; |
| for ( |
| i = 0; |
| i < loadersRequest.length && loadersRequest[i] === "!"; |
| i++ |
| ) { |
| loadersPrefix += "!"; |
| } |
| loadersRequest = loadersRequest |
| .substr(i) |
| .replace(/!+$/, "") |
| .replace(/!!+/g, "!"); |
| if (loadersRequest === "") { |
| loaders = []; |
| } else { |
| loaders = loadersRequest.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), |
| (segment, callback) => { |
| const subResource = path.join(directory, segment); |
| |
| 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); |
| } |
| }; |