2025-04-02 21:57:33 +08:00

414 lines
13 KiB
JavaScript

var findRoot = require('find-root')
, path = require('path')
, get = require('lodash/get')
, isEqual = require('lodash/isEqual')
, find = require('array-find')
, interpret = require('interpret')
, fs = require('fs')
, coreLibs = require('node-libs-browser')
, resolve = require('resolve')
, semver = require('semver')
, has = require('has')
var log = require('debug')('eslint-plugin-import:resolver:webpack')
exports.interfaceVersion = 2
/**
* Find the full path to 'source', given 'file' as a full reference path.
*
* resolveImport('./foo', '/Users/ben/bar.js') => '/Users/ben/foo.js'
* @param {string} source - the module to resolve; i.e './some-module'
* @param {string} file - the importing file's full path; i.e. '/usr/local/bin/file.js'
* @param {object} settings - the webpack config file name, as well as cwd
* @example
* options: {
* // Path to the webpack config
* config: 'webpack.config.js',
* // Path to be used to determine where to resolve webpack from
* // (may differ from the cwd in some cases)
* cwd: process.cwd()
* }
* @return {string?} the resolved path to source, undefined if not resolved, or null
* if resolved to a non-FS resource (i.e. script tag at page load)
*/
exports.resolve = function (source, file, settings) {
// strip loaders
var finalBang = source.lastIndexOf('!')
if (finalBang >= 0) {
source = source.slice(finalBang + 1)
}
// strip resource query
var finalQuestionMark = source.lastIndexOf('?')
if (finalQuestionMark >= 0) {
source = source.slice(0, finalQuestionMark)
}
var webpackConfig
var configPath = get(settings, 'config')
/**
* Attempt to set the current working directory.
* If none is passed, default to the `cwd` where the config is located.
*/
, cwd = get(settings, 'cwd')
, configIndex = get(settings, 'config-index')
, env = get(settings, 'env')
, argv = get(settings, 'argv', {})
, packageDir
log('Config path from settings:', configPath)
// see if we've got a config path, a config object, an array of config objects or a config function
if (!configPath || typeof configPath === 'string') {
// see if we've got an absolute path
if (!configPath || !path.isAbsolute(configPath)) {
// if not, find ancestral package.json and use its directory as base for the path
packageDir = findRoot(path.resolve(file))
if (!packageDir) throw new Error('package not found above ' + file)
}
configPath = findConfigPath(configPath, packageDir)
log('Config path resolved to:', configPath)
if (configPath) {
try {
webpackConfig = require(configPath)
} catch(e) {
console.log('Error resolving webpackConfig', e)
throw e
}
} else {
log('No config path found relative to', file, '; using {}')
webpackConfig = {}
}
if (webpackConfig && webpackConfig.default) {
log('Using ES6 module "default" key instead of module.exports.')
webpackConfig = webpackConfig.default
}
} else {
webpackConfig = configPath
configPath = null
}
if (typeof webpackConfig === 'function') {
webpackConfig = webpackConfig(env, argv)
}
if (Array.isArray(webpackConfig)) {
webpackConfig = webpackConfig.map(cfg => {
if (typeof cfg === 'function') {
return cfg(env, argv)
}
return cfg
})
if (typeof configIndex !== 'undefined' && webpackConfig.length > configIndex) {
webpackConfig = webpackConfig[configIndex]
}
else {
webpackConfig = find(webpackConfig, function findFirstWithResolve(config) {
return !!config.resolve
})
}
}
if (webpackConfig == null) {
webpackConfig = {}
console.warn('No webpack configuration with a "resolve" field found. Using empty object instead')
}
log('Using config: ', webpackConfig)
// externals
if (findExternal(source, webpackConfig.externals, path.dirname(file))) {
return { found: true, path: null }
}
// otherwise, resolve "normally"
var resolveSync = getResolveSync(configPath, webpackConfig, cwd)
try {
return { found: true, path: resolveSync(path.dirname(file), source) }
} catch (err) {
if (source in coreLibs) {
return { found: true, path: coreLibs[source] }
}
log('Error during module resolution:', err)
return { found: false }
}
}
var MAX_CACHE = 10
var _cache = []
function getResolveSync(configPath, webpackConfig, cwd) {
var cacheKey = { configPath: configPath, webpackConfig: webpackConfig }
var cached = find(_cache, function (entry) { return isEqual(entry.key, cacheKey) })
if (!cached) {
cached = {
key: cacheKey,
value: createResolveSync(configPath, webpackConfig, cwd),
}
// put in front and pop last item
if (_cache.unshift(cached) > MAX_CACHE) {
_cache.pop()
}
}
return cached.value
}
function createResolveSync(configPath, webpackConfig, cwd) {
var webpackRequire
, basedir = null
if (typeof configPath === 'string') {
// This can be changed via the settings passed in when defining the resolver
basedir = cwd || configPath
log(`Attempting to load webpack path from ${basedir}`)
}
try {
// Attempt to resolve webpack from the given `basedir`
var webpackFilename = resolve.sync('webpack', { basedir, preserveSymlinks: false })
var webpackResolveOpts = { basedir: path.dirname(webpackFilename), preserveSymlinks: false }
webpackRequire = function (id) {
return require(resolve.sync(id, webpackResolveOpts))
}
} catch (e) {
// Something has gone wrong (or we're in a test). Use our own bundled
// enhanced-resolve.
log('Using bundled enhanced-resolve.')
webpackRequire = require
}
var enhancedResolvePackage = webpackRequire('enhanced-resolve/package.json')
var enhancedResolveVersion = enhancedResolvePackage.version
log('enhanced-resolve version:', enhancedResolveVersion)
var resolveConfig = webpackConfig.resolve || {}
if (semver.major(enhancedResolveVersion) >= 2) {
return createWebpack2ResolveSync(webpackRequire, resolveConfig)
}
return createWebpack1ResolveSync(webpackRequire, resolveConfig, webpackConfig.plugins)
}
function createWebpack2ResolveSync(webpackRequire, resolveConfig) {
var EnhancedResolve = webpackRequire('enhanced-resolve')
return EnhancedResolve.create.sync(Object.assign({}, webpack2DefaultResolveConfig, resolveConfig))
}
/**
* webpack 2 defaults:
* https://github.com/webpack/webpack/blob/v2.1.0-beta.20/lib/WebpackOptionsDefaulter.js#L72-L87
* @type {Object}
*/
var webpack2DefaultResolveConfig = {
unsafeCache: true, // Probably a no-op, since how can we cache anything at all here?
modules: ['node_modules'],
extensions: ['.js', '.json'],
aliasFields: ['browser'],
mainFields: ['browser', 'module', 'main'],
}
// adapted from tests &
// https://github.com/webpack/webpack/blob/v1.13.0/lib/WebpackOptionsApply.js#L322
function createWebpack1ResolveSync(webpackRequire, resolveConfig, plugins) {
var Resolver = webpackRequire('enhanced-resolve/lib/Resolver')
var SyncNodeJsInputFileSystem = webpackRequire('enhanced-resolve/lib/SyncNodeJsInputFileSystem')
var ModuleAliasPlugin = webpackRequire('enhanced-resolve/lib/ModuleAliasPlugin')
var ModulesInDirectoriesPlugin =
webpackRequire('enhanced-resolve/lib/ModulesInDirectoriesPlugin')
var ModulesInRootPlugin = webpackRequire('enhanced-resolve/lib/ModulesInRootPlugin')
var ModuleAsFilePlugin = webpackRequire('enhanced-resolve/lib/ModuleAsFilePlugin')
var ModuleAsDirectoryPlugin = webpackRequire('enhanced-resolve/lib/ModuleAsDirectoryPlugin')
var DirectoryDescriptionFilePlugin =
webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFilePlugin')
var DirectoryDefaultFilePlugin =
webpackRequire('enhanced-resolve/lib/DirectoryDefaultFilePlugin')
var FileAppendPlugin = webpackRequire('enhanced-resolve/lib/FileAppendPlugin')
var ResultSymlinkPlugin = webpackRequire('enhanced-resolve/lib/ResultSymlinkPlugin')
var DirectoryDescriptionFileFieldAliasPlugin =
webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFileFieldAliasPlugin')
var resolver = new Resolver(new SyncNodeJsInputFileSystem())
resolver.apply(
resolveConfig.packageAlias
? new DirectoryDescriptionFileFieldAliasPlugin('package.json', resolveConfig.packageAlias)
: function() {},
new ModuleAliasPlugin(resolveConfig.alias || {}),
makeRootPlugin(ModulesInRootPlugin, 'module', resolveConfig.root),
new ModulesInDirectoriesPlugin(
'module',
resolveConfig.modulesDirectories || resolveConfig.modules || ['web_modules', 'node_modules']
),
makeRootPlugin(ModulesInRootPlugin, 'module', resolveConfig.fallback),
new ModuleAsFilePlugin('module'),
new ModuleAsDirectoryPlugin('module'),
new DirectoryDescriptionFilePlugin(
'package.json',
['module', 'jsnext:main'].concat(resolveConfig.packageMains || webpack1DefaultMains)
),
new DirectoryDefaultFilePlugin(['index']),
new FileAppendPlugin(resolveConfig.extensions || ['', '.webpack.js', '.web.js', '.js']),
new ResultSymlinkPlugin()
)
var resolvePlugins = []
// support webpack.ResolverPlugin
if (plugins) {
plugins.forEach(function (plugin) {
if (
plugin.constructor &&
plugin.constructor.name === 'ResolverPlugin' &&
Array.isArray(plugin.plugins)
) {
resolvePlugins.push.apply(resolvePlugins, plugin.plugins)
}
})
}
resolver.apply.apply(resolver, resolvePlugins)
return function() {
return resolver.resolveSync.apply(resolver, arguments)
}
}
/* eslint-disable */
// from https://github.com/webpack/webpack/blob/v1.13.0/lib/WebpackOptionsApply.js#L365
function makeRootPlugin(ModulesInRootPlugin, name, root) {
if(typeof root === "string")
return new ModulesInRootPlugin(name, root);
else if(Array.isArray(root)) {
return function() {
root.forEach(function(root) {
this.apply(new ModulesInRootPlugin(name, root));
}, this);
};
}
return function() {};
}
/* eslint-enable */
function findExternal(source, externals, context) {
if (!externals) return false
// string match
if (typeof externals === 'string') return (source === externals)
// array: recurse
if (externals instanceof Array) {
return externals.some(function (e) { return findExternal(source, e, context) })
}
if (externals instanceof RegExp) {
return externals.test(source)
}
if (typeof externals === 'function') {
var functionExternalFound = false
externals.call(null, context, source, function(err, value) {
if (err) {
functionExternalFound = false
} else {
functionExternalFound = findExternal(source, value, context)
}
})
return functionExternalFound
}
// else, vanilla object
for (var key in externals) {
if (!has(externals, key)) continue
if (source === key) return true
}
return false
}
/**
* webpack 1 defaults: http://webpack.github.io/docs/configuration.html#resolve-packagemains
* @type {Array}
*/
var webpack1DefaultMains = [
'webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main',
]
function findConfigPath(configPath, packageDir) {
var extensions = Object.keys(interpret.extensions).sort(function(a, b) {
return a === '.js' ? -1 : b === '.js' ? 1 : a.length - b.length
})
, extension
if (configPath) {
// extensions is not reused below, so safe to mutate it here.
extensions.reverse()
extensions.forEach(function (maybeExtension) {
if (extension) {
return
}
if (configPath.substr(-maybeExtension.length) === maybeExtension) {
extension = maybeExtension
}
})
// see if we've got an absolute path
if (!path.isAbsolute(configPath)) {
configPath = path.join(packageDir, configPath)
}
} else {
extensions.forEach(function (maybeExtension) {
if (extension) {
return
}
var maybePath = path.resolve(
path.join(packageDir, 'webpack.config' + maybeExtension)
)
if (fs.existsSync(maybePath)) {
configPath = maybePath
extension = maybeExtension
}
})
}
registerCompiler(interpret.extensions[extension])
return configPath
}
function registerCompiler(moduleDescriptor) {
if(moduleDescriptor) {
if(typeof moduleDescriptor === 'string') {
require(moduleDescriptor)
} else if(!Array.isArray(moduleDescriptor)) {
moduleDescriptor.register(require(moduleDescriptor.module))
} else {
for(var i = 0; i < moduleDescriptor.length; i++) {
try {
registerCompiler(moduleDescriptor[i])
break
} catch(e) {
log('Failed to register compiler for moduleDescriptor[]:', i, moduleDescriptor)
}
}
}
}
}