Skip to content

Commit

Permalink
wip: v1 rc (#44)
Browse files Browse the repository at this point in the history
* wip: implicit src

* add more tests

* spec change & more tests

* all tests for JS are now passed

* refactor config

* minify by filename convention

* wip rewrite

* rename parsedConfig -> context

* fix typo

* more test

* wip: build command and expand context module more

* wip

* build bundle and files

* add tsconfig tests

* set declaration=true as default

* wip: types entries

* added failing tests for typescript

* types entry for conditional exports

* fix broken tests

* validate types entry extension

* validate types entry format

* validate types entry order

* entry tests all passed

* warn if module for dts isn't determined

* update browserslist

* entries on different rootDir, outDir

* fix context parsing

* allow rootDir=outDir in TypeScript project

* refactor

* Node v16 requirement

* build script

* format messages

* support bin entry

* state: self-hosting binary

* prepare major release
  • Loading branch information
cometkim authored Dec 4, 2022
1 parent 636d868 commit c6fad2b
Show file tree
Hide file tree
Showing 47 changed files with 6,461 additions and 1,570 deletions.
11 changes: 11 additions & 0 deletions .changeset/tender-boxes-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"nanobundle": major
---

v1 features

- support multiple entries
- support nested conditional exports
- source inference from rootDir and outDir
- enable tree-shaking by default
- pretty reporter
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
.yarn/** linguist-vendored
.yarn/releases/* binary linguist-vendored

*.typegen.ts linguist-generated
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
node_modules/
coverage/

/bin.mjs
/bin.min.mjs

.yarn/*
!.yarn/patches
Expand Down
768 changes: 0 additions & 768 deletions .yarn/releases/yarn-3.1.1.cjs

This file was deleted.

807 changes: 807 additions & 0 deletions .yarn/releases/yarn-3.3.0.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ nmMode: hardlinks-global

nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-3.1.1.cjs
yarnPath: .yarn/releases/yarn-3.3.0.cjs
16 changes: 16 additions & 0 deletions build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import esbuild from 'esbuild';
import manifest from './package.json' assert { type: 'json' };

esbuild.build({
entryPoints: ['src/bin.ts'],
outfile: 'bin.min.mjs',
bundle: true,
write: true,
treeShaking: true,
sourcemap: false,
minify: true,
format: 'esm',
platform: 'node',
target: ['node16'],
external: Object.keys(manifest.dependencies),
});
28 changes: 19 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.28",
"type": "module",
"license": "MIT",
"bin": "./bin.mjs",
"bin": "./bin.min.mjs",
"repository": {
"type": "git",
"url": "https://github.com/cometkim/nanobundle.git"
Expand All @@ -17,7 +17,8 @@
},
"scripts": {
"prepack": "yarn build",
"build": "esbuild src/bin.ts --bundle --format=esm --platform=node --target=node14.8 --outfile=bin.mjs --external:meow --external:esbuild --external:browserslist --external:typescript --external:pretty-bytes --external:tsconfck",
"build": "node build.mjs",
"build:self": "node bin.min.mjs build",
"dev": "vitest --watch",
"test": "vitest",
"changeset": "changeset"
Expand All @@ -26,7 +27,7 @@
"bin.mjs"
],
"engines": {
"node": ">=14.8.0"
"node": ">=16.0.0"
},
"peerDependencies": {
"typescript": "^3.7.0 || ^4.0.0"
Expand All @@ -37,18 +38,27 @@
}
},
"dependencies": {
"browserslist": "^4.19.3",
"@cometjs/core": "^2.1.0",
"browserslist": "^4.21.4",
"esbuild": "^0.14.23",
"kleur": "^4.1.5",
"meow": "^10.1.2",
"pretty-bytes": "^6.0.0",
"tsconfck": "^1.2.0"
"semver": "^7.3.8",
"string-dedent": "^3.0.1",
"tsconfck": "^1.2.0",
"xstate": "^4.34.0"
},
"devDependencies": {
"@changesets/cli": "^2.20.0",
"@types/node": "^17.0.18",
"@types/semver": "^7.3.13",
"@xstate/cli": "^0.3.3",
"c8": "^7.11.0",
"typescript": "^4.5.5",
"vitest": "^0.5.7"
"pkg-types": "^1.0.1",
"typescript": "^4.9.3",
"vite": "^3.2.4",
"vitest": "^0.25.2"
},
"packageManager": "yarn@3.1.1"
}
"packageManager": "yarn@3.3.0"
}
3 changes: 3 additions & 0 deletions src/__snapshots__/config.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Vitest Snapshot v1

exports[`parseConfig > flags > rootDir=outDir 1`] = `"Directory rootDir(.) and outDir(.) are conflict! Please specify different directory for one of them."`;
7 changes: 7 additions & 0 deletions src/__snapshots__/context.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Vitest Snapshot v1

exports[`parseConfig > flags > rootDir=outDir is not allowed without TypeScript 1`] = `
"\\"rootDir\\" (.) and \\"outDir\\" (.) are conflict!
Please specify different directory for one of them."
`;
20 changes: 20 additions & 0 deletions src/__snapshots__/entry.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Vitest Snapshot v1

exports[`getEntriesFromContext > bin entry only accepts js 1`] = `"Only JavaScript files are allowed for bin entry."`;

exports[`getEntriesFromContext > throw if "main" and "module" is on conflict 1`] = `
"Hint: Did you forgot to set \\"type\\" to 'module' for ESM-first approach?
"
`;

exports[`getEntriesFromContext - in TypeScript project > types entry does not accept nesting 1`] = `
"\\"types\\" entry must be .d.ts file and cannot be nested!
"
`;

exports[`getEntriesFromContext - in TypeScript project > types entry must has .d.ts extension 1`] = `"Only .d.ts or .d.cts or .d.mts allowed for \\"types\\" entry."`;

exports[`getEntriesFromContext - in TypeScript project > types entry must occur first in conditional exports 1`] = `
"\\"types\\" entry must occur first in conditional exports for correct type resolution.
"
`;
196 changes: 54 additions & 142 deletions src/bin.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,27 @@
#!/usr/bin/env node

import { performance } from 'node:perf_hooks';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { parseNative } from 'tsconfck';
import type {
Program as TSProgram,
CompilerOptions as TSCompilerOptions,
} from 'typescript';
import { parse as parseTsConfig } from 'tsconfck';
import dedent from 'string-dedent';

import type { Reporter } from './report';
import { cli } from './cli';
import { loadConfig } from './config';
import { ConsoleReporter } from './reporter';
import { loadTargets } from './target';
import { loadImportMaps, normalizeImportMaps, validateImportMaps } from './importMaps';
import { getEntriesFromConfig } from './entry';
import { buildCommand } from './commands/build';
import { makePlugin as makeEmbedPlugin } from './plugins/esbuildEmbedPlugin';
import { makePlugin as makeImportMapsPlugin } from './plugins/esbuildImportMapsPlugin';
import { loadManifest } from './manifest';
import { parseConfig } from './context';
import { getEntriesFromContext } from './entry';
import * as formatUtils from './formatUtils';
import { NanobundleError } from './errors';

import { buildCommand } from './commands/build/build';

const { flags, input } = cli;
const [command] = input;
const noop = () => {};
const debugEnabled = process.env.DEBUG === 'true';

const {
cwd: basePath,
external: forceExternalDependencies,
minify,
sourcemap,
standalone,
} = flags;

const reporter: Reporter = {
debug: debugEnabled ? console.debug : noop,
info: console.info,
warn: console.warn,
error: console.error,
};
const reporter = new ConsoleReporter(console);
reporter.level = process.env.DEBUG === 'true' ? 'debug' : 'default';

const resolvePath = (file: string) => path.resolve(basePath, file);
const resolve = (cwd: string, subpath: string) => path.resolve(cwd, subpath);

try {
switch (command) {
Expand All @@ -48,137 +30,67 @@ try {
}

case 'build': {
const startedAt = performance.now();

const config = await loadConfig({ resolvePath });
const sourceFile = config.source && resolvePath(config.source);
if (!sourceFile || !fs.existsSync(sourceFile)) {
throw new Error('`"source"` field must be specified in the package.json');
}

reporter.debug(`build ${config.name || 'unnamed'} package`);
reporter.debug(`load source from ${sourceFile}`);
const manifest = await loadManifest({
cwd: flags.cwd,
resolve,
});
reporter.info(`build ${manifest.name || 'unnamed'} package`);

const externalDependencies = [
...(config.dependencies ? Object.keys(config.dependencies) : []),
...(config.peerDependencies ? Object.keys(config.peerDependencies) : []),
...forceExternalDependencies,
];
const importMaps = await loadImportMaps(
flags.importMaps,
{ resolvePath },
);
const webImportMaps = validateImportMaps(
normalizeImportMaps(importMaps, 'web'),
{ resolvePath },
const tsconfigResult = await parseTsConfig(flags.tsconfig, {
resolveWithEmptyIfConfigNotFound: true,
});
const tsconfigPath = (
tsconfigResult.tsconfigFile !== 'no_tsconfig_file_found'
? tsconfigResult.tsconfigFile
: undefined
);
const nodeImportMaps = validateImportMaps(
normalizeImportMaps(importMaps, 'node'),
{ resolvePath },
const tsconfig = (
tsconfigResult.tsconfigFile !== 'no_tsconfig_file_found'
? tsconfigResult.tsconfig
: undefined
);
const embedPlugin = makeEmbedPlugin({
if (tsconfigPath) {
reporter.debug(`load tsconfig from ${tsconfigPath}`);
}
const targets = await loadTargets({ basePath: flags.cwd });
const context = parseConfig({
flags,
targets,
manifest,
tsconfig,
tsconfigPath,
resolve,
reporter,
standalone,
externalDependencies,
forceExternalDependencies,
});
const webImportMapsPlugin = makeImportMapsPlugin({
name: 'web',
imports: webImportMaps.imports,
resolvePath,
});
const nodeImportMapsPlugin = makeImportMapsPlugin({
name: 'node',
imports: nodeImportMaps.imports,
resolvePath,
});
const webPlugins = [
webImportMapsPlugin,
embedPlugin,
];
const nodePlugins = [
nodeImportMapsPlugin,
embedPlugin,
];

let tsconfig: string | undefined;
let tsProgram: TSProgram | undefined;
if (flags.dts && config.types) {
const ts = await import('typescript').then(mod => mod.default);
const tsconfigResult = await parseNative(flags.tsconfig);

tsconfig = tsconfigResult.tsconfigFile;
reporter.debug(`load tsconfig from ${tsconfig}`);

const compilerOptions: TSCompilerOptions = {
...tsconfigResult.result.options,
reporter.debug(`load targets ${context.targets.join(', ')}`);

allowJs: true,
incremental: false,
skipLibCheck: true,
declaration: true,
emitDeclarationOnly: true,
};

if (compilerOptions.noEmit) {
reporter.warn('Ignored `compilerOptions.noEmit` since the package required `types` entry.');
reporter.warn('You can still disable emitting declaration via `--dts=false` option');
compilerOptions.noEmit = false;
}

const host = ts.createCompilerHost(compilerOptions);
tsProgram = ts.createProgram([sourceFile], compilerOptions, host);
}

const targets = await loadTargets({ basePath });
reporter.debug(`targets to ${targets.join(', ')}`);

const entries = getEntriesFromConfig({
config,
const entries = getEntriesFromContext({
context,
reporter,
resolvePath,
resolve,
});

await buildCommand({
reporter,
sourceFile,
context,
entries,
targets,
tsconfig,
resolvePath,
minify,
sourcemap,
webPlugins,
nodePlugins,
});

if (tsProgram) {
reporter.info('Emitting .d.ts files...');

const { emittedFiles } = tsProgram.emit();
reporter.debug('emitted', emittedFiles);
}

const endedAt = performance.now();
const elapsedTime = (endedAt - startedAt).toFixed(1);
reporter.info(`\n⚡ Done in ${elapsedTime}ms.`);

break;
}

case 'watch': {
throw new Error('sorry, not implemeted yet');
}

default: {
throw new Error(`
Command "${command}" is not available.
throw new NanobundleError(dedent`
Command "${command}" is not available.
Run \`nanobundle --help\` for more detail.`,
);
Run ${formatUtils.command('nanobundle --help')} for usage.
`);
}
}
} catch (error) {
reporter.error(error);
if (error instanceof NanobundleError) {
reporter.error(error.message);
} else {
reporter.captureException(error);
}
process.exit(1);
}
Loading

0 comments on commit c6fad2b

Please sign in to comment.