Skip to content

Commit

Permalink
fix(styled): enable #1276 optimisation for complex expressions (#1277)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anber committed Jul 13, 2023
1 parent 06b3dad commit ceca161
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 40 deletions.
8 changes: 8 additions & 0 deletions .changeset/breezy-terms-march.md
@@ -0,0 +1,8 @@
---
'@linaria/babel-preset': patch
'@linaria/react': patch
'@linaria/tags': patch
'@linaria/testkit': patch
---

Enable optimisation from #1276 for complex expressions such as `styled(Component as unknow)` or `styled(connect(Component))`.
26 changes: 20 additions & 6 deletions packages/babel/src/utils/collectTemplateDependencies.ts
Expand Up @@ -228,11 +228,25 @@ export function extractExpression(
referenceAll(inserted);
rootScope.registerDeclaration(inserted);

const exImport = ex.isIdentifier()
? imports.find(
(i) => i.local.node === ex.scope.getBinding(ex.node.name)?.identifier
) ?? null
: null;
const importedFrom: string[] = [];
function findImportSourceOfIdentifier(idPath: NodePath<Identifier>) {
const exBindingIdentifier = idPath.scope.getBinding(
idPath.node.name
)?.identifier;
const exImport =
imports.find((i) => i.local.node === exBindingIdentifier) ?? null;
if (exImport) {
importedFrom.push(exImport.source);
}
}

if (ex.isIdentifier()) {
findImportSourceOfIdentifier(ex);
} else {
ex.traverse({
Identifier: findImportSourceOfIdentifier,
});
}

// Replace the expression with the _expN() call
mutate(ex, (p) => {
Expand All @@ -257,7 +271,7 @@ export function extractExpression(
> = {
kind,
ex: createId(expUid, loc),
importedFrom: exImport?.source,
importedFrom,
};

return result;
Expand Down
64 changes: 34 additions & 30 deletions packages/react/src/processors/styled.ts
Expand Up @@ -89,41 +89,45 @@ export default class StyledProcessor extends TaggedTemplateProcessor {
} else if (value.kind === ValueType.CONST) {
component = typeof value.value === 'string' ? value.value : undefined;
} else {
if (value.importedFrom) {
if (value.importedFrom?.length) {
const selfPkg = findPackageJSON('.', this.context.filename);
const importedPkg = findPackageJSON(
value.importedFrom,
this.context.filename
);

if (importedPkg) {
const packageJSON = JSON.parse(readFileSync(importedPkg, 'utf8'));
let isMatched = false;
let mask: string | undefined = packageJSON?.linaria?.components;
if (importedPkg === selfPkg && mask === undefined) {
// If mask is not specified for the local package, all components are treated as styled.
mask = '**/*';
// Check if at least one used identifier is a Linaria component.
const isSomeMatched = value.importedFrom.some((importedFrom) => {
const importedPkg = findPackageJSON(
importedFrom,
this.context.filename
);

if (importedPkg) {
const packageJSON = JSON.parse(readFileSync(importedPkg, 'utf8'));
let mask: string | undefined = packageJSON?.linaria?.components;
if (importedPkg === selfPkg && mask === undefined) {
// If mask is not specified for the local package, all components are treated as styled.
mask = '**/*';
}

if (mask) {
const packageDir = dirname(importedPkg);
const normalizedMask = mask.replace(/\//g, sep);
const fullMask = join(packageDir, normalizedMask);
const fileWithComponent = require.resolve(importedFrom, {
paths: [dirname(this.context.filename!)],
});

return minimatch(fileWithComponent, fullMask);
}
}

if (mask) {
const packageDir = dirname(importedPkg);
const normalizedMask = mask.replace(/\//g, sep);
const fullMask = join(packageDir, normalizedMask);
const fileWithComponent = require.resolve(value.importedFrom, {
paths: [dirname(this.context.filename!)],
});
isMatched = minimatch(fileWithComponent, fullMask);
}
return false;
});

if (!isMatched) {
// If a wrapped component is not imported from a package with
// Linaria-styled components, we can treat it as a simple component.
component = {
node: value.ex,
nonLinaria: true,
source: value.source,
};
}
if (!isSomeMatched) {
component = {
node: value.ex,
nonLinaria: true,
source: value.source,
};
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/tags/src/types.ts
Expand Up @@ -102,15 +102,15 @@ export enum ValueType {
export type LazyValue = {
buildCodeFrameError: BuildCodeFrameErrorFn;
ex: Identifier;
importedFrom?: string;
importedFrom?: string[];
kind: ValueType.LAZY;
source: string;
};

export type FunctionValue = {
buildCodeFrameError: BuildCodeFrameErrorFn;
ex: Identifier;
importedFrom?: string;
importedFrom?: string[];
kind: ValueType.FUNCTION;
source: string;
};
Expand Down
@@ -0,0 +1 @@
export const connect = (i) => i;
44 changes: 44 additions & 0 deletions packages/testkit/src/__snapshots__/babel.test.ts.snap
Expand Up @@ -1878,6 +1878,28 @@ Dependencies: ./__fixtures__/linaria-ui-library/components/index
`;
exports[`strategy shaker should eval wrapped component from a linaria library 1`] = `
"import { styled } from \\"@linaria/react\\";
import { connect } from \\"./__fixtures__/linaria-ui-library/hocs\\";
import { Title } from \\"./__fixtures__/linaria-ui-library/components/index\\";
const _exp = /*#__PURE__*/() => connect(Title);
export const StyledTitle = /*#__PURE__*/styled(_exp())({
name: \\"StyledTitle\\",
class: \\"StyledTitle_s13jq05\\",
propsAsIs: true
});"
`;
exports[`strategy shaker should eval wrapped component from a linaria library 2`] = `
CSS:
.StyledTitle_s13jq05 {}
Dependencies: ./__fixtures__/linaria-ui-library/hocs, ./__fixtures__/linaria-ui-library/components/index
`;
exports[`strategy shaker should handle shadowed identifier inside components 1`] = `
"import React from 'react';
const color = 'red';
Expand Down Expand Up @@ -1988,6 +2010,28 @@ Dependencies: NA
`;
exports[`strategy shaker should not eval wrapped component from a non-linaria library 1`] = `
"import { styled } from \\"@linaria/react\\";
import { connect } from \\"./__fixtures__/linaria-ui-library/hocs\\";
import { Title } from \\"./__fixtures__/non-linaria-ui-library/index\\";
const _exp = /*#__PURE__*/() => connect(Title);
export const StyledTitle = /*#__PURE__*/styled(_exp())({
name: \\"StyledTitle\\",
class: \\"StyledTitle_s13jq05\\",
propsAsIs: true
});"
`;
exports[`strategy shaker should not eval wrapped component from a non-linaria library 2`] = `
CSS:
.StyledTitle_s13jq05 {}
Dependencies: NA
`;
exports[`strategy shaker should process \`css\` calls inside components 1`] = `
"import React from 'react';
export function Component() {
Expand Down
32 changes: 32 additions & 0 deletions packages/testkit/src/babel.test.ts
Expand Up @@ -2748,4 +2748,36 @@ describe('strategy shaker', () => {
expect(code).toMatchSnapshot();
expect(metadata).toMatchSnapshot();
});

it('should eval wrapped component from a linaria library', async () => {
const { code, metadata } = await transform(
dedent`
import { styled } from "@linaria/react";
import { connect } from "./__fixtures__/linaria-ui-library/hocs";
import { Title } from "./__fixtures__/linaria-ui-library/components/index";
export const StyledTitle = styled(connect(Title))\`\`;
`,
[evaluator]
);

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

it('should not eval wrapped component from a non-linaria library', async () => {
const { code, metadata } = await transform(
dedent`
import { styled } from "@linaria/react";
import { connect } from "./__fixtures__/linaria-ui-library/hocs";
import { Title } from "./__fixtures__/non-linaria-ui-library/index";
export const StyledTitle = styled(connect(Title))\`\`;
`,
[evaluator]
);

expect(code).toMatchSnapshot();
expect(metadata).toMatchSnapshot();
});
});
62 changes: 60 additions & 2 deletions packages/testkit/src/utils/getTagProcessor.test.ts
Expand Up @@ -412,7 +412,7 @@ describe('getTagProcessor', () => {
import { Title } from "../__fixtures__/linaria-ui-library/components/index";
import { styled } from "@linaria/react";
export const StyledLayout = styled(Title)\`\`;
export const StyledTitle = styled(Title)\`\`;
`
);

Expand All @@ -430,7 +430,7 @@ describe('getTagProcessor', () => {
import { Title } from "../__fixtures__/linaria-ui-library/non-linaria-components";
import { styled } from "@linaria/react";
export const StyledLayout = styled(Title)\`\`;
export const StyledTitle = styled(Title)\`\`;
`
);

Expand All @@ -441,4 +441,62 @@ describe('getTagProcessor', () => {
source: '@linaria/react',
});
});

it('imported a non-linaria component from a linaria library (and casted with as)', () => {
const result = run(
dedent`
import { Title } from "../__fixtures__/linaria-ui-library/non-linaria-components";
import { styled } from "@linaria/react";
export const StyledTitle = styled(Title as unknown)\`\`;
`,
{ ts: true }
);

expect(tagToString(result)).toBe('styled(Title as unknown)`…`');
expect(result?.dependencies).toHaveLength(0);
expect(result?.tagSource).toEqual({
imported: 'styled',
source: '@linaria/react',
});
});

it('imported a non-linaria component from a linaria library (and used with connect)', () => {
const result = run(
dedent`
import { Title } from "../__fixtures__/linaria-ui-library/non-linaria-components";
import { styled } from "@linaria/react";
import { connect } from "react-redux";
export const StyledTitle = styled(connect(Title))\`\`;
`,
{ ts: true }
);

expect(tagToString(result)).toBe('styled(connect(Title))`…`');
expect(result?.dependencies).toHaveLength(0);
expect(result?.tagSource).toEqual({
imported: 'styled',
source: '@linaria/react',
});
});

it('imported from a linaria library (and used with connect)', () => {
const result = run(
dedent`
import { Title } from "../__fixtures__/linaria-ui-library/components/index";
import { connect } from "./__fixtures__/linaria-ui-library/hocs";
import { styled } from "@linaria/react";
export const StyledTitle = styled(connect(Title))\`\`;
`
);

expect(tagToString(result)).toBe('styled(connect(Title))`…`');
expect(result?.dependencies).toMatchObject([{ source: 'connect(Title)' }]);
expect(result?.tagSource).toEqual({
imported: 'styled',
source: '@linaria/react',
});
});
});

0 comments on commit ceca161

Please sign in to comment.