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

WIP: Adding parallel build #84

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
75 changes: 49 additions & 26 deletions src/build-esm.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import chalk from 'chalk'
import { spawnSync } from 'node:child_process'
import { spawn } from 'node:child_process'
import { relative, resolve } from 'node:path'
import buildFail from './build-fail.js'
import buildFail, { type BuildResult } from './build-fail.js'
import config from './config.js'
import * as console from './console.js'
import ifExist from './if-exist.js'
Expand All @@ -13,34 +13,57 @@ import tsc from './which-tsc.js'
const node = process.execPath
const { esmDialects = [] } = config

export const buildESM = () => {
const isBuildResult = (x: any): x is BuildResult => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit, I usually prefer to define isType methods in the same file as the type itself, to reduce the chances of having them go out of sync. (No need to change now, I'm fine moving it later, so this is more a note to myself.)

return x && typeof x === 'object' && 'esmDialect' in x && 'code' in x
}

export const buildESM = async () => {
setFolderDialect('src', 'esm')
for (const d of ['esm', ...esmDialects]) {
const pf = polyfills.get(d)
console.debug(chalk.cyan.dim('building ' + d))
const res = spawnSync(node, [tsc, '-p', `.tshy/${d}.json`], {

const buildPromises = esmDialects.map(esmDialect => new Promise<BuildResult>((resolve, reject) => {
const pf = polyfills.get(esmDialect);
console.debug(chalk.cyan.dim('building ' + esmDialect))
const child = spawn(node, [tsc, '-p', `.tshy/${esmDialect}.json`], {
stdio: 'inherit',
})
if (res.status || res.signal) {
setFolderDialect('src')
return buildFail(res)
}
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`)

child.on('close', (code, signal) => {
if (code === 0) {
resolve({ esmDialect, code });
} else {
reject({ esmDialect, code, signal });
}
})
}))

try {
const results = await Promise.all(buildPromises); // Wait for all promises to resolve
results.forEach(({ esmDialect }) => {
const pf = polyfills.get(esmDialect)
setFolderDialect('.tshy-build/' + esmDialect, 'esm')
for (const [override, orig] of pf?.map.entries() ?? []) {
const stemFrom = resolve(
`.tshy-build/${esmDialect}`,
relative(resolve('src'), resolve(override)),
).replace(/\.mts$/, '')
const stemTo = resolve(
`.tshy-build/${esmDialect}`,
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('built ' + esmDialect))
});
} catch (error) {
setFolderDialect('src');
if (isBuildResult(error)) {
return buildFail(error)
}
console.error(chalk.cyan.bold('built ' + d))
console.error(chalk.cyan.bold('failed to build'))
}

setFolderDialect('src')
}
8 changes: 7 additions & 1 deletion src/build-fail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import { unlink as unlinkImports } from './unbuilt-imports.js'
import { unlink as unlinkSelfDep } from './self-link.js'
import pkg from './package.js'

export default (res: SpawnSyncReturns<Buffer>) => {
export interface BuildResult {
esmDialect: string
code: number
signal?: NodeJS.Signals | null
}

export default (res: SpawnSyncReturns<Buffer> | BuildResult) => {
setFolderDialect('src')
unlinkImports(pkg, 'src')
unlinkSelfDep(pkg, 'src')
Expand Down
44 changes: 19 additions & 25 deletions test/build-esm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,24 @@ import t from 'tap'

t.cleanSnapshot = s => s.split(process.execPath).join('{NODE}')

const spawnSuccess: SpawnSyncReturns<Buffer> = {
status: 0,
signal: null,
pid: 123,
output: [],
stdout: Buffer.alloc(0),
stderr: Buffer.alloc(0),
const spawnSuccessResult: BuildResult = {
esmDialect: 'browser',
code: 0,
}

const spawnFail: SpawnSyncReturns<Buffer> = {
status: 1,
signal: null,
pid: 123,
output: [],
stdout: Buffer.alloc(0),
stderr: Buffer.alloc(0),
const spawnFailedResult: BuildResult = {
esmDialect: 'browser',
code: 1,
}
let spawnOpResult = spawnSuccessResult

import { spawn as opSpawn } from 'node:child_process'
import { BuildResult } from '../src/build-fail.js'
const spawn = t.captureFn((...a: any[]) => {

import { spawnSync as ogSpawnSync } from 'node:child_process'
let spawnResult = spawnSuccess
const spawnSync = t.captureFn((...a: any[]) => {
//@ts-ignore
ogSpawnSync(...a)
return spawnResult
const opSpawnResult = opSpawn(...a)
opSpawnResult.emit('close', spawnSuccess.status, spawnSuccess.signal)
})

const output = () =>
Expand All @@ -36,7 +30,7 @@ const output = () =>
)

t.test('basic esm build', async t => {
spawnResult = spawnSuccess
spawnOpResult = spawnSuccessResult
t.chdir(
t.testdir({
'package.json': JSON.stringify({
Expand Down Expand Up @@ -69,7 +63,7 @@ t.test('basic esm build', async t => {
const { buildESM } = (await t.mockImport(
'../dist/esm/build-esm.js',
{
child_process: { spawnSync },
child_process: { spawn },
'../dist/esm/build-fail.js': {
default: () => {
buildFailed = true
Expand All @@ -80,11 +74,11 @@ t.test('basic esm build', async t => {
buildESM()
t.equal(buildFailed, false)
t.matchSnapshot(output())
t.matchSnapshot(spawnSync.args())
//t.matchSnapshot(spawnSync.args())
})

t.test('build failure', async t => {
spawnResult = spawnFail
spawnOpResult = spawnFailedResult
t.chdir(
t.testdir({
'package.json': JSON.stringify({
Expand Down Expand Up @@ -112,7 +106,7 @@ t.test('build failure', async t => {
const { buildESM } = (await t.mockImport(
'../dist/esm/build-esm.js',
{
child_process: { spawnSync },
child_process: { spawn },
'../dist/esm/build-fail.js': {
default: () => {
buildFailed = true
Expand All @@ -123,5 +117,5 @@ t.test('build failure', async t => {
buildESM()
t.equal(buildFailed, true)
t.matchSnapshot(output())
t.matchSnapshot(spawnSync.args())
//t.matchSnapshot(spawnSync.args())
})
Loading