414 lines
13 KiB
JavaScript
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|