Skip to content

Commit

Permalink
perf: Speed up dev prefix generation for long file paths (#1466)
Browse files Browse the repository at this point in the history
  • Loading branch information
askoufis authored Aug 21, 2024
1 parent 96dd466 commit 6432199
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/lemon-colts-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@vanilla-extract/css': patch
---

Speed up dev prefix generation for long file paths
1 change: 1 addition & 0 deletions packages/css/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
"dedent": "^1.5.3",
"deep-object-diff": "^1.1.9",
"deepmerge": "^4.2.2",
"lru-cache": "^10.4.3",
"media-query-parser": "^2.0.2",
"modern-ahocorasick": "^1.0.0",
"picocolors": "^1.0.0"
Expand Down
45 changes: 45 additions & 0 deletions packages/css/src/getDebugFileName.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { getDebugFileName } from './getDebugFileName';

const testCases = [
{
name: 'longPath',
input:
'node_modules/.pnpm/[email protected]_@[email protected][email protected][email protected]_re_ctndskkf4y74v2qksfjwfz6ezy/node_modules/braid-design-system/dist/lib/components/ButtonDir/Button.css.mjs',
expected: 'Button',
},
{
name: 'emojiPath',
input: 'node_modules/my_package/dist/👨‍👩‍👦Test🎉Dir👨‍🚀/Test🎉File👨‍🚀.css.ts',
expected: 'Test🎉File👨‍🚀',
},
{
name: 'loneSurrogates',
input: 'node_modules/my_package/dist/Test\uD801Dir/Test\uDC01File.css.ts',
expected: 'Test\uDC01File',
},
{
name: 'noExtension',
input: 'node_modules/my_package/dist/TestDir/TestFile',
expected: '',
},
{
name: 'singleFileSparator',
input: 'src/Button.css.ts',
expected: 'Button',
},
{
name: 'noDir',
input: 'myFile.css.ts',
expected: 'myFile',
},
];

describe('getDebugFileName', () => {
testCases.forEach(({ name, input, expected }) => {
it(name, () => {
const result = getDebugFileName(input);

expect(result).toStrictEqual(expected);
});
});
});
73 changes: 73 additions & 0 deletions packages/css/src/getDebugFileName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { LRUCache } from 'lru-cache';

const getLastSlashBeforeIndex = (path: string, index: number) => {
let pathIndex = index - 1;

while (pathIndex >= 0) {
if (path[pathIndex] === '/') {
return pathIndex;
}

pathIndex--;
}

return -1;
};

/**
* Assumptions:
* - The path is always normalized to use posix file separators (/) (see `addFileScope`)
* - The path is always relative to the project root, i.e. there will never be a leading slash (see `addFileScope`)
* - As long as `.css` is there, we have a valid `.css.*` file path, because otherwise there wouldn't
* be a file scope to begin with
*
* The LRU cache we use can't cache undefined/null values, so we opt to return an empty string,
* rather than using a custom Symbol or something similar.
*/
const _getDebugFileName = (path: string): string => {
let file: string;

const lastIndexOfDotCss = path.lastIndexOf('.css');

if (lastIndexOfDotCss === -1) {
return '';
}

const lastSlashIndex = getLastSlashBeforeIndex(path, lastIndexOfDotCss);
file = path.slice(lastSlashIndex + 1, lastIndexOfDotCss);

// There are no slashes, therefore theres no directory to extract
if (lastSlashIndex === -1) {
return file;
}

let secondLastSlashIndex = getLastSlashBeforeIndex(path, lastSlashIndex - 1);
// If secondLastSlashIndex is -1, it means that the path looks like `directory/file.css.ts`,
// in which case dir will still be sliced starting at 0, which is what we want
const dir = path.slice(secondLastSlashIndex + 1, lastSlashIndex);

const debugFileName = file !== 'index' ? file : dir;

return debugFileName;
};

const memoizedGetDebugFileName = () => {
const cache = new LRUCache<string, string>({
max: 500,
});

return (path: string) => {
const cachedResult = cache.get(path);

if (cachedResult) {
return cachedResult;
}

const result = _getDebugFileName(path);
cache.set(path, result);

return result;
};
};

export const getDebugFileName = memoizedGetDebugFileName();
11 changes: 5 additions & 6 deletions packages/css/src/identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import hash from '@emotion/hash';

import { getIdentOption } from './adapter';
import { getAndIncrementRefCounter, getFileScope } from './fileScope';
import { getDebugFileName } from './getDebugFileName';

function getDevPrefix({
debugId,
Expand All @@ -15,13 +16,11 @@ function getDevPrefix({
if (debugFileName) {
const { filePath } = getFileScope();

const matches = filePath.match(
/(?<dir>[^\/\\]*)?[\/\\]?(?<file>[^\/\\]*)\.css\.(ts|js|tsx|jsx|cjs|mjs)$/,
);
const debugFileName = getDebugFileName(filePath);

if (matches && matches.groups) {
const { dir, file } = matches.groups;
parts.unshift(file && file !== 'index' ? file : dir);
// debugFileName could be an empty string
if (debugFileName) {
parts.unshift(debugFileName);
}
}

Expand Down
12 changes: 7 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6432199

Please sign in to comment.