import { dirname, resolve, extname, normalize, sep } from 'path'; import builtins from 'builtin-modules'; import resolveId from 'resolve'; import isModule from 'is-module'; import fs from 'fs'; const ES6_BROWSER_EMPTY = resolve( __dirname, '../src/empty.js' ); const CONSOLE_WARN = ( ...args ) => console.warn( ...args ); // eslint-disable-line no-console // It is important that .mjs occur before .js so that Rollup will interpret npm modules // which deploy both ESM .mjs and CommonJS .js files as ESM. const DEFAULT_EXTS = [ '.mjs', '.js', '.json', '.node' ]; let readFileCache = {}; const readFileAsync = file => new Promise((fulfil, reject) => fs.readFile(file, (err, contents) => err ? reject(err) : fulfil(contents))); const statAsync = file => new Promise((fulfil, reject) => fs.stat(file, (err, contents) => err ? reject(err) : fulfil(contents))); function cachedReadFile (file, cb) { if (file in readFileCache === false) { readFileCache[file] = readFileAsync(file).catch(err => { delete readFileCache[file]; throw err; }); } readFileCache[file].then(contents => cb(null, contents), cb); } let isFileCache = {}; function cachedIsFile (file, cb) { if (file in isFileCache === false) { isFileCache[file] = statAsync(file) .then( stat => stat.isFile(), err => { if (err.code == 'ENOENT') return false; delete isFileCache[file]; throw err; }); } isFileCache[file].then(contents => cb(null, contents), cb); } const resolveIdAsync = (file, opts) => new Promise((fulfil, reject) => resolveId(file, opts, (err, contents) => err ? reject(err) : fulfil(contents))); export default function nodeResolve ( options = {} ) { const useModule = options.module !== false; const useMain = options.main !== false; const useJsnext = options.jsnext === true; const isPreferBuiltinsSet = options.preferBuiltins === true || options.preferBuiltins === false; const preferBuiltins = isPreferBuiltinsSet ? options.preferBuiltins : true; const customResolveOptions = options.customResolveOptions || {}; const jail = options.jail; const only = Array.isArray(options.only) ? options.only.map(o => o instanceof RegExp ? o : new RegExp('^' + String(o).replace(/[\\^$*+?.()|[\]{}]/g, '\\$&') + '$') ) : null; const browserMapCache = {}; const onwarn = options.onwarn || CONSOLE_WARN; if ( options.skip ) { throw new Error( 'options.skip is no longer supported — you should use the main Rollup `external` option instead' ); } if ( !useModule && !useMain && !useJsnext ) { throw new Error( `At least one of options.module, options.main or options.jsnext must be true` ); } let preserveSymlinks; return { name: 'node-resolve', options ( options ) { preserveSymlinks = options.preserveSymlinks; }, onwrite () { isFileCache = {}; readFileCache = {}; }, resolveId ( importee, importer ) { if ( /\0/.test( importee ) ) return null; // ignore IDs with null character, these belong to other plugins // disregard entry module if ( !importer ) return null; if (options.browser && browserMapCache[importer]) { const resolvedImportee = resolve( dirname( importer ), importee ); const browser = browserMapCache[importer]; if (browser[importee] === false || browser[resolvedImportee] === false) { return ES6_BROWSER_EMPTY; } if (browser[importee] || browser[resolvedImportee] || browser[resolvedImportee + '.js'] || browser[resolvedImportee + '.json']) { importee = browser[importee] || browser[resolvedImportee] || browser[resolvedImportee + '.js'] || browser[resolvedImportee + '.json']; } } const parts = importee.split( /[/\\]/ ); let id = parts.shift(); if ( id[0] === '@' && parts.length ) { // scoped packages id += `/${parts.shift()}`; } else if ( id[0] === '.' ) { // an import relative to the parent dir of the importer id = resolve( importer, '..', importee ); } if (only && !only.some(pattern => pattern.test(id))) return null; let disregardResult = false; let packageBrowserField = false; const extensions = options.extensions || DEFAULT_EXTS; const resolveOptions = { basedir: dirname( importer ), packageFilter ( pkg, pkgPath ) { const pkgRoot = dirname( pkgPath ); if (options.browser && typeof pkg[ 'browser' ] === 'object') { packageBrowserField = Object.keys(pkg[ 'browser' ]).reduce((browser, key) => { const resolved = pkg[ 'browser' ][ key ] === false ? false : resolve( pkgRoot, pkg[ 'browser' ][ key ] ); browser[ key ] = resolved; if ( key[0] === '.' ) { const absoluteKey = resolve( pkgRoot, key ); browser[ absoluteKey ] = resolved; if ( !extname(key) ) { extensions.reduce( ( browser, ext ) => { browser[ absoluteKey + ext ] = browser[ key ]; return browser; }, browser ); } } return browser; }, {}); } if (options.browser && typeof pkg[ 'browser' ] === 'string') { pkg[ 'main' ] = pkg[ 'browser' ]; } else if ( useModule && pkg[ 'module' ] ) { pkg[ 'main' ] = pkg[ 'module' ]; } else if ( useJsnext && pkg[ 'jsnext:main' ] ) { pkg[ 'main' ] = pkg[ 'jsnext:main' ]; } else if ( ( useJsnext || useModule ) && !useMain ) { disregardResult = true; } return pkg; }, readFile: cachedReadFile, isFile: cachedIsFile, extensions: extensions }; if (preserveSymlinks !== undefined) { resolveOptions.preserveSymlinks = preserveSymlinks; } return resolveIdAsync( importee, Object.assign( resolveOptions, customResolveOptions ) ) .catch(() => false) .then(resolved => { if (options.browser && packageBrowserField) { if (packageBrowserField[ resolved ]) { resolved = packageBrowserField[ resolved ]; } browserMapCache[resolved] = packageBrowserField; } if ( !disregardResult && resolved !== false ) { if ( !preserveSymlinks && resolved && fs.existsSync( resolved ) ) { resolved = fs.realpathSync( resolved ); } if ( ~builtins.indexOf( resolved ) ) { return null; } else if ( ~builtins.indexOf( importee ) && preferBuiltins ) { if ( !isPreferBuiltinsSet ) { onwarn( `preferring built-in module '${importee}' over local alternative ` + `at '${resolved}', pass 'preferBuiltins: false' to disable this ` + `behavior or 'preferBuiltins: true' to disable this warning` ); } return null; } else if ( jail && resolved.indexOf( normalize( jail.trim( sep ) ) ) !== 0 ) { return null; } } if ( resolved && options.modulesOnly ) { return readFileAsync( resolved, 'utf-8').then(code => isModule( code ) ? resolved : null); } else { return resolved === false ? null : resolved; } }); } }; }