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

define "source" export condition #72

Merged
merged 2 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,12 @@ appropriate build target locations, like:
"./foo": {
"import": {
"types": "./dist/esm/foo.d.ts",
"source": "./src/foo.ts",
"default": "./dist/esm/foo.js"
},
"require": {
"types": "./dist/commonjs/foo.d.ts",
"source": "./src/foo.ts",
"default": "./dist/commonjs/foo.js"
}
}
Expand Down Expand Up @@ -345,20 +347,24 @@ be:
".": {
"require": {
"types": "./dist/commonjs/index.d.ts",
"source": "./src/index.ts",
"default": "./dist/commonjs/index.js"
},
"import": {
"types": "./dist/esm/index.d.ts",
"source": "./src/index.ts",
"default": "./dist/esm/index.js"
}
},
"./component/foo": {
"require": {
"types": "./dist/commonjs/component/foo.d.ts",
"source": "./src/component/foo.ts",
"default": "./dist/commonjs/component/foo.js"
},
"import": {
"types": "./dist/esm/component/foo.d.ts",
"source": "./src/component/foo.ts",
"default": "./dist/esm/component/foo.js"
}
},
Expand All @@ -381,6 +387,31 @@ 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.

See also: "Loading from Source", below.

### Package `#imports`

You can use `"imports"` in your package.json, and it will be
Expand Down Expand Up @@ -574,10 +605,12 @@ will produce:
".": {
"require": {
"types": "./dist/commonjs/index.d.ts",
"source": "./src/index.js",
"default": "./dist/commonjs/index.js"
},
"import": {
"types": "./dist/esm/index.d.ts",
"source": "./src/index.ts",
"default": "./dist/esm/index.js"
}
}
Expand Down Expand Up @@ -682,22 +715,27 @@ Will result in:
".": {
"deno": {
"types": "./dist/deno/index.d.ts",
"source": "./src/index.ts",
"default": "./dist/deno/index.js"
},
"browser": {
"types": "./dist/browser/index.d.ts",
"default": "./src/index.ts",
"default": "./dist/browser/index.js"
},
"webpack": {
"types": "./dist/webpack/index.d.ts",
"source": "./src/index.ts",
"default": "./dist/webpack/index.js"
},
"require": {
"types": "./dist/commonjs/index.d.ts",
"source": "./src/index.ts",
"default": "./dist/commonjs/index.js"
},
"import": {
"types": "./dist/esm/index.d.ts",
"source": "./src/index.ts",
"default": "./dist/esm/index.js"
}
}
Expand All @@ -722,6 +760,44 @@ src/index-deno.mts # esm variant for deno
src/index-webpack.cts # cjs variant for webpack
```

If dialect overrides are used, then the `"source"` export
condition will refer to the original source for the override. For
example:

```json
{
"exports": {
".": {
"deno": {
"types": "./dist/deno/index.d.ts",
"source": "./src/index-deno.mts",
"default": "./dist/deno/index.js"
},
"browser": {
"types": "./dist/browser/index.d.ts",
"default": "./src/index-browser.mts",
"default": "./dist/browser/index.js"
},
"webpack": {
"types": "./dist/webpack/index.d.ts",
"source": "./src/index-webpack.cts",
"default": "./dist/webpack/index.js"
},
"require": {
"types": "./dist/commonjs/index.d.ts",
"source": "./src/index-cjs.cts",
"default": "./dist/commonjs/index.js"
},
"import": {
"types": "./dist/esm/index.d.ts",
"source": "./src/index.ts",
"default": "./dist/esm/index.js"
}
}
}
}
```

Note that the `commonjs` override uses the abbreviated `cjs`
name (historical reasons, it was originally the only override
supported), and that the file extension must be `cts` or `mts`
Expand Down Expand Up @@ -778,6 +854,39 @@ provided for you.
Then the `tsconfig.json` file will be used as the default project
for code hints in VSCode, neovim, tests, etc.

### Loading from Source

If you are using tshy in a monorepo environment, you can
configure TypeScript (and other tools) to resolve to the _source_
files rather than built artifacts by telling them to use the
`"source"` export condition.

For example, in editors such as VS Code and neovim/CoC that use
the TypeScript language services, you can give them a `tsconfig`
that contains this:

```json
{
"compilerOptions": {
"customConditions": ["source"]
}
}
```

If you are loading your program with a custom Node.js importer
like [`tsx`](https://npm.im/tsx) that can load TypeScript
directly, you can specify it like this:

```bash
node --import=tsx --conditions=source ./script.ts
```

Other TypeScript-aware tools may have other mechanisms for
specifying export conditions. Refer to their documentation for
more information.

See also: "Live Dev", above.

### Custom `project`

Configure `tshy.project` if you want tshy to extend from a custom
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))
}
}
13 changes: 11 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,22 @@ 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
3 changes: 2 additions & 1 deletion 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 Down
Loading
Loading