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