Skip to content

Commit

Permalink
feat(styled): optimisation for props-based interpolation (#1279)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anber committed Jul 13, 2023
1 parent ceca161 commit 1325830
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 48 deletions.
18 changes: 18 additions & 0 deletions .changeset/four-cats-brush.md
@@ -0,0 +1,18 @@
---
'@linaria/babel-preset': patch
'@linaria/react': patch
'@linaria/tags': patch
'@linaria/testkit': patch
---

Variables in props-based interpolation functions are no longer required for the evaluation stage.
Here's an example:
```
import { getColor } from "very-big-library";
export const Box = styled.div\`
color: ${props => getColor(props.kind)};
\`;
```

In versions prior to and including 4.5.0, the evaluator would attempt to import `getColor` from `very-big-library`, despite it having no relevance to style generation. However, in versions greater than 4.5.0, `very-big-library` will be ignored.
Expand Up @@ -21,7 +21,7 @@ async function go(code: string): Promise<string> {
const expressions = path.get('expressions');
expressions.forEach((exp) => {
if (exp.isExpression()) {
extractExpression(exp, true, false);
extractExpression(exp, true);
}
});
},
Expand Down
7 changes: 0 additions & 7 deletions packages/babel/src/utils/collectTemplateDependencies.ts
Expand Up @@ -25,7 +25,6 @@ import type { ConstValue, FunctionValue, LazyValue } from '@linaria/tags';
import { hasMeta } from '@linaria/tags';
import type { IImport } from '@linaria/utils';
import {
addIdentifierToLinariaPreval,
createId,
findIdentifiers,
mutate,
Expand Down Expand Up @@ -157,13 +156,11 @@ function hoistIdentifier(idPath: NodePath<Identifier>): void {
* used in a Linaria template. This function tries to hoist the expression.
* @param ex The expression to hoist.
* @param evaluate If true, we try to statically evaluate the expression.
* @param addToExport If true, we add the expression to the __linariaPreval.
* @param imports All the imports of the file.
*/
export function extractExpression(
ex: NodePath<Expression>,
evaluate = false,
addToExport = true,
imports: IImport[] = []
): Omit<ExpressionValue, 'buildCodeFrameError' | 'source'> {
if (
Expand Down Expand Up @@ -257,10 +254,6 @@ export function extractExpression(
});
});

if (addToExport) {
addIdentifierToLinariaPreval(rootScope, expUid);
}

// eslint-disable-next-line no-param-reassign
ex.node.loc = loc;

Expand Down
7 changes: 1 addition & 6 deletions packages/babel/src/utils/getTagProcessor.ts
Expand Up @@ -255,12 +255,7 @@ function getBuilderForIdentifier(
throw buildError(`Unexpected type of an argument ${arg.type}`);
}
const source = getSource(arg);
const extracted = extractExpression(
arg,
options.evaluate,
false,
imports
);
const extracted = extractExpression(arg, options.evaluate, imports);
return {
...extracted,
source,
Expand Down
3 changes: 2 additions & 1 deletion packages/tags/src/TaggedTemplateProcessor.ts
Expand Up @@ -3,6 +3,7 @@ import type { TemplateElement, Expression, SourceLocation } from '@babel/types';
import type { TailProcessorParams } from './BaseProcessor';
import BaseProcessor from './BaseProcessor';
import type { ExpressionValue, ValueCache, Rules, Params } from './types';
import { ValueType } from './types';
import templateProcessor from './utils/templateProcessor';
import { validateParams } from './utils/validateParams';

Expand All @@ -27,7 +28,7 @@ export default abstract class TaggedTemplateProcessor extends BaseProcessor {
super([tag], ...args);

template.forEach((element) => {
if ('kind' in element) {
if ('kind' in element && element.kind !== ValueType.FUNCTION) {
this.dependencies.push(element);
}
});
Expand Down
65 changes: 33 additions & 32 deletions packages/tags/src/utils/templateProcessor.ts
Expand Up @@ -13,6 +13,7 @@ import type {
Rules,
Replacements,
} from '../types';
import { ValueType } from '../types';

import { getVariableName } from './getVariableName';
import hasMeta from './hasMeta';
Expand Down Expand Up @@ -71,39 +72,8 @@ export default function templateProcessor(

const value = 'value' in item ? item.value : valueCache.get(item.ex.name);

throwIfInvalid(
tagProcessor.isValidValue.bind(tagProcessor),
value,
item,
item.source
);

if (value !== undefined && typeof value !== 'function') {
// Skip the blank string instead of throw ing an error
if (value === '') {
continue;
}

if (hasMeta(value)) {
// If it's a React component wrapped in styled, get the class name
// Useful for interpolating components
cssText += `.${value.__linaria.className}`;
} else if (isCSSable(value)) {
// If it's a plain object or an array, convert it to a CSS string
cssText += stripLines(loc, toCSS(value));
} else {
// For anything else, assume it'll be stringified
cssText += stripLines(loc, value);
}

sourceMapReplacements.push({
original: loc,
length: cssText.length - beforeLength,
});
}

// Is it props based interpolation?
if (typeof value === 'function') {
if (item.kind === ValueType.FUNCTION || typeof value === 'function') {
// Check if previous expression was a CSS variable that we replaced
// If it has a unit after it, we need to move the unit into the interpolation
// e.g. `var(--size)px` should actually be `var(--size)`
Expand Down Expand Up @@ -141,6 +111,37 @@ export default function templateProcessor(

throw e;
}
} else {
throwIfInvalid(
tagProcessor.isValidValue.bind(tagProcessor),
value,
item,
item.source
);

if (value !== undefined && typeof value !== 'function') {
// Skip the blank string instead of throw ing an error
if (value === '') {
continue;
}

if (hasMeta(value)) {
// If it's a React component wrapped in styled, get the class name
// Useful for interpolating components
cssText += `.${value.__linaria.className}`;
} else if (isCSSable(value)) {
// If it's a plain object or an array, convert it to a CSS string
cssText += stripLines(loc, toCSS(value));
} else {
// For anything else, assume it'll be stringified
cssText += stripLines(loc, value);
}

sourceMapReplacements.push({
original: loc,
length: cssText.length - beforeLength,
});
}
}
}

Expand Down
30 changes: 30 additions & 0 deletions packages/testkit/src/__snapshots__/babel.test.ts.snap
Expand Up @@ -402,6 +402,36 @@ Dependencies: NA
`;
exports[`strategy shaker do not include in dependencies expressions from interpolation functions bodies 1`] = `
"import { styled } from '@linaria/react';
import constant from './broken-dependency-1';
import modifier from './broken-dependency-2';
const _exp = /*#__PURE__*/() => props => props.size + constant;
const _exp2 = /*#__PURE__*/() => props => modifier(props.size);
export const Box = /*#__PURE__*/styled('div')({
name: \\"Box\\",
class: \\"Box_b13jq05\\",
propsAsIs: false,
vars: {
\\"b13jq05-0\\": [_exp(), \\"px\\"],
\\"b13jq05-1\\": [_exp2(), \\"px\\"]
}
});"
`;
exports[`strategy shaker do not include in dependencies expressions from interpolation functions bodies 2`] = `
CSS:
.Box_b13jq05 {
height: var(--b13jq05-0);
width: var(--b13jq05-1);
}
Dependencies: NA
`;
exports[`strategy shaker does not include styles if not referenced anywhere 1`] = `
"import { styled } from '@linaria/react';
const Title = /*#__PURE__*/styled('h1')({
Expand Down
19 changes: 19 additions & 0 deletions packages/testkit/src/babel.test.ts
Expand Up @@ -772,6 +772,25 @@ describe('strategy shaker', () => {
expect(metadata).toMatchSnapshot();
});

it('do not include in dependencies expressions from interpolation functions bodies', async () => {
const { code, metadata } = await transform(
dedent`
import { styled } from '@linaria/react';
import constant from './broken-dependency-1';
import modifier from './broken-dependency-2';
export const Box = styled.div\`
height: ${'${props => props.size + constant}'}px;
width: ${'${props => modifier(props.size)}'}px;
\`;
`,
[evaluator]
);

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

it('handles nested blocks', async () => {
const { code, metadata } = await transform(
dedent`
Expand Down
2 changes: 1 addition & 1 deletion packages/testkit/src/utils/extractExpression.test.ts
Expand Up @@ -35,7 +35,7 @@ const run = (rawCode: TemplateStringsArray, evaluate: boolean) => {
// eslint-disable-next-line no-param-reassign
path.node.leadingComments = [];

extractExpression(path, evaluate, false);
extractExpression(path, evaluate);
},
});

Expand Down

0 comments on commit 1325830

Please sign in to comment.