Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: make zero deps pkg #67

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
31 changes: 31 additions & 0 deletions .github/workflows/dev-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Dev Publish

on:
workflow_dispatch:

jobs:
publish:
runs-on: ubuntu-latest
permissions:
checks: read
statuses: write
contents: write
packages: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'yarn'
- name: build
run: |
yarn install --immutable
yarn workspace esbuild-node-externals build

- run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: |
yarn workspace esbuild-node-externals npm version $(node --eval="process.stdout.write(require('./package.json').version)")-dev.$(git rev-parse --short HEAD) --no-git-tag-version
yarn workspace esbuild-node-externals npm publish --provenance --access=public --no-git-tag-version --tag dev
6 changes: 4 additions & 2 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ on:

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [18.x, 20.x, 22.x]

name: Test (Nodejs v${{ matrix.node-version }}, OS ${{ matrix.os }})
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4

Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#on:
# push:
# branches:
# - main
on:
push:
branches:
- main
workflow_dispatch:

name: release-please
jobs:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ node_modules
# Build artefacts
dist
temp
.npmrc
340 changes: 259 additions & 81 deletions .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs

Large diffs are not rendered by default.

768 changes: 0 additions & 768 deletions .yarn/releases/yarn-3.1.0.cjs

This file was deleted.

875 changes: 875 additions & 0 deletions .yarn/releases/yarn-3.8.5.cjs

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
compressionLevel: mixed

enableGlobalCache: false

nodeLinker: node-modules

npmAuthToken: "${NPM_TOKEN-''}"

plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: '@yarnpkg/plugin-interactive-tools'
spec: "@yarnpkg/plugin-interactive-tools"

yarnPath: .yarn/releases/yarn-3.1.0.cjs
yarnPath: .yarn/releases/yarn-3.8.5.cjs
42 changes: 10 additions & 32 deletions esbuild-node-externals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,39 +56,17 @@ esbuild.build({
});
```

#### `options.packagePath`
| Option | Description | Default |
|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------|
| `packagePath` | Path to your `package.json`. Can be a string or an array of strings. If this option is not specified the default behavior is to start with the current directory's package.json then go up scan for all package.json files in parent directories recursively until either the root git directory is reached or until no other package.json can be found. | `undefined` |
| `dependencies` | Make package.json `dependencies` external. | `true` |
| `devDependencies` | Make package.json `devDependencies` external. | `true` |
| `peerDependencies` | Make package.json `peerDependencies` external. | `true` |
| `optionalDependencies` | Make package.json `optionalDependencies` external. | `true` |
| `allowList` | An array for the externals to allow, so they will be included in the bundle. | `[]` |
| `allowWorkspaces` | Automatically exclude all packages defined as workspaces (`workspace:*`) in a monorepo. | `false` |
| `cwd` | Sets the current working directory for the plugin. | `buildOptions.absWorkingDir \|\| process.cwd()` |

Path to your `package.json`. Can be a string or an array of strings. If you are using a monorepo you can provide a list of all the `package.json` to check.

If this option is not specified the default behavior is to start with the current directory's package.json then go up scan for all package.json files in parent directories recursively until either the root git directory is reached or until no other package.json can be found.

#### `options.dependencies` (default to `true`)

Make package.json `dependencies` external.

#### `options.devDependencies` (default to `true`)

Make package.json `devDependencies` external.

#### `options.peerDependencies` (default to `true`)

Make package.json `peerDependencies` external.

#### `options.optionalDependencies` (default to `true`)

Make package.json `optionalDependencies` external.

#### `options.allowList` (default to `[]`)

An array for the externals to allow, so they will be included in the bundle. Can accept exact strings ('module_name'), regex patterns (/^module_name/), or a function that accepts the module name and returns whether it should be included.

#### `options.allowWorkspaces` (default to `false`)

Automatically exclude all packages defined as workspaces (`workspace:*`) in a monorepo.

#### `options.cwd` (default to `buildOptions.absWorkingDir || process.cwd()`)

Sets the current working directory for the plugin.

## Inspiration

Expand Down
17 changes: 9 additions & 8 deletions esbuild-node-externals/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@
"scripts": {
"typecheck": "tsc --noEmit",
"prebuild": "rimraf ./dist",
"build": "tsc",
"build": "yarn build:js && yarn build:dts",
"build:js": "esbuild --platform=node --target=node12 --sourcemap --sources-content=false --outdir=dist --format=cjs src/*.ts",
"build:dts": "tsc --declaration --emitDeclarationOnly --outDir dist",
"watch": "tsc --watch",
"test": "node ./test/unit/index.test.mjs"
"test": "node ./test/unit/all.test.mjs"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pradel/esbuild-node-externals.git"
},
"repository": "pradel/esbuild-node-externals",
"author": "Leo Pradel <[email protected]>",
"license": "MIT",
"keywords": [
Expand All @@ -26,17 +31,13 @@
"engines": {
"node": ">=12"
},
"dependencies": {
"find-up": "^5.0.0",
"tslib": "^2.4.1"
},
"peerDependencies": {
"esbuild": "0.12 - 0.24"
},
"devDependencies": {
"@types/node": "^18.15.10",
"esbuild": "^0.24.0",
"rimraf": "^4.4.1",
"typescript": "^4.9.4"
"typescript": "^5.6.3"
}
}
18 changes: 8 additions & 10 deletions esbuild-node-externals/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ export const nodeExternalsPlugin = (paramsOptions: Options = {}): Plugin => {
const allowPredicate =
options.allowList && createAllowPredicate(options.allowList);


return {
name: 'node-externals',
setup(build) {
const cwd = options.cwd || build.initialOptions.absWorkingDir || process.cwd()
const cwd =
options.cwd || build.initialOptions.absWorkingDir || process.cwd();
const nodeModules = findDependencies({
packagePaths: options.packagePath
? options.packagePath
Expand All @@ -68,14 +68,12 @@ export const nodeExternalsPlugin = (paramsOptions: Options = {}): Plugin => {
return null;
}

// To allow sub imports from packages we take only the first path to deduct the name
let moduleName = args.path.split('/')[0];

// In case of scoped package
if (args.path.startsWith('@')) {
const split = args.path.split('/');
moduleName = `${split[0]}/${split[1]}`;
}
const chunks = args.path.split('/');
const moduleName = args.path.startsWith('@')
// In case of scoped package
? chunks.slice(0, 2).join('/')
// To allow sub imports from packages we take only the first path to deduct the name
: chunks[0];

// Mark the module as external so it is not resolved
if (nodeModules.includes(moduleName)) {
Expand Down
52 changes: 18 additions & 34 deletions esbuild-node-externals/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import path from 'path';
import fs from 'fs';
import findUp from 'find-up';

export type AllowPredicate = (path: string) => boolean;
export type AllowList = (string | RegExp)[] | AllowPredicate;
Expand All @@ -16,46 +15,29 @@ export const createAllowPredicate = (allowList: AllowList): AllowPredicate => {
);
};

/**
* Determines if the `child` path is under the `parent` path.
*/
const isInDirectory = (parent: string, child: string): boolean => {
const relativePath = path.relative(parent, child);
return !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
};

const isInGitDirectory = (path: string, gitRootPath?: string): boolean => {
return gitRootPath === undefined || isInDirectory(gitRootPath, path);
};

/**
* Iterates over package.json file paths recursively found in parent directories, starting from the
* current working directory. If the current working directory is in a git repository, then package.json
* files outside of the git repository will not be yielded.
* files outside the git repository will not be yielded.
* Inspired by https://github.com/Septh/rollup-plugin-node-externals/blob/f13ee95c6f1f01d8ba2276bf491aac399adc5482/src/dependencies.ts#L18
*/
export const findPackagePaths = (_cwd: string = process.cwd()): string[] => {
// Find git root if in git repository
const gitDirectoryPath = findUp.sync('.git', {
type: 'directory',
cwd: _cwd,
});
const gitRootPath: string | undefined =
gitDirectoryPath === undefined ? undefined : path.dirname(gitDirectoryPath);

let cwd: string = _cwd;
let packagePath: string | undefined;
const packagePaths: string[] = [];
export const findPackagePaths = (cwd: string = process.cwd()): string[] => {
const chunks = path.resolve(cwd).split(path.sep);
const paths = [];

while (
(packagePath = findUp.sync('package.json', { type: 'file', cwd })) &&
isInGitDirectory(packagePath, gitRootPath)
) {
packagePaths.push(packagePath);
cwd = path.dirname(path.dirname(packagePath));
for (let i = chunks.length; i > 0; i--) {
const dir = chunks.slice(0, i).join(path.sep);
const packagePath = path.join(dir, 'package.json');
const gitPath = path.join(dir, '.git');
if (fs.statSync(packagePath, { throwIfNoEntry: false })?.isFile()) {
paths.push(packagePath);
}
if (fs.statSync(gitPath, { throwIfNoEntry: false })?.isDirectory()) {
return paths;
}
}

return packagePaths;
return paths;
};

function getDependencyKeys(
Expand All @@ -69,7 +51,9 @@ function getDependencyKeys(
return Object.keys(map);
}
// Filter out shared workspaces
return Object.keys(map).filter((depKey) => !map[depKey].startsWith('workspace:'));
return Object.keys(map).filter(
(depKey) => !map[depKey].startsWith('workspace:')
);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions esbuild-node-externals/test/fixtures/index.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { __extends } from 'tslib'
import { version } from 'typescript'

console.log(__extends)
console.log(version)
2 changes: 1 addition & 1 deletion esbuild-node-externals/test/fixtures/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"name": "fixture-pkg",
"private": true,
"dependencies": {
"tslib": "*"
"typescript": "*"
}
}
2 changes: 2 additions & 0 deletions esbuild-node-externals/test/unit/all.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import './index.test.mjs'
import './utils.test.mjs'
67 changes: 49 additions & 18 deletions esbuild-node-externals/test/unit/index.test.mjs
Original file line number Diff line number Diff line change
@@ -1,31 +1,62 @@
import assert from 'node:assert'
import fs from 'node:fs/promises'
import path from 'node:path'
import { describe, it } from 'node:test'
import os from 'node:os'
import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'
import { describe, it } from 'node:test'
import { build } from 'esbuild'
import { nodeExternalsPlugin } from 'esbuild-node-externals'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const require = createRequire(import.meta.url)
const tempdir = async (prefix = `t${Math.random().toString(36).slice(2)}`) => {
const dirpath = path.join(os.tmpdir(), prefix)
await fs.mkdir(dirpath, { recursive: true })

return dirpath
}

describe('index', () => {
describe('nodeExternalsPlugin', () => {
it('should exclude node_modules from bundle', async () => {
const plugin = nodeExternalsPlugin()
const temp = await tempdir()
const config = {
absWorkingDir: path.resolve(__dirname, '../fixtures'),
entryPoints: ['index.mjs'],
outdir: temp,
bundle: true,
platform: 'node',
}
await build(config)
const r1 = await fs.readFile(path.resolve(__dirname, `${temp}/index.js`), 'utf8')
assert.ok(r1.includes('node_modules/typescript'))

await build({
...config,
plugins: [plugin]
})
const r2 = await fs.readFile(path.resolve(__dirname, `${temp}/index.js`), 'utf8')
assert.ok(!r2.includes('node_modules/typescript'))
})

describe('nodeExternalsPlugin', () => {
it('should exclude node_modules from bundle', async () => {
const plugin = nodeExternalsPlugin()
const config = {
absWorkingDir: path.resolve(__dirname, '../fixtures'),
entryPoints: ['index.mjs'],
outdir: '../temp',
bundle: true,
}
await build(config)
const result1 = await fs.readFile(path.resolve(__dirname, '../temp/index.js'), 'utf8')
assert.equal(result1.includes('node_modules/tslib/tslib.es6.mjs'), true)
it('works in commonjs mode too', async () => {
const { nodeExternalsPlugin: nodeExternalsPluginCjs } = require('esbuild-node-externals')
const plugin = nodeExternalsPluginCjs()
const temp = await tempdir()
const config = {
absWorkingDir: path.resolve(__dirname, '../fixtures'),
entryPoints: ['index.mjs'],
outdir: temp,
bundle: true,
plugins: [plugin]
}
await build(config)
const result = await fs.readFile(path.resolve(__dirname, `${temp}/index.js`), 'utf8')

await build({
...config,
plugins: [plugin]
assert.equal(result.includes('node_modules/typescript'), false)
assert.ok(nodeExternalsPlugin === nodeExternalsPluginCjs)
})
const result2 = await fs.readFile(path.resolve(__dirname, '../temp/index.js'), 'utf8')
assert.equal(result2.includes('node_modules/tslib/tslib.es6.mjs'), false)
})
})
Loading