From 9f12fad69611b8f593cf82da2fc5f0f042a59a87 Mon Sep 17 00:00:00 2001 From: await-ovo <13152410380@163.com> Date: Wed, 2 Nov 2022 19:46:11 +0800 Subject: [PATCH 1/3] feat: rebuild when package.json dependencies changed --- src/index.ts | 21 ++++++++++++++++++--- src/load.ts | 16 ++++++++++++---- src/utils.ts | 5 +++++ test/__snapshots__/index.test.ts.snap | 18 +++++++++--------- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8ed9c026..aa755074 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 { getDepsHash, loadTsupConfig } from './load' import glob from 'globby' import { loadTsConfig } from 'bundle-require' import { handleError, PrettyError } from './errors' @@ -185,6 +185,8 @@ export async function build(_options: Options) { let onSuccessCleanup: (() => any) | undefined | void /** Files imported by the entry */ const buildDependencies: Set = new Set() + + let depsHash = await getDepsHash(process.cwd()) const doOnSuccessCleanup = async () => { if (onSuccessProcess) { @@ -319,14 +321,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') { + const currentHash = await getDepsHash(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..ba97bd2c 100644 --- a/src/load.ts +++ b/src/load.ts @@ -3,7 +3,7 @@ import JoyCon from 'joycon' import path from 'path' import { bundleRequire } from 'bundle-require' import { defineConfig } from './' -import { jsoncParse } from './utils' +import { hash, jsoncParse } from './utils' const joycon = new JoyCon() @@ -77,13 +77,16 @@ 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) +export async function getDeps(cwd: string, clearCache: boolean = false) { + const data = await loadPkg(cwd, clearCache) const deps = Array.from( new Set([ @@ -94,3 +97,8 @@ export async function getDeps(cwd: string) { return deps } + +export async function getDepsHash(cwd: string) { + const deps = await getDeps(cwd, true) + return hash(deps.join(',')) +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 9b9ec271..07e5c2cc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,4 @@ +import { createHash } from 'crypto' import fs from 'fs' import glob from 'globby' import resolveFrom from 'resolve-from' @@ -129,3 +130,7 @@ export function jsoncParse(data: string) { return {} } } + +export function hash(content: string) { + return createHash('sha256').update(content).digest('hex'); +} \ No newline at end of file diff --git a/test/__snapshots__/index.test.ts.snap b/test/__snapshots__/index.test.ts.snap index 5f76bbf5..38ec57bc 100644 --- a/test/__snapshots__/index.test.ts.snap +++ b/test/__snapshots__/index.test.ts.snap @@ -167,6 +167,15 @@ console.log(import_node_fs.default); " `; +exports[`not bundle \`package/subpath\` in dts (resolve) 1`] = ` +"import * as foo_bar from 'foo/bar'; + +declare const stuff: foo_bar.Foobar; + +export { stuff }; +" +`; + exports[`simple 1`] = ` "\\"use strict\\"; var __defProp = Object.defineProperty; @@ -204,15 +213,6 @@ var input_default = foo_default; " `; -exports[`not bundle \`package/subpath\` in dts (resolve) 1`] = ` -"import * as foo_bar from 'foo/bar'; - -declare const stuff: foo_bar.Foobar; - -export { stuff }; -" -`; - exports[`support baseUrl and paths in tsconfig.json 1`] = ` "var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; From 41ebb1dfc62043eab32550f8cb81a98794248155 Mon Sep 17 00:00:00 2001 From: EGOIST Date: Thu, 3 Nov 2022 22:51:13 +0800 Subject: [PATCH 2/3] use all deps as hash --- src/esbuild/index.ts | 36 +++++++++++++++++++++--------------- src/index.ts | 12 ++++++------ src/load.ts | 28 +++++++++++++++++++++------- src/rollup.ts | 6 +++--- src/utils.ts | 5 ----- 5 files changed, 51 insertions(+), 36 deletions(-) diff --git a/src/esbuild/index.ts b/src/esbuild/index.ts index c02b5f4c..ebffbe1d 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 || {} @@ -152,15 +154,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 || []), ] @@ -220,8 +226,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 aa755074..3a0be7d7 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 { getDepsHash, loadTsupConfig } from './load' +import { getAllDepsHash, loadTsupConfig } from './load' import glob from 'globby' import { loadTsConfig } from 'bundle-require' import { handleError, PrettyError } from './errors' @@ -185,8 +185,8 @@ export async function build(_options: Options) { let onSuccessCleanup: (() => any) | undefined | void /** Files imported by the entry */ const buildDependencies: Set = new Set() - - let depsHash = await getDepsHash(process.cwd()) + + let depsHash = await getAllDepsHash(process.cwd()) const doOnSuccessCleanup = async () => { if (onSuccessProcess) { @@ -327,10 +327,10 @@ export async function build(_options: Options) { // If you specify custom `watch`, a string or multiple strings // We rebuild when those files change let shouldSkipChange = false - - if (options.watch === true ) { + + if (options.watch === true) { if (file === 'package.json') { - const currentHash = await getDepsHash(process.cwd()) + const currentHash = await getAllDepsHash(process.cwd()) shouldSkipChange = currentHash === depsHash depsHash = currentHash } else if (!buildDependencies.has(file)) { diff --git a/src/load.ts b/src/load.ts index ba97bd2c..bda258ec 100644 --- a/src/load.ts +++ b/src/load.ts @@ -3,7 +3,7 @@ import JoyCon from 'joycon' import path from 'path' import { bundleRequire } from 'bundle-require' import { defineConfig } from './' -import { hash, jsoncParse } from './utils' +import { jsoncParse } from './utils' const joycon = new JoyCon() @@ -79,13 +79,19 @@ export async function loadTsupConfig( export async function loadPkg(cwd: string, clearCache: boolean = false) { if (clearCache) { - joycon.clearCache(); + joycon.clearCache() } const { data } = await joycon.load(['package.json'], cwd, path.dirname(cwd)) return data || {} } -export async function getDeps(cwd: string, clearCache: boolean = false) { +/* + * 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( @@ -98,7 +104,15 @@ export async function getDeps(cwd: string, clearCache: boolean = false) { return deps } -export async function getDepsHash(cwd: string) { - const deps = await getDeps(cwd, true) - return hash(deps.join(',')) -} \ No newline at end of file +/** + * 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: { diff --git a/src/utils.ts b/src/utils.ts index 07e5c2cc..9b9ec271 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,3 @@ -import { createHash } from 'crypto' import fs from 'fs' import glob from 'globby' import resolveFrom from 'resolve-from' @@ -130,7 +129,3 @@ export function jsoncParse(data: string) { return {} } } - -export function hash(content: string) { - return createHash('sha256').update(content).digest('hex'); -} \ No newline at end of file From e4ff9ef605f3e4ed88185ecf05da669bd666be07 Mon Sep 17 00:00:00 2001 From: EGOIST Date: Thu, 3 Nov 2022 23:03:13 +0800 Subject: [PATCH 3/3] rebuild if package.json is a dependency --- src/cli-main.ts | 11 ++++++----- src/index.ts | 2 +- 2 files changed, 7 insertions(+), 6 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/index.ts b/src/index.ts index 3a0be7d7..a5c5e5b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -329,7 +329,7 @@ export async function build(_options: Options) { let shouldSkipChange = false if (options.watch === true) { - if (file === 'package.json') { + if (file === 'package.json' && !buildDependencies.has(file)) { const currentHash = await getAllDepsHash(process.cwd()) shouldSkipChange = currentHash === depsHash depsHash = currentHash