Skip to content

Commit

Permalink
Speed up generation of dev prefix for long file paths
Browse files Browse the repository at this point in the history
  • Loading branch information
askoufis committed Aug 20, 2024
1 parent 96dd466 commit 8b9f594
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 6 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
---

perf: Speed up generation of dev prefix for long file paths
38 changes: 38 additions & 0 deletions packages/css/src/getFileAndDirFromPath.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { getFileAndDirFromPath } from './getFileAndDirFromPath';

const testCases = [
{
name: 'longPath',
input:
'node_modules/.pnpm/braid-design-system@32.23.0_@types+react@18.3.3_babel-plugin-macros@3.1.0_react-dom@18.3.1_re_ctndskkf4y74v2qksfjwfz6ezy/node_modules/braid-design-system/dist/lib/components/ButtonDir/Button.css.mjs',
expected: { dir: 'ButtonDir', file: 'Button' },
},
{
name: 'emojiPath',
input: 'node_modules/my_package/dist/👨‍👩‍👦Test🎉Dir👨‍🚀/Test🎉File👨‍🚀.css.ts',
expected: { dir: '👨‍👩‍👦Test🎉Dir👨‍🚀', file: 'Test🎉File👨‍🚀' },
},
{
name: 'loneSurrogates',
input: 'node_modules/my_package/dist/Test\uD801Dir/Test\uDC01File.css.ts',
expected: { dir: 'Test\uD801Dir', file: 'Test\uDC01File' },
},
{
name: 'noExtension',
input: 'node_modules/my_package/dist/TestDir/TestFile',
expected: undefined,
},
{
name: 'noDir',
input: 'myFile.css.ts',
expected: { file: 'myFile' },
},
];

describe('getFileAndDirFromPath', () => {
testCases.forEach(({ name, input, expected }) => {
it(name, () => {
expect(getFileAndDirFromPath(input)).toStrictEqual(expected);
});
});
});
91 changes: 91 additions & 0 deletions packages/css/src/getFileAndDirFromPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const forwardSlash = '/'.charCodeAt(0);

const getLastSlashBeforeIndex = (path: string, startingIndex: number) => {
let pathIndex = startingIndex;

while (pathIndex >= 0) {
let codeUnit = path.charCodeAt(pathIndex);

if (codeUnit === forwardSlash) {
return pathIndex;
}

// UTF-16 surrogate pair handling
// Check if codeUnit is a low surrogate
if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
// Ensure there's a previous character
if (pathIndex > 0) {
let maybeHighSurrogate = path.charCodeAt(pathIndex - 1);

// Check if the previous code unit is a high surrogate
if (maybeHighSurrogate >= 0xd800 && maybeHighSurrogate <= 0xdbff) {
// Move past the high surrogate and continue
pathIndex -= 2;
continue;
}
}
}

// At this point, we know we either have a regular character or a lone surrogate, which is
// valid in windows file paths
pathIndex--;
}

return -1;
};

type FileAndDir = {
file: string;
dir?: string;
};

/**
* Assumptions:
* - The path is always normalized to use posix file separators (see `addFileScope`)
* - The path is always relative to the project root (see `addFileScope`)
* - There's no need to validate the file extension as we know that we only run this function if the
* path was inside a VE file
* */
const _getFileAndDirFromPath = (path: string): FileAndDir | undefined => {
let file: string;
let dir: string | undefined;

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

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

let i = lastIndexOfDotCss - 1;

const lastSlashIndex = getLastSlashBeforeIndex(path, i);
if (lastSlashIndex === -1) {
return { file: path.slice(0, lastIndexOfDotCss) };
}

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

dir = path.slice(secondLastSlashIndex + 1, lastSlashIndex);
file = path.slice(lastSlashIndex + 1, lastIndexOfDotCss);

return { dir, file };
};

const memoizedGetFileAndDirFromPath = () => {
const cache = new Map();

return (path: string) => {
if (cache.has(path)) {
return cache.get(path);
}

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

return result;
};
};

export const getFileAndDirFromPath = memoizedGetFileAndDirFromPath();
12 changes: 6 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 { getFileAndDirFromPath } from './getFileAndDirFromPath';

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

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

if (matches && matches.groups) {
const { dir, file } = matches.groups;
parts.unshift(file && file !== 'index' ? file : dir);
if (fileAndDir) {
const { dir, file } = fileAndDir;
const part = (file !== 'index' ? file : dir) || file;
parts.unshift(part);
}
}

Expand Down

0 comments on commit 8b9f594

Please sign in to comment.