1 | var 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 |
|
13 | var log = require('debug')('eslint-plugin-import:resolver:webpack')
|
14 |
|
15 | exports.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 | * @param {object} settings - the webpack config file name, as well as cwd
|
24 | * @example
|
25 | * options: {
|
26 | *
|
27 | * config: 'webpack.config.js',
|
28 | *
|
29 | *
|
30 | * cwd: process.cwd()
|
31 | * }
|
32 | * @return {string?} the resolved path to source, undefined if not resolved, or null
|
33 | * if resolved to a non-FS resource (i.e. script tag at page load)
|
34 | */
|
35 | exports.resolve = function (source, file, settings) {
|
36 |
|
37 |
|
38 | var finalBang = source.lastIndexOf('!')
|
39 | if (finalBang >= 0) {
|
40 | source = source.slice(finalBang + 1)
|
41 | }
|
42 |
|
43 |
|
44 | var finalQuestionMark = source.lastIndexOf('?')
|
45 | if (finalQuestionMark >= 0) {
|
46 | source = source.slice(0, finalQuestionMark)
|
47 | }
|
48 |
|
49 | var webpackConfig
|
50 |
|
51 | var configPath = get(settings, 'config')
|
52 | /**
|
53 | * Attempt to set the current working directory.
|
54 | * If none is passed, default to the `cwd` where the config is located.
|
55 | */
|
56 | , cwd = get(settings, 'cwd')
|
57 | , configIndex = get(settings, 'config-index')
|
58 | , env = get(settings, 'env')
|
59 | , argv = get(settings, 'argv', {})
|
60 | , packageDir
|
61 |
|
62 | log('Config path from settings:', configPath)
|
63 |
|
64 |
|
65 | if (!configPath || typeof configPath === 'string') {
|
66 |
|
67 |
|
68 | if (!configPath || !path.isAbsolute(configPath)) {
|
69 |
|
70 | packageDir = findRoot(path.resolve(file))
|
71 | if (!packageDir) throw new Error('package not found above ' + file)
|
72 | }
|
73 |
|
74 | configPath = findConfigPath(configPath, packageDir)
|
75 |
|
76 | log('Config path resolved to:', configPath)
|
77 | if (configPath) {
|
78 | try {
|
79 | webpackConfig = require(configPath)
|
80 | } catch(e) {
|
81 | console.log('Error resolving webpackConfig', e)
|
82 | throw e
|
83 | }
|
84 | } else {
|
85 | log('No config path found relative to', file, '; using {}')
|
86 | webpackConfig = {}
|
87 | }
|
88 |
|
89 | if (webpackConfig && webpackConfig.default) {
|
90 | log('Using ES6 module "default" key instead of module.exports.')
|
91 | webpackConfig = webpackConfig.default
|
92 | }
|
93 |
|
94 | } else {
|
95 | webpackConfig = configPath
|
96 | configPath = null
|
97 | }
|
98 |
|
99 | if (typeof webpackConfig === 'function') {
|
100 | webpackConfig = webpackConfig(env, argv)
|
101 | }
|
102 |
|
103 | if (Array.isArray(webpackConfig)) {
|
104 | webpackConfig = webpackConfig.map(cfg => {
|
105 | if (typeof cfg === 'function') {
|
106 | return cfg(env, argv)
|
107 | }
|
108 |
|
109 | return cfg
|
110 | })
|
111 |
|
112 | if (typeof configIndex !== 'undefined' && webpackConfig.length > configIndex) {
|
113 | webpackConfig = webpackConfig[configIndex]
|
114 | }
|
115 | else {
|
116 | webpackConfig = find(webpackConfig, function findFirstWithResolve(config) {
|
117 | return !!config.resolve
|
118 | })
|
119 | }
|
120 | }
|
121 |
|
122 | log('Using config: ', webpackConfig)
|
123 |
|
124 |
|
125 | if (findExternal(source, webpackConfig.externals, path.dirname(file))) {
|
126 | return { found: true, path: null }
|
127 | }
|
128 |
|
129 |
|
130 | var resolveSync = getResolveSync(configPath, webpackConfig, cwd)
|
131 |
|
132 | try {
|
133 | return { found: true, path: resolveSync(path.dirname(file), source) }
|
134 | } catch (err) {
|
135 | if (source in coreLibs) {
|
136 | return { found: true, path: coreLibs[source] }
|
137 | }
|
138 |
|
139 | log('Error during module resolution:', err)
|
140 | return { found: false }
|
141 | }
|
142 | }
|
143 |
|
144 | var MAX_CACHE = 10
|
145 | var _cache = []
|
146 | function getResolveSync(configPath, webpackConfig, cwd) {
|
147 | var cacheKey = { configPath: configPath, webpackConfig: webpackConfig }
|
148 | var cached = find(_cache, function (entry) { return isEqual(entry.key, cacheKey) })
|
149 | if (!cached) {
|
150 | cached = {
|
151 | key: cacheKey,
|
152 | value: createResolveSync(configPath, webpackConfig, cwd),
|
153 | }
|
154 |
|
155 | if (_cache.unshift(cached) > MAX_CACHE) {
|
156 | _cache.pop()
|
157 | }
|
158 | }
|
159 | return cached.value
|
160 | }
|
161 |
|
162 | function createResolveSync(configPath, webpackConfig, cwd) {
|
163 | var webpackRequire
|
164 | , basedir = null
|
165 |
|
166 | if (typeof configPath === 'string') {
|
167 |
|
168 | basedir = cwd || configPath
|
169 | log(`Attempting to load webpack path from ${basedir}`)
|
170 | }
|
171 |
|
172 | try {
|
173 |
|
174 | var webpackFilename = resolve.sync('webpack', { basedir, preserveSymlinks: false })
|
175 | var webpackResolveOpts = { basedir: path.dirname(webpackFilename), preserveSymlinks: false }
|
176 |
|
177 | webpackRequire = function (id) {
|
178 | return require(resolve.sync(id, webpackResolveOpts))
|
179 | }
|
180 | } catch (e) {
|
181 |
|
182 |
|
183 | log('Using bundled enhanced-resolve.')
|
184 | webpackRequire = require
|
185 | }
|
186 |
|
187 | var enhancedResolvePackage = webpackRequire('enhanced-resolve/package.json')
|
188 | var enhancedResolveVersion = enhancedResolvePackage.version
|
189 | log('enhanced-resolve version:', enhancedResolveVersion)
|
190 |
|
191 | var resolveConfig = webpackConfig.resolve || {}
|
192 |
|
193 | if (semver.major(enhancedResolveVersion) >= 2) {
|
194 | return createWebpack2ResolveSync(webpackRequire, resolveConfig)
|
195 | }
|
196 |
|
197 | return createWebpack1ResolveSync(webpackRequire, resolveConfig, webpackConfig.plugins)
|
198 | }
|
199 |
|
200 | function createWebpack2ResolveSync(webpackRequire, resolveConfig) {
|
201 | var EnhancedResolve = webpackRequire('enhanced-resolve')
|
202 |
|
203 | return EnhancedResolve.create.sync(Object.assign({}, webpack2DefaultResolveConfig, resolveConfig))
|
204 | }
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | var webpack2DefaultResolveConfig = {
|
212 | unsafeCache: true,
|
213 | modules: ['node_modules'],
|
214 | extensions: ['.js', '.json'],
|
215 | aliasFields: ['browser'],
|
216 | mainFields: ['browser', 'module', 'main'],
|
217 | }
|
218 |
|
219 |
|
220 |
|
221 | function createWebpack1ResolveSync(webpackRequire, resolveConfig, plugins) {
|
222 | var Resolver = webpackRequire('enhanced-resolve/lib/Resolver')
|
223 | var SyncNodeJsInputFileSystem = webpackRequire('enhanced-resolve/lib/SyncNodeJsInputFileSystem')
|
224 |
|
225 | var ModuleAliasPlugin = webpackRequire('enhanced-resolve/lib/ModuleAliasPlugin')
|
226 | var ModulesInDirectoriesPlugin =
|
227 | webpackRequire('enhanced-resolve/lib/ModulesInDirectoriesPlugin')
|
228 | var ModulesInRootPlugin = webpackRequire('enhanced-resolve/lib/ModulesInRootPlugin')
|
229 | var ModuleAsFilePlugin = webpackRequire('enhanced-resolve/lib/ModuleAsFilePlugin')
|
230 | var ModuleAsDirectoryPlugin = webpackRequire('enhanced-resolve/lib/ModuleAsDirectoryPlugin')
|
231 | var DirectoryDescriptionFilePlugin =
|
232 | webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFilePlugin')
|
233 | var DirectoryDefaultFilePlugin =
|
234 | webpackRequire('enhanced-resolve/lib/DirectoryDefaultFilePlugin')
|
235 | var FileAppendPlugin = webpackRequire('enhanced-resolve/lib/FileAppendPlugin')
|
236 | var ResultSymlinkPlugin = webpackRequire('enhanced-resolve/lib/ResultSymlinkPlugin')
|
237 | var DirectoryDescriptionFileFieldAliasPlugin =
|
238 | webpackRequire('enhanced-resolve/lib/DirectoryDescriptionFileFieldAliasPlugin')
|
239 |
|
240 | var resolver = new Resolver(new SyncNodeJsInputFileSystem())
|
241 |
|
242 | resolver.apply(
|
243 | resolveConfig.packageAlias
|
244 | ? new DirectoryDescriptionFileFieldAliasPlugin('package.json', resolveConfig.packageAlias)
|
245 | : function() {},
|
246 | new ModuleAliasPlugin(resolveConfig.alias || {}),
|
247 | makeRootPlugin(ModulesInRootPlugin, 'module', resolveConfig.root),
|
248 | new ModulesInDirectoriesPlugin(
|
249 | 'module',
|
250 | resolveConfig.modulesDirectories || resolveConfig.modules || ['web_modules', 'node_modules']
|
251 | ),
|
252 | makeRootPlugin(ModulesInRootPlugin, 'module', resolveConfig.fallback),
|
253 | new ModuleAsFilePlugin('module'),
|
254 | new ModuleAsDirectoryPlugin('module'),
|
255 | new DirectoryDescriptionFilePlugin(
|
256 | 'package.json',
|
257 | ['module', 'jsnext:main'].concat(resolveConfig.packageMains || webpack1DefaultMains)
|
258 | ),
|
259 | new DirectoryDefaultFilePlugin(['index']),
|
260 | new FileAppendPlugin(resolveConfig.extensions || ['', '.webpack.js', '.web.js', '.js']),
|
261 | new ResultSymlinkPlugin()
|
262 | )
|
263 |
|
264 |
|
265 | var resolvePlugins = []
|
266 |
|
267 |
|
268 | if (plugins) {
|
269 | plugins.forEach(function (plugin) {
|
270 | if (
|
271 | plugin.constructor &&
|
272 | plugin.constructor.name === 'ResolverPlugin' &&
|
273 | Array.isArray(plugin.plugins)
|
274 | ) {
|
275 | resolvePlugins.push.apply(resolvePlugins, plugin.plugins)
|
276 | }
|
277 | })
|
278 | }
|
279 |
|
280 | resolver.apply.apply(resolver, resolvePlugins)
|
281 |
|
282 | return function() {
|
283 | return resolver.resolveSync.apply(resolver, arguments)
|
284 | }
|
285 | }
|
286 |
|
287 |
|
288 |
|
289 | function makeRootPlugin(ModulesInRootPlugin, name, root) {
|
290 | if(typeof root === "string")
|
291 | return new ModulesInRootPlugin(name, root);
|
292 | else if(Array.isArray(root)) {
|
293 | return function() {
|
294 | root.forEach(function(root) {
|
295 | this.apply(new ModulesInRootPlugin(name, root));
|
296 | }, this);
|
297 | };
|
298 | }
|
299 | return function() {};
|
300 | }
|
301 |
|
302 |
|
303 | function findExternal(source, externals, context) {
|
304 | if (!externals) return false
|
305 |
|
306 |
|
307 | if (typeof externals === 'string') return (source === externals)
|
308 |
|
309 |
|
310 | if (externals instanceof Array) {
|
311 | return externals.some(function (e) { return findExternal(source, e, context) })
|
312 | }
|
313 |
|
314 | if (externals instanceof RegExp) {
|
315 | return externals.test(source)
|
316 | }
|
317 |
|
318 | if (typeof externals === 'function') {
|
319 | var functionExternalFound = false
|
320 | externals.call(null, context, source, function(err, value) {
|
321 | if (err) {
|
322 | functionExternalFound = false
|
323 | } else {
|
324 | functionExternalFound = findExternal(source, value, context)
|
325 | }
|
326 | })
|
327 | return functionExternalFound
|
328 | }
|
329 |
|
330 |
|
331 | for (var key in externals) {
|
332 | if (!has(externals, key)) continue
|
333 | if (source === key) return true
|
334 | }
|
335 | return false
|
336 | }
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 | var webpack1DefaultMains = [
|
343 | 'webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main',
|
344 | ]
|
345 |
|
346 | function findConfigPath(configPath, packageDir) {
|
347 | var extensions = Object.keys(interpret.extensions).sort(function(a, b) {
|
348 | return a === '.js' ? -1 : b === '.js' ? 1 : a.length - b.length
|
349 | })
|
350 | , extension
|
351 |
|
352 |
|
353 | if (configPath) {
|
354 |
|
355 | extensions.reverse()
|
356 | extensions.forEach(function (maybeExtension) {
|
357 | if (extension) {
|
358 | return
|
359 | }
|
360 |
|
361 | if (configPath.substr(-maybeExtension.length) === maybeExtension) {
|
362 | extension = maybeExtension
|
363 | }
|
364 | })
|
365 |
|
366 |
|
367 | if (!path.isAbsolute(configPath)) {
|
368 | configPath = path.join(packageDir, configPath)
|
369 | }
|
370 | } else {
|
371 | extensions.forEach(function (maybeExtension) {
|
372 | if (extension) {
|
373 | return
|
374 | }
|
375 |
|
376 | var maybePath = path.resolve(
|
377 | path.join(packageDir, 'webpack.config' + maybeExtension)
|
378 | )
|
379 | if (fs.existsSync(maybePath)) {
|
380 | configPath = maybePath
|
381 | extension = maybeExtension
|
382 | }
|
383 | })
|
384 | }
|
385 |
|
386 | registerCompiler(interpret.extensions[extension])
|
387 | return configPath
|
388 | }
|
389 |
|
390 | function registerCompiler(moduleDescriptor) {
|
391 | if(moduleDescriptor) {
|
392 | if(typeof moduleDescriptor === 'string') {
|
393 | require(moduleDescriptor)
|
394 | } else if(!Array.isArray(moduleDescriptor)) {
|
395 | moduleDescriptor.register(require(moduleDescriptor.module))
|
396 | } else {
|
397 | for(var i = 0; i < moduleDescriptor.length; i++) {
|
398 | try {
|
399 | registerCompiler(moduleDescriptor[i])
|
400 | break
|
401 | } catch(e) {
|
402 | log('Failed to register compiler for moduleDescriptor[]:', i, moduleDescriptor)
|
403 | }
|
404 | }
|
405 | }
|
406 | }
|
407 | }
|