Skip to content

Commit

Permalink
feat: add worktop.build package (#94)
Browse files Browse the repository at this point in the history
* initial commit

* chore: setup pkg files

* chore: setup module builder

* feat: add `build` export

* chore: self-reference type defs

* chore: rename `bin` script

* chore: update ci steps

* feat: add `bin` script

* feat(bin): write output file

* fix: include `jsc.minify` options

* fix(bin): track time

* fix: replace swc -> esbuild;

- see: swc-project/swc#1725 (comment)

* chore: type param

* 0.0.1

* fix: resolve output file from --cwd option

* feat: pass `--cwd` thru as `absWorkingDir` option

* fix: do not double-print esbuild errors

* 0.0.2

* feat: add `define` helper

* feat: auto-load `worktop.config.js` config file

* feat: add "exports" map

* 0.0.3

* chore: remove `worktop.build` repo root/config files

* chore(bin): modify build system for single-entry pkg
  • Loading branch information
lukeed committed Sep 26, 2021
1 parent ebb1976 commit 9e689da
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 16 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ node_modules
!/packages/worktop/src
!/packages/worktop/types

/packages/worktop.build/index.js
/packages/worktop.build/index.mjs

/examples/build
39 changes: 25 additions & 14 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ function bail(message) {
process.exit(1);
}

/** @param {string} modname */
async function bundle(modname) {
/**
* @param {string} modname
* @param {boolean} isMulti
*/
async function bundle(modname, isMulti = true) {
let pkgdir = join(packages, modname);
let pkg = require(join(pkgdir, 'package.json'));
let files = await fs.promises.readdir(
Expand Down Expand Up @@ -58,9 +61,14 @@ async function bundle(modname) {
if (file == 'node_modules') continue;
if (/\.(test|d)\.ts$/.test(file)) continue;

let dts = file.replace(isTS, '.d.ts');
files.includes(dts) || bail(`Missing "${dts}" file!`);
let key = overs[file] || ('./' + file.replace(isTS, ''));
if (isMulti) {
var dts = file.replace(isTS, '.d.ts');
files.includes(dts) || bail(`Missing "${dts}" file!`);
}

let key = overs[file];
if (!key && file === 'index.ts') key = '.';
else if (!key) key = './' + file.replace(isTS, '');

let entry = pkg.exports[key];
if (!entry) return bail(`Missing "exports" entry: ${key}`);
Expand Down Expand Up @@ -93,27 +101,29 @@ async function bundle(modname) {
let outdir = dirname(esm.path);

// purge existing directory
if (fs.existsSync(outdir)) {
if (isMulti && fs.existsSync(outdir)) {
await fs.promises.rm(outdir, {
recursive: true,
force: true,
});
}

// create dir (safe writes)
await fs.promises.mkdir(outdir);
if (isMulti) await fs.promises.mkdir(outdir);
await write(esm.path, esm.contents);

// convert esm -> cjs
output = join(pkgdir, entry.require);
await write(output, utils.rewrite(esm.text));

// foo.d.ts -> foo/index.d.ts
input = join(pkgdir, 'src', dts);
await write(
join(outdir, 'index.d.ts'),
await fs.promises.readFile(input)
);
if (isMulti) {
// foo.d.ts -> foo/index.d.ts
input = join(pkgdir, 'src', dts);
await write(
join(outdir, 'index.d.ts'),
await fs.promises.readFile(input)
);
}
}());
}

Expand All @@ -125,5 +135,6 @@ async function bundle(modname) {
* init
*/
Promise.all([
bundle('worktop')
bundle('worktop', true),
bundle('worktop.build', false),
]);
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"types": "tsc --noEmit --skipLibCheck"
},
"devDependencies": {
"@types/node": "16.7.10",
"esbuild": "0.11.14",
"@types/node": "16.9.6",
"esbuild": "0.13.2",
"fetchy": "next",
"is-uuid": "1.0.2",
"kleur": "4.1.4",
Expand Down
68 changes: 68 additions & 0 deletions packages/worktop.build/bin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node
const argv = process.argv.slice(2);
const flags = require('mri')(argv, {
alias: {
C: 'cwd',
h: 'help',
c: 'config',
f: 'format',
l: 'loglevel',
a: 'analyze',
m: 'minify',
},
default: {
C: '.',
h: false,
a: false,
f: 'module',
c: 'worktop.config.js',
l: 'info',
m: false,
}
});

/** @param {string} msg */
function bail(msg, code = 1) {
console.error(msg);
process.exit(code);
}

function help() {
let msg = '';
msg += '\n Usage';
msg += '\n $ worktop build [input] [options]\n';
msg += '\n Options';
msg += '\n -C, --cwd Current working directory (default ".")';
msg += '\n -c, --config Path to config file (default "worktop.config.js")';
msg += '\n -f, --format Worker/Script output format (default "module")';
msg += '\n -l, --loglevel Logging display level (default "info")';
msg += '\n -a, --analyze Render bundle output analysis';
msg += '\n -m, --minify Minify built output file(s)';
msg += '\n -h, --help Displays this message\n';
msg += '\n Examples';
msg += '\n $ worktop build';
msg += '\n $ worktop build --format sw';
msg += '\n $ worktop build --minify --format sw';
msg += '\n $ worktop build src/main.ts --format sw';
console.log(msg + '\n');
process.exit(0);
}

if (flags.help) help();
let [cmd, entry] = flags._;
if (cmd && cmd.toLowerCase() !== 'build') {
bail(`Invalid command: ${cmd}\nPlease run \`worktop --help\` for more information.`);
}

require('.').build({
config: flags.config,
input: entry || 'index.ts',
output: 'build/index.mjs',
loglevel: flags.loglevel,
analyze: flags.analyze,
minify: flags.minify,
cwd: flags.cwd,
}).catch(err => {
if (err.errors) process.exitCode = 1;
else bail(err.stack || err.message);
});
31 changes: 31 additions & 0 deletions packages/worktop.build/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { BuildOptions, LogLevel } from 'esbuild';

export type Platform = 'node' | 'browser' | 'cloudflare';

export interface Options {
input: string;
output: string;
config?: string;
/** @default "cloudflare" */
platform?: Platform;
/** @default "esnext" */
target?: string | string[];
/** @default "esm" */
format?: 'esm' | 'cjs';
/** @default false */
sourcemap?: boolean;
external?: string[];
/** @default false */
minify?: boolean;
/** @default false */
analyze?: boolean;
/** @default "." */
cwd?: string;
/** @default "info" */
loglevel?: LogLevel;
overrides?: BuildOptions;
modify?(config: BuildOptions): void;
}

export function define(options: Options): Options;
export function build(options: Options): Promise<void>;
38 changes: 38 additions & 0 deletions packages/worktop.build/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"version": "0.0.3",
"name": "worktop.build",
"repository": "lukeed/worktop",
"description": "wip",
"module": "index.mjs",
"types": "index.d.ts",
"main": "index.js",
"license": "MIT",
"bin": {
"worktop": "bin.js"
},
"author": {
"name": "Luke Edwards",
"email": "luke.edwards05@gmail.com",
"url": "https://lukeed.com"
},
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.js"
},
"./package.json": "./package.json"
},
"files": [
"index.d.ts",
"index.mjs",
"index.js",
"bin.js"
],
"engines": {
"node": ">=12"
},
"dependencies": {
"esbuild": "^0.13.2",
"mri": "^1.2.0"
}
}
94 changes: 94 additions & 0 deletions packages/worktop.build/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { existsSync } from 'fs';
import { dirname, join, resolve } from 'path';

import type { BuildOptions } from 'esbuild';
import type { Options } from '../';

let esbuild: typeof import('esbuild');

// NOTE: prevent Rollup import() -> require()
async function load<T = any>(ident: string): Promise<T> {
return Function('x', 'return import("file:///" + x)')(ident);
}

// TODO? custom config OR allow default set
// TODO? transform typescript config file w/o -r hook
// TODO: validate --format and/or --platform combination
export async function build(options: Options): Promise<void> {
esbuild = esbuild || await import('esbuild');

let cwd = resolve(options.cwd || '.');

if (options.config) {
let tmp, b, dir=cwd;
let root = process.cwd();
while (true) {
tmp = join(dir, options.config);
if (b = existsSync(tmp)) break;
else if (dir === root) break;
else dir = dirname(dir);
}
if (b) await load(tmp).then(m => {
m = (m.default || m) as Options;
Object.assign(options, m.config || m);
});
}

let { platform, sourcemap, external=[] } = options;
let fields = ['worker', 'browser', 'module', 'jsnext', 'main'];
let conds = ['worker', 'browser', 'import', 'production', 'default'];

let config: BuildOptions = {
bundle: true,
splitting: false,
absWorkingDir: cwd,
outfile: options.output,
entryPoints: [options.input],
format: options.format || 'esm',
target: options.target || 'esnext',
sourcemap: sourcemap ? 'inline' : false,
resolveExtensions: ['.tsx', '.ts', '.jsx', '.mjs', '.js', '.json', '.htm', '.html'],
external: ([] as string[]).concat(external),
logLevel: options.loglevel || 'info',
minify: !!options.minify,
mainFields: fields,
conditions: conds,
charset: 'utf8',
loader: {
'.htm': 'text',
'.html': 'text',
}
};

if (platform === 'node') {
fields = fields.slice(2);
conds = ['node', 'require', ...conds.slice(2)];
}

if (options.modify) {
options.modify(config);
} else if (options.overrides) {
Object.assign(config, options.overrides);
}

config.write = true;

if (options.analyze) {
config.metafile = true;
}

let result = await esbuild.build(config);

if (options.analyze) {
console.log(
await esbuild.analyzeMetafile(result.metafile!, {
verbose: /^(debug|verb)$/i.test(config.logLevel!),
color: true,
})
);
}
}

export function define(config: Options): Options {
return config;
}
2 changes: 2 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"include": [
"packages/worktop/src",
"packages/worktop/types",
"packages/worktop.build/src",
"packages/worktop.build/bin.js",
"examples/workers/**/*"
],
"exclude": [
Expand Down

0 comments on commit 9e689da

Please sign in to comment.