Skip to content

Commit

Permalink
feat(babel): add file, name, dir, ext to classNameSlug (#825, fixes #650
Browse files Browse the repository at this point in the history
 and #571)
  • Loading branch information
mikestopcontinues authored Aug 30, 2021
1 parent 2463881 commit c1fdb7c
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 18 deletions.
28 changes: 24 additions & 4 deletions packages/babel/__tests__/__snapshots__/babel.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,15 @@ exports[`handles fn passed in as classNameSlug 1`] = `
"import { styled } from '@linaria/react';
export const Title = /*#__PURE__*/styled('h1')({
name: \\"Title\\",
class: \\"t17gu1mi_42_Title\\"
class: \\"t17gu1mi_Title_t17gu1mi_Title__tests___app_index_js_index__js_app\\"
});"
`;
exports[`handles fn passed in as classNameSlug 2`] = `
CSS:
.t17gu1mi_42_Title {
.t17gu1mi_Title_t17gu1mi_Title__tests___app_index_js_index__js_app {
font-size: 14px;
}
Expand Down Expand Up @@ -389,6 +389,26 @@ Dependencies: NA
`;
exports[`removes fake replacement patterns in string classNameSlug 1`] = `
"import { styled } from '@linaria/react';
export const Title = /*#__PURE__*/styled('h1')({
name: \\"Title\\",
class: \\"__\\"
});"
`;
exports[`removes fake replacement patterns in string classNameSlug 2`] = `
CSS:
.__ {
font-size: 14px;
}
Dependencies: NA
`;
exports[`replaces unknown expressions with CSS custom properties 1`] = `
"import { styled } from '@linaria/react';
export const Title = /*#__PURE__*/styled(\\"h1\\")({
Expand Down Expand Up @@ -552,15 +572,15 @@ exports[`uses string passed in as classNameSlug 1`] = `
"import { styled } from '@linaria/react';
export const Title = /*#__PURE__*/styled('h1')({
name: \\"Title\\",
class: \\"testSlug\\"
class: \\"t17gu1mi_Title__tests___app_index_js_index__js_app\\"
});"
`;
exports[`uses string passed in as classNameSlug 2`] = `
CSS:
.testSlug {
.t17gu1mi_Title__tests___app_index_js_index__js_app {
font-size: 14px;
}
Expand Down
37 changes: 34 additions & 3 deletions packages/babel/__tests__/babel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,29 @@ it('uses string passed in as classNameSlug', async () => {
font-size: 14px;
\`;
`,
{ classNameSlug: 'testSlug' }
{
classNameSlug: ['hash', 'title', 'file', 'name', 'ext', 'dir']
.map((s) => `[${s}]`)
.join('_'),
}
);

expect(code).toMatchSnapshot();
expect(metadata).toMatchSnapshot();
});

it('removes fake replacement patterns in string classNameSlug', async () => {
const { code, metadata } = await transpile(
dedent`
import { styled } from '@linaria/react';
export const Title = styled('h1')\`
font-size: 14px;
\`;
`,
{
classNameSlug: `[not]_[actual]_[replacements]`,
}
);

expect(code).toMatchSnapshot();
Expand All @@ -60,8 +82,17 @@ it('handles fn passed in as classNameSlug', async () => {
\`;
`,
{
classNameSlug: (hash, title) => {
return `${hash}_${7 * 6}_${title}`;
classNameSlug: (hash, title, vars) => {
return [
hash,
title,
vars.hash,
vars.title,
vars.file,
vars.name,
vars.ext,
vars.dir,
].join('_');
},
}
);
Expand Down
15 changes: 14 additions & 1 deletion packages/babel/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,20 @@ export type EvalRule = {
action: Evaluator | 'ignore' | string;
};

type ClassNameFn = (hash: string, title: string) => string;
export type ClassNameSlugVars = {
hash: string;
title: string;
file: string;
ext: string;
name: string;
dir: string;
};

type ClassNameFn = (
hash: string,
title: string,
args: ClassNameSlugVars
) => string;

export type StrictOptions = {
classNameSlug?: string | ClassNameFn;
Expand Down
8 changes: 8 additions & 0 deletions packages/babel/src/utils/isSlugVar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ClassNameSlugVars } from '../types';

const isSlugVar = (
key: string,
slugVars: ClassNameSlugVars
): key is keyof ClassNameSlugVars => key in slugVars;

export default isSlugVar;
31 changes: 21 additions & 10 deletions packages/babel/src/visitors/GenerateClassNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
* It saves that meta data as comment above the template, to be later used in templateProcessor.
*/

import { basename, dirname, relative } from 'path';
import { basename, dirname, relative, extname, sep } from 'path';
import type { ObjectProperty, TaggedTemplateExpression } from '@babel/types';
import type { NodePath } from '@babel/traverse';
import { debug } from '@linaria/logger';
import type { State, StrictOptions } from '../types';
import type { ClassNameSlugVars, State, StrictOptions } from '../types';
import toValidCSSIdentifier from '../utils/toValidCSSIdentifier';
import slugify from '../utils/slugify';
import getLinariaComment from '../utils/getLinariaComment';
import isStyledOrCss from '../utils/isStyledOrCss';
import { Core } from '../babel';
import isSlugVar from '../utils/isSlugVar';

export default function GenerateClassNames(
babel: Core,
Expand Down Expand Up @@ -106,6 +107,19 @@ export default function GenerateClassNames(
)}`
);

// Collect some useful replacement patterns from the filename
// Available variables for the square brackets used in `classNameSlug` options
const file = relative(process.cwd(), state.file.opts.filename).slice(1);
const ext = extname(file);
const slugVars: ClassNameSlugVars = {
hash: slug,
title: displayName,
file,
ext,
name: basename(file, ext),
dir: dirname(file).split(sep).pop() as string,
};

let className = predefinedClassName
? predefinedClassName
: options.displayName
Expand All @@ -116,7 +130,7 @@ export default function GenerateClassNames(
if (typeof options.classNameSlug === 'function') {
try {
className = toValidCSSIdentifier(
options.classNameSlug(slug, displayName)
options.classNameSlug(slug, displayName, slugVars)
);
} catch {
throw new Error(`classNameSlug option must return a string`);
Expand All @@ -126,12 +140,6 @@ export default function GenerateClassNames(
if (typeof options.classNameSlug === 'string') {
const { classNameSlug } = options;

// Available variables for the square brackets used in `classNameSlug` options
const classNameSlugVars: Record<string, string | null> = {
hash: slug,
title: displayName,
};

// Variables that were used in the config for `classNameSlug`
const optionVariables = classNameSlug.match(/\[.*?]/g) || [];
let cnSlug = classNameSlug;
Expand All @@ -140,7 +148,10 @@ export default function GenerateClassNames(
const v = optionVariables[i].slice(1, -1); // Remove the brackets around the variable name

// Replace the var if it key and value exist otherwise place an empty string
cnSlug = cnSlug.replace(`[${v}]`, classNameSlugVars[v] || '');
cnSlug = cnSlug.replace(
`[${v}]`,
isSlugVar(v, slugVars) ? slugVars[v] : ''
);
}

className = toValidCSSIdentifier(cnSlug);
Expand Down

0 comments on commit c1fdb7c

Please sign in to comment.