1 | 'use strict';
|
2 |
|
3 | const findRoot = require('find-root');
|
4 | const path = require('path');
|
5 | const isEqual = require('lodash/isEqual');
|
6 | const find = require('array.prototype.find');
|
7 | const interpret = require('interpret');
|
8 | const fs = require('fs');
|
9 | const isCore = require('is-core-module');
|
10 | const resolve = require('resolve/sync');
|
11 | const semver = require('semver');
|
12 | const hasOwn = require('hasown');
|
13 | const isRegex = require('is-regex');
|
14 |
|
15 | const log = require('debug')('eslint-plugin-import:resolver:webpack');
|
16 |
|
17 | exports.interfaceVersion = 2;
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | exports.resolve = function (source, file, settings) {
|
38 |
|
39 |
|
40 | const finalBang = source.lastIndexOf('!');
|
41 | if (finalBang >= 0) {
|
42 | source = source.slice(finalBang + 1);
|
43 | }
|
44 |
|
45 |
|
46 | const finalQuestionMark = source.lastIndexOf('?');
|
47 | if (finalQuestionMark >= 0) {
|
48 | source = source.slice(0, finalQuestionMark);
|
49 | }
|
50 |
|
51 | let webpackConfig;
|
52 |
|
53 | const _configPath = settings && settings.config;
|
54 | |
55 |
|
56 |
|
57 |
|
58 | const cwd = settings && settings.cwd;
|
59 | const configIndex = settings && settings['config-index'];
|
60 | const env = settings && settings.env;
|
61 | const argv = settings && typeof settings.argv !== 'undefined' ? settings.argv : {};
|
62 | let packageDir;
|
63 |
|
64 | let configPath = typeof _configPath === 'string' && _configPath.startsWith('.')
|
65 | ? path.resolve(_configPath)
|
66 | : _configPath;
|
67 |
|
68 | log('Config path from settings:', configPath);
|
69 |
|
70 |
|
71 | if (!configPath || typeof configPath === 'string') {
|
72 |
|
73 |
|
74 | if (!configPath || !path.isAbsolute(configPath)) {
|
75 |
|
76 | packageDir = findRoot(path.resolve(file));
|
77 | if (!packageDir) { throw new Error('package not found above ' + file); }
|
78 | }
|
79 |
|
80 | configPath = findConfigPath(configPath, packageDir);
|
81 |
|
82 | log('Config path resolved to:', configPath);
|
83 | if (configPath) {
|
84 | try {
|
85 | webpackConfig = require(configPath);
|
86 | } catch (e) {
|
87 | console.log('Error resolving webpackConfig', e);
|
88 | throw e;
|
89 | }
|
90 | } else {
|
91 | log('No config path found relative to', file, '; using {}');
|
92 | webpackConfig = {};
|
93 | }
|
94 |
|
95 | if (webpackConfig && webpackConfig.default) {
|
96 | log('Using ES6 module "default" key instead of module.exports.');
|
97 | webpackConfig = webpackConfig.default;
|
98 | }
|
99 |
|
100 | } else {
|
101 | webpackConfig = configPath;
|
102 | configPath = null;
|
103 | }
|
104 |
|
105 | if (typeof webpackConfig === 'function') {
|
106 | webpackConfig = webpackConfig(env, argv);
|
107 | }
|
108 |
|
109 | if (Array.isArray(webpackConfig)) {
|
110 | webpackConfig = webpackConfig.map((cfg) => {
|
111 | if (typeof cfg === 'function') {
|
112 | return cfg(env, argv);
|
113 | }
|
114 |
|
115 | return cfg;
|
116 | });
|
117 |
|
118 | if (typeof configIndex !== 'undefined' && webpackConfig.length > configIndex) {
|
119 | webpackConfig = webpackConfig[configIndex];
|
120 | } else {
|
121 | webpackConfig = find(webpackConfig, function findFirstWithResolve(config) {
|
122 | return !!config.resolve;
|
123 | });
|
124 | }
|
125 | }
|
126 |
|
127 | if (typeof webpackConfig.then === 'function') {
|
128 | webpackConfig = {};
|
129 |
|
130 | console.warn('Webpack config returns a `Promise`; that signature is not supported at the moment. Using empty object instead.');
|
131 | }
|
132 |
|
133 | if (webpackConfig == null) {
|
134 | webpackConfig = {};
|
135 |
|
136 | console.warn('No webpack configuration with a "resolve" field found. Using empty object instead.');
|
137 | }
|
138 |
|
139 | log('Using config: ', webpackConfig);
|
140 |
|
141 | const resolveSync = getResolveSync(configPath, webpackConfig, cwd);
|
142 |
|
143 |
|
144 | if (findExternal(source, webpackConfig.externals, path.dirname(file), resolveSync)) {
|
145 | return { found: true, path: null };
|
146 | }
|
147 |
|
148 |
|
149 |
|
150 | try {
|
151 | return { found: true, path: resolveSync(path.dirname(file), source) };
|
152 | } catch (err) {
|
153 | if (isCore(source)) {
|
154 | return { found: true, path: null };
|
155 | }
|
156 |
|
157 | log('Error during module resolution:', err);
|
158 | return { found: false };
|
159 | }
|
160 | };
|
161 |
|
162 | const MAX_CACHE = 10;
|
163 | const _cache = [];
|
164 | function getResolveSync(configPath, webpackConfig, cwd) {
|
165 | const cacheKey = { configPath, webpackConfig };
|
166 | let cached = find(_cache, function (entry) { return isEqual(entry.key, cacheKey); });
|
167 | if (!cached) {
|
168 | cached = {
|
169 | key: cacheKey,
|
170 | value: createResolveSync(configPath, webpackConfig, cwd),
|
171 | };
|
172 |
|
173 | if (_cache.unshift(cached) > MAX_CACHE) {
|
174 | _cache.pop();
|
175 | }
|
176 | }
|
177 | return cached.value;
|
178 | }
|
179 |
|
180 | function createResolveSync(configPath, webpackConfig, cwd) {
|
181 | let webpackRequire;
|
182 | let basedir = null;
|
183 |
|
184 | if (typeof configPath === 'string') {
|
185 |
|
186 | basedir = cwd || path.dirname(configPath);
|
187 | log(`Attempting to load webpack path from ${basedir}`);
|
188 | }
|
189 |
|
190 | try {
|
191 |
|
192 | const webpackFilename = resolve('webpack', { basedir, preserveSymlinks: false });
|
193 | const webpackResolveOpts = { basedir: path.dirname(webpackFilename), preserveSymlinks: false };
|
194 |
|
195 | webpackRequire = function (id) {
|
196 | return require(resolve(id, webpackResolveOpts));
|
197 | };
|
198 | } catch (e) {
|
199 |
|
200 |
|
201 | log('Using bundled enhanced-resolve.');
|
202 | webpackRequire = require;
|
203 | }
|
204 |
|
205 | const enhancedResolvePackage = webpackRequire('enhanced-resolve/package.json');
|
206 | const enhancedResolveVersion = enhancedResolvePackage.version;
|
207 | log('enhanced-resolve version:', enhancedResolveVersion);
|
208 |
|
209 | const resolveConfig = webpackConfig.resolve || {};
|
210 |
|
211 | if (semver.major(enhancedResolveVersion) >= 2) {
|
212 | return createWebpack2ResolveSync(webpackRequire, resolveConfig);
|
213 | }
|
214 |
|
215 | return createWebpack1ResolveSync(webpackRequire, resolveConfig, webpackConfig.plugins);
|
216 | }
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | const webpack2DefaultResolveConfig = {
|
224 | unsafeCache: true,
|
225 | modules: ['node_modules'],
|
226 | extensions: ['.js', '.json'],
|
227 | aliasFields: ['browser'],
|
228 | mainFields: ['browser', 'module', 'main'],
|
229 | };
|
230 |
|
231 | function createWebpack2ResolveSync(webpackRequire, resolveConfig) {
|
232 | const EnhancedResolve = webpackRequire('enhanced-resolve');
|
233 |
|
234 | return EnhancedResolve.create.sync(Object.assign({}, webpack2DefaultResolveConfig, resolveConfig));
|
235 | }
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 | const webpack1DefaultMains = [
|
242 | 'webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main',
|
243 | ];
|
244 |
|
245 |
|
246 |
|
247 | function createWebpack1ResolveSync(webpackRequire, resolveConfig, plugins) {
|
248 | const Resolver = webpackRequire('enhanced-resolve/lib/Resolver');
|
249 | const SyncNodeJsInputFileSystem = webpackRequire('enhanced-resolve/lib/SyncNodeJsInputFileSystem');
|
250 |
|
251 | const ModuleAliasPlugin = webpackRequire('enhanced-resolve/lib/ModuleAliasPlugin');
|
252 | const ModulesInDirectoriesPlugin = webpackRequire('enhanced-resolve/lib/ModulesInDirectoriesPlugin');
|
253 | const ModulesInRootPlugin = webpackRequire('enhanced-resolve/lib/ModulesInRootPlugin');
|
254 | const ModuleAsFilePlugin = webpackRequire('enhanced-resolve/lib/ModuleAsFilePlugin');
|
255 | const ModuleAsDirectoryPlugin = webpackRequire('enhanced-resolve/lib/ModuleAsDirectoryPlugin');
|
256 | const DirectoryDescriptionFilePlugin = webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFilePlugin');
|
257 | const DirectoryDefaultFilePlugin = webpackRequire('enhanced-resolve/lib/DirectoryDefaultFilePlugin');
|
258 | const FileAppendPlugin = webpackRequire('enhanced-resolve/lib/FileAppendPlugin');
|
259 | const ResultSymlinkPlugin = webpackRequire('enhanced-resolve/lib/ResultSymlinkPlugin');
|
260 | const DirectoryDescriptionFileFieldAliasPlugin = webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFileFieldAliasPlugin');
|
261 |
|
262 | const resolver = new Resolver(new SyncNodeJsInputFileSystem());
|
263 |
|
264 | resolver.apply(
|
265 | resolveConfig.packageAlias
|
266 | ? new DirectoryDescriptionFileFieldAliasPlugin('package.json', resolveConfig.packageAlias)
|
267 | : function () {},
|
268 | new ModuleAliasPlugin(resolveConfig.alias || {}),
|
269 | makeRootPlugin(ModulesInRootPlugin, 'module', resolveConfig.root),
|
270 | new ModulesInDirectoriesPlugin(
|
271 | 'module',
|
272 | resolveConfig.modulesDirectories || resolveConfig.modules || ['web_modules', 'node_modules']
|
273 | ),
|
274 | makeRootPlugin(ModulesInRootPlugin, 'module', resolveConfig.fallback),
|
275 | new ModuleAsFilePlugin('module'),
|
276 | new ModuleAsDirectoryPlugin('module'),
|
277 | new DirectoryDescriptionFilePlugin(
|
278 | 'package.json',
|
279 | ['module', 'jsnext:main'].concat(resolveConfig.packageMains || webpack1DefaultMains)
|
280 | ),
|
281 | new DirectoryDefaultFilePlugin(['index']),
|
282 | new FileAppendPlugin(resolveConfig.extensions || ['', '.webpack.js', '.web.js', '.js']),
|
283 | new ResultSymlinkPlugin()
|
284 | );
|
285 |
|
286 | const resolvePlugins = [];
|
287 |
|
288 |
|
289 | if (plugins) {
|
290 | plugins.forEach(function (plugin) {
|
291 | if (
|
292 | plugin.constructor
|
293 | && plugin.constructor.name === 'ResolverPlugin'
|
294 | && Array.isArray(plugin.plugins)
|
295 | ) {
|
296 | resolvePlugins.push.apply(resolvePlugins, plugin.plugins);
|
297 | }
|
298 | });
|
299 | }
|
300 |
|
301 | resolver.apply.apply(resolver, resolvePlugins);
|
302 |
|
303 | return function () {
|
304 | return resolver.resolveSync.apply(resolver, arguments);
|
305 | };
|
306 | }
|
307 |
|
308 |
|
309 |
|
310 | function makeRootPlugin(ModulesInRootPlugin, name, root) {
|
311 | if (typeof root === 'string') {
|
312 | return new ModulesInRootPlugin(name, root);
|
313 | } else if (Array.isArray(root)) {
|
314 | return function() {
|
315 | root.forEach(function (root) {
|
316 | this.apply(new ModulesInRootPlugin(name, root));
|
317 | }, this);
|
318 | };
|
319 | }
|
320 | return function () {};
|
321 | }
|
322 |
|
323 |
|
324 | function findExternal(source, externals, context, resolveSync) {
|
325 | if (!externals) { return false; }
|
326 |
|
327 |
|
328 | if (typeof externals === 'string') { return source === externals; }
|
329 |
|
330 |
|
331 | if (Array.isArray(externals)) {
|
332 | return externals.some(function (e) { return findExternal(source, e, context, resolveSync); });
|
333 | }
|
334 |
|
335 | if (isRegex(externals)) {
|
336 | return externals.test(source);
|
337 | }
|
338 |
|
339 | if (typeof externals === 'function') {
|
340 | let functionExternalFound = false;
|
341 | const callback = function (err, value) {
|
342 | if (err) {
|
343 | functionExternalFound = false;
|
344 | } else {
|
345 | functionExternalFound = findExternal(source, value, context, resolveSync);
|
346 | }
|
347 | };
|
348 |
|
349 |
|
350 | if (externals.length === 3) {
|
351 | externals.call(null, context, source, callback);
|
352 | } else {
|
353 | const ctx = {
|
354 | context,
|
355 | request: source,
|
356 | contextInfo: {
|
357 | issuer: '',
|
358 | issuerLayer: null,
|
359 | compiler: '',
|
360 | },
|
361 | getResolve: () => (resolveContext, requestToResolve, cb) => {
|
362 | if (cb) {
|
363 | try {
|
364 | cb(null, resolveSync(resolveContext, requestToResolve));
|
365 | } catch (e) {
|
366 | cb(e);
|
367 | }
|
368 | } else {
|
369 | log('getResolve without callback not supported');
|
370 | return Promise.reject(new Error('Not supported'));
|
371 | }
|
372 | },
|
373 | };
|
374 | const result = externals.call(null, ctx, callback);
|
375 |
|
376 | if (result && typeof result.then === 'function') {
|
377 | log('Asynchronous functions for externals not supported');
|
378 | }
|
379 | }
|
380 | return functionExternalFound;
|
381 | }
|
382 |
|
383 |
|
384 | for (const key in externals) {
|
385 | if (!hasOwn(externals, key)) { continue; }
|
386 | if (source === key) { return true; }
|
387 | }
|
388 | return false;
|
389 | }
|
390 |
|
391 | function findConfigPath(configPath, packageDir) {
|
392 | const extensions = Object.keys(interpret.extensions).sort(function (a, b) {
|
393 | return a === '.js' ? -1 : b === '.js' ? 1 : a.length - b.length;
|
394 | });
|
395 | let extension;
|
396 |
|
397 | if (configPath) {
|
398 |
|
399 | extensions.reverse();
|
400 | extensions.forEach(function (maybeExtension) {
|
401 | if (extension) {
|
402 | return;
|
403 | }
|
404 |
|
405 | if (configPath.substr(-maybeExtension.length) === maybeExtension) {
|
406 | extension = maybeExtension;
|
407 | }
|
408 | });
|
409 |
|
410 |
|
411 | if (!path.isAbsolute(configPath)) {
|
412 | configPath = path.join(packageDir, configPath);
|
413 | }
|
414 | } else {
|
415 | extensions.forEach(function (maybeExtension) {
|
416 | if (extension) {
|
417 | return;
|
418 | }
|
419 |
|
420 | const maybePath = path.resolve(
|
421 | path.join(packageDir, 'webpack.config' + maybeExtension)
|
422 | );
|
423 | if (fs.existsSync(maybePath)) {
|
424 | configPath = maybePath;
|
425 | extension = maybeExtension;
|
426 | }
|
427 | });
|
428 | }
|
429 |
|
430 | registerCompiler(interpret.extensions[extension]);
|
431 | return configPath;
|
432 | }
|
433 |
|
434 | function registerCompiler(moduleDescriptor) {
|
435 | if (moduleDescriptor) {
|
436 | if (typeof moduleDescriptor === 'string') {
|
437 | require(moduleDescriptor);
|
438 | } else if (!Array.isArray(moduleDescriptor)) {
|
439 | moduleDescriptor.register(require(moduleDescriptor.module));
|
440 | } else {
|
441 | for (let i = 0; i < moduleDescriptor.length; i++) {
|
442 | try {
|
443 | registerCompiler(moduleDescriptor[i]);
|
444 | break;
|
445 | } catch (e) {
|
446 | log('Failed to register compiler for moduleDescriptor[]:', i, moduleDescriptor);
|
447 | }
|
448 | }
|
449 | }
|
450 | }
|
451 | }
|