Skip to content

Commit

Permalink
feat: rebuild when package.json dependencies changed (#759)
Browse files Browse the repository at this point in the history
Co-authored-by: EGOIST <hi@egoist.dev>
  • Loading branch information
await-ovo and egoist committed Nov 3, 2022
1 parent 6cbbea1 commit d4b317b
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 29 deletions.
11 changes: 6 additions & 5 deletions src/cli-main.ts
@@ -1,8 +1,7 @@
import { readFileSync } from 'fs'
import { join } from 'path'
import { cac } from 'cac'
import flat from 'flat'
import { Format, Options } from '.'
import { version } from '../package.json'
import { slash } from './utils'

function ensureArray(input: string): string[] {
Expand Down Expand Up @@ -58,7 +57,10 @@ export async function main(options: Options = {}) {
'Replace a global variable with an import from another file'
)
.option('--define.* <value>', 'Define compile-time constants')
.option('--external <name>', 'Mark specific packages / package.json (dependencies and peerDependencies) as external')
.option(
'--external <name>',
'Mark specific packages / package.json (dependencies and peerDependencies) as external'
)
.option('--global-name <name>', 'Global variable name for iife format')
.option('--jsxFactory <jsxFactory>', 'Name of JSX factory function', {
default: 'React.createElement',
Expand Down Expand Up @@ -145,8 +147,7 @@ export async function main(options: Options = {}) {

cli.help()

const pkgPath = join(__dirname, '../package.json')
cli.version(JSON.parse(readFileSync(pkgPath, 'utf8')).version)
cli.version(version)

cli.parse(process.argv, { run: false })
await cli.runMatchedCommand()
Expand Down
36 changes: 21 additions & 15 deletions src/esbuild/index.ts
Expand Up @@ -7,7 +7,7 @@ import {
Plugin as EsbuildPlugin,
} from 'esbuild'
import { NormalizedOptions, Format } from '..'
import { getDeps, loadPkg } from '../load'
import { getProductionDeps, loadPkg } from '../load'
import { Logger, getSilent } from '../log'
import { nodeProtocolPlugin } from './node-protocol'
import { externalPlugin } from './external'
Expand Down Expand Up @@ -70,9 +70,11 @@ const generateExternal = async (external: (string | RegExp)[]) => {
continue
}

let pkgPath: string = path.isAbsolute(item) ? path.dirname(item) : path.dirname(path.resolve(process.cwd(), item))
let pkgPath: string = path.isAbsolute(item)
? path.dirname(item)
: path.dirname(path.resolve(process.cwd(), item))

const deps = await getDeps(pkgPath)
const deps = await getProductionDeps(pkgPath)
result.push(...deps)
}

Expand All @@ -96,7 +98,7 @@ export async function runEsbuild(
}
) {
const pkg = await loadPkg(process.cwd())
const deps = await getDeps(process.cwd())
const deps = await getProductionDeps(process.cwd())
const external = [
// Exclude dependencies, e.g. `lodash`, `lodash/get`
...deps.map((dep) => new RegExp(`^${dep}($|\\/|\\\\)`)),
Expand Down Expand Up @@ -124,8 +126,8 @@ export async function runEsbuild(
format === 'iife'
? false
: typeof options.splitting === 'boolean'
? options.splitting
: format === 'esm'
? options.splitting
: format === 'esm'

const platform = options.platform || 'node'
const loader = options.loader || {}
Expand Down Expand Up @@ -153,15 +155,19 @@ export async function runEsbuild(
// esbuild's `external` option doesn't support RegExp
// So here we use a custom plugin to implement it
format !== 'iife' &&
externalPlugin({
external,
noExternal: options.noExternal,
skipNodeModulesBundle: options.skipNodeModulesBundle,
tsconfigResolvePaths: options.tsconfigResolvePaths,
}),
externalPlugin({
external,
noExternal: options.noExternal,
skipNodeModulesBundle: options.skipNodeModulesBundle,
tsconfigResolvePaths: options.tsconfigResolvePaths,
}),
options.tsconfigDecoratorMetadata && swcPlugin({ logger }),
nativeNodeModulesPlugin(),
postcssPlugin({ css, inject: options.injectStyle, cssLoader: loader['.css'] }),
postcssPlugin({
css,
inject: options.injectStyle,
cssLoader: loader['.css'],
}),
sveltePlugin({ css }),
...(options.esbuildPlugins || []),
]
Expand Down Expand Up @@ -221,8 +227,8 @@ export async function runEsbuild(
TSUP_FORMAT: JSON.stringify(format),
...(format === 'cjs' && injectShims
? {
'import.meta.url': 'importMetaUrl',
}
'import.meta.url': 'importMetaUrl',
}
: {}),
...options.define,
...Object.keys(env).reduce((res, key) => {
Expand Down
21 changes: 18 additions & 3 deletions src/index.ts
Expand Up @@ -2,7 +2,7 @@ import path from 'path'
import fs from 'fs'
import { Worker } from 'worker_threads'
import { removeFiles, debouncePromise, slash, MaybePromise } from './utils'
import { loadTsupConfig } from './load'
import { getAllDepsHash, loadTsupConfig } from './load'
import glob from 'globby'
import { loadTsConfig } from 'bundle-require'
import { handleError, PrettyError } from './errors'
Expand Down Expand Up @@ -192,6 +192,8 @@ export async function build(_options: Options) {
/** Files imported by the entry */
const buildDependencies: Set<string> = new Set()

let depsHash = await getAllDepsHash(process.cwd())

const doOnSuccessCleanup = async () => {
if (onSuccessProcess) {
await killProcess({
Expand Down Expand Up @@ -324,14 +326,27 @@ export async function build(_options: Options) {
ignorePermissionErrors: true,
ignored,
})
watcher.on('all', (type, file) => {
watcher.on('all', async (type, file) => {
file = slash(file)
// By default we only rebuild when imported files change
// If you specify custom `watch`, a string or multiple strings
// We rebuild when those files change
if (options.watch === true && !buildDependencies.has(file)) {
let shouldSkipChange = false

if (options.watch === true) {
if (file === 'package.json' && !buildDependencies.has(file)) {
const currentHash = await getAllDepsHash(process.cwd())
shouldSkipChange = currentHash === depsHash
depsHash = currentHash
} else if (!buildDependencies.has(file)) {
shouldSkipChange = true
}
}

if (shouldSkipChange) {
return
}

logger.info('CLI', `Change detected: ${type} ${file}`)
debouncedBuildAll()
})
Expand Down
28 changes: 25 additions & 3 deletions src/load.ts
Expand Up @@ -77,13 +77,22 @@ export async function loadTsupConfig(
return {}
}

export async function loadPkg(cwd: string) {
export async function loadPkg(cwd: string, clearCache: boolean = false) {
if (clearCache) {
joycon.clearCache()
}
const { data } = await joycon.load(['package.json'], cwd, path.dirname(cwd))
return data || {}
}

export async function getDeps(cwd: string) {
const data = await loadPkg(cwd)
/*
* Production deps should be excluded from the bundle
*/
export async function getProductionDeps(
cwd: string,
clearCache: boolean = false
) {
const data = await loadPkg(cwd, clearCache)

const deps = Array.from(
new Set([
Expand All @@ -94,3 +103,16 @@ export async function getDeps(cwd: string) {

return deps
}

/**
* Use this to determine if we should rebuild when package.json changes
*/
export async function getAllDepsHash(cwd: string) {
const data = await loadPkg(cwd, true)

return JSON.stringify({
...data.dependencies,
...data.peerDependencies,
...data.devDependencies,
})
}
6 changes: 3 additions & 3 deletions src/rollup.ts
Expand Up @@ -8,7 +8,7 @@ import { handleError } from './errors'
import { removeFiles } from './utils'
import { TsResolveOptions, tsResolvePlugin } from './rollup/ts-resolve'
import { createLogger, setSilent } from './log'
import { getDeps } from './load'
import { getProductionDeps } from './load'
import path from 'path'
import { reportSize } from './lib/report-size'
import resolveFrom from 'resolve-from'
Expand Down Expand Up @@ -111,7 +111,7 @@ const getRollupConfig = async (
}
}

const deps = await getDeps(process.cwd())
const deps = await getProductionDeps(process.cwd())

const tsupCleanPlugin: Plugin = {
name: 'tsup:clean',
Expand Down Expand Up @@ -174,7 +174,7 @@ const getRollupConfig = async (
external: [
// Exclude dependencies, e.g. `lodash`, `lodash/get`
...deps.map((dep) => new RegExp(`^${dep}($|\\/|\\\\)`)),
...(options.external || [])
...(options.external || []),
],
},
outputConfig: {
Expand Down

1 comment on commit d4b317b

@vercel
Copy link

@vercel vercel bot commented on d4b317b Nov 3, 2022

Choose a reason for hiding this comment

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

Please sign in to comment.