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

feat: cache invalidation on .babelrc update #865

Merged
merged 14 commits into from
Jan 25, 2025
Merged
30 changes: 30 additions & 0 deletions packages/cli/__tests__/compile-stylex-folder-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ describe('cache mechanism works as expected', () => {

beforeAll(async () => {
await fs.rm(cachePath, { recursive: true, force: true });
await fs.writeFile(
path.join(process.cwd(), '.babelrc'),
JSON.stringify({ presets: ['@babel/preset-env'] }, null, 2),
);
});

beforeEach(() => {
Expand All @@ -274,6 +278,7 @@ describe('cache mechanism works as expected', () => {
afterAll(async () => {
await fs.rm(config.output, { recursive: true, force: true });
await fs.rm(cachePath, { recursive: true, force: true });
await fs.rm(path.join(process.cwd(), '.babelrc'));
});

test('first compilation populates the cache', async () => {
Expand Down Expand Up @@ -329,6 +334,31 @@ describe('cache mechanism works as expected', () => {
}
});

test('recompiles when babelrc changes', async () => {
await fs.writeFile(
path.join(process.cwd(), '.babelrc'),
JSON.stringify({ presets: ['@babel/preset-react'] }, null, 2),
);
await compileDirectory(config);

// Ensure cache is rewritten due to cache invalidation
expect(writeSpy).toHaveBeenCalledTimes(3);

const cacheFiles = await fs.readdir(cachePath);
expect(cacheFiles.length).toEqual(3);

for (const cacheFile of cacheFiles) {
const cacheFilePath = path.join(cachePath, cacheFile);
const cacheContent = JSON.parse(
await fs.readFile(cacheFilePath, 'utf-8'),
);
expect(cacheContent).toHaveProperty('inputHash');
expect(cacheContent).toHaveProperty('outputHash');
expect(cacheContent).toHaveProperty('collectedCSS');
expect(cacheContent).toHaveProperty('configHash');
}
});

test('recompiles when input changes', async () => {
const mockFilePath = path.join(config.input, 'index.js');
const mockFileOutputPath = path.join(config.output, 'index.js');
Expand Down
33 changes: 33 additions & 0 deletions packages/cli/src/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ async function findProjectRoot(startDir = __dirname) {
throw new Error('Project root not found');
}

async function findNearestBabelRC(dir) {
let currentDir = dir;

while (currentDir !== path.parse(currentDir).root) {
const babelrcPath = path.join(currentDir, '.babelrc');
try {
await fs.access(babelrcPath);
console.log('Found babelrc:', babelrcPath);
return babelrcPath;
} catch {
currentDir = path.dirname(currentDir);
}
Comment on lines +39 to +45
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't use fs.access. Use fs.exists instead. That returns a boolean and won't throw an error.

Copy link
Member Author

@mellyeliu mellyeliu Jan 21, 2025

Choose a reason for hiding this comment

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

https://nodejs.org/api/fs.html#fs_fs_exists_path_callback fs.exists is deprecated and may cause issues with modern node versions. if error specificity isn't a concern we can use fs.exists but it might require updates later, which could impact maintainability

}
console.log('Found no babelrc:');
return null;
}

export async function getCacheFilePath(cachePath, filePath) {
const projectRoot = await findProjectRoot(filePath);
const absoluteFilePath = path.resolve(filePath);
Expand Down Expand Up @@ -74,6 +91,22 @@ export async function deleteCache(cachePath, filePath) {
}
}

export async function computeBabelRCHash(path) {
const babelPath = await findNearestBabelRC(path);
if (!babelPath) {
return null; // No .babelrc found
}

try {
const fileBuffer = await fs.readFile(babelPath);
const fileContent = fileBuffer.toString('utf8');
return hash(fileContent);
} catch (error) {
console.error(`Error reading or hashing file: ${error.message}`);
throw error;
}
}

export function computeStyleXConfigHash(config) {
// Excluding `input` and `output` paths to hash config settings
const configOptions = Object.fromEntries(
Expand Down
7 changes: 6 additions & 1 deletion packages/cli/src/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
readCache,
computeFilePathHash,
computeStyleXConfigHash,
computeBabelRCHash,
getDefaultCachePath,
} from './cache';
import {
Expand Down Expand Up @@ -110,6 +111,8 @@ export async function compileFile(
}

const configHash = computeStyleXConfigHash(config);
const babelHash = await computeBabelRCHash(inputFilePath);
console.log(`[stylex] babelHash: ${babelHash}`);

const cacheData = await readCache(cachePath, filePath);

Expand All @@ -118,7 +121,8 @@ export async function compileFile(
cacheData.inputHash === inputHash &&
oldOutputHash &&
cacheData.outputHash === oldOutputHash &&
cacheData.configHash === configHash
cacheData.configHash === configHash &&
cacheData.babelHash === babelHash
) {
console.log(`[stylex] Using cached CSS for: ${filePath}`);
config.state.styleXRules.set(filePath, cacheData.collectedCSS);
Expand All @@ -144,6 +148,7 @@ export async function compileFile(
outputHash: newOutputHash,
collectedCSS: rules,
configHash,
babelHash,
});
}
}
Expand Down
Loading