Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacs committed Jun 6, 2024
1 parent eb57c19 commit 4bfcd67
Show file tree
Hide file tree
Showing 13 changed files with 5,053 additions and 2,570 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,29 @@ This would export a file at `./src/foo.ts` as `./foo`, and a file
at `./src/utils/bar.ts` as `./utils/bar`, but would ignore a file
at `./internal/private.ts`.
### Live Dev
Set `"liveDev": true` in the tshy config in `package.json` to
build in link mode. In this mode, the files are hard-linked into
place in the `dist` folder, so that edits are immediately visible.
This is particularly beneficial in monorepo projects, where
workspaces may be edited in parallel, and so it's handy to have
changes reflected in real time without a rebuild.
Of course, tools that can't handle TypeScript will have a problem
with this, so any generic `node` program will not be able to run
your code. For this reason:
- `liveDev` is always disabled when the `npm_command` environment
variable is `'publish'` or `'pack'`. In these situations, your
code is being built for public consumption, and must be
compiled.
- Code in dist will not be able to be loaded in the node repl
unless you run it with a loader, such as `node --import=tsx`.
- Because it links files into place, a rebuild _is_ required when
a file is added or removed.
### Package `#imports`
You can use `"imports"` in your package.json, and it will be
Expand Down
14 changes: 5 additions & 9 deletions src/build-commonjs.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import chalk from 'chalk'
import { spawnSync } from 'node:child_process'
import { existsSync, renameSync, unlinkSync } from 'node:fs'
import { relative, resolve } from 'node:path/posix'
import buildFail from './build-fail.js'
import config from './config.js'
import * as console from './console.js'
import ifExist from './if-exist.js'
import polyfills from './polyfills.js'
import setFolderDialect from './set-folder-dialect.js'
import './tsconfig.js'
Expand All @@ -13,10 +13,6 @@ import tsc from './which-tsc.js'
const node = process.execPath
const { commonjsDialects = [] } = config

const unlinkIfExist = (f: string) => existsSync(f) && unlinkSync(f)
const renameIfExist = (f: string, to: string) =>
existsSync(f) && renameSync(f, to)

export const buildCommonJS = () => {
setFolderDialect('src', 'commonjs')
for (const d of ['commonjs', ...commonjsDialects]) {
Expand All @@ -41,10 +37,10 @@ export const buildCommonJS = () => {
).replace(/\.tsx?$/, '')
const stemToPath = `${stemTo}.js.map`
const stemToDtsPath = `${stemTo}.d.ts.map`
unlinkIfExist(stemToPath)
unlinkIfExist(stemToDtsPath)
renameIfExist(`${stemFrom}.cjs`, `${stemTo}.js`)
renameIfExist(`${stemFrom}.d.cts`, `${stemTo}.d.ts`)
ifExist.unlink(stemToPath)
ifExist.unlink(stemToDtsPath)
ifExist.rename(`${stemFrom}.cjs`, `${stemTo}.js`)
ifExist.rename(`${stemFrom}.d.cts`, `${stemTo}.d.ts`)
}
console.error(chalk.cyan.bold('built commonjs'))
}
Expand Down
14 changes: 5 additions & 9 deletions src/build-esm.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import chalk from 'chalk'
import { spawnSync } from 'node:child_process'
import { existsSync, renameSync, unlinkSync } from 'node:fs'
import { relative, resolve } from 'node:path'
import buildFail from './build-fail.js'
import config from './config.js'
import * as console from './console.js'
import ifExist from './if-exist.js'
import polyfills from './polyfills.js'
import setFolderDialect from './set-folder-dialect.js'
import './tsconfig.js'
import tsc from './which-tsc.js'

const unlinkIfExist = (f: string) => existsSync(f) && unlinkSync(f)
const renameIfExist = (f: string, to: string) =>
existsSync(f) && renameSync(f, to)

const node = process.execPath
const { esmDialects = [] } = config

Expand All @@ -39,10 +35,10 @@ export const buildESM = () => {
`.tshy-build/${d}`,
relative(resolve('src'), resolve(orig))
).replace(/\.tsx?$/, '')
unlinkIfExist(`${stemTo}.js.map`)
unlinkIfExist(`${stemTo}.d.ts.map`)
renameIfExist(`${stemFrom}.mjs`, `${stemTo}.js`)
renameIfExist(`${stemFrom}.d.mts`, `${stemTo}.d.ts`)
ifExist.unlink(`${stemTo}.js.map`)
ifExist.unlink(`${stemTo}.d.ts.map`)
ifExist.rename(`${stemFrom}.mjs`, `${stemTo}.js`)
ifExist.rename(`${stemFrom}.d.mts`, `${stemTo}.d.ts`)
}
console.error(chalk.cyan.bold('built ' + d))
}
Expand Down
43 changes: 43 additions & 0 deletions src/build-live-commonjs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import chalk from 'chalk'
import { linkSync, mkdirSync } from 'node:fs'
import { dirname } from 'node:path'
import { relative, resolve } from 'node:path/posix'
import config from './config.js'
import * as console from './console.js'
import ifExist from './if-exist.js'
import polyfills from './polyfills.js'
import setFolderDialect from './set-folder-dialect.js'
import sources from './sources.js'
import './tsconfig.js'

const { commonjsDialects = [] } = config

// don't actually do a build, just link files into places.
export const buildLiveCommonJS = () => {
for (const d of ['commonjs', ...commonjsDialects]) {
const pf = polyfills.get(d === 'commonjs' ? 'cjs' : d)
console.debug(chalk.cyan.dim('linking ' + d))
for (const s of sources) {
const source = s.substring('./src/'.length)
const target = resolve(`.tshy-build/${d}/${source}`)
mkdirSync(dirname(target), { recursive: true })
linkSync(s, target)
}
setFolderDialect('.tshy-build/' + d, 'commonjs')
for (const [override, orig] of pf?.map.entries() ?? []) {
const stemFrom = resolve(
`.tshy-build/${d}`,
relative(resolve('src'), resolve(override))
).replace(/\.cts$/, '')
const stemTo = resolve(
`.tshy-build/${d}`,
relative(resolve('src'), resolve(orig))
).replace(/\.tsx?$/, '')
ifExist.unlink(`${stemTo}.js.map`)
ifExist.unlink(`${stemTo}.d.ts.map`)
ifExist.rename(`${stemFrom}.cjs`, `${stemTo}.js`)
ifExist.rename(`${stemFrom}.d.cts`, `${stemTo}.d.ts`)
}
console.error(chalk.cyan.bold('linked commonjs'))
}
}
41 changes: 41 additions & 0 deletions src/build-live-esm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import chalk from 'chalk'
import { linkSync, mkdirSync } from 'node:fs'
import { dirname, relative, resolve } from 'node:path'
import config from './config.js'
import * as console from './console.js'
import ifExist from './if-exist.js'
import polyfills from './polyfills.js'
import setFolderDialect from './set-folder-dialect.js'
import sources from './sources.js'
import './tsconfig.js'

const { esmDialects = [] } = config

export const buildLiveESM = () => {
for (const d of ['esm', ...esmDialects]) {
const pf = polyfills.get(d)
console.debug(chalk.cyan.dim('linking ' + d))
for (const s of sources) {
const source = s.substring('./src/'.length)
const target = resolve(`.tshy-build/${d}/${source}`)
mkdirSync(dirname(target), { recursive: true })
linkSync(s, target)
}
setFolderDialect('.tshy-build/' + d, 'esm')
for (const [override, orig] of pf?.map.entries() ?? []) {
const stemFrom = resolve(
`.tshy-build/${d}`,
relative(resolve('src'), resolve(override))
).replace(/\.mts$/, '')
const stemTo = resolve(
`.tshy-build/${d}`,
relative(resolve('src'), resolve(orig))
).replace(/\.tsx?$/, '')
ifExist.unlink(`${stemTo}.js.map`)
ifExist.unlink(`${stemTo}.d.ts.map`)
ifExist.rename(`${stemFrom}.mjs`, `${stemTo}.js`)
ifExist.rename(`${stemFrom}.d.mts`, `${stemTo}.d.ts`)
}
console.error(chalk.cyan.bold('linked ' + d))
}
}
12 changes: 10 additions & 2 deletions src/build.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import chalk from 'chalk'
import config from './config.js'
import { syncContentSync } from 'sync-content'
import bins from './bins.js'
import { buildCommonJS } from './build-commonjs.js'
Expand All @@ -18,14 +19,21 @@ import {
unlink as unlinkImports,
} from './unbuilt-imports.js'
import writePackage from './write-package.js'
import {buildLiveESM} from './build-live-esm.js'
import {buildLiveCommonJS} from './build-live-commonjs.js'

export default async () => {
cleanBuildTmp()

linkSelfDep(pkg, 'src')
await linkImports(pkg, 'src')
if (dialects.includes('esm')) buildESM()
if (dialects.includes('commonjs')) buildCommonJS()
const liveDev = config.liveDev &&
process.env.npm_command !== 'publish' &&
process.env.npm_command !== 'pack'
const esm = liveDev ? buildLiveESM : buildESM
const commonjs = liveDev ? buildLiveCommonJS : buildCommonJS
if (dialects.includes('esm')) esm()
if (dialects.includes('commonjs')) commonjs()
await unlinkImports(pkg, 'src')
unlinkSelfDep(pkg, 'src')

Expand Down
8 changes: 6 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ const validConfig = (e: any): e is TshyConfigMaybeGlobExports =>
(e.exclude === undefined || validExclude(e.exclude)) &&
validExtraDialects(e) &&
validBoolean(e, 'selfLink') &&
validBoolean(e, 'main')
validBoolean(e, 'main') &&
validBoolean(e, 'liveDev')

const match = (e: string, pattern: Minimatch[]): boolean =>
pattern.some(m => m.match(e))
Expand All @@ -58,7 +59,10 @@ const getConfig = (
? pkg.tshy
: {}
let exportsConfig = tshy.exports
if (typeof exportsConfig === 'string' || Array.isArray(exportsConfig)) {
if (
typeof exportsConfig === 'string' ||
Array.isArray(exportsConfig)
) {
// Strip off the `./src` prefix and the extension
// exports: "src/**/*.ts" => exports: {"./foo": "./src/foo.ts"}
const exp: Exclude<TshyConfig['exports'], undefined> = {}
Expand Down
57 changes: 37 additions & 20 deletions src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import { resolveExport } from './resolve-export.js'
import { Package, TshyConfig, TshyExport } from './types.js'
const { esmDialects = [], commonjsDialects = [] } = config

const liveDev =
config.liveDev &&
process.env.npm_command !== 'publish' &&
process.env.npm_command !== 'pack'

const getTargetForDialectCondition = <T extends string>(
s: string | TshyExport | undefined | null,
dialect: T,
Expand All @@ -35,13 +40,17 @@ const getTargetForDialectCondition = <T extends string>(
const xts = type === 'commonjs' ? '.mts' : '.cts'
if (s.endsWith(xts)) return undefined
const pf = dialect === 'commonjs' ? 'cjs' : dialect
const rel = relative(
resolve('./src'),
resolve(polyfills.get(pf)?.map.get(s) ?? s)
)
const target = liveDev
? rel
: rel.replace(/\.([mc]?)tsx?$/, '.$1js')
return !s || !s.startsWith('./src/')
? s
: dialects.includes(type)
? `./dist/${dialect}/${relative(
resolve('./src'),
resolve(polyfills.get(pf)?.map.get(s) ?? s)
).replace(/\.([mc]?)tsx?$/, '.$1js')}`
? `./dist/${dialect}/${target}`
: undefined
}
return resolveExport(s, [condition])
Expand Down Expand Up @@ -106,10 +115,12 @@ const getExports = (
polyfills
)
if (target) {
exp[d] = {
types: target.replace(/\.js$/, '.d.ts'),
default: target,
}
exp[d] = liveDev
? target
: {
types: target.replace(/\.js$/, '.d.ts'),
default: target,
}
}
}
}
Expand All @@ -124,25 +135,31 @@ const getExports = (
polyfills
)
if (target) {
exp[d] = {
types: target.replace(/\.js$/, '.d.ts'),
default: target,
}
exp[d] = liveDev
? target
: {
types: target.replace(/\.js$/, '.d.ts'),
default: target,
}
}
}
}
// put the default import/require after all the other special ones.
if (impTarget) {
exp.import = {
types: impTarget.replace(/\.(m?)js$/, '.d.$1ts'),
default: impTarget,
}
exp.import = liveDev
? impTarget
: {
types: impTarget.replace(/\.(m?)js$/, '.d.$1ts'),
default: impTarget,
}
}
if (reqTarget) {
exp.require = {
types: reqTarget.replace(/\.(c?)js$/, '.d.$1ts'),
default: reqTarget,
}
exp.require = liveDev
? reqTarget
: {
types: reqTarget.replace(/\.(c?)js$/, '.d.$1ts'),
default: reqTarget,
}
}
}
return e
Expand Down
10 changes: 10 additions & 0 deletions src/if-exist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { existsSync, renameSync, unlinkSync } from 'fs'

const unlink = (f: string) => existsSync(f) && unlinkSync(f)
const rename = (f: string, to: string) =>
existsSync(f) && renameSync(f, to)

export default {
unlink,
rename,
}
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type TshyConfigMaybeGlobExports = {
esmDialects?: string[]
project?: string
exclude?: string[]
liveDev?: boolean
}

export type TshyConfig = TshyConfigMaybeGlobExports & {
Expand Down
Loading

0 comments on commit 4bfcd67

Please sign in to comment.