123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- /*
- 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);
- }
- };
|