UNPKG

6.61 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5"use strict";
6
7const asyncLib = require("neo-async");
8const path = require("path");
9
10const {
11 Tapable,
12 AsyncSeriesWaterfallHook,
13 SyncWaterfallHook
14} = require("tapable");
15const ContextModule = require("./ContextModule");
16const ContextElementDependency = require("./dependencies/ContextElementDependency");
17
18/** @typedef {import("./Module")} Module */
19
20const EMPTY_RESOLVE_OPTIONS = {};
21
22module.exports = class ContextModuleFactory extends Tapable {
23 constructor(resolverFactory) {
24 super();
25 this.hooks = {
26 /** @type {AsyncSeriesWaterfallHook<TODO>} */
27 beforeResolve: new AsyncSeriesWaterfallHook(["data"]),
28 /** @type {AsyncSeriesWaterfallHook<TODO>} */
29 afterResolve: new AsyncSeriesWaterfallHook(["data"]),
30 /** @type {SyncWaterfallHook<string[]>} */
31 contextModuleFiles: new SyncWaterfallHook(["files"]),
32 /** @type {SyncWaterfallHook<TODO[]>} */
33 alternatives: new AsyncSeriesWaterfallHook(["modules"])
34 };
35 this._pluginCompat.tap("ContextModuleFactory", options => {
36 switch (options.name) {
37 case "before-resolve":
38 case "after-resolve":
39 case "alternatives":
40 options.async = true;
41 break;
42 }
43 });
44 this.resolverFactory = resolverFactory;
45 }
46
47 create(data, callback) {
48 const context = data.context;
49 const dependencies = data.dependencies;
50 const resolveOptions = data.resolveOptions;
51 const dependency = dependencies[0];
52 this.hooks.beforeResolve.callAsync(
53 Object.assign(
54 {
55 context: context,
56 dependencies: dependencies,
57 resolveOptions
58 },
59 dependency.options
60 ),
61 (err, beforeResolveResult) => {
62 if (err) return callback(err);
63
64 // Ignored
65 if (!beforeResolveResult) return callback();
66
67 const context = beforeResolveResult.context;
68 const request = beforeResolveResult.request;
69 const resolveOptions = beforeResolveResult.resolveOptions;
70
71 let loaders,
72 resource,
73 loadersPrefix = "";
74 const idx = request.lastIndexOf("!");
75 if (idx >= 0) {
76 let loadersRequest = request.substr(0, idx + 1);
77 let i;
78 for (
79 i = 0;
80 i < loadersRequest.length && loadersRequest[i] === "!";
81 i++
82 ) {
83 loadersPrefix += "!";
84 }
85 loadersRequest = loadersRequest
86 .substr(i)
87 .replace(/!+$/, "")
88 .replace(/!!+/g, "!");
89 if (loadersRequest === "") {
90 loaders = [];
91 } else {
92 loaders = loadersRequest.split("!");
93 }
94 resource = request.substr(idx + 1);
95 } else {
96 loaders = [];
97 resource = request;
98 }
99
100 const contextResolver = this.resolverFactory.get(
101 "context",
102 resolveOptions || EMPTY_RESOLVE_OPTIONS
103 );
104 const loaderResolver = this.resolverFactory.get(
105 "loader",
106 EMPTY_RESOLVE_OPTIONS
107 );
108
109 asyncLib.parallel(
110 [
111 callback => {
112 contextResolver.resolve(
113 {},
114 context,
115 resource,
116 {},
117 (err, result) => {
118 if (err) return callback(err);
119 callback(null, result);
120 }
121 );
122 },
123 callback => {
124 asyncLib.map(
125 loaders,
126 (loader, callback) => {
127 loaderResolver.resolve(
128 {},
129 context,
130 loader,
131 {},
132 (err, result) => {
133 if (err) return callback(err);
134 callback(null, result);
135 }
136 );
137 },
138 callback
139 );
140 }
141 ],
142 (err, result) => {
143 if (err) return callback(err);
144
145 this.hooks.afterResolve.callAsync(
146 Object.assign(
147 {
148 addon:
149 loadersPrefix +
150 result[1].join("!") +
151 (result[1].length > 0 ? "!" : ""),
152 resource: result[0],
153 resolveDependencies: this.resolveDependencies.bind(this)
154 },
155 beforeResolveResult
156 ),
157 (err, result) => {
158 if (err) return callback(err);
159
160 // Ignored
161 if (!result) return callback();
162
163 return callback(
164 null,
165 new ContextModule(result.resolveDependencies, result)
166 );
167 }
168 );
169 }
170 );
171 }
172 );
173 }
174
175 resolveDependencies(fs, options, callback) {
176 const cmf = this;
177 let resource = options.resource;
178 let resourceQuery = options.resourceQuery;
179 let recursive = options.recursive;
180 let regExp = options.regExp;
181 let include = options.include;
182 let exclude = options.exclude;
183 if (!regExp || !resource) return callback(null, []);
184
185 const addDirectory = (directory, callback) => {
186 fs.readdir(directory, (err, files) => {
187 if (err) return callback(err);
188 files = cmf.hooks.contextModuleFiles.call(files);
189 if (!files || files.length === 0) return callback(null, []);
190 asyncLib.map(
191 files.filter(p => p.indexOf(".") !== 0),
192 (segment, callback) => {
193 const subResource = path.join(directory, segment);
194
195 if (!exclude || !subResource.match(exclude)) {
196 fs.stat(subResource, (err, stat) => {
197 if (err) {
198 if (err.code === "ENOENT") {
199 // ENOENT is ok here because the file may have been deleted between
200 // the readdir and stat calls.
201 return callback();
202 } else {
203 return callback(err);
204 }
205 }
206
207 if (stat.isDirectory()) {
208 if (!recursive) return callback();
209 addDirectory.call(this, subResource, callback);
210 } else if (
211 stat.isFile() &&
212 (!include || subResource.match(include))
213 ) {
214 const obj = {
215 context: resource,
216 request:
217 "." +
218 subResource.substr(resource.length).replace(/\\/g, "/")
219 };
220
221 this.hooks.alternatives.callAsync(
222 [obj],
223 (err, alternatives) => {
224 if (err) return callback(err);
225 alternatives = alternatives
226 .filter(obj => regExp.test(obj.request))
227 .map(obj => {
228 const dep = new ContextElementDependency(
229 obj.request + resourceQuery,
230 obj.request
231 );
232 dep.optional = true;
233 return dep;
234 });
235 callback(null, alternatives);
236 }
237 );
238 } else {
239 callback();
240 }
241 });
242 } else {
243 callback();
244 }
245 },
246 (err, result) => {
247 if (err) return callback(err);
248
249 if (!result) return callback(null, []);
250
251 callback(
252 null,
253 result.filter(Boolean).reduce((a, i) => a.concat(i), [])
254 );
255 }
256 );
257 });
258 };
259
260 addDirectory(resource, callback);
261 }
262};