Skip to content

Commit

Permalink
Add support for incremental builds
Browse files Browse the repository at this point in the history
Re: #26
  • Loading branch information
isaacs committed Nov 4, 2023
1 parent 7524249 commit 0d4b528
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 27 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,10 @@ you may have packages that depend on one another and are all
being built in parallel (as long as they've been built one time,
of course).

If you use `"incremental": true` in your tsconfig, then this
folder will persist, so that TSC can benefit from the
`.tsbuildinfo` files it creates in there.

## Exports Management

The `exports` field in your package.json file will be updated
Expand Down
8 changes: 5 additions & 3 deletions src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ import {
} from './self-dep.js'
import './tsconfig.js'
import writePackage from './write-package.js'
import cleanBuildTmp from './clean-build-tmp.js'

export default async () => {
rimrafSync('.tshy-build-tmp')
cleanBuildTmp()

linkSelfDep(pkg, 'src')
await linkImports(pkg, 'src')
Expand All @@ -31,8 +32,9 @@ export default async () => {

console.debug(chalk.cyan.dim('moving to ./dist'))
syncContentSync('.tshy-build-tmp', 'dist')
console.debug(chalk.cyan.dim('removing build temp dir'))
rimrafSync('.tshy-build-tmp')
console.debug(chalk.cyan.dim('cleaning build temp dir'))

cleanBuildTmp()

linkSelfDep(pkg, 'dist')

Expand Down
86 changes: 86 additions & 0 deletions src/clean-build-tmp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Remove the .tshy-build-tmp folder, but ONLY if
// the "incremental" config value is not set, or if
// it does not contain any tsbuildinfo files.
// If we are in incremental mode, and have tsbuildinfo files,
// then find and remove any files here that do not have a matching
// source file in ./src

import { readdirSync } from 'fs'
import { parse } from 'path'
import { rimrafSync } from 'rimraf'
import * as console from './console.js'
import readTypescriptConfig from './read-typescript-config.js'

const cleanRemovedOutputs = (path: string, root: string) => {
const entries = readdirSync(`${root}/${path}`, {
withFileTypes: true,
})
let sources: Set<string> | undefined = undefined
try {
sources = new Set(readdirSync(`src/${path}`))
} catch {}
// directory was removed
if (!sources) {
return rimrafSync(`${root}/${path}`)
}
for (const e of entries) {
const outputFile = `${path}/${e.name}`
if (e.isDirectory()) {
cleanRemovedOutputs(outputFile, root)
continue
}
let { ext, name } = parse(outputFile)
if (ext === '.map') {
continue
}
if (name.endsWith('.d') && ext.endsWith('ts')) {
ext = '.d' + ext
name = name.substring(0, name.length - '.d'.length)
}

const inputSearch =
ext === '.js' || ext === '.d.ts'
? ['.tsx', '.ts']
: ext === '.mjs' || ext === '.d.mts'
? ['.mts']
: ext === '.cjs' || ext === '.d.cts'
? ['.cts']
: []
inputSearch.push(ext)
let del = true
for (const ext of inputSearch) {
if (sources.has(`${name}${ext}`)) {
del = false
break
}
}
if (del) {
console.debug('removing output file', outputFile)
rimrafSync([
`${root}/${outputFile}`,
`${root}/${outputFile}.map`,
])
}
}
}

export default () => {
const config = readTypescriptConfig()
if (config.options.incremental !== true) {
return rimrafSync('.tshy-build-tmp')
}

let buildInfos: string[] | undefined = undefined
try {
buildInfos = readdirSync('.tshy-build-tmp/.tshy')
} catch {}
if (!buildInfos?.length) {
return rimrafSync('.tshy-build-tmp')
}

// delete anything that has been removed from src.
for (const dialect of readdirSync('.tshy-build-tmp')) {
if (dialect === '.tshy') continue
cleanRemovedOutputs('.', `.tshy-build-tmp/${dialect}`)
}
}
12 changes: 2 additions & 10 deletions src/prevent-verbatim-module-syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,12 @@
// be made to work in a hybrid context.
// Note: cannot just use JSON.parse, because ts config files
// are jsonc.
import { resolve } from 'path'
import ts from 'typescript'
import * as console from './console.js'
import fail from './fail.js'
const { readFile } = ts.sys
import readTypescriptConfig from './read-typescript-config.js'

export default () => {
const configPath = resolve('tsconfig.json')
const readResult = ts.readConfigFile(configPath, readFile)
const config = ts.parseJsonConfigFileContent(
readResult.config,
ts.sys,
process.cwd()
)
const config = readTypescriptConfig()
if (config.options.verbatimModuleSyntax) {
fail('verbatimModuleSyntax detected')
console.error(
Expand Down
18 changes: 18 additions & 0 deletions src/read-typescript-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// read the actual configuration that tsc is using
// Note: cannot just use JSON.parse, because ts config files
// are jsonc.
import { resolve } from 'path'
import ts from 'typescript'
const { readFile } = ts.sys

let parsedTsConfig: ts.ParsedCommandLine | undefined = undefined
export default () => {
if (parsedTsConfig) return parsedTsConfig
const configPath = resolve('tsconfig.json')
const readResult = ts.readConfigFile(configPath, readFile)
return (parsedTsConfig = ts.parseJsonConfigFileContent(
readResult.config,
ts.sys,
process.cwd()
))
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Make sure to inspect the output below. Do not ignore changes!
*/
'use strict'
exports[`test/prevent-verbatim-module-syntax.ts > TAP > not set, no worries > must match snapshot 1`] = `
exports[`test/prevent-verbatim-module-syntax.ts > TAP > is set, many worries > must match snapshot 1`] = `
verbatimModuleSyntax detected
verbatimModuleSyntax is incompatible with multi-dialect builds. Either remove
this field from tsconfig.json, or set a single dialect in the "dialects"
Expand Down
79 changes: 79 additions & 0 deletions test/clean-build-tmp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { statSync } from 'fs'
import t from 'tap'
import cleanBuildTmp from '../src/clean-build-tmp.js'
import readTypescriptConfig from '../src/read-typescript-config.js'
const cwd = process.cwd()
t.afterEach(() => process.chdir(cwd))

t.test('no incremental build, just delete it', t => {
readTypescriptConfig().options.incremental = false
process.chdir(
t.testdir({
'.tshy-build-tmp': {},
})
)
cleanBuildTmp()
t.throws(() => statSync('.tshy-build-tmp'))
t.end()
})

t.test('no tsbuildinfo, just delete it', t => {
readTypescriptConfig().options.incremental = true
process.chdir(
t.testdir({
'.tshy-build-tmp': {},
})
)
cleanBuildTmp()
t.throws(() => statSync('.tshy-build-tmp'))
t.end()
})

t.test('remove files not found in src', t => {
readTypescriptConfig().options.incremental = true
process.chdir(
t.testdir({
'.tshy-build-tmp': {
'.tshy': { 'esm.tsbuildinfo': '{}' },
esm: {
'a.d.ts': '',
'a.js': '',
'a.js.map': '',
'a.d.ts.map': '',
'a.jsx': '',
'a.jsx.map': '',
dir: {
'b.d.ts': '',
'b.js': '',
'b.js.map': '',
'b.d.ts.map': '',
},
'x.d.ts': '',
'x.js': '',
'x.js.map': '',
'x.d.ts.map': '',
'm.mjs': '',
'm.d.mts': '',
'c.cjs': '',
'c.d.cts': '',
xdir: {
'x.d.ts': '',
'x.js': '',
'x.js.map': '',
'x.d.ts.map': '',
},
},
},
src: {
'a.tsx': '',
dir: {
'b.ts': '',
},
},
})
)
cleanBuildTmp()
t.throws(() => statSync('.tshy-build-tmp/dist/esm/x.js.map'))
t.throws(() => statSync('.tshy-build-tmp/dist/esm/x.d.ts'))
t.end()
})
15 changes: 3 additions & 12 deletions test/prevent-verbatim-module-syntax.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import chalk from 'chalk'
import t from 'tap'
import readTypescriptConfig from '../src/read-typescript-config.js'
import preventVerbatimModuleSyntax from '../src/prevent-verbatim-module-syntax.js'

chalk.level = 3
Expand All @@ -16,17 +17,8 @@ t.test('not set, no worries', t => {
t.end()
})

t.test('not set, no worries', t => {
const cwd = process.cwd()
process.chdir(
t.testdir({
'tsconfig.json': JSON.stringify({
compilerOptions: {
verbatimModuleSyntax: true,
},
}),
})
)
t.test('is set, many worries', t => {
readTypescriptConfig().options.verbatimModuleSyntax = true
preventVerbatimModuleSyntax()
t.strictSame(exits(), [[1]])
t.matchSnapshot(
Expand All @@ -35,6 +27,5 @@ t.test('not set, no worries', t => {
.join('\n')
)
t.strictSame(logs(), [])
process.chdir(cwd)
t.end()
})
5 changes: 5 additions & 0 deletions test/read-typescript-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import t from 'tap'
import readTypescriptConfig from '../src/read-typescript-config.js'

const config = readTypescriptConfig()
t.equal(config, readTypescriptConfig(), 'cached')
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"sourceMap": true,
"strict": true,
"target": "es2022",
"module": "nodenext"
"module": "nodenext",
"composite": true,
"incremental": true
}
}

0 comments on commit 0d4b528

Please sign in to comment.