UNPKG

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