Skip to content

Commit

Permalink
Allow custom identifier hashing (#1160)
Browse files Browse the repository at this point in the history
  • Loading branch information
SombreroElGringo committed Aug 21, 2023
1 parent cb2132f commit e391bae
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 6 deletions.
10 changes: 10 additions & 0 deletions .changeset/curly-lies-flash.md
@@ -0,0 +1,10 @@
---
'@vanilla-extract/css': minor
'@vanilla-extract/webpack-plugin': minor
'@vanilla-extract/esbuild-plugin': minor
'@vanilla-extract/rollup-plugin': minor
'@vanilla-extract/vite-plugin': minor
'@vanilla-extract/next-plugin': minor
---

Users can now provide a custom identifier hashing function
42 changes: 41 additions & 1 deletion packages/css/src/identifier.test.ts
@@ -1,4 +1,5 @@
import { setFileScope, endFileScope } from './fileScope';
import { removeAdapter, setAdapter } from './adapter';
import { endFileScope, setFileScope } from './fileScope';
import { generateIdentifier } from './identifier';

describe('identifier', () => {
Expand Down Expand Up @@ -57,4 +58,43 @@ describe('identifier', () => {
).toMatchInlineSnapshot(`"_18bazsm6"`);
});
});

describe('with custom callback', () => {
beforeAll(() => {
setFileScope('path/to/file.css.ts', 'packagetest');
setAdapter({
appendCss: () => {},
registerClassName: () => {},
onEndFileScope: () => {},
registerComposition: () => {},
markCompositionUsed: () => {},
getIdentOption:
() =>
({ hash, debugId, filePath, packageName }) => {
const filenameWithExtension = filePath?.split('/').pop();
const filenameWithoutExtension =
filenameWithExtension?.split('.')?.[0];

return `abc_${debugId}_${hash}_${packageName}_${filenameWithoutExtension}`;
},
});
});

afterAll(() => {
removeAdapter();
endFileScope();
});

it('defers to a custom callback', () => {
expect(generateIdentifier(`a`)).toMatchInlineSnapshot(
`"abc_a_s0xkdr0_packagetest_file"`,
);
});

it('rejects invalid identifiers', () => {
// getIdentOption() does not remove spaces from the debug info so the
// resulting identifier should be invalid here.
expect(() => generateIdentifier(`a b`)).toThrow();
});
});
});
27 changes: 25 additions & 2 deletions packages/css/src/identifier.ts
Expand Up @@ -28,6 +28,10 @@ function getDevPrefix({
return parts.join('_');
}

function normalizeIdentifier(identifier: string) {
return identifier.match(/^[0-9]/) ? `_${identifier}` : identifier;
}

interface GenerateIdentifierOptions {
debugId?: string;
debugFileName?: boolean;
Expand All @@ -38,6 +42,7 @@ export function generateIdentifier(options?: GenerateIdentifierOptions): string;
export function generateIdentifier(
arg?: string | GenerateIdentifierOptions,
): string {
const identOption = getIdentOption();
const { debugId, debugFileName = true } = {
...(typeof arg === 'string' ? { debugId: arg } : null),
...(typeof arg === 'object' ? arg : null),
Expand All @@ -53,13 +58,31 @@ export function generateIdentifier(

let identifier = `${fileScopeHash}${refCount}`;

if (getIdentOption() === 'debug') {
if (identOption === 'debug') {
const devPrefix = getDevPrefix({ debugId, debugFileName });

if (devPrefix) {
identifier = `${devPrefix}__${identifier}`;
}

return normalizeIdentifier(identifier);
}
if (typeof identOption === 'function') {
identifier = identOption({
hash: identifier,
debugId,
filePath,
packageName,
});

return identifier.match(/^[0-9]/) ? `_${identifier}` : identifier;
if (!identifier.match(/^[A-Z_][0-9A-Z_]+$/i)) {
throw new Error(
`Identifier function returned invalid indentifier: "${identifier}"`,
);
}

return identifier;
}

return normalizeIdentifier(identifier);
}
14 changes: 11 additions & 3 deletions packages/css/src/types.ts
@@ -1,5 +1,5 @@
import type { MapLeafNodes, CSSVarFunction } from '@vanilla-extract/private';
import type { Properties, AtRule } from 'csstype';
import type { CSSVarFunction, MapLeafNodes } from '@vanilla-extract/private';
import type { AtRule, Properties } from 'csstype';

import type { SimplePseudos } from './simplePseudos';

Expand Down Expand Up @@ -120,7 +120,15 @@ export interface Composition {
classList: string;
}

type IdentOption = 'short' | 'debug';
type CustomIdentFunction = (params: {
hash: string;
filePath: string;
debugId?: string;
packageName?: string;
}) => string;

type IdentOption = 'short' | 'debug' | CustomIdentFunction;

export interface Adapter {
appendCss: (css: CSS, fileScope: FileScope) => void;
registerClassName: (className: string, fileScope: FileScope) => void;
Expand Down
7 changes: 7 additions & 0 deletions site/docs/integrations/esbuild.md
Expand Up @@ -100,6 +100,13 @@ Different formatting of identifiers (e.g. class names, keyframes, CSS Vars, etc)

- `short` identifiers are a 7+ character hash. e.g. `hnw5tz3`
- `debug` identifiers contain human readable prefixes representing the owning filename and a potential rule level debug name. e.g. `myfile_mystyle_hnw5tz3`
- A custom identifier function takes an object parameter with properties `hash`, `filePath`, `debugId`, and `packageName`, and returns a customized identifier. e.g.

```ts
vanillaExtractPlugin({
identifiers: ({ hash }) => `prefix_${hash}`
});
```

Each integration will set a default value based on the configuration options passed to the bundler.

Expand Down
7 changes: 7 additions & 0 deletions site/docs/integrations/next.md
Expand Up @@ -57,5 +57,12 @@ Different formatting of identifiers (e.g. class names, keyframes, CSS Vars, etc)

- `short` identifiers are a 7+ character hash. e.g. `hnw5tz3`
- `debug` identifiers contain human readable prefixes representing the owning filename and a potential rule level debug name. e.g. `myfile_mystyle_hnw5tz3`
- A custom identifier function takes an object parameter with properties `hash`, `filePath`, `debugId`, and `packageName`, and returns a customized identifier. e.g.

```ts
const withVanillaExtract = createVanillaExtractPlugin({
identifiers: ({ hash }) => `prefix_${hash}`
});
```

Each integration will set a default value based on the configuration options passed to the bundler.
7 changes: 7 additions & 0 deletions site/docs/integrations/rollup.md
Expand Up @@ -76,6 +76,13 @@ Different formatting of identifiers (e.g. class names, keyframes, CSS Vars, etc)
- `short` identifiers are a 7+ character hash. e.g. `hnw5tz3`
- `debug` identifiers contain human readable prefixes representing the owning filename and a potential rule level debug name. e.g. `myfile_mystyle_hnw5tz3`
- A custom identifier function takes an object parameter with properties `hash`, `filePath`, `debugId`, and `packageName`, and returns a customized identifier. e.g.
```ts
vanillaExtractPlugin({
identifiers: ({ hash }) => `prefix_${hash}`
});
```
Each integration will set a default value based on the configuration options passed to the bundler.
Expand Down
7 changes: 7 additions & 0 deletions site/docs/integrations/vite.md
Expand Up @@ -51,6 +51,13 @@ Different formatting of identifiers (e.g. class names, keyframes, CSS Vars, etc)

- `short` identifiers are a 7+ character hash. e.g. `hnw5tz3`
- `debug` identifiers contain human readable prefixes representing the owning filename and a potential rule level debug name. e.g. `myfile_mystyle_hnw5tz3`
- A custom identifier function takes an object parameter with properties `hash`, `filePath`, `debugId`, and `packageName`, and returns a customized identifier. e.g.

```ts
vanillaExtractPlugin({
identifiers: ({ hash }) => `prefix_${hash}`
});
```

Each integration will set a default value based on the configuration options passed to the bundler.

Expand Down
7 changes: 7 additions & 0 deletions site/docs/integrations/webpack.md
Expand Up @@ -91,5 +91,12 @@ Different formatting of identifiers (e.g. class names, keyframes, CSS Vars, etc)

- `short` identifiers are a 7+ character hash. e.g. `hnw5tz3`
- `debug` identifiers contain human readable prefixes representing the owning filename and a potential rule level debug name. e.g. `myfile_mystyle_hnw5tz3`
- A custom identifier function takes an object parameter with properties `hash`, `filePath`, `debugId`, and `packageName`, and returns a customized identifier. e.g.

```ts
VanillaExtractPlugin({
identifiers: ({ hash }) => `prefix_${hash}`
});
```

Each integration will set a default value based on the configuration options passed to the bundler.

0 comments on commit e391bae

Please sign in to comment.