diff --git a/bin/build.ts b/bin/build.ts new file mode 100644 index 0000000..9db2d4a --- /dev/null +++ b/bin/build.ts @@ -0,0 +1,35 @@ +import { resolve } from 'path'; +import * as bundt from 'bundt'; + +const root = resolve('packages'); +const minify = !process.argv.includes('--dev'); + +let result: bundt.Output = {}; + +async function run(mod: string) { + let dir = resolve(root, mod); + let stats = await bundt.build(dir, { minify }); + Object.assign(result, stats); +} + +try { + var timer = process.hrtime(); + await Promise.all([ + run('create-worktop'), + run('worktop.build'), + run('worktop'), + ]); + timer = process.hrtime(timer); +} catch (err) { + let msg = (err as Error).stack || err; + console.error(msg); + process.exit(1); +} + +console.log( + await bundt.report(result, { + cwd: root, + gzip: true, + delta: timer, + }) +); diff --git a/bin/index.js b/bin/index.js deleted file mode 100644 index ac512c9..0000000 --- a/bin/index.js +++ /dev/null @@ -1,154 +0,0 @@ -// @ts-check -const fs = require('node:fs'); -const { build } = require('esbuild'); -const { join, dirname } = require('node:path'); -const { builtinModules } = require('node:module'); -const utils = require('./utils'); - -const packages = join(__dirname, '../packages'); -const overrides = { - worktop: { - 'router.ts': '.' - } -}; - -/** @param {string} message */ -function bail(message) { - console.error(message); - process.exit(1); -} - -/** - * @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( - join(pkgdir, 'src') - ); - - let externals = [ - pkg.name, ...builtinModules, - ...Object.keys(pkg.dependencies||{}), - ...Object.keys(pkg.peerDependencies||{}), - ]; - - if (pkg.exports == null) { - return bail(`Missing "exports" in module: ${modname}`); - } - - let outputs = []; - let encoder = new TextEncoder; - - /** - * @param {string} file - * @param {Uint8Array|string} content - */ - async function write(file, content) { - await fs.promises.writeFile(file, content); - if (typeof content === 'string') content = encoder.encode(content); - outputs.push(utils.inspect(file, content)); - } - - let i=0, isTS=/\.ts$/, tasks=[]; - let overs = overrides[modname] || {}; - - for (files.sort(); i < files.length; i++) { - let dts, file = files[i]; - if (!isTS.test(file)) continue; - if (file == 'node_modules') continue; - if (/\.(test|d)\.ts$/.test(file)) continue; - - if (isMulti) { - 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) { - if (typeof pkg.exports === 'string') { - let isCJS = /\.c?js$/.test(pkg.exports); - entry = { [isCJS ? 'require' : 'import']: pkg.exports }; - } else if (pkg.exports.require || pkg.exports.import) { - entry = pkg.exports; - } else { - return bail(`Missing "exports" entry: ${key}`); - } - - entry.import = entry.import || './fake.mjs'; - } - - if (!entry.import) return bail(`Missing "import" condition: ${key}`); - if (!entry.require) return bail(`Missing "require" condition: ${key}`); - - tasks.push(async function () { - let input = join(pkgdir, 'src', file); - let output = join(pkgdir, entry.import); - - // build ts -> esm - let esm = await build({ - bundle: true, - format: 'esm', - sourcemap: false, - entryPoints: [input], - external: externals, - outfile: output, - target: 'es2019', - treeShaking: true, - logLevel: 'warning', - minifyIdentifiers: true, - minifySyntax: true, - charset: 'utf8', - write: false, - }).then(bundle => { - return bundle.outputFiles[0]; - }); - - let outdir = dirname(esm.path); - - // purge existing directory - if (isMulti && fs.existsSync(outdir)) { - await fs.promises.rm(outdir, { - recursive: true, - force: true, - }); - } - - // create dir (safe writes) - if (isMulti) await fs.promises.mkdir(outdir); - esm.path.endsWith('fake.mjs') || await write(esm.path, esm.contents); - - // convert esm -> cjs - output = join(pkgdir, entry.require); - await write(output, utils.rewrite(esm.text)); - - 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) - ); - } - }()); - } - - await Promise.all(tasks); - utils.table(modname, pkgdir, outputs); -} - -/** - * init - */ -Promise.all([ - bundle('worktop', true), - bundle('create-worktop', false), - bundle('worktop.build', false), -]); diff --git a/bin/register.js b/bin/register.js index 2b4769c..62bc549 100644 --- a/bin/register.js +++ b/bin/register.js @@ -1,7 +1,5 @@ // @ts-check -const { transformSync } = require('esbuild'); - -const loadJS = require.extensions['.js']; +require('fetchy/polyfill'); // @ts-ignore - worktop/cfw.cache globalThis.caches = { default: {} }; @@ -12,33 +10,3 @@ globalThis.crypto = require('crypto').webcrypto; // worktop/base64 globalThis.btoa = (x) => Buffer.from(x, 'binary').toString('base64'); globalThis.atob = (x) => Buffer.from(x, 'base64').toString('binary'); - -require('fetchy/polyfill'); - -require.extensions['.ts'] = function (Module, filename) { - const pitch = Module._compile.bind(Module); - - Module._compile = source => { - const { code, warnings } = transformSync(source, { - loader: 'ts', - format: 'cjs', - target: 'es2019', - sourcemap: 'inline', - sourcefile: filename, - minifyIdentifiers: true, - minifySyntax: true, - treeShaking: true, - charset: 'utf8', - }); - - warnings.forEach(msg => { - console.warn(`\nesbuild warning in ${filename}:`); - console.warn(msg.location); - console.warn(msg.text); - }); - - return pitch(code, filename); - }; - - loadJS(Module, filename); -} diff --git a/bin/utils.js b/bin/utils.js deleted file mode 100644 index ce9915e..0000000 --- a/bin/utils.js +++ /dev/null @@ -1,103 +0,0 @@ -// @ts-check -// @see https://github.com/lukeed/bundt/blob/master/index.js -const { white, cyan, dim } = require('kleur'); -const rimports = require('rewrite-imports'); -const { normalize } = require('path'); -const { gzipSync } = require('zlib'); - -const _ = ' '; -const UNITS = ['B ', 'kB', 'MB', 'GB']; -const lpad = (str, max) => _.repeat(max - str.length) + str; -const rpad = (str, max) => str + _.repeat(max - str.length); -const th = dim().bold().italic().underline; - -/** - * @typedef FileData - * @property {string} file - * @property {string} size - * @property {string} gzip - */ - -/** - * @param {string} file - * @param {Uint8Array} buffer - * @returns {FileData} - */ -exports.inspect = function (file, buffer) { - let gz = gzipSync(buffer).byteLength; - return { - file: file, - gzip: size(gz), - size: size(buffer.byteLength), - }; -} - -function size(val=0) { - if (val < 1e3) return `${val} ${UNITS[0]}`; - let exp = Math.min(Math.floor(Math.log10(val) / 3), UNITS.length - 1) || 1; - let out = (val / Math.pow(1e3, exp)).toPrecision(3); - let idx = out.indexOf('.'); - if (idx === -1) { - out += '.00'; - } else if (out.length - idx - 1 !== 2) { - out = (out + '00').substring(0, idx + 3); // 2 + 1 for 0-based - } - return out + ' ' + UNITS[exp]; -} - -/** - * @param {string} modname - * @param {string} pkgdir - * @param {FileData[]} files - * @returns {void} - */ -exports.table = function (modname, pkgdir, files) { - let f=modname.length, s=8, g=6; - let G1 = _+_, G2 = G1+G1, out=''; - - files.sort((a, b) => a.file.localeCompare(b.file)).forEach(obj => { - obj.file = normalize( - obj.file.replace(pkgdir, '') - ); - - f = Math.max(f, obj.file.length); - s = Math.max(s, obj.size.length); - g = Math.max(g, obj.gzip.length); - }); - - f += 2; // underline extension - - out += G1 + th(rpad(modname, f)) + G2 + th(lpad('Filesize', s)) + G1 + dim().bold().italic(lpad('(gzip)', g)); - - files.forEach((obj, idx) => { - if (idx && idx % 3 === 0) out += '\n'; - out += ('\n' + G1 + white(rpad(obj.file, f)) + G2 + cyan(lpad(obj.size, s)) + G1 + dim().italic(lpad(obj.gzip, g))); - }); - - console.log('\n' + out + '\n'); -} - -/** - * @param {string} content The ESM input file - * @see https://github.com/lukeed/bundt/blob/master/index.js#L131 - * @returns {string} - */ -exports.rewrite = function (content) { - let footer = ''; - return rimports(content) - .replace(/(^|\s|;)export default/, '$1module.exports =') - .replace(/(^|\s|;)export (const|(?:async )?function|class|let|var) (.+?)(?=(\(|\s|\=))/gi, (_, x, type, name) => { - footer += `\nexports.${name} = ${name};`; - return `${x}${type} ${name}`; - }) - .replace(/(^|\s|\n|;?)export \{([\s\S]*?)\};?([\n\s]*?|$)/g, (_, x, names) => { - names.split(',').forEach(name => { - let [src, dest] = name.trim().split(/\s+as\s+/); - footer += `\nexports.${dest || src} = ${src};`; - }); - return x; - }) - .concat( - footer - ); -} diff --git a/package.json b/package.json index 09c98fe..8847fa1 100644 --- a/package.json +++ b/package.json @@ -10,17 +10,16 @@ "node": ">=12" }, "scripts": { - "build": "node bin", - "test": "uvu packages \".test.ts$\" -r ./bin/register", + "build": "tsm bin/build.ts", + "test": "uvu packages \".test.ts$\" -r tsm -r ./bin/register", "types": "tsc --noEmit --skipLibCheck" }, "devDependencies": { "@types/node": "16.11.6", - "esbuild": "0.13.2", + "bundt": "2.0.0-next.1", "fetchy": "next", "is-uuid": "1.0.2", - "kleur": "4.1.4", - "rewrite-imports": "2.0.3", + "tsm": "2.2.1", "typescript": "4.5.4", "uvu": "0.5.2" } diff --git a/packages/worktop/src/router.d.ts b/packages/worktop/src/index.d.ts similarity index 100% rename from packages/worktop/src/router.d.ts rename to packages/worktop/src/index.d.ts diff --git a/packages/worktop/src/router.ts b/packages/worktop/src/index.ts similarity index 100% rename from packages/worktop/src/router.ts rename to packages/worktop/src/index.ts diff --git a/tsconfig.json b/tsconfig.json index a7704c7..7e27916 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,12 +16,13 @@ "checkJs": true, "noEmit": true, "paths": { - "worktop": ["./packages/worktop/src/router.d.ts"], + "worktop": ["./packages/worktop/src/index.d.ts"], "worktop/*": ["./packages/worktop/src/*.d.ts"], "worktop.build": ["./packages/worktop.build/index.d.ts"], } }, "include": [ + "bin/*.ts", "packages/combos.ts", "packages/worktop/src", "packages/worktop/types",