// @flow import type { FilePath, FileCreateInvalidation, PackageJSON, ResolveResult, Environment, SpecifierType, PluginLogger, SourceLocation, } from '@parcel/types'; import type {FileSystem} from '@parcel/fs'; import type {PackageManager} from '@parcel/package-manager'; import invariant from 'assert'; import path from 'path'; import { isGlob, relativePath, normalizeSeparators, findAlternativeNodeModules, findAlternativeFiles, loadConfig, globToRegex, isGlobMatch, } from '@parcel/utils'; import ThrowableDiagnostic, { generateJSONCodeHighlights, md, } from '@parcel/diagnostic'; import builtins, {empty} from './builtins'; import nullthrows from 'nullthrows'; import _Module from 'module'; import {fileURLToPath} from 'url'; const EMPTY_SHIM = require.resolve('./_empty'); type InternalPackageJSON = PackageJSON & {pkgdir: string, pkgfile: string, ...}; type Options = {| fs: FileSystem, projectRoot: FilePath, extensions: Array, mainFields: Array, packageManager?: PackageManager, logger?: PluginLogger, |}; type ResolvedFile = {| path: string, pkg: InternalPackageJSON | null, |}; type Aliases = | string | {[string]: string, ...} | {[string]: string | boolean, ...}; type ResolvedAlias = {| type: 'file' | 'global', sourcePath: FilePath, resolved: string, |}; type Module = {| moduleName?: string, subPath?: ?string, moduleDir?: FilePath, filePath?: FilePath, code?: string, query?: URLSearchParams, |}; type ResolverContext = {| invalidateOnFileCreate: Array, invalidateOnFileChange: Set, specifierType: SpecifierType, loc: ?SourceLocation, |}; /** * This resolver implements a modified version of the node_modules resolution algorithm: * https://nodejs.org/api/modules.html#modules_all_together * * In addition to the standard algorithm, Parcel supports: * - All file extensions supported by Parcel. * - Glob file paths * - Absolute paths (e.g. /foo) resolved relative to the project root. * - Tilde paths (e.g. ~/foo) resolved relative to the nearest module root in node_modules. * - The package.json module, jsnext:main, and browser field as replacements for package.main. * - The package.json browser and alias fields as an alias map within a local module. * - The package.json alias field in the root package for global aliases across all modules. */ export default class NodeResolver { fs: FileSystem; projectRoot: FilePath; extensions: Array; mainFields: Array; packageCache: Map; rootPackage: InternalPackageJSON | null; packageManager: ?PackageManager; logger: ?PluginLogger; constructor(opts: Options) { this.extensions = opts.extensions.map(ext => ext.startsWith('.') ? ext : '.' + ext, ); this.mainFields = opts.mainFields; this.fs = opts.fs; this.projectRoot = opts.projectRoot; this.packageCache = new Map(); this.rootPackage = null; this.packageManager = opts.packageManager; this.logger = opts.logger; } async resolve({ filename, parent, specifierType, env, sourcePath, loc, }: {| filename: FilePath, parent: ?FilePath, specifierType: SpecifierType, env: Environment, sourcePath?: ?FilePath, loc?: ?SourceLocation, |}): Promise { let ctx = { invalidateOnFileCreate: [], invalidateOnFileChange: new Set(), specifierType, loc, }; // Get file extensions to search let extensions = this.extensions.slice(); if (parent) { // parent's extension given high priority let parentExt = path.extname(parent); extensions = [parentExt, ...extensions.filter(ext => ext !== parentExt)]; } extensions.unshift(''); try { // Resolve the module directory or local file path let module = await this.resolveModule({ filename, parent, env, ctx, sourcePath, }); if (!module) { return { isExcluded: true, }; } let resolved; if (module.moduleDir) { resolved = await this.loadNodeModules(module, extensions, env, ctx); } else if (module.filePath) { if (module.code != null) { return { filePath: await this.fs.realpath(module.filePath), code: module.code, invalidateOnFileCreate: ctx.invalidateOnFileCreate, invalidateOnFileChange: [...ctx.invalidateOnFileChange], query: module.query, }; } resolved = await this.loadRelative( module.filePath, extensions, env, parent ? path.dirname(parent) : this.projectRoot, ctx, ); } if (resolved) { let _resolved = resolved; // For Flow return { filePath: await this.fs.realpath(_resolved.path), sideEffects: _resolved.pkg && !this.hasSideEffects(_resolved.path, _resolved.pkg) ? false : undefined, invalidateOnFileCreate: ctx.invalidateOnFileCreate, invalidateOnFileChange: [...ctx.invalidateOnFileChange], query: module.query, }; } } catch (err) { if (err instanceof ThrowableDiagnostic) { return { diagnostics: err.diagnostics, invalidateOnFileCreate: ctx.invalidateOnFileCreate, invalidateOnFileChange: [...ctx.invalidateOnFileChange], }; } else { throw err; } } return null; } async resolveModule({ filename, parent, env, ctx, sourcePath, }: {| filename: string, parent: ?FilePath, env: Environment, ctx: ResolverContext, sourcePath: ?FilePath, |}): Promise { let specifier = filename; let sourceFile = parent || path.join(this.projectRoot, 'index'); let query; // If this isn't the entrypoint, resolve the input file to an absolute path if (parent) { let res = await this.resolveFilename( filename, path.dirname(sourceFile), ctx.specifierType, ); if (!res) { return null; } filename = res.filePath; query = res.query; } // Resolve aliases in the parent module for this file. let alias = await this.loadAlias(filename, sourceFile, env, ctx); if (alias) { if (alias.type === 'global') { return { filePath: path.join(this.projectRoot, `${alias.resolved}.js`), code: `module.exports=${alias.resolved};`, query, }; } filename = alias.resolved; } // Return just the file path if this is a file, not in node_modules if (path.isAbsolute(filename)) { return { filePath: filename, query, }; } let builtin = this.findBuiltin(filename, env); if (builtin === null) { return null; } else if (builtin === empty) { return {filePath: empty}; } else if (builtin !== undefined) { filename = builtin; } if (this.shouldIncludeNodeModule(env, filename) === false) { if (sourcePath && env.isLibrary && !builtin) { await this.checkExcludedDependency(sourcePath, filename, ctx); } return null; } // Resolve the module in node_modules let resolved: ?Module; try { resolved = this.findNodeModulePath(filename, sourceFile, ctx); } catch (err) { // ignore } // Auto install node builtin polyfills if not already available if (resolved === undefined && builtin != null) { let packageName = builtin.split('/')[0]; let packageManager = this.packageManager; if (packageManager) { this.logger?.warn({ message: md`Auto installing polyfill for Node builtin module "${specifier}"...`, codeFrames: [ { filePath: ctx.loc?.filePath ?? sourceFile, codeHighlights: ctx.loc ? [ { message: 'used here', start: ctx.loc.start, end: ctx.loc.end, }, ] : [], }, ], documentationURL: 'https://parceljs.org/features/node-emulation/#polyfilling-%26-excluding-builtin-node-modules', }); await packageManager.resolve(builtin, this.projectRoot + '/index', { saveDev: true, shouldAutoInstall: true, }); // Re-resolve try { resolved = this.findNodeModulePath(filename, sourceFile, ctx); } catch (err) { // ignore } } else { throw new ThrowableDiagnostic({ diagnostic: { message: md`Node builtin polyfill "${packageName}" is not installed, but auto install is disabled.`, codeFrames: [ { filePath: ctx.loc?.filePath ?? sourceFile, codeHighlights: ctx.loc ? [ { message: 'used here', start: ctx.loc.start, end: ctx.loc.end, }, ] : [], }, ], documentationURL: 'https://parceljs.org/features/node-emulation/#polyfilling-%26-excluding-builtin-node-modules', hints: [ md`Install the "${packageName}" package with your package manager, and run Parcel again.`, ], }, }); } } if (resolved === undefined && process.versions.pnp != null && parent) { try { let [moduleName, subPath] = this.getModuleParts(filename); // $FlowFixMe[prop-missing] let pnp = _Module.findPnpApi(path.dirname(parent)); let res = pnp.resolveToUnqualified( moduleName + // retain slash in `require('assert/')` to force loading builtin from npm (filename[moduleName.length] === '/' ? '/' : ''), parent, ); resolved = { moduleName, subPath, moduleDir: res, filePath: path.join(res, subPath || ''), }; // Invalidate whenever the .pnp.js file changes. ctx.invalidateOnFileChange.add( pnp.resolveToUnqualified('pnpapi', null), ); } catch (e) { if (e.code !== 'MODULE_NOT_FOUND') { return null; } } } // If we couldn't resolve the node_modules path, just return the module name info if (resolved === undefined) { let [moduleName, subPath] = this.getModuleParts(filename); resolved = { moduleName, subPath, }; let alternativeModules = await findAlternativeNodeModules( this.fs, moduleName, path.dirname(sourceFile), ); if (alternativeModules.length) { throw new ThrowableDiagnostic({ diagnostic: { message: md`Cannot find module ${nullthrows(resolved?.moduleName)}`, hints: alternativeModules.map(r => { return `Did you mean '__${r}__'?`; }), }, }); } } if (resolved != null) { resolved.query = query; } return resolved; } shouldIncludeNodeModule( {includeNodeModules}: Environment, name: string, ): ?boolean { if (includeNodeModules === false) { return false; } if (Array.isArray(includeNodeModules)) { let [moduleName] = this.getModuleParts(name); return includeNodeModules.includes(moduleName); } if (includeNodeModules && typeof includeNodeModules === 'object') { let [moduleName] = this.getModuleParts(name); let include = includeNodeModules[moduleName]; if (include != null) { return !!include; } } } async checkExcludedDependency( sourceFile: FilePath, name: string, ctx: ResolverContext, ) { let [moduleName] = this.getModuleParts(name); let pkg = await this.findPackage(sourceFile, ctx); if (!pkg) { return; } if ( !pkg.dependencies?.[moduleName] && !pkg.peerDependencies?.[moduleName] && !pkg.engines?.[moduleName] ) { let pkgContent = await this.fs.readFile(pkg.pkgfile, 'utf8'); throw new ThrowableDiagnostic({ diagnostic: { message: md`External dependency "${moduleName}" is not declared in package.json.`, codeFrames: [ { filePath: pkg.pkgfile, language: 'json', code: pkgContent, codeHighlights: pkg.dependencies ? generateJSONCodeHighlights(pkgContent, [ { key: `/dependencies`, type: 'key', }, ]) : [ { start: { line: 1, column: 1, }, end: { line: 1, column: 1, }, }, ], }, ], hints: [`Add "${moduleName}" as a dependency.`], }, }); } } async resolveFilename( filename: string, dir: string, specifierType: SpecifierType, ): Promise { let url; switch (filename[0]) { case '/': { if (specifierType === 'url' && filename[1] === '/') { // A protocol-relative URL, e.g `url('//example.com/foo.png')`. Ignore. return null; } // Absolute path. Resolve relative to project root. dir = this.projectRoot; filename = '.' + filename; break; } case '~': { // Tilde path. Resolve relative to nearest node_modules directory, // the nearest directory with package.json or the project root - whichever comes first. const insideNodeModules = dir.includes('node_modules'); while ( dir !== this.projectRoot && path.basename(path.dirname(dir)) !== 'node_modules' && (insideNodeModules || !(await this.fs.exists(path.join(dir, 'package.json')))) ) { dir = path.dirname(dir); if (dir === path.dirname(dir)) { dir = this.projectRoot; break; } } filename = filename.slice(1); if (filename[0] === '/' || filename[0] === '\\') { filename = '.' + filename; } break; } case '.': { // Relative path. break; } case '#': { if (specifierType === 'url') { // An ID-only URL, e.g. `url(#clip-path)` for CSS rules. Ignore. return null; } break; } default: { // Bare specifier. If this is a URL, it's treated as relative, // otherwise as a node_modules package. if (specifierType === 'esm') { // Try parsing as a URL first in case there is a scheme. // Otherwise, fall back to an `npm:` specifier, parsed below. try { url = new URL(filename); } catch (e) { filename = 'npm:' + filename; } } else if (specifierType === 'commonjs') { return { filePath: filename, }; } } } // If this is a URL dependency or ESM specifier, parse as a URL. // Otherwise, if this is CommonJS, parse as a platform path. if (specifierType === 'url' || specifierType === 'esm') { url = url ?? new URL(filename, `file:${dir}/index`); let filePath; if (url.protocol === 'npm:') { // The `npm:` scheme allows URLs to resolve to node_modules packages. filePath = decodeURIComponent(url.pathname); } else if (url.protocol === 'node:') { // Preserve the `node:` prefix for use later. // Node does not URL decode or support query params here. // See https://github.com/nodejs/node/issues/39710. return { filePath: filename, }; } else if (url.protocol === 'file:') { // $FlowFixMe filePath = fileURLToPath(url); } else if (specifierType === 'url') { // Don't handle other protocols like http: return null; } else { // Throw on unsupported url schemes in ESM dependencies. // We may support http: or data: urls eventually. throw new ThrowableDiagnostic({ diagnostic: { message: `Unknown url scheme or pipeline '${url.protocol}'`, }, }); } return { filePath, query: url.search ? new URLSearchParams(url.search) : undefined, }; } else { // CommonJS specifier. Query params are not supported. return { filePath: path.resolve(dir, filename), }; } } async loadRelative( filename: string, extensions: Array, env: Environment, parentdir: string, ctx: ResolverContext, ): Promise { // Find a package.json file in the current package. let pkg = await this.findPackage(filename, ctx); // First try as a file, then as a directory. let resolvedFile = await this.loadAsFile({ file: filename, extensions, env, pkg, ctx, }); // Don't load as a directory if this is a URL dependency. if (!resolvedFile && ctx.specifierType !== 'url') { resolvedFile = await this.loadDirectory({ dir: filename, extensions, env, ctx, pkg, }); } if (!resolvedFile) { // If we can't load the file do a fuzzySearch for potential hints let relativeFileSpecifier = relativePath(parentdir, filename); let potentialFiles = await findAlternativeFiles( this.fs, relativeFileSpecifier, parentdir, this.projectRoot, true, ctx.specifierType !== 'url', extensions.length === 0, ); throw new ThrowableDiagnostic({ diagnostic: { message: md`Cannot load file '${relativeFileSpecifier}' in '${relativePath( this.projectRoot, parentdir, )}'.`, hints: potentialFiles.map(r => { return `Did you mean '__${r}__'?`; }), }, }); } return resolvedFile; } findBuiltin(filename: string, env: Environment): ?string { const isExplicitNode = filename.startsWith('node:'); if (isExplicitNode || builtins[filename]) { if (env.isNode()) { return null; } if (isExplicitNode) { filename = filename.substr(5); } // By default, exclude node builtins from libraries unless explicitly opted in. if ( env.isLibrary && this.shouldIncludeNodeModule(env, filename) !== true ) { return null; } return builtins[filename] || empty; } if (env.isElectron() && filename === 'electron') { return null; } } findNodeModulePath( filename: string, sourceFile: FilePath, ctx: ResolverContext, ): ?Module { let [moduleName, subPath] = this.getModuleParts(filename); ctx.invalidateOnFileCreate.push({ fileName: `node_modules/${moduleName}`, aboveFilePath: sourceFile, }); let dir = path.dirname(sourceFile); let moduleDir = this.fs.findNodeModule(moduleName, dir); if (moduleDir) { return { moduleName, subPath, moduleDir, filePath: subPath ? path.join(moduleDir, subPath) : moduleDir, }; } return undefined; } async loadNodeModules( module: Module, extensions: Array, env: Environment, ctx: ResolverContext, ): Promise { // If a module was specified as a module sub-path (e.g. some-module/some/path), // it is likely a file. Try loading it as a file first. if (module.subPath && module.moduleDir) { let pkg = await this.readPackage(module.moduleDir, ctx); let res = await this.loadAsFile({ file: nullthrows(module.filePath), extensions, env, pkg, ctx, }); if (res) { return res; } } // Otherwise, load as a directory. return this.loadDirectory({ dir: nullthrows(module.filePath), extensions, env, ctx, }); } async loadDirectory({ dir, extensions, env, ctx, pkg, }: {| dir: string, extensions: Array, env: Environment, ctx: ResolverContext, pkg?: InternalPackageJSON | null, |}): Promise { let failedEntry; try { pkg = await this.readPackage(dir, ctx); if (pkg) { // Get a list of possible package entry points. let entries = this.getPackageEntries(pkg, env); for (let entry of entries) { // First try loading package.main as a file, then try as a directory. let res = (await this.loadAsFile({ file: entry.filename, extensions, env, pkg, ctx, })) || (await this.loadDirectory({ dir: entry.filename, extensions, env, pkg, ctx, })); if (res) { return res; } else { failedEntry = entry; throw new Error(''); } } } } catch (e) { if (failedEntry && pkg) { // If loading the entry failed, try to load an index file, and fall back // to it if it exists. let indexFallback = await this.loadAsFile({ file: path.join(dir, 'index'), extensions, env, pkg, ctx, }); if (indexFallback != null) { return indexFallback; } let fileSpecifier = relativePath(dir, failedEntry.filename); let alternatives = await findAlternativeFiles( this.fs, fileSpecifier, pkg.pkgdir, this.projectRoot, ); let alternative = alternatives[0]; let pkgContent = await this.fs.readFile(pkg.pkgfile, 'utf8'); throw new ThrowableDiagnostic({ diagnostic: { message: md`Could not load '${fileSpecifier}' from module '${pkg.name}' found in package.json#${failedEntry.field}`, codeFrames: [ { filePath: pkg.pkgfile, language: 'json', code: pkgContent, codeHighlights: generateJSONCodeHighlights(pkgContent, [ { key: `/${failedEntry.field}`, type: 'value', message: md`'${fileSpecifier}' does not exist${ alternative ? `, did you mean '${alternative}'?` : '' }'`, }, ]), }, ], }, }); } } // Skip index fallback unless this is actually a directory. try { if (!(await this.fs.stat(dir)).isDirectory()) { return; } } catch (err) { return; } // Fall back to an index file inside the directory. return this.loadAsFile({ file: path.join(dir, 'index'), extensions, env, pkg: pkg ?? (await this.findPackage(path.join(dir, 'index'), ctx)), ctx, }); } async readPackage( dir: string, ctx: ResolverContext, ): Promise { let file = path.join(dir, 'package.json'); let cached = this.packageCache.get(file); if (cached) { ctx.invalidateOnFileChange.add(cached.pkgfile); return cached; } let json; try { json = await this.fs.readFile(file, 'utf8'); } catch (err) { // If the package.json doesn't exist, watch for it to be created. ctx.invalidateOnFileCreate.push({ filePath: file, }); throw err; } // Add the invalidation *before* we try to parse the JSON in case of errors // so that changes are picked up if the file is edited to fix the error. ctx.invalidateOnFileChange.add(file); let pkg = JSON.parse(json); await this.processPackage(pkg, file, dir); this.packageCache.set(file, pkg); return pkg; } async processPackage(pkg: InternalPackageJSON, file: string, dir: string) { pkg.pkgfile = file; pkg.pkgdir = dir; // If the package has a `source` field, check if it is behind a symlink. // If so, we treat the module as source code rather than a pre-compiled module. if (pkg.source) { let realpath = await this.fs.realpath(file); if (realpath === file) { delete pkg.source; } } } getPackageEntries( pkg: InternalPackageJSON, env: Environment, ): Array<{| filename: string, field: string, |}> { return this.mainFields .map(field => { if (field === 'browser' && pkg.browser != null) { if (!env.isBrowser()) { return null; } else if (typeof pkg.browser === 'string') { return {field, filename: pkg.browser}; } else if (typeof pkg.browser === 'object' && pkg.browser[pkg.name]) { return { field: `browser/${pkg.name}`, filename: pkg.browser[pkg.name], }; } } return { field, filename: pkg[field], }; }) .filter( entry => entry && entry.filename && typeof entry.filename === 'string', ) .map(entry => { invariant(entry != null && typeof entry.filename === 'string'); // Current dir refers to an index file if (entry.filename === '.' || entry.filename === './') { entry.filename = 'index'; } return { field: entry.field, filename: path.resolve(pkg.pkgdir, entry.filename), }; }); } async loadAsFile({ file, extensions, env, pkg, ctx, }: {| file: string, extensions: Array, env: Environment, pkg: InternalPackageJSON | null, ctx: ResolverContext, |}): Promise { // Try all supported extensions let files = await this.expandFile(file, extensions, env, pkg); let found = this.fs.findFirstFile(files); // Add invalidations for higher priority files so we // re-resolve if any of them are created. for (let file of files) { if (file === found) { break; } ctx.invalidateOnFileCreate.push({ filePath: file, }); } if (found) { return {path: found, pkg}; } return null; } async expandFile( file: string, extensions: Array, env: Environment, pkg: InternalPackageJSON | null, expandAliases?: boolean = true, ): Promise> { // Expand extensions and aliases let res = []; for (let ext of extensions) { let f = file + ext; if (expandAliases) { let alias = await this.resolveAliases(f, env, pkg); let aliasPath; if (alias && alias.type === 'file') { aliasPath = alias.resolved; } if (aliasPath && aliasPath !== f) { res = res.concat( await this.expandFile(aliasPath, extensions, env, pkg, false), ); } } if (path.extname(f)) { res.push(f); } } return res; } async resolveAliases( filename: string, env: Environment, pkg: InternalPackageJSON | null, ): Promise { let localAliases = await this.resolvePackageAliases(filename, env, pkg); if (localAliases) { return localAliases; } // First resolve local package aliases, then project global ones. return this.resolvePackageAliases(filename, env, this.rootPackage); } async resolvePackageAliases( filename: string, env: Environment, pkg: InternalPackageJSON | null, ): Promise { if (!pkg) { return null; } if (pkg.source && !Array.isArray(pkg.source)) { let alias = await this.getAlias(filename, pkg, pkg.source); if (alias != null) { return alias; } } if (pkg.alias) { let alias = await this.getAlias(filename, pkg, pkg.alias); if (alias != null) { return alias; } } if (pkg.browser && env.isBrowser()) { let alias = await this.getAlias(filename, pkg, pkg.browser); if (alias != null) { return alias; } } return null; } async getAlias( filename: FilePath, pkg: InternalPackageJSON, aliases: ?Aliases, ): Promise { if (!filename || !aliases || typeof aliases !== 'object') { return null; } let dir = pkg.pkgdir; let alias; // If filename is an absolute path, get one relative to the package.json directory. if (path.isAbsolute(filename)) { filename = relativePath(dir, filename); alias = this.lookupAlias(aliases, filename); } else { // It is a node_module. First try the entire filename as a key. alias = this.lookupAlias(aliases, normalizeSeparators(filename)); if (alias == null) { // If it didn't match, try only the module name. let [moduleName, subPath] = this.getModuleParts(filename); alias = this.lookupAlias(aliases, moduleName); if (typeof alias === 'string' && subPath) { let isRelative = alias.startsWith('./'); // Append the filename back onto the aliased module. alias = path.posix.join(alias, subPath); // because of path.join('./nested', 'sub') === 'nested/sub' if (isRelative) alias = './' + alias; } } } // If the alias is set to `false`, return an empty file. if (alias === false) { return { type: 'file', sourcePath: pkg.pkgfile, resolved: EMPTY_SHIM, }; } if (alias instanceof Object) { if (alias.global) { if (typeof alias.global !== 'string' || alias.global.length === 0) { throw new ThrowableDiagnostic({ diagnostic: { message: md`The global alias for ${filename} is invalid.`, hints: [`Only nonzero-length strings are valid global aliases.`], }, }); } return { type: 'global', sourcePath: pkg.pkgfile, resolved: alias.global, }; } else if (alias.fileName) { alias = alias.fileName; } } if (typeof alias === 'string') { // Assume file let resolved = await this.resolveFilename(alias, dir, 'commonjs'); if (!resolved) { return null; } return { type: 'file', sourcePath: pkg.pkgfile, resolved: resolved.filePath, }; } return null; } lookupAlias(aliases: Aliases, filename: FilePath): null | boolean | string { if (typeof aliases !== 'object') { return null; } // First, try looking up the exact filename let alias = aliases[filename]; if (alias == null) { // Otherwise, try replacing glob keys for (let key in aliases) { let val = aliases[key]; if (typeof val === 'string' && isGlob(key)) { // https://github.com/micromatch/picomatch/issues/77 if (filename.startsWith('./')) { filename = filename.slice(2); } let re = globToRegex(key, {capture: true}); if (re.test(filename)) { alias = filename.replace(re, val); break; } } } } return alias; } async findPackage( sourceFile: string, ctx: ResolverContext, ): Promise { ctx.invalidateOnFileCreate.push({ fileName: 'package.json', aboveFilePath: sourceFile, }); // Find the nearest package.json file within the current node_modules folder let res = await loadConfig( this.fs, sourceFile, ['package.json'], this.projectRoot, // By default, loadConfig uses JSON5. Use normal JSON for package.json files // since they don't support comments and JSON.parse is faster. {parser: (...args) => JSON.parse(...args)}, ); if (res != null) { let file = res.files[0].filePath; let dir = path.dirname(file); ctx.invalidateOnFileChange.add(file); let pkg = res.config; await this.processPackage(pkg, file, dir); return pkg; } return null; } async loadAlias( filename: string, sourceFile: FilePath, env: Environment, ctx: ResolverContext, ): Promise { // Load the root project's package.json file if we haven't already if (!this.rootPackage) { this.rootPackage = await this.findPackage( path.join(this.projectRoot, 'index'), ctx, ); } // Load the local package, and resolve aliases let pkg = await this.findPackage(sourceFile, ctx); return this.resolveAliases(filename, env, pkg); } getModuleParts(name: string): [FilePath, ?string] { name = path.normalize(name); let splitOn = name.indexOf(path.sep); if (name.charAt(0) === '@') { splitOn = name.indexOf(path.sep, splitOn + 1); } if (splitOn < 0) { return [normalizeSeparators(name), undefined]; } else { return [ normalizeSeparators(name.substring(0, splitOn)), name.substring(splitOn + 1) || undefined, ]; } } hasSideEffects(filePath: FilePath, pkg: InternalPackageJSON): boolean { switch (typeof pkg.sideEffects) { case 'boolean': return pkg.sideEffects; case 'string': { let glob = pkg.sideEffects; invariant(typeof glob === 'string'); let relative = path.relative(pkg.pkgdir, filePath); if (!glob.includes('/')) { glob = `**/${glob}`; } // Trim off "./" to make micromatch behave correctly, // `path.relative` never returns a leading "./" if (glob.startsWith('./')) { glob = glob.substr(2); } return isGlobMatch(relative, glob, {dot: true}); } case 'object': return pkg.sideEffects.some(sideEffects => this.hasSideEffects(filePath, {...pkg, sideEffects}), ); } return true; } }