Skip to content

Commit

Permalink
merge two resolution plugins to make sure both work together (#130)
Browse files Browse the repository at this point in the history
* merge two resolution plugins to make sure both work together

* docs(changeset): Fix standalone mode (embedding externals)

See evanw/esbuild#2216

Fixed #106
  • Loading branch information
cometkim committed Mar 17, 2023
1 parent a3b954c commit 3d07b82
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/rare-apes-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nanobundle": patch
---

Fix standalone mode (embedding externals)
3 changes: 3 additions & 0 deletions src/plugins/esbuildEmbedPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ type PluginOptions = {
context: Context,
};

/**
* @deprecated since esbuild resolution cannot be chained
*/
export function makePlugin({
context: {
reporter,
Expand Down
3 changes: 3 additions & 0 deletions src/plugins/esbuildImportMapsPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ type PluginOptions = {
importMaps: ImportMaps,
};

/**
* @deprecated since esbuild resolution cannot be chained
*/
export function makePlugin({
context,
importMaps,
Expand Down
138 changes: 138 additions & 0 deletions src/plugins/esbuildNanobundlePlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import * as path from 'node:path';
import { type Plugin } from 'esbuild';

import { type Context } from '../context';
import * as fsUtils from '../fsUtils';
import { type ImportMaps, replaceSubpathPattern } from '../importMaps';

type PluginOptions = {
context: Context,
importMaps: ImportMaps,
};

export function makePlugin({
context,
context: {
reporter,
standalone,
externalDependencies,
forceExternalDependencies,
},
importMaps,
}: PluginOptions): Plugin {
const ownedModule = (packageName: string, modulePath: string) => {
return packageName === modulePath || modulePath.startsWith(packageName + '/');
};

const isNodeApi = (modulePath: string) => {
if (externalDependencies.some(dep => modulePath.startsWith(dep))) {
return false;
}
return modulePath.startsWith('node:') || nodeApis.some(api => ownedModule(api, modulePath));
};

const shouldEmbed = (modulePath: string) => {
if (forceExternalDependencies.some(dep => ownedModule(dep, modulePath))) {
return false;
}
return standalone || !externalDependencies.some(dep => ownedModule(dep, modulePath));
};

const resolveModulePathFromImportMaps = async (modulePath: string) => {
const resolved = path.resolve(path.dirname(context.importMapsPath), modulePath);
const exist = await fsUtils.chooseExist([
resolved.replace(/\.(c|m)?js$/, '.tsx'),
resolved.replace(/\.(c|m)?js$/, '.ts'),
resolved,
]);
return exist || resolved;
};

return {
name: 'nanobundle',
setup(build) {
let dependOnNode = false;

build.onResolve({ filter: /.*/ }, async args => {
if (fsUtils.isFileSystemReference(args.path)) {
return;
}

const modulePath = replaceSubpathPattern(importMaps, args.path);
const external = !fsUtils.isFileSystemReference(modulePath);

let resolvedAsNodeApi = isNodeApi(modulePath);
if (resolvedAsNodeApi) {
dependOnNode = true;
}

if (!resolvedAsNodeApi && shouldEmbed(modulePath)) {
return {};
}

return {
external,
path: external
? modulePath
: await resolveModulePathFromImportMaps(modulePath),
};
});

build.onEnd(() => {
if (standalone && dependOnNode) {
reporter.warn('Not completely standalone bundle, while the code depends on some Node.js APIs.');
}
});
},
};
}

const nodeApis = [
'assert',
'async_hooks',
'buffer',
'child_process',
'cluster',
'console',
'crypto',
'diagnostics_channel',
'dns',
'events',
'fs',
'http',
'http2',
'https',
'inspector',
'module',
'net',
'os',
'path',
'perf_hooks',
'process',
'readline',
'stream',
'string_decoder',
'timers',
'tls',
'trace_events',
'tty',
'dgram',
'url',
'util',
'v8',
'vm',
'wasi',
'worker_threads',
'zlib',

// legacy
'querystring',

// deprecated
'_linklist',
'_stream_wrap',
'constants',
'domain',
'punycode',
'sys',
];
15 changes: 3 additions & 12 deletions src/tasks/buildBundleTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import {
type ValidNodeImportMaps,
} from '../importMaps';
import { type OutputFile } from '../outputFile';
import { makePlugin as makeImportMapsPlugin } from '../plugins/esbuildImportMapsPlugin';
import { makePlugin as makeEmbedPlugin } from '../plugins/esbuildEmbedPlugin';
import { makePlugin as makeDefaultPlugin } from '../plugins/esbuildNanobundlePlugin';

export class BuildBundleTaskError extends NanobundleError {
esbuildErrors: esbuild.Message[];
Expand Down Expand Up @@ -167,7 +166,6 @@ async function buildBundleGroup({
treeShaking: true,
keepNames: true,
format: options.module === 'commonjs' ? 'cjs' : 'esm',
plugins: [],
conditions: options.customConditions,
};

Expand All @@ -186,16 +184,9 @@ async function buildBundleGroup({
}

const importMaps = normalizeImportMaps(validImportMaps, options);
const importMapsPlugin = makeImportMapsPlugin({ context, importMaps });
esbuildOptions.plugins?.push(importMapsPlugin);

const embedPlugin = makeEmbedPlugin({ context });
esbuildOptions.plugins?.push(embedPlugin);

esbuildOptions.plugins = [
...esbuildOptions.plugins ?? [],
...plugins,
];
const defaultPlugin = makeDefaultPlugin({ context, importMaps });
esbuildOptions.plugins = [defaultPlugin, ...plugins];
}

context.reporter.debug('esbuild build options %o', esbuildOptions);
Expand Down

0 comments on commit 3d07b82

Please sign in to comment.