Build if Changed
build-if-changed (bic) is a utility designed to optimize monorepo build processes by executing `npm run build` scripts only for packages that have undergone changes since their last build. It achieves this by crawling specified directories within a package, generating SHA-1 hashes of watched files, and storing them in a `.bic_cache` file alongside each `package.json`. If the hashes indicate a change, the build script is triggered. The current stable version is 1.5.5. It focuses on reducing build times and CI/CD costs by avoiding redundant builds, differentiating itself through its simple `package.json`-based configuration and custom glob syntax for file watching.
Common errors
-
Error: Command failed with exit code 127: build-if-changed
cause The `build-if-changed` command (or `bic` alias) is not found in the system's PATH, usually because the package was not installed or installed incorrectly.fixInstall `build-if-changed` as a dev dependency (`npm install build-if-changed -D` or `yarn add build-if-changed -D`) and then run it using `npx build-if-changed` (for npm) or `yarn build-if-changed` (for yarn). -
No build scripts found in the following packages: [list of packages]
cause The specified packages' `package.json` files do not contain a `scripts.build` entry, which `build-if-changed` expects to execute.fixAdd a `"build": "your-build-command-here"` entry to the `scripts` section of the `package.json` for each package that `build-if-changed` should process. -
Files are not being watched/skipped as expected.
cause Incorrect `bic` configuration in `package.json`, often due to misunderstanding the custom glob syntax or `skip`/`only` precedence.fixCarefully review the `bic` field in your `package.json`. Verify the custom glob patterns against the `recrawl` documentation and ensure the `skip` array does not inadvertently exclude desired files.
Warnings
- gotcha The `bic` configuration in `package.json` uses a custom glob syntax, not standard glob patterns. Refer to the `recrawl` package documentation for specific pattern syntax rules.
- gotcha When configuring watched files via the `bic` field in `package.json`, the `skip` array takes precedence over the `only` array. Files explicitly listed in `skip` will always be ignored, even if also present in `only`.
- gotcha By default, the `.git` and `node_modules` directories are always skipped during file crawling. This behavior cannot be overridden by `only` configurations.
- gotcha Any package whose 'build' script in `package.json` contains either 'bic' or 'build-if-changed' will be skipped to prevent infinite loops.
Install
-
npm install build-if-changed -
yarn add build-if-changed -
pnpm add build-if-changed
Quickstart
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
// Ensure a package.json exists for a mock build
const pkgPath = path.join(process.cwd(), 'package.json');
if (!fs.existsSync(pkgPath)) {
console.log('Creating a dummy package.json...');
fs.writeFileSync(pkgPath, JSON.stringify({
"name": "my-test-app",
"version": "1.0.0",
"scripts": {
"build": "echo 'Building my-test-app...' && touch build-output.txt"
},
"devDependencies": {}
}, null, 2));
}
console.log('Installing build-if-changed as a dev dependency...');
execSync('npm install build-if-changed -D', { stdio: 'inherit' });
console.log('Running build-if-changed for the first time...');
execSync('npx build-if-changed', { stdio: 'inherit' });
// Simulate a file change to trigger a build next time
console.log('\nSimulating a file change...');
fs.writeFileSync(path.join(process.cwd(), 'src/index.js'), '// Some new content', { flag: 'a' });
console.log('Running build-if-changed again after a change...');
execSync('npx build-if-changed', { stdio: 'inherit' });
console.log('\nCleanup (optional)...');
// fs.unlinkSync(pkgPath);
// fs.unlinkSync(path.join(process.cwd(), '.bic_cache'));
// fs.rmSync(path.join(process.cwd(), 'node_modules'), { recursive: true, force: true });
// fs.rmSync(path.join(process.cwd(), 'src'), { recursive: true, force: true });
console.log('Quickstart complete. Check build-output.txt.');