From d4b317bce365299921e0cea47fb7ab62cf4a7bb8 Mon Sep 17 00:00:00 2001 From: await-ovo <41503212+await-ovo@users.noreply.github.com> Date: Thu, 3 Nov 2022 23:04:04 +0800 Subject: [PATCH] feat: rebuild when package.json dependencies changed (#759) Co-authored-by: EGOIST --- src/cli-main.ts | 11 ++++++----- src/esbuild/index.ts | 36 +++++++++++++++++++++--------------- src/index.ts | 21 ++++++++++++++++++--- src/load.ts | 28 +++++++++++++++++++++++++--- src/rollup.ts | 6 +++--- 5 files changed, 73 insertions(+), 29 deletions(-) diff --git a/src/cli-main.ts b/src/cli-main.ts index 92163444..9e36ff86 100644 --- a/src/cli-main.ts +++ b/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[] { @@ -58,7 +57,10 @@ export async function main(options: Options = {}) { 'Replace a global variable with an import from another file' ) .option('--define.* ', 'Define compile-time constants') - .option('--external ', 'Mark specific packages / package.json (dependencies and peerDependencies) as external') + .option( + '--external ', + 'Mark specific packages / package.json (dependencies and peerDependencies) as external' + ) .option('--global-name ', 'Global variable name for iife format') .option('--jsxFactory ', 'Name of JSX factory function', { default: 'React.createElement', @@ -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() diff --git a/src/esbuild/index.ts b/src/esbuild/index.ts index 614ad453..e0f14ea8 100644 --- a/src/esbuild/index.ts +++ b/src/esbuild/index.ts @@ -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' @@ -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) } @@ -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}($|\\/|\\\\)`)), @@ -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 || {} @@ -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 || []), ] @@ -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) => { diff --git a/src/index.ts b/src/index.ts index 97f33949..4efe9039 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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' @@ -192,6 +192,8 @@ export async function build(_options: Options) { /** Files imported by the entry */ const buildDependencies: Set = new Set() + let depsHash = await getAllDepsHash(process.cwd()) + const doOnSuccessCleanup = async () => { if (onSuccessProcess) { await killProcess({ @@ -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() }) diff --git a/src/load.ts b/src/load.ts index 4a8fe024..bda258ec 100644 --- a/src/load.ts +++ b/src/load.ts @@ -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([ @@ -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, + }) +} diff --git a/src/rollup.ts b/src/rollup.ts index 8fe4e022..4a3d4988 100644 --- a/src/rollup.ts +++ b/src/rollup.ts @@ -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' @@ -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', @@ -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: {