From a5262e4fa998d4fe4a733ac656c23fe198b72589 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 15 Mar 2022 18:39:39 +0100 Subject: [PATCH] fix: improve externals handling --- src/rollup/plugins/externals.ts | 67 ++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/src/rollup/plugins/externals.ts b/src/rollup/plugins/externals.ts index 02c8450ea8..68beb7cc40 100644 --- a/src/rollup/plugins/externals.ts +++ b/src/rollup/plugins/externals.ts @@ -2,14 +2,13 @@ import { existsSync, promises as fsp } from 'fs' import { resolve, dirname, normalize } from 'pathe' import { nodeFileTrace, NodeFileTraceOptions } from '@vercel/nft' import type { Plugin } from 'rollup' -import { resolvePath, isValidNodeImport, normalizeid } from 'mlly' +import { resolvePath, isValidNodeImport } from 'mlly' export interface NodeExternalsOptions { inline?: string[] external?: string[] outDir?: string trace?: boolean - normalizeId?: boolean traceOptions?: NodeFileTraceOptions moduleDirectories?: string[] exportConditions?: string[] @@ -21,26 +20,32 @@ export function externals (opts: NodeExternalsOptions): Plugin { return { name: 'node-externals', - async resolveId (id, importer, options) { + async resolveId (originalId, importer, options) { // Skip internals - if (!id || id.startsWith('\x00') || id.includes('?') || id.startsWith('#')) { + if (!originalId || originalId.startsWith('\x00') || originalId.includes('?') || originalId.startsWith('#')) { return null } - // Normalize path on windows - const normalizedId = normalize(id) + // Skip relative paths + if (originalId.startsWith('.')) { + return null + } - const _id = normalizedId.split('node_modules/').pop() - if (!opts.external.find(i => _id.startsWith(i) || id.startsWith(i))) { - // Resolve relative paths and exceptions - // Ensure to take absolute and relative id - if (_id.startsWith('.') || opts.inline.find(i => _id.startsWith(i) || normalizedId.startsWith(i))) { - return null - } + // Normalize path (windows) + const id = normalize(originalId) + + // Id without .../node_modules/ + const idWithoutNodeModules = id.split('node_modules/').pop() + + // const matchedExternal = opts.external.find(i => idWithoutNodeModules.startsWith(i) || id.startsWith(i)) + + // Check for explicit inlines + if (opts.inline.find(i => (id.startsWith(i) || idWithoutNodeModules.startsWith(i)))) { + return null } - // Resolve external (rollup => node) - const resolved = await this.resolve(id, importer, { ...options, skipSelf: true }) || { id } + // Resolve id (rollup then native node ESM) + const resolved = await this.resolve(originalId, importer, { ...options, skipSelf: true }) || { id } if (!existsSync(resolved.id)) { resolved.id = await resolvePath(resolved.id, { conditions: opts.exportConditions, @@ -48,7 +53,7 @@ export function externals (opts: NodeExternalsOptions): Plugin { }) } - // Ensure id is a valid import + // Inline invalid node imports if (!await isValidNodeImport(resolved.id)) { return { ...resolved, @@ -59,11 +64,23 @@ export function externals (opts: NodeExternalsOptions): Plugin { // Track externals trackedExternals.add(resolved.id) - // Normalize id with explicit protocol - if (opts.normalizeId) { - resolved.id = normalizeid(resolved.id) + // Try to extract package name from path + const { pkgName } = parseNodeModulePath(resolved.id) + + // Inline in trace-mode when cannot extract package name + if (!pkgName && opts.trace !== false) { + return null + } + + // External with package name in trace mode + if (opts.trace !== false) { + return { + id: pkgName, + external: true + } } + // External with full pack in normal mode return { ...resolved, external: true @@ -81,10 +98,10 @@ export function externals (opts: NodeExternalsOptions): Plugin { .then(r => Array.from(r.fileList).map(f => resolve(opts.traceOptions.base, f))) .then(r => r.filter(file => file.includes('node_modules'))) - // // Find all unique package names + // Find all unique package names const pkgs = new Set() for (const file of tracedFiles) { - const [, baseDir, pkgName] = /^(.+\/node_modules\/)([^@/]+|@[^/]+\/[^/]+)(\/?.*?)?$/.exec(file) + const { baseDir, pkgName } = parseNodeModulePath(file) pkgs.add(resolve(baseDir, pkgName, 'package.json')) } @@ -114,6 +131,14 @@ export function externals (opts: NodeExternalsOptions): Plugin { } } +function parseNodeModulePath (path: string) { + const [, baseDir, pkgName] = /^(.+\/node_modules\/)([^@/]+|@[^/]+\/[^/]+)(\/?.*?)?$/.exec(path) + return { + baseDir, + pkgName + } +} + async function isFile (file: string) { try { const stat = await fsp.stat(file)