UNPKG

11.3 kBJavaScriptView Raw
1var findRoot = require('find-root')
2 , path = require('path')
3 , get = require('lodash.get')
4 , find = require('array-find')
5 , interpret = require('interpret')
6 // not available on 0.10.x
7 , isAbsolute = path.isAbsolute || require('is-absolute')
8 , fs = require('fs')
9 , coreLibs = require('node-libs-browser')
10 , resolve = require('resolve')
11 , semver = require('semver')
12 , has = require('has')
13
14var log = require('debug')('eslint-plugin-import:resolver:webpack')
15
16exports.interfaceVersion = 2
17
18/**
19 * Find the full path to 'source', given 'file' as a full reference path.
20 *
21 * resolveImport('./foo', '/Users/ben/bar.js') => '/Users/ben/foo.js'
22 * @param {string} source - the module to resolve; i.e './some-module'
23 * @param {string} file - the importing file's full path; i.e. '/usr/local/bin/file.js'
24 * TODO: take options as a third param, with webpack config file name
25 * @return {string?} the resolved path to source, undefined if not resolved, or null
26 * if resolved to a non-FS resource (i.e. script tag at page load)
27 */
28exports.resolve = function (source, file, settings) {
29
30 // strip loaders
31 var finalBang = source.lastIndexOf('!')
32 if (finalBang >= 0) {
33 source = source.slice(finalBang + 1)
34 }
35
36 // strip resource query
37 var finalQuestionMark = source.lastIndexOf('?')
38 if (finalQuestionMark >= 0) {
39 source = source.slice(0, finalQuestionMark)
40 }
41
42 if (source in coreLibs) {
43 return { found: true, path: coreLibs[source] }
44 }
45
46 var webpackConfig
47
48 var configPath = get(settings, 'config')
49 , configIndex = get(settings, 'config-index')
50 , packageDir
51
52 log('Config path from settings:', configPath)
53
54 // see if we've got a config path, a config object, an array of config objects or a config function
55 if (!configPath || typeof configPath === 'string') {
56
57 // see if we've got an absolute path
58 if (!configPath || !isAbsolute(configPath)) {
59 // if not, find ancestral package.json and use its directory as base for the path
60 packageDir = findRoot(path.resolve(file))
61 if (!packageDir) throw new Error('package not found above ' + file)
62 }
63
64 configPath = findConfigPath(configPath, packageDir)
65
66 log('Config path resolved to:', configPath)
67 if (configPath) {
68 webpackConfig = require(configPath)
69 } else {
70 log("No config path found relative to", file, "; using {}")
71 webpackConfig = {}
72 }
73
74 if (webpackConfig && webpackConfig.default) {
75 log('Using ES6 module "default" key instead of module.exports.')
76 webpackConfig = webpackConfig.default
77 }
78
79 } else {
80 webpackConfig = configPath
81 configPath = null
82 }
83
84 if (typeof webpackConfig === 'function') {
85 webpackConfig = webpackConfig()
86 }
87
88 if (Array.isArray(webpackConfig)) {
89 if (typeof configIndex !== 'undefined' && webpackConfig.length > configIndex) {
90 webpackConfig = webpackConfig[configIndex]
91 }
92 else {
93 webpackConfig = find(webpackConfig, function findFirstWithResolve(config) {
94 return !!config.resolve
95 })
96 }
97 }
98
99 log('Using config: ', webpackConfig)
100
101 // externals
102 if (findExternal(source, webpackConfig.externals, path.dirname(file))) {
103 return { found: true, path: null }
104 }
105
106 // otherwise, resolve "normally"
107 var resolveSync = createResolveSync(configPath, webpackConfig)
108 try {
109 return { found: true, path: resolveSync(path.dirname(file), source) }
110 } catch (err) {
111 log('Error during module resolution:', err)
112 return { found: false }
113 }
114}
115
116function createResolveSync(configPath, webpackConfig) {
117 var webpackRequire
118 , basedir = null
119
120 if (typeof configPath === 'string') {
121 basedir = path.dirname(configPath)
122 }
123
124 try {
125 var webpackFilename = resolve.sync('webpack', { basedir })
126 var webpackResolveOpts = { basedir: path.dirname(webpackFilename) }
127
128 webpackRequire = function (id) {
129 return require(resolve.sync(id, webpackResolveOpts))
130 }
131 } catch (e) {
132 // Something has gone wrong (or we're in a test). Use our own bundled
133 // enhanced-resolve.
134 log('Using bundled enhanced-resolve.')
135 webpackRequire = require
136 }
137
138 var enhancedResolvePackage = webpackRequire('enhanced-resolve/package.json')
139 var enhancedResolveVersion = enhancedResolvePackage.version
140 log('enhanced-resolve version:', enhancedResolveVersion)
141
142 var resolveConfig = webpackConfig.resolve || {}
143
144 if (semver.major(enhancedResolveVersion) >= 2) {
145 return createWebpack2ResolveSync(webpackRequire, resolveConfig)
146 }
147
148 return createWebpack1ResolveSync(webpackRequire, resolveConfig, webpackConfig.plugins)
149}
150
151function createWebpack2ResolveSync(webpackRequire, resolveConfig) {
152 var EnhancedResolve = webpackRequire('enhanced-resolve')
153
154 return EnhancedResolve.create.sync(Object.assign({}, webpack2DefaultResolveConfig, resolveConfig))
155}
156
157/**
158 * webpack 2 defaults:
159 * https://github.com/webpack/webpack/blob/v2.1.0-beta.20/lib/WebpackOptionsDefaulter.js#L72-L87
160 * @type {Object}
161 */
162var webpack2DefaultResolveConfig = {
163 unsafeCache: true, // Probably a no-op, since how can we cache anything at all here?
164 modules: ['node_modules'],
165 extensions: ['.js', '.json'],
166 aliasFields: ['browser'],
167 mainFields: ['browser', 'module', 'main'],
168}
169
170// adapted from tests &
171// https://github.com/webpack/webpack/blob/v1.13.0/lib/WebpackOptionsApply.js#L322
172function createWebpack1ResolveSync(webpackRequire, resolveConfig, plugins) {
173 var Resolver = webpackRequire('enhanced-resolve/lib/Resolver')
174 var SyncNodeJsInputFileSystem = webpackRequire('enhanced-resolve/lib/SyncNodeJsInputFileSystem')
175
176 var ModuleAliasPlugin = webpackRequire('enhanced-resolve/lib/ModuleAliasPlugin')
177 var ModulesInDirectoriesPlugin =
178 webpackRequire('enhanced-resolve/lib/ModulesInDirectoriesPlugin')
179 var ModulesInRootPlugin = webpackRequire('enhanced-resolve/lib/ModulesInRootPlugin')
180 var ModuleAsFilePlugin = webpackRequire('enhanced-resolve/lib/ModuleAsFilePlugin')
181 var ModuleAsDirectoryPlugin = webpackRequire('enhanced-resolve/lib/ModuleAsDirectoryPlugin')
182 var DirectoryDescriptionFilePlugin =
183 webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFilePlugin')
184 var DirectoryDefaultFilePlugin =
185 webpackRequire('enhanced-resolve/lib/DirectoryDefaultFilePlugin')
186 var FileAppendPlugin = webpackRequire('enhanced-resolve/lib/FileAppendPlugin')
187 var ResultSymlinkPlugin = webpackRequire('enhanced-resolve/lib/ResultSymlinkPlugin')
188 var DirectoryDescriptionFileFieldAliasPlugin =
189 webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFileFieldAliasPlugin')
190
191 var resolver = new Resolver(new SyncNodeJsInputFileSystem())
192
193 resolver.apply(
194 resolveConfig.packageAlias
195 ? new DirectoryDescriptionFileFieldAliasPlugin('package.json', resolveConfig.packageAlias)
196 : function() {},
197 new ModuleAliasPlugin(resolveConfig.alias || {}),
198 makeRootPlugin(ModulesInRootPlugin, 'module', resolveConfig.root),
199 new ModulesInDirectoriesPlugin(
200 'module',
201 resolveConfig.modulesDirectories || resolveConfig.modules || ['web_modules', 'node_modules']
202 ),
203 makeRootPlugin(ModulesInRootPlugin, 'module', resolveConfig.fallback),
204 new ModuleAsFilePlugin('module'),
205 new ModuleAsDirectoryPlugin('module'),
206 new DirectoryDescriptionFilePlugin(
207 'package.json',
208 ['module', 'jsnext:main'].concat(resolveConfig.packageMains || webpack1DefaultMains)
209 ),
210 new DirectoryDefaultFilePlugin(['index']),
211 new FileAppendPlugin(resolveConfig.extensions || ['', '.webpack.js', '.web.js', '.js']),
212 new ResultSymlinkPlugin()
213 )
214
215
216 var resolvePlugins = []
217
218 // support webpack.ResolverPlugin
219 if (plugins) {
220 plugins.forEach(function (plugin) {
221 if (
222 plugin.constructor &&
223 plugin.constructor.name === 'ResolverPlugin' &&
224 Array.isArray(plugin.plugins)
225 ) {
226 resolvePlugins.push.apply(resolvePlugins, plugin.plugins)
227 }
228 })
229 }
230
231 resolver.apply.apply(resolver, resolvePlugins)
232
233 return function() {
234 return resolver.resolveSync.apply(resolver, arguments)
235 }
236}
237
238/* eslint-disable */
239// from https://github.com/webpack/webpack/blob/v1.13.0/lib/WebpackOptionsApply.js#L365
240function makeRootPlugin(ModulesInRootPlugin, name, root) {
241 if(typeof root === "string")
242 return new ModulesInRootPlugin(name, root);
243 else if(Array.isArray(root)) {
244 return function() {
245 root.forEach(function(root) {
246 this.apply(new ModulesInRootPlugin(name, root));
247 }, this);
248 };
249 }
250 return function() {};
251}
252/* eslint-enable */
253
254function findExternal(source, externals, context) {
255 if (!externals) return false
256
257 // string match
258 if (typeof externals === 'string') return (source === externals)
259
260 // array: recurse
261 if (externals instanceof Array) {
262 return externals.some(function (e) { return findExternal(source, e, context) })
263 }
264
265 if (externals instanceof RegExp) {
266 return externals.test(source)
267 }
268
269 if (typeof externals === 'function') {
270 var functionExternalFound = false
271 externals.call(null, context, source, function(err, value) {
272 if (err) {
273 functionExternalFound = false
274 } else {
275 functionExternalFound = findExternal(source, value, context)
276 }
277 })
278 return functionExternalFound
279 }
280
281 // else, vanilla object
282 for (var key in externals) {
283 if (!has(externals, key)) continue
284 if (source === key) return true
285 }
286 return false
287}
288
289/**
290 * webpack 1 defaults: http://webpack.github.io/docs/configuration.html#resolve-packagemains
291 * @type {Array}
292 */
293var webpack1DefaultMains = [
294 'webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main',
295]
296
297function findConfigPath(configPath, packageDir) {
298 var extensions = Object.keys(interpret.extensions).sort(function(a, b) {
299 return a === '.js' ? -1 : b === '.js' ? 1 : a.length - b.length
300 })
301 , extension
302
303
304 if (configPath) {
305 // extensions is not reused below, so safe to mutate it here.
306 extensions.reverse()
307 extensions.forEach(function (maybeExtension) {
308 if (extension) {
309 return
310 }
311
312 if (configPath.substr(-maybeExtension.length) === maybeExtension) {
313 extension = maybeExtension
314 }
315 })
316
317 // see if we've got an absolute path
318 if (!isAbsolute(configPath)) {
319 configPath = path.join(packageDir, configPath)
320 }
321 } else {
322 extensions.forEach(function (maybeExtension) {
323 if (extension) {
324 return
325 }
326
327 var maybePath = path.resolve(
328 path.join(packageDir, 'webpack.config' + maybeExtension)
329 )
330 if (fs.existsSync(maybePath)) {
331 configPath = maybePath
332 extension = maybeExtension
333 }
334 })
335 }
336
337 registerCompiler(interpret.extensions[extension])
338 return configPath
339}
340
341function registerCompiler(moduleDescriptor) {
342 if(moduleDescriptor) {
343 if(typeof moduleDescriptor === 'string') {
344 require(moduleDescriptor)
345 } else if(!Array.isArray(moduleDescriptor)) {
346 moduleDescriptor.register(require(moduleDescriptor.module))
347 } else {
348 for(var i = 0; i < moduleDescriptor.length; i++) {
349 try {
350 registerCompiler(moduleDescriptor[i])
351 break
352 } catch(e) {
353 log('Failed to register compiler for moduleDescriptor[]:', i, moduleDescriptor)
354 }
355 }
356 }
357 }
358}