diff --git a/packages/nitro/src/context.ts b/packages/nitro/src/context.ts index 8cd5f0e561..e0a917a95a 100644 --- a/packages/nitro/src/context.ts +++ b/packages/nitro/src/context.ts @@ -5,6 +5,7 @@ import Hookable, { configHooksT } from 'hookable' import type { Preset } from '@nuxt/un' import { tryImport, resolvePath, detectTarget, extendPreset } from './utils' import * as PRESETS from './presets' +import type { NodeExternalsOptions } from './rollup/plugins/externals' export interface ServerMiddleware { route: string @@ -18,7 +19,7 @@ export interface SigmaContext { inlineChunks: boolean minify: boolean sourceMap: boolean - externals: boolean + externals: boolean | NodeExternalsOptions analyze: boolean entry: string node: boolean diff --git a/packages/nitro/src/rollup/config.ts b/packages/nitro/src/rollup/config.ts index 34f994c8ce..56491792a0 100644 --- a/packages/nitro/src/rollup/config.ts +++ b/packages/nitro/src/rollup/config.ts @@ -1,5 +1,6 @@ import { dirname, join, relative, resolve } from 'upath' import { InputOptions, OutputOptions } from 'rollup' +import defu from 'defu' import { terser } from 'rollup-plugin-terser' import commonjs from '@rollup/plugin-commonjs' import nodeResolve from '@rollup/plugin-node-resolve' @@ -51,6 +52,8 @@ export const getRollupConfig = (sigmaContext: SigmaContext) => { const env = un.env(nodePreset, builtinPreset, sigmaContext.env) + delete env.alias['node-fetch'] // FIX ME + if (sigmaContext.sourceMap) { env.polyfill.push('source-map-support/register') } @@ -165,15 +168,27 @@ export const getRollupConfig = (sigmaContext: SigmaContext) => { } })) - // External Plugin + const moduleDirectories = [ + resolve(sigmaContext._nuxt.rootDir, 'node_modules'), + resolve(MODULE_DIR, 'node_modules'), + resolve(MODULE_DIR, '../node_modules'), + 'node_modules' + ] + + // Externals Plugin if (sigmaContext.externals) { - rollupConfig.plugins.push(externals({ - relativeTo: sigmaContext.output.serverDir, - include: [ + rollupConfig.plugins.push(externals(defu(sigmaContext.externals as any, { + outDir: sigmaContext.output.serverDir, + moduleDirectories, + ignore: [ sigmaContext._internal.runtimeDir, + ...(sigmaContext._nuxt.dev ? [] : [sigmaContext._nuxt.buildDir]), ...sigmaContext.middleware.map(m => m.handle) - ] - })) + ], + traceOptions: { + base: sigmaContext._nuxt.rootDir + } + }))) } // https://github.com/rollup/plugins/tree/master/packages/node-resolve @@ -181,11 +196,7 @@ export const getRollupConfig = (sigmaContext: SigmaContext) => { extensions, preferBuiltins: true, rootDir: sigmaContext._nuxt.rootDir, - moduleDirectories: [ - resolve(sigmaContext._nuxt.rootDir, 'node_modules'), - resolve(MODULE_DIR, 'node_modules'), - 'node_modules' - ], + moduleDirectories, mainFields: ['main'] // Force resolve CJS (@vue/runtime-core ssrUtils) })) diff --git a/packages/nitro/src/rollup/plugins/externals.ts b/packages/nitro/src/rollup/plugins/externals.ts index e05537c9b7..71aefa4ca8 100644 --- a/packages/nitro/src/rollup/plugins/externals.ts +++ b/packages/nitro/src/rollup/plugins/externals.ts @@ -1,24 +1,62 @@ -import { isAbsolute, relative } from 'upath' +import { isAbsolute, relative } from 'path' +import type { Plugin } from 'rollup' +import { resolve, dirname } from 'upath' +import { copyFile, mkdirp } from 'fs-extra' +import { nodeFileTrace, NodeFileTraceOptions } from '@vercel/nft' -export function externals ({ include = [], relativeTo }) { +export interface NodeExternalsOptions { + ignore?: string[] + outDir?: string + trace?: boolean + traceOptions?: NodeFileTraceOptions + moduleDirectories?: string[] +} + +export function externals (opts: NodeExternalsOptions): Plugin { + const resolvedExternals = {} return { - name: 'externals', - resolveId (source) { - if ( - source[0] === '.' || // Compile relative imports - source[0] === '\x00' || // Skip helpers - source.includes('?') || // Skip helpers - include.find(i => source.startsWith(i)) - ) { return null } - - if (!isAbsolute(source)) { - source = require.resolve(source) + name: 'node-externals', + resolveId (id) { + // Internals + if (id.startsWith('\x00') || id.includes('?')) { + return null } + // Resolve relative paths and exceptions + if (id.startsWith('.') || opts.ignore.find(i => id.startsWith(i))) { + return null + } + + for (const dir of opts.moduleDirectories) { + if (id.startsWith(dir)) { + id = id.substr(dir.length + 1) + break + } + } + + try { + resolvedExternals[id] = require.resolve(id, { paths: opts.moduleDirectories }) + } catch (_err) { } + return { - id: relative(relativeTo, source), + id: isAbsolute(id) ? relative(opts.outDir, id) : id, external: true } + }, + async buildEnd () { + if (opts.trace) { + const { fileList } = await nodeFileTrace(Object.values(resolvedExternals), opts.traceOptions) + await Promise.all(fileList.map(async (file) => { + if (!file.startsWith('node_modules')) { + return + } + // TODO: Minify package.json + const src = resolve(opts.traceOptions.base, file) + const dst = resolve(opts.outDir, file) + await mkdirp(dirname(dst)) + await copyFile(src, dst) + })) + } } } } diff --git a/packages/nitro/src/utils/tree.ts b/packages/nitro/src/utils/tree.ts index 15dad502b9..15e6607774 100644 --- a/packages/nitro/src/utils/tree.ts +++ b/packages/nitro/src/utils/tree.ts @@ -6,7 +6,7 @@ import { readFile } from 'fs-extra' import chalk from 'chalk' export async function printFSTree (dir) { - const files = await globby('**/*.js', { cwd: dir }) + const files = await globby('**/*.*', { cwd: dir }) const items = (await Promise.all(files.map(async (file) => { const path = resolve(dir, file) @@ -19,16 +19,27 @@ export async function printFSTree (dir) { let totalSize = 0 let totalGzip = 0 + let totalNodeModulesSize = 0 + let totalNodeModulesGzip = 0 + items.forEach((item, index) => { let dir = dirname(item.file) if (dir === '.') { dir = '' } const rpath = relative(process.cwd(), item.path) const treeChar = index === items.length - 1 ? '└─' : '├─' - process.stdout.write(chalk.gray(` ${treeChar} ${rpath} (${prettyBytes(item.size)}) (${prettyBytes(item.gzip)} gzip)\n`)) + const isNodeModules = item.file.includes('node_modules') + + if (isNodeModules) { + totalNodeModulesSize += item.size + totalNodeModulesGzip += item.gzip + return + } + + process.stdout.write(chalk.gray(` ${treeChar} ${rpath} (${prettyBytes(item.size)}) (${prettyBytes(item.gzip)} gzip)\n`)) totalSize += item.size totalGzip += item.gzip }) - process.stdout.write(`${chalk.cyan('Σ Total size:')} ${prettyBytes(totalSize)} (${prettyBytes(totalGzip)} gzip)\n`) + process.stdout.write(`${chalk.cyan('Σ Total size:')} ${prettyBytes(totalSize + totalNodeModulesSize)} (${prettyBytes(totalGzip + totalNodeModulesGzip)} gzip)\n`) }