summaryrefslogtreecommitdiff
path: root/@linaria/packages/babel/__tests__
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-08-23 16:46:06 -0300
committerSebastian <sebasjm@gmail.com>2021-08-23 16:48:30 -0300
commit38acabfa6089ab8ac469c12b5f55022fb96935e5 (patch)
tree453dbf70000cc5e338b06201af1eaca8343f8f73 /@linaria/packages/babel/__tests__
parentf26125e039143b92dc0d84e7775f508ab0cdcaa8 (diff)
downloadnode-vendor-master.tar.gz
node-vendor-master.tar.bz2
node-vendor-master.zip
added web vendorsHEADmaster
Diffstat (limited to '@linaria/packages/babel/__tests__')
-rw-r--r--@linaria/packages/babel/__tests__/__snapshots__/babel.test.ts.snap617
-rw-r--r--@linaria/packages/babel/__tests__/__snapshots__/dynamic-import-noop.test.js.snap8
-rw-r--r--@linaria/packages/babel/__tests__/__snapshots__/transform.test.ts.snap16
-rw-r--r--@linaria/packages/babel/__tests__/babel.test.ts524
-rw-r--r--@linaria/packages/babel/__tests__/depsGraph.test.ts356
-rw-r--r--@linaria/packages/babel/__tests__/dynamic-import-noop.test.js15
-rw-r--r--@linaria/packages/babel/__tests__/evaluators/__snapshots__/extractor.test.ts.snap237
-rw-r--r--@linaria/packages/babel/__tests__/evaluators/__snapshots__/preeval.test.ts.snap60
-rw-r--r--@linaria/packages/babel/__tests__/evaluators/__snapshots__/shaker.test.ts.snap213
-rw-r--r--@linaria/packages/babel/__tests__/evaluators/extractor.test.ts214
-rw-r--r--@linaria/packages/babel/__tests__/evaluators/preeval.test.ts121
-rw-r--r--@linaria/packages/babel/__tests__/evaluators/shaker.test.ts259
-rw-r--r--@linaria/packages/babel/__tests__/module.test.ts320
-rw-r--r--@linaria/packages/babel/__tests__/transform.test.ts237
14 files changed, 3197 insertions, 0 deletions
diff --git a/@linaria/packages/babel/__tests__/__snapshots__/babel.test.ts.snap b/@linaria/packages/babel/__tests__/__snapshots__/babel.test.ts.snap
new file mode 100644
index 0000000..84be71a
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/__snapshots__/babel.test.ts.snap
@@ -0,0 +1,617 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`does not include styles if not referenced anywhere 1`] = `
+"import { css } from '@linaria/core';
+import { styled } from '@linaria/react';
+const Title = /*#__PURE__*/styled(\\"h1\\")({
+ name: \\"Title\\",
+ class: \\"t17gu1mi\\"
+});
+const title = \\"tccybe9\\";"
+`;
+
+exports[`does not include styles if not referenced anywhere 2`] = `Object {}`;
+
+exports[`does not output CSS if none present 1`] = `
+"const number = 42;
+const title = String.raw\`This is something\`;"
+`;
+
+exports[`does not output CSS if none present 2`] = `Object {}`;
+
+exports[`does not output CSS property when value is a blank string 1`] = `
+"import { css } from '@linaria/core';
+export const title = \\"t17gu1mi\\";"
+`;
+
+exports[`does not output CSS property when value is a blank string 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ font-size: ;
+ margin: 6px;
+}
+
+Dependencies: NA
+
+`;
+
+exports[`evaluates and inlines expressions in scope 1`] = `
+"import { styled } from '@linaria/react';
+const color = 'blue';
+export const Title = /*#__PURE__*/styled(\\"h1\\")({
+ name: \\"Title\\",
+ class: \\"t17gu1mi\\"
+});"
+`;
+
+exports[`evaluates and inlines expressions in scope 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ color: blue;
+ width: 33.333333333333336%;
+}
+
+Dependencies: NA
+
+`;
+
+exports[`handles css template literal in JSX element 1`] = `
+"import { css } from '@linaria/core';
+<Title class={\\"t17gu1mi\\"} />;"
+`;
+
+exports[`handles css template literal in JSX element 2`] = `
+
+CSS:
+
+.t17gu1mi { font-size: 14px; }
+
+Dependencies: NA
+
+`;
+
+exports[`handles css template literal in object property 1`] = `
+"import { css } from '@linaria/core';
+const components = {
+ title: \\"t17gu1mi\\"
+};"
+`;
+
+exports[`handles css template literal in object property 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ font-size: 14px;
+ }
+
+Dependencies: NA
+
+`;
+
+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\\"
+});"
+`;
+
+exports[`handles fn passed in as classNameSlug 2`] = `
+
+CSS:
+
+.t17gu1mi_42_Title {
+ font-size: 14px;
+}
+
+Dependencies: NA
+
+`;
+
+exports[`handles interpolation followed by unit 1`] = `
+"import { styled } from '@linaria/react';
+export const Title = /*#__PURE__*/styled(\\"h1\\")({
+ name: \\"Title\\",
+ class: \\"t17gu1mi\\",
+ vars: {
+ \\"t17gu1mi-0\\": [size, \\"em\\"],
+ \\"t17gu1mi-1\\": [shadow, \\"px\\"],
+ \\"t17gu1mi-2\\": [size, \\"px\\"],
+ \\"t17gu1mi-3\\": [props => props.width, \\"vw\\"],
+ \\"t17gu1mi-4\\": [props => {
+ if (true) {
+ return props.height;
+ } else {
+ return 200;
+ }
+ }, \\"px\\"],
+ \\"t17gu1mi-5\\": [unit, \\"fr\\"],
+ \\"t17gu1mi-7\\": [function (props) {
+ return 200;
+ }, \\"px\\"]
+ }
+});"
+`;
+
+exports[`handles interpolation followed by unit 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ font-size: var(--t17gu1mi-0);
+ text-shadow: black 1px var(--t17gu1mi-1), white -2px -2px;
+ margin: var(--t17gu1mi-2);
+ width: calc(2 * var(--t17gu1mi-3));
+ height: var(--t17gu1mi-4);
+ grid-template-columns: var(--t17gu1mi-5) 1fr 1fr var(--t17gu1mi-5);
+ border-radius: var(--t17gu1mi-7)
+}
+
+Dependencies: NA
+
+`;
+
+exports[`handles nested blocks 1`] = `
+"import { styled } from '@linaria/react';
+export const Button = /*#__PURE__*/styled(\\"button\\")({
+ name: \\"Button\\",
+ class: \\"b17gu1mi\\",
+ vars: {
+ \\"b17gu1mi-0\\": [regular]
+ }
+});"
+`;
+
+exports[`handles nested blocks 2`] = `
+
+CSS:
+
+.b17gu1mi {
+ font-family: var(--b17gu1mi-0);
+
+ &:hover {
+ border-color: blue;
+ }
+
+ @media (max-width: 200px) {
+ width: 100%;
+ }
+}
+
+Dependencies: NA
+
+`;
+
+exports[`handles objects with enums as keys 1`] = `
+"import { css } from '@linaria/core';
+import { TestEnum } from './ts-data.ts';
+export const object = {
+ [TestEnum.FirstValue]: \\"t17gu1mi\\",
+ [TestEnum.SecondValue]: \\"tccybe9\\"
+};"
+`;
+
+exports[`handles objects with enums as keys 2`] = `
+
+CSS:
+
+.t17gu1mi {}
+.tccybe9 {}
+
+Dependencies: NA
+
+`;
+
+exports[`handles objects with numeric keys 1`] = `
+"import { css } from '@linaria/core';
+export const object = {
+ stringKey: \\"s17gu1mi\\",
+ 42: \\"_ccybe9\\"
+};"
+`;
+
+exports[`handles objects with numeric keys 2`] = `
+
+CSS:
+
+.s17gu1mi {}
+._ccybe9 {}
+
+Dependencies: NA
+
+`;
+
+exports[`includes unreferenced styles for :global 1`] = `
+"import { css } from '@linaria/core';
+import { styled } from '@linaria/react';
+const a = \\"a17gu1mi\\";
+const B = /*#__PURE__*/styled(\\"div\\")({
+ name: \\"B\\",
+ class: \\"bccybe9\\"
+});"
+`;
+
+exports[`includes unreferenced styles for :global 2`] = `
+
+CSS:
+
+.a17gu1mi {
+ :global() {
+ .title {
+ font-size: 14px;
+ }
+ }
+}
+.bccybe9 {
+ :global(.title) {
+ font-size: 14px;
+ }
+}
+
+Dependencies: NA
+
+`;
+
+exports[`inlines array styles as CSS string 1`] = `
+"import { styled } from '@linaria/react';
+const styles = [{
+ flex: 1
+}, {
+ display: 'block',
+ height: 24
+}];
+export const Title = /*#__PURE__*/styled(\\"h1\\")({
+ name: \\"Title\\",
+ class: \\"t17gu1mi\\"
+});"
+`;
+
+exports[`inlines array styles as CSS string 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ flex: 1; display: block; height: 24px;
+}
+
+Dependencies: NA
+
+`;
+
+exports[`inlines object styles as CSS string 1`] = `
+"import { styled } from '@linaria/react';
+const cover = {
+ '--color-primaryText': '#222',
+ position: 'absolute',
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0,
+ opacity: 1,
+ minHeight: 420,
+ '&.shouldNotBeChanged': {
+ borderColor: '#fff'
+ },
+ '@media (min-width: 200px)': {
+ WebkitOpacity: .8,
+ MozOpacity: .8,
+ msOpacity: .8,
+ OOpacity: .8,
+ WebkitBorderRadius: 2,
+ MozBorderRadius: 2,
+ msBorderRadius: 2,
+ OBorderRadius: 2,
+ WebkitTransition: '400ms',
+ MozTransition: '400ms',
+ OTransition: '400ms',
+ msTransition: '400ms'
+ }
+};
+export const Title = /*#__PURE__*/styled(\\"h1\\")({
+ name: \\"Title\\",
+ class: \\"t17gu1mi\\"
+});"
+`;
+
+exports[`inlines object styles as CSS string 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ --color-primaryText: #222; position: absolute; top: 0; right: 0; bottom: 0; left: 0; opacity: 1; min-height: 420px; &.shouldNotBeChanged { border-color: #fff; } @media (min-width: 200px) { -webkit-opacity: 0.8; -moz-opacity: 0.8; -ms-opacity: 0.8; -o-opacity: 0.8; -webkit-border-radius: 2px; -moz-border-radius: 2px; -ms-border-radius: 2px; -o-border-radius: 2px; -webkit-transition: 400ms; -moz-transition: 400ms; -o-transition: 400ms; -ms-transition: 400ms; }
+}
+
+Dependencies: NA
+
+`;
+
+exports[`outputs valid CSS classname 1`] = `
+"import { styled } from '@linaria/react';
+export const ΩPage$Title = /*#__PURE__*/styled(\\"h1\\")({
+ name: \\"\\\\u03A9Page$Title\\",
+ class: \\"\\\\u03C917gu1mi\\"
+});"
+`;
+
+exports[`outputs valid CSS classname 2`] = `
+
+CSS:
+
+.ω17gu1mi {
+ font-size: 14px;
+}
+
+Dependencies: NA
+
+`;
+
+exports[`prevents class name collision 1`] = `
+"import { styled } from '@linaria/react';
+export const Title = /*#__PURE__*/styled(\\"h1\\")({
+ name: \\"Title\\",
+ class: \\"t17gu1mi\\",
+ vars: {
+ \\"t17gu1mi-0\\": [size, \\"px\\"],
+ \\"t17gu1mi-1\\": [props => props.color]
+ }
+});
+
+function Something() {
+ const Title = /*#__PURE__*/styled(\\"h1\\")({
+ name: \\"Title\\",
+ class: \\"tccybe9\\",
+ vars: {
+ \\"tccybe9-0\\": [regular]
+ }
+ });
+ return <Title />;
+}"
+`;
+
+exports[`prevents class name collision 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ font-size: var(--t17gu1mi-0);
+ color: var(--t17gu1mi-1)
+}
+.tccybe9 {
+ font-family: var(--tccybe9-0);
+ }
+
+Dependencies: NA
+
+`;
+
+exports[`replaces unknown expressions with CSS custom properties 1`] = `
+"import { styled } from '@linaria/react';
+export const Title = /*#__PURE__*/styled(\\"h1\\")({
+ name: \\"Title\\",
+ class: \\"t17gu1mi\\",
+ vars: {
+ \\"t17gu1mi-0\\": [size, \\"px\\"],
+ \\"t17gu1mi-1\\": [props => props.color]
+ }
+});"
+`;
+
+exports[`replaces unknown expressions with CSS custom properties 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ font-size: var(--t17gu1mi-0);
+ color: var(--t17gu1mi-1);
+}
+
+Dependencies: NA
+
+`;
+
+exports[`supports both css and styled tags 1`] = `
+"import { css } from '@linaria/core';
+import { styled } from '@linaria/react';
+export const Title = /*#__PURE__*/styled(\\"h1\\")({
+ name: \\"Title\\",
+ class: \\"t17gu1mi\\"
+});
+export const title = \\"tccybe9\\";"
+`;
+
+exports[`supports both css and styled tags 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ font-size: 14px;
+}
+.tccybe9 {
+ color: blue;
+}
+
+Dependencies: NA
+
+`;
+
+exports[`throws when contains dynamic expression without evaluate: true in css tag 1`] = `
+"<<DIRNAME>>/app/index.js: The CSS cannot contain JavaScript expressions when using the 'css' tag. To evaluate the expressions at build time, pass 'evaluate: true' to the babel plugin.
+ 2 |
+ 3 | const title = css\`
+> 4 | font-size: \${size}px;
+ | ^^^^
+ 5 | \`;"
+`;
+
+exports[`transpiles css template literal 1`] = `
+"import { css } from '@linaria/core';
+export const title = \\"t17gu1mi\\";"
+`;
+
+exports[`transpiles css template literal 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ font-size: 14px;
+}
+
+Dependencies: NA
+
+`;
+
+exports[`transpiles renamed styled import 1`] = `
+"import { styled as custom } from '@linaria/react';
+export const Title = /*#__PURE__*/custom('h1')({
+ name: \\"Title\\",
+ class: \\"t17gu1mi\\"
+});"
+`;
+
+exports[`transpiles renamed styled import 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ font-size: 14px;
+}
+
+Dependencies: NA
+
+`;
+
+exports[`transpiles styled template literal with function and component 1`] = `
+"import { styled } from '@linaria/react';
+
+const Heading = () => null;
+
+export const Title = /*#__PURE__*/styled(Heading)({
+ name: \\"Title\\",
+ class: \\"t17gu1mi\\"
+});"
+`;
+
+exports[`transpiles styled template literal with function and component 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ font-size: 14px;
+}
+
+Dependencies: NA
+
+`;
+
+exports[`transpiles styled template literal with function and tag 1`] = `
+"import { styled } from '@linaria/react';
+export const Title = /*#__PURE__*/styled('h1')({
+ name: \\"Title\\",
+ class: \\"t17gu1mi\\"
+});"
+`;
+
+exports[`transpiles styled template literal with function and tag 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ font-size: 14px;
+}
+
+Dependencies: NA
+
+`;
+
+exports[`transpiles styled template literal with object 1`] = `
+"import { styled } from '@linaria/react';
+export const Title = /*#__PURE__*/styled(\\"h1\\")({
+ name: \\"Title\\",
+ class: \\"t17gu1mi\\"
+});"
+`;
+
+exports[`transpiles styled template literal with object 2`] = `
+
+CSS:
+
+.t17gu1mi {
+ font-size: 14px;
+}
+
+Dependencies: NA
+
+`;
+
+exports[`uses string passed in as classNameSlug 1`] = `
+"import { styled } from '@linaria/react';
+export const Title = /*#__PURE__*/styled('h1')({
+ name: \\"Title\\",
+ class: \\"testSlug\\"
+});"
+`;
+
+exports[`uses string passed in as classNameSlug 2`] = `
+
+CSS:
+
+.testSlug {
+ font-size: 14px;
+}
+
+Dependencies: NA
+
+`;
+
+exports[`uses the same custom property for the same expression 1`] = `
+"import { styled } from '@linaria/react';
+export const Box = /*#__PURE__*/styled(\\"div\\")({
+ name: \\"Box\\",
+ class: \\"b17gu1mi\\",
+ vars: {
+ \\"b17gu1mi-0\\": [props => props.size, \\"px\\"]
+ }
+});"
+`;
+
+exports[`uses the same custom property for the same expression 2`] = `
+
+CSS:
+
+.b17gu1mi {
+ height: var(--b17gu1mi-0);
+ width: var(--b17gu1mi-0);
+}
+
+Dependencies: NA
+
+`;
+
+exports[`uses the same custom property for the same identifier 1`] = `
+"import { styled } from '@linaria/react';
+export const Box = /*#__PURE__*/styled(\\"div\\")({
+ name: \\"Box\\",
+ class: \\"b17gu1mi\\",
+ vars: {
+ \\"b17gu1mi-0\\": [size, \\"px\\"]
+ }
+});"
+`;
+
+exports[`uses the same custom property for the same identifier 2`] = `
+
+CSS:
+
+.b17gu1mi {
+ height: var(--b17gu1mi-0);
+ width: var(--b17gu1mi-0);
+}
+
+Dependencies: NA
+
+`;
diff --git a/@linaria/packages/babel/__tests__/__snapshots__/dynamic-import-noop.test.js.snap b/@linaria/packages/babel/__tests__/__snapshots__/dynamic-import-noop.test.js.snap
new file mode 100644
index 0000000..02d59d1
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/__snapshots__/dynamic-import-noop.test.js.snap
@@ -0,0 +1,8 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`replaces dynamic imports with a noop 1`] = `
+"({
+ then: () => undefined,
+ catch: () => undefined
+}).then(foo => foo.init());"
+`;
diff --git a/@linaria/packages/babel/__tests__/__snapshots__/transform.test.ts.snap b/@linaria/packages/babel/__tests__/__snapshots__/transform.test.ts.snap
new file mode 100644
index 0000000..77636ca
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/__snapshots__/transform.test.ts.snap
@@ -0,0 +1,16 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`doesn't rewrite an absolute path in url() declarations 1`] = `
+".tpyglzj{background-image:url(/assets/test.jpg);}
+"
+`;
+
+exports[`rewrites a relative path in url() declarations 1`] = `
+".tpyglzj{background-image:url(../assets/test.jpg);background-image:url(\\"../assets/test.jpg\\");background-image:url('../assets/test.jpg');}
+"
+`;
+
+exports[`rewrites multiple relative paths in url() declarations 1`] = `
+"@font-face{font-family:Test;src:url(../assets/font.woff2) format(\\"woff2\\"),url(../assets/font.woff) format(\\"woff\\");}
+"
+`;
diff --git a/@linaria/packages/babel/__tests__/babel.test.ts b/@linaria/packages/babel/__tests__/babel.test.ts
new file mode 100644
index 0000000..99cc816
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/babel.test.ts
@@ -0,0 +1,524 @@
+import { join } from 'path';
+import { transformAsync } from '@babel/core';
+import dedent from 'dedent';
+import stripAnsi from 'strip-ansi';
+import type { StrictOptions } from '../src';
+import serializer from '../__utils__/linaria-snapshot-serializer';
+
+expect.addSnapshotSerializer(serializer);
+
+const transpile = async (
+ input: string,
+ opts: Partial<StrictOptions> = { evaluate: false }
+) =>
+ (await transformAsync(input, {
+ babelrc: false,
+ presets: [[require.resolve('../src'), opts]],
+ plugins: ['@babel/plugin-syntax-jsx'],
+ filename: join(__dirname, 'app/index.js'),
+ configFile: false,
+ }))!;
+
+it('transpiles styled template literal with object', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ export const Title = styled.h1\`
+ font-size: 14px;
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('uses string passed in as classNameSlug', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ export const Title = styled('h1')\`
+ font-size: 14px;
+ \`;
+`,
+ { classNameSlug: 'testSlug' }
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('handles fn passed in as classNameSlug', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ export const Title = styled('h1')\`
+ font-size: 14px;
+ \`;
+`,
+ {
+ classNameSlug: (hash, title) => {
+ return `${hash}_${7 * 6}_${title}`;
+ },
+ }
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('transpiles styled template literal with function and tag', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ export const Title = styled('h1')\`
+ font-size: 14px;
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('transpiles renamed styled import', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled as custom } from '@linaria/react';
+
+ export const Title = custom('h1')\`
+ font-size: 14px;
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('transpiles styled template literal with function and component', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+ const Heading = () => null;
+
+ export const Title = styled(Heading)\`
+ font-size: 14px;
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('outputs valid CSS classname', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ export const ΩPage$Title = styled.h1\`
+ font-size: 14px;
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('evaluates and inlines expressions in scope', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ const color = 'blue';
+
+ export const Title = styled.h1\`
+ color: ${'${color}'};
+ width: ${'${100 / 3}'}%;
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('inlines object styles as CSS string', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ const cover = {
+ '--color-primaryText': '#222',
+ position: 'absolute',
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0,
+ opacity: 1,
+ minHeight: 420,
+
+ '&.shouldNotBeChanged': {
+ borderColor: '#fff',
+ },
+
+ '@media (min-width: 200px)': {
+ WebkitOpacity: .8,
+ MozOpacity: .8,
+ msOpacity: .8,
+ OOpacity: .8,
+ WebkitBorderRadius: 2,
+ MozBorderRadius: 2,
+ msBorderRadius: 2,
+ OBorderRadius: 2,
+ WebkitTransition: '400ms',
+ MozTransition: '400ms',
+ OTransition: '400ms',
+ msTransition: '400ms',
+ }
+ };
+
+ export const Title = styled.h1\`
+ ${'${cover}'}
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('inlines array styles as CSS string', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ const styles = [
+ { flex: 1 },
+ { display: 'block', height: 24 },
+ ];
+
+ export const Title = styled.h1\`
+ ${'${styles}'}
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('replaces unknown expressions with CSS custom properties', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ export const Title = styled.h1\`
+ font-size: ${'${size}'}px;
+ color: ${'${props => props.color}'};
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('handles interpolation followed by unit', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ export const Title = styled.h1\`
+ font-size: ${'${size}'}em;
+ text-shadow: black 1px ${'${shadow}'}px, white -2px -2px;
+ margin: ${'${size}'}px;
+ width: calc(2 * ${'${props => props.width}'}vw);
+ height: ${'${props => { if (true) { return props.height } else { return 200 } }}'}px;
+ grid-template-columns: ${'${unit}'}fr 1fr 1fr ${'${unit}'}fr;
+ border-radius: ${'${function(props) { return 200 }}'}px
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('uses the same custom property for the same identifier', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ export const Box = styled.div\`
+ height: ${'${size}'}px;
+ width: ${'${size}'}px;
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('uses the same custom property for the same expression', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ export const Box = styled.div\`
+ height: ${'${props => props.size}'}px;
+ width: ${'${props => props.size}'}px;
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('handles nested blocks', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ export const Button = styled.button\`
+ font-family: ${'${regular}'};
+
+ &:hover {
+ border-color: blue;
+ }
+
+ @media (max-width: 200px) {
+ width: 100%;
+ }
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('prevents class name collision', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ export const Title = styled.h1\`
+ font-size: ${'${size}'}px;
+ color: ${'${props => props.color}'}
+ \`;
+
+ function Something() {
+ const Title = styled.h1\`
+ font-family: ${'${regular}'};
+ \`;
+
+ return <Title />;
+ }
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('does not output CSS if none present', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ const number = 42;
+
+ const title = String.raw\`This is something\`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('does not output CSS property when value is a blank string', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { css } from '@linaria/core';
+
+ export const title = css\`
+ font-size: ${''};
+ margin: 6px;
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('transpiles css template literal', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { css } from '@linaria/core';
+
+ export const title = css\`
+ font-size: 14px;
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('handles css template literal in object property', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { css } from '@linaria/core';
+
+ const components = {
+ title: css\`
+ font-size: 14px;
+ \`
+ };
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('handles css template literal in JSX element', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { css } from '@linaria/core';
+
+ <Title class={css\` font-size: 14px; \`} />
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('throws when contains dynamic expression without evaluate: true in css tag', async () => {
+ expect.assertions(1);
+
+ try {
+ await transpile(
+ dedent`
+ import { css } from '@linaria/core';
+
+ const title = css\`
+ font-size: ${'${size}'}px;
+ \`;
+ `
+ );
+ } catch (e) {
+ expect(
+ stripAnsi(e.message.replace(__dirname, '<<DIRNAME>>'))
+ ).toMatchSnapshot();
+ }
+});
+
+it('supports both css and styled tags', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { css } from '@linaria/core';
+ import { styled } from '@linaria/react';
+
+ export const Title = styled.h1\`
+ font-size: 14px;
+ \`;
+
+ export const title = css\`
+ color: blue;
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('does not include styles if not referenced anywhere', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { css } from '@linaria/core';
+ import { styled } from '@linaria/react';
+
+ const Title = styled.h1\`
+ font-size: 14px;
+ \`;
+
+ const title = css\`
+ color: blue;
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('includes unreferenced styles for :global', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { css } from '@linaria/core';
+ import { styled } from '@linaria/react';
+
+ const a = css\`
+ :global() {
+ .title {
+ font-size: 14px;
+ }
+ }
+ \`;
+
+ const B = styled.div\`
+ :global(.title) {
+ font-size: 14px;
+ }
+ \`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('handles objects with numeric keys', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { css } from '@linaria/core';
+
+ export const object = {
+ stringKey: css\`\`,
+ 42: css\`\`,
+ }
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
+
+it('handles objects with enums as keys', async () => {
+ const { code, metadata } = await transpile(
+ dedent`
+ import { css } from '@linaria/core';
+ import { TestEnum } from './ts-data.ts';
+
+ export const object = {
+ [TestEnum.FirstValue]: css\`\`,
+ [TestEnum.SecondValue]: css\`\`,
+ }
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+ expect(metadata).toMatchSnapshot();
+});
diff --git a/@linaria/packages/babel/__tests__/depsGraph.test.ts b/@linaria/packages/babel/__tests__/depsGraph.test.ts
new file mode 100644
index 0000000..000bd30
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/depsGraph.test.ts
@@ -0,0 +1,356 @@
+/* eslint-disable no-template-curly-in-string */
+
+import dedent from 'dedent';
+import * as babel from '@babel/core';
+import buildDepsGraph from '../../shaker/src/graphBuilder';
+
+function _build(literal: TemplateStringsArray, ...placeholders: string[]) {
+ const code = dedent(literal, ...placeholders);
+ return {
+ ast: babel.parseSync(code, { filename: 'source.js' })!,
+ code,
+ };
+}
+
+function _buildGraph(literal: TemplateStringsArray, ...placeholders: string[]) {
+ const { ast } = _build(literal, ...placeholders);
+ return buildDepsGraph(ast);
+}
+
+describe('VariableDeclaration', () => {
+ it('Identifier', () => {
+ const graph = _buildGraph`
+ const a = 42;
+ `;
+
+ const deps = graph.getDependenciesByBinding('0:a');
+ expect(deps).toMatchObject([
+ {
+ type: 'VariableDeclarator',
+ },
+ {
+ type: 'NumericLiteral',
+ value: 42,
+ },
+ ]);
+
+ expect(graph.findDependencies({ name: 'a' })).toContainEqual(deps[0]);
+ });
+});
+
+describe('scopes', () => {
+ it('BlockStatement', () => {
+ const graph = _buildGraph`
+ let a = 42;
+ {
+ const a = "21";
+ }
+
+ a = 21;
+ `;
+
+ const deps0 = graph.getDependenciesByBinding('0:a');
+ expect(deps0).toMatchObject([
+ {
+ type: 'VariableDeclarator',
+ },
+ {
+ type: 'NumericLiteral',
+ value: 42,
+ },
+ {
+ type: 'Identifier',
+ start: 4,
+ },
+ {
+ type: 'Identifier',
+ start: 35,
+ },
+ {
+ type: 'AssignmentExpression',
+ },
+ {
+ type: 'NumericLiteral',
+ value: 21,
+ },
+ ]);
+
+ const deps1 = graph.getDependenciesByBinding('1:a');
+ expect(deps1).toMatchObject([
+ {
+ type: 'VariableDeclarator',
+ },
+ {
+ type: 'StringLiteral',
+ value: '21',
+ },
+ ]);
+
+ expect(graph.findDependencies({ name: 'a' })).toHaveLength(8);
+ });
+
+ it('Function', () => {
+ const graph = _buildGraph`
+ const a = (arg1, arg2, arg3) => arg1 + arg2 + arg3;
+ `;
+
+ const aDeps = graph.getDependenciesByBinding('0:a');
+ expect(aDeps).toMatchObject([
+ {
+ type: 'VariableDeclarator',
+ },
+ {
+ type: 'ArrowFunctionExpression',
+ },
+ ]);
+
+ expect(graph.getDependenciesByBinding('1:arg1')).toHaveLength(3);
+ expect(graph.getDependentsByBinding('1:arg1')).toMatchObject([
+ {
+ // arg1 in the binary expression
+ type: 'Identifier',
+ name: 'arg1',
+ start: 32,
+ },
+ {
+ // arg1 + arg2
+ type: 'BinaryExpression',
+ right: {
+ name: 'arg2',
+ },
+ },
+ {
+ // (arg1 + arg2) + arg3, because of it is the whole function body
+ type: 'BinaryExpression',
+ right: {
+ name: 'arg3',
+ },
+ },
+ ]);
+
+ expect(graph.getDependenciesByBinding('1:arg2')).toMatchObject([
+ {
+ type: 'ArrowFunctionExpression',
+ },
+ {
+ type: 'Identifier',
+ name: 'arg2',
+ start: 17,
+ },
+ {
+ type: 'BinaryExpression',
+ start: 32,
+ },
+ ]);
+
+ expect(graph.getDependenciesByBinding('1:arg3')).toMatchObject([
+ {
+ type: 'ArrowFunctionExpression',
+ },
+ {
+ type: 'Identifier',
+ name: 'arg3',
+ start: 23,
+ },
+ {
+ type: 'BinaryExpression',
+ start: 32,
+ },
+ ]);
+ });
+});
+
+describe('AssignmentExpression', () => {
+ it('Identifier', () => {
+ const graph = _buildGraph`
+ let a = 42;
+ a = 24;
+ `;
+
+ const deps = graph.getDependenciesByBinding('0:a');
+ expect(deps).toMatchObject([
+ {
+ type: 'VariableDeclarator',
+ },
+ {
+ type: 'NumericLiteral',
+ value: 42,
+ },
+ {
+ type: 'Identifier',
+ name: 'a',
+ start: 4,
+ },
+ {
+ type: 'Identifier',
+ name: 'a',
+ start: 12,
+ },
+ {
+ type: 'AssignmentExpression',
+ },
+ {
+ type: 'NumericLiteral',
+ value: 24,
+ },
+ ]);
+
+ expect(graph.findDependents({ value: 42 })).toHaveLength(1);
+ expect(graph.findDependents({ value: 24 })).toHaveLength(1);
+ });
+
+ it('MemberExpression', () => {
+ const graph = _buildGraph`
+ const a = {};
+ a.foo.bar = 42;
+ `;
+
+ expect(graph.getDependenciesByBinding('0:a')).toMatchObject([
+ {
+ type: 'VariableDeclarator',
+ },
+ {
+ type: 'ObjectExpression',
+ properties: [],
+ },
+ {
+ type: 'Identifier',
+ name: 'a',
+ start: 6,
+ },
+ {
+ type: 'Identifier',
+ name: 'a',
+ start: 14,
+ },
+ {
+ type: 'MemberExpression',
+ property: {
+ name: 'foo',
+ },
+ },
+ ]);
+
+ expect(graph.findDependents({ value: 42 })).toMatchObject([
+ {
+ type: 'MemberExpression',
+ property: {
+ name: 'bar',
+ },
+ },
+ ]);
+ });
+});
+
+it('SequenceExpression', () => {
+ const graph = _buildGraph`
+ const color1 = 'blue';
+ let local = '';
+ const color2 = (true, local = color1, () => local);
+ `;
+
+ const seqDeps = graph.findDependencies({
+ type: 'SequenceExpression',
+ });
+ expect(seqDeps).toMatchObject([
+ {
+ type: 'ArrowFunctionExpression',
+ },
+ {
+ id: {
+ name: 'color2',
+ },
+ type: 'VariableDeclarator',
+ },
+ ]);
+
+ const fnDeps = graph.findDependencies({
+ type: 'ArrowFunctionExpression',
+ });
+ expect(fnDeps).toMatchObject([
+ {
+ body: {
+ name: 'local',
+ type: 'Identifier',
+ },
+
+ type: 'ArrowFunctionExpression',
+ },
+ {
+ name: 'local',
+ type: 'Identifier',
+ },
+ {
+ type: 'SequenceExpression',
+ },
+ ]);
+
+ const localDeps = graph.getDependenciesByBinding('0:local');
+ expect(localDeps).toMatchObject([
+ {
+ type: 'VariableDeclarator',
+ },
+ {
+ type: 'StringLiteral',
+ value: '',
+ },
+ {
+ type: 'Identifier',
+ name: 'local',
+ start: 27,
+ },
+ {
+ type: 'Identifier',
+ name: 'local',
+ start: 61,
+ },
+ {
+ type: 'AssignmentExpression',
+ },
+ {
+ type: 'Identifier',
+ name: 'color1',
+ },
+ {
+ type: 'Identifier',
+ name: 'local',
+ start: 27,
+ },
+ {
+ type: 'ArrowFunctionExpression',
+ },
+ ]);
+
+ const bool = { type: 'BooleanLiteral' };
+ expect(graph.findDependents(bool)).toHaveLength(0);
+ expect(graph.findDependencies(bool)).toHaveLength(1);
+});
+
+it('MemberExpression', () => {
+ const graph = _buildGraph`
+ const key = 'blue';
+ const obj = { blue: '#00F' };
+ const blue = obj[key];
+ `;
+
+ const memberExprDeps = graph.findDependencies({
+ type: 'MemberExpression',
+ });
+
+ expect(memberExprDeps).toMatchObject([
+ {
+ type: 'Identifier',
+ name: 'obj',
+ },
+ {
+ type: 'Identifier',
+ name: 'key',
+ },
+ {
+ type: 'VariableDeclarator',
+ id: {
+ name: 'blue',
+ },
+ },
+ ]);
+});
diff --git a/@linaria/packages/babel/__tests__/dynamic-import-noop.test.js b/@linaria/packages/babel/__tests__/dynamic-import-noop.test.js
new file mode 100644
index 0000000..04f0452
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/dynamic-import-noop.test.js
@@ -0,0 +1,15 @@
+import { transformAsync } from '@babel/core';
+
+it('replaces dynamic imports with a noop', async () => {
+ const { code } = await transformAsync(
+ `import('./foo').then(foo => foo.init())`,
+ {
+ plugins: [require.resolve('../src/dynamic-import-noop')],
+ filename: 'source.js',
+ configFile: false,
+ babelrc: false,
+ }
+ );
+
+ expect(code).toMatchSnapshot();
+});
diff --git a/@linaria/packages/babel/__tests__/evaluators/__snapshots__/extractor.test.ts.snap b/@linaria/packages/babel/__tests__/evaluators/__snapshots__/extractor.test.ts.snap
new file mode 100644
index 0000000..7ee2296
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/evaluators/__snapshots__/extractor.test.ts.snap
@@ -0,0 +1,237 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`keeps objects as is 1`] = `
+"{
+ const fill1 = (top = 0, left = 0, right = 0, bottom = 0) => ({
+ position: 'absolute',
+ top,
+ right,
+ bottom,
+ left
+ });
+
+ {
+ const fill2 = (top = 0, left = 0, right = 0, bottom = 0) => {
+ return {
+ position: 'absolute',
+ top,
+ right,
+ bottom,
+ left
+ };
+ };
+
+ {
+ exports.__linariaPreval = [fill1, fill2];
+ }
+ }
+}"
+`;
+
+exports[`keeps only code which is related to \`a\` 1`] = `
+"{
+ const {
+ whiteColor: color,
+ anotherColor
+ } = require('…');
+
+ {
+ const a = color || anotherColor;
+ {
+ exports.__linariaPreval = [a];
+ }
+ }
+}"
+`;
+
+exports[`keeps only code which is related to \`anotherColor\` 1`] = `
+"{
+ const {
+ whiteColor: color,
+ anotherColor
+ } = require('…');
+
+ {
+ exports.__linariaPreval = [anotherColor];
+ }
+}"
+`;
+
+exports[`keeps only code which is related to \`color\` 1`] = `
+"{
+ const {
+ whiteColor: color,
+ anotherColor
+ } = require('…');
+
+ {
+ exports.__linariaPreval = [color];
+ }
+}"
+`;
+
+exports[`removes all 1`] = `
+"{
+ exports.__linariaPreval = [];
+}"
+`;
+
+exports[`shakes assignment patterns 1`] = `
+"{
+ const [identifier = 1] = [2];
+ {
+ const [{ ...object
+ } = {}] = [{
+ a: 1,
+ b: 2
+ }];
+ {
+ const [[...array] = []] = [[1, 2, 3, 4]];
+ {
+ const obj = {
+ member: null
+ };
+ {
+ exports.__linariaPreval = [identifier, object, array, obj];
+ }
+ }
+ }
+ }
+}"
+`;
+
+exports[`shakes es5 exports 1`] = `
+"\\"use strict\\";
+
+Object.defineProperty(exports, \\"__esModule\\", {
+ value: true
+});
+exports.redColor = 'red';
+Object.defineProperty(exports, \\"blueColor\\", {
+ enumerable: true,
+ get: function get() {
+ return 'blue';
+ }
+});
+Object.defineProperty(exports, \\"greenColor\\", {
+ enumerable: true,
+ get: function get() {
+ return 'green';
+ }
+});"
+`;
+
+exports[`shakes exports 1`] = `
+"{
+ var _ = require(\\"\\\\u2026\\");
+
+ {
+ const a = _.whiteColor;
+ {
+ exports.__linariaPreval = [a];
+ }
+ }
+}"
+`;
+
+exports[`shakes imports 1`] = `
+"{
+ var _ = _interopRequireWildcard(require(\\"\\\\u2026\\"));
+
+ {
+ function _getRequireWildcardCache(nodeInterop) {
+ if (typeof WeakMap !== \\"function\\") return null;
+ var cacheBabelInterop = new WeakMap();
+ var cacheNodeInterop = new WeakMap();
+ return (_getRequireWildcardCache = function (nodeInterop) {
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
+ })(nodeInterop);
+ }
+
+ {
+ function _interopRequireWildcard(obj, nodeInterop) {
+ if (!nodeInterop && obj && obj.__esModule) {
+ return obj;
+ }
+
+ if (obj === null || typeof obj !== \\"object\\" && typeof obj !== \\"function\\") {
+ return {
+ default: obj
+ };
+ }
+
+ var cache = _getRequireWildcardCache(nodeInterop);
+
+ if (cache && cache.has(obj)) {
+ return cache.get(obj);
+ }
+
+ var newObj = {};
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
+
+ for (var key in obj) {
+ if (key !== \\"default\\" && Object.prototype.hasOwnProperty.call(obj, key)) {
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
+
+ if (desc && (desc.get || desc.set)) {
+ Object.defineProperty(newObj, key, desc);
+ } else {
+ newObj[key] = obj[key];
+ }
+ }
+ }
+
+ newObj.default = obj;
+
+ if (cache) {
+ cache.set(obj, newObj);
+ }
+
+ return newObj;
+ }
+
+ {
+ exports.__linariaPreval = [_.whiteColor, _.default];
+ }
+ }
+ }
+}"
+`;
+
+exports[`shakes sequence expression 1`] = `
+"{
+ var _ = require(\\"\\\\u2026\\");
+
+ {
+ const color1 = () => 'blue';
+
+ {
+ let local = '';
+ {
+ const color2 = () => local;
+
+ {
+ exports.__linariaPreval = [color2];
+ }
+ }
+ }
+ }
+}"
+`;
+
+exports[`should keep member expression key 1`] = `
+"{
+ const key = 'blue';
+ {
+ const obj = {
+ blue: '#00F'
+ };
+ {
+ const blue = obj[key];
+ {
+ exports.__linariaPreval = [blue];
+ }
+ }
+ }
+}"
+`;
diff --git a/@linaria/packages/babel/__tests__/evaluators/__snapshots__/preeval.test.ts.snap b/@linaria/packages/babel/__tests__/evaluators/__snapshots__/preeval.test.ts.snap
new file mode 100644
index 0000000..93660fa
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/evaluators/__snapshots__/preeval.test.ts.snap
@@ -0,0 +1,60 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`handles locally named import 1`] = `
+"import { styled as custom } from '@linaria/react';
+const Component =
+/*linaria styled ci72cjv Component Component_ci72cjv*/
+custom.div\`\`;"
+`;
+
+exports[`hoists exports 1`] = `
+"\\"use strict\\";
+
+var _foo = require(\\"./foo\\");
+
+Object.defineProperty(exports, \\"__esModule\\", {
+ value: true
+});
+Object.defineProperty(exports, \\"foo\\", {
+ enumerable: true,
+ get: function get() {
+ return _foo.foo;
+ }
+});
+Object.defineProperty(exports, \\"bar\\", {
+ enumerable: true,
+ get: function get() {
+ return _foo.bar;
+ }
+});"
+`;
+
+exports[`preserves classNames 1`] = `
+"import { styled } from '@linaria/react';
+const Component =
+/*linaria styled ci72cjv Component Component_ci72cjv*/
+styled.div\`\`;"
+`;
+
+exports[`replaces class component 1`] = `
+"import React from 'react';
+
+function Component() {
+ return <></>;
+}"
+`;
+
+exports[`replaces constant 1`] = `
+"import React from 'react';
+const tag = <></>;
+
+const Component = props => tag;"
+`;
+
+exports[`replaces functional component 1`] = `
+"import React from 'react';
+
+const Component = () => {
+ return <></>;
+};"
+`;
diff --git a/@linaria/packages/babel/__tests__/evaluators/__snapshots__/shaker.test.ts.snap b/@linaria/packages/babel/__tests__/evaluators/__snapshots__/shaker.test.ts.snap
new file mode 100644
index 0000000..92b9b9e
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/evaluators/__snapshots__/shaker.test.ts.snap
@@ -0,0 +1,213 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`keeps objects as is 1`] = `
+"\\"use strict\\";
+
+var fill1 = function fill1() {
+ var top = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
+ var left = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
+ var right = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
+ var bottom = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
+ return {
+ position: 'absolute',
+ top: top,
+ right: right,
+ bottom: bottom,
+ left: left
+ };
+};
+
+var fill2 = function fill2() {
+ var top = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
+ var left = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
+ var right = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
+ var bottom = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
+ return {
+ position: 'absolute',
+ top: top,
+ right: right,
+ bottom: bottom,
+ left: left
+ };
+};
+
+exports.__linariaPreval = [fill1, fill2];"
+`;
+
+exports[`keeps only code which is related to \`a\` 1`] = `
+"\\"use strict\\";
+
+var _require = require('…'),
+ color = _require.whiteColor,
+ anotherColor = _require.anotherColor;
+
+var a = color || anotherColor;
+color.green = '#0f0';
+exports.__linariaPreval = [a];"
+`;
+
+exports[`keeps only code which is related to \`anotherColor\` 1`] = `
+"\\"use strict\\";
+
+var _require = require('…'),
+ anotherColor = _require.anotherColor;
+
+exports.__linariaPreval = [anotherColor];"
+`;
+
+exports[`keeps only code which is related to \`color\` 1`] = `
+"\\"use strict\\";
+
+var _require = require('…'),
+ color = _require.whiteColor;
+
+color.green = '#0f0';
+exports.__linariaPreval = [color];"
+`;
+
+exports[`keeps only the last assignment of each exported variable 1`] = `
+"\\"use strict\\";
+
+var bar = function bar() {
+ return 'hello world';
+};
+
+exports.bar = bar;
+var foo = exports.bar();
+exports.__linariaPreval = [foo];"
+`;
+
+exports[`keeps reused exports 1`] = `
+"\\"use strict\\";
+
+var bar = function bar() {
+ return 'hello world';
+};
+
+exports.bar = bar;
+var foo = exports.bar();
+exports.__linariaPreval = [foo];"
+`;
+
+exports[`removes all 1`] = `
+"\\"use strict\\";
+
+exports.__linariaPreval = [];"
+`;
+
+exports[`shakes assignment patterns 1`] = `
+"\\"use strict\\";
+
+var _interopRequireDefault = require(\\"@babel/runtime/helpers/interopRequireDefault\\");
+
+var _toArray2 = _interopRequireDefault(require(\\"@babel/runtime/helpers/toArray\\"));
+
+var _extends2 = _interopRequireDefault(require(\\"@babel/runtime/helpers/extends\\"));
+
+var _ = 2,
+ identifier = _ === void 0 ? 1 : _;
+var _a$b = {
+ a: 1,
+ b: 2
+};
+_a$b = _a$b === void 0 ? {} : _a$b;
+var object = (_extends2.default)({}, _a$b);
+var _ref = [1, 2, 3, 4];
+_ref = _ref === void 0 ? [] : _ref;
+
+var _ref2 = (_toArray2.default)(_ref),
+ array = _ref2.slice(0);
+
+var obj = {
+ member: null
+};
+var _2 = 1;
+obj.member = _2 === void 0 ? 42 : _2;
+exports.__linariaPreval = [identifier, object, array, obj];"
+`;
+
+exports[`shakes es5 exports 1`] = `
+"\\"use strict\\";
+
+Object.defineProperty(exports, \\"__esModule\\", {
+ value: true
+});
+exports.redColor = 'red';
+exports['yellowColor'] = 'yellow';
+Object.defineProperty(exports, \\"greenColor\\", {
+ enumerable: true,
+ get: function get() {
+ return 'green';
+ }
+});"
+`;
+
+exports[`shakes exports 1`] = `
+"\\"use strict\\";
+
+var _ = require(\\"\\\\u2026\\");
+
+Object.defineProperty(exports, \\"__esModule\\", {
+ value: true
+});
+var a = _.whiteColor;
+exports.__linariaPreval = [a];"
+`;
+
+exports[`shakes for-in statements 1`] = `
+"\\"use strict\\";
+
+var obj1 = {
+ a: 1,
+ b: 2
+};
+var obj2 = {};
+
+for (var key in obj1) {
+ obj2[key] = obj1[key];
+}
+
+exports.__linariaPreval = [obj2];"
+`;
+
+exports[`shakes imports 1`] = `
+"\\"use strict\\";
+
+var _typeof = require(\\"@babel/runtime/helpers/typeof\\");
+
+Object.defineProperty(exports, \\"__esModule\\", {
+ value: true
+});
+
+var _ = _interopRequireWildcard(require(\\"\\\\u2026\\"));
+
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== \\"function\\") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== \\"object\\" && typeof obj !== \\"function\\") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== \\"default\\" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
+
+exports.__linariaPreval = [_.whiteColor, _.default];"
+`;
+
+exports[`shakes sequence expression 1`] = `
+"\\"use strict\\";
+
+var color1 = (function () {
+ return 'blue';
+});
+var local = '';
+var color2 = (local = color1(), function () {
+ return local;
+});
+exports.__linariaPreval = [color2];"
+`;
+
+exports[`should keep member expression key 1`] = `
+"\\"use strict\\";
+
+var key = 'blue';
+var obj = {
+ blue: '#00F'
+};
+var blue = obj[key];
+exports.__linariaPreval = [blue];"
+`;
diff --git a/@linaria/packages/babel/__tests__/evaluators/extractor.test.ts b/@linaria/packages/babel/__tests__/evaluators/extractor.test.ts
new file mode 100644
index 0000000..b764a25
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/evaluators/extractor.test.ts
@@ -0,0 +1,214 @@
+import path from 'path';
+import dedent from 'dedent';
+import type { TransformOptions } from '@babel/core';
+import exctract from '../../../extractor/src';
+
+function getFileName() {
+ return path.resolve(__dirname, `../__fixtures__/test.js`);
+}
+
+function _shake(opts?: TransformOptions, only: string[] = ['__linariaPreval']) {
+ return (
+ literal: TemplateStringsArray,
+ ...placeholders: string[]
+ ): [string, Map<string, string[]>] => {
+ const code = dedent(literal, ...placeholders);
+ const [shaken, deps] = exctract(
+ getFileName(),
+ {
+ babelOptions: opts || {},
+ displayName: true,
+ evaluate: true,
+ rules: [
+ {
+ action: require('../../../extractor/src').default,
+ },
+ {
+ test: /\/node_modules\//,
+ action: 'ignore',
+ },
+ ],
+ },
+ code,
+ only
+ );
+
+ return [shaken, deps!];
+ };
+}
+
+it('removes all', () => {
+ const [shaken] = _shake()`
+ const { whiteColor: color, anotherColor } = require('…');
+ const a = color || anotherColor;
+ color.green = '#0f0';
+
+ exports.__linariaPreval = [];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('keeps only code which is related to `color`', () => {
+ const [shaken] = _shake()`
+ const { whiteColor: color, anotherColor } = require('…');
+ const wrap = '';
+ const a = color || anotherColor;
+ color.green = '#0f0';
+ module.exports = { color, anotherColor };
+ exports.__linariaPreval = [color];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('keeps only code which is related to `anotherColor`', () => {
+ const [shaken] = _shake()`
+ const { whiteColor: color, anotherColor } = require('…');
+ const a = color || anotherColor;
+ color.green = '#0f0';
+ exports.__linariaPreval = [anotherColor];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('keeps only code which is related to `a`', () => {
+ const [shaken] = _shake()`
+ const { whiteColor: color, anotherColor } = require('…');
+ const a = color || anotherColor;
+ color.green = '#0f0';
+ exports.__linariaPreval = [a];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('shakes imports', () => {
+ const [shaken] = _shake()`
+ import { unrelatedImport } from '…';
+ import { whiteColor as color, anotherColor } from '…';
+ import defaultColor from '…';
+ import anotherDefaultColor from '…';
+ import '…';
+ require('…');
+ export default color;
+ exports.__linariaPreval = [color, defaultColor];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('should keep member expression key', () => {
+ const [shaken] = _shake()`
+ const key = 'blue';
+ const obj = { blue: '#00F' };
+ const blue = obj[key];
+ exports.__linariaPreval = [blue];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('shakes exports', () => {
+ const [shaken] = _shake()`
+ import { whiteColor as color, anotherColor } from '…';
+ export const a = color;
+ export { redColor } from "…";
+ export { anotherColor };
+ exports.__linariaPreval = [a];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('shakes es5 exports', () => {
+ const [shaken] = _shake(undefined, ['redColor', 'greenColor'])`
+ "use strict";
+ Object.defineProperty(exports, "__esModule", {
+ value: true
+ });
+ exports.redColor = 'red';
+ Object.defineProperty(exports, "blueColor", {
+ enumerable: true,
+ get: function get() {
+ return 'blue';
+ }
+ });
+ Object.defineProperty(exports, "greenColor", {
+ enumerable: true,
+ get: function get() {
+ return 'green';
+ }
+ });
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+// TODO: this test will be disabled until the shaker is fully implemented
+// eslint-disable-next-line jest/no-disabled-tests
+it.skip('should throw away any side effects', () => {
+ const [shaken] = _shake()`
+ const objects = { key: { fontSize: 12 } };
+ const foo = (k) => {
+ const obj = objects[k];
+ console.log('side effect');
+ return obj;
+ };
+ exports.__linariaPreval = [foo];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('keeps objects as is', () => {
+ const [shaken] = _shake()`
+ const fill1 = (top = 0, left = 0, right = 0, bottom = 0) => ({
+ position: 'absolute',
+ top,
+ right,
+ bottom,
+ left,
+ });
+
+ const fill2 = (top = 0, left = 0, right = 0, bottom = 0) => {
+ return {
+ position: 'absolute',
+ top,
+ right,
+ bottom,
+ left,
+ };
+ };
+
+ exports.__linariaPreval = [fill1, fill2];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('shakes sequence expression', () => {
+ const [shaken] = _shake()`
+ import { external } from '…';
+ const color1 = (external, () => 'blue');
+ let local = '';
+ const color2 = (local = color1(), () => local);
+ exports.__linariaPreval = [color2];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('shakes assignment patterns', () => {
+ const [shaken] = _shake()`
+ const [identifier = 1] = [2];
+ const [{...object} = {}] = [{ a: 1, b: 2 }];
+ const [[...array] = []] = [[1,2,3,4]];
+ const obj = { member: null };
+ ([obj.member = 42] = [1]);
+ exports.__linariaPreval = [identifier, object, array, obj];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
diff --git a/@linaria/packages/babel/__tests__/evaluators/preeval.test.ts b/@linaria/packages/babel/__tests__/evaluators/preeval.test.ts
new file mode 100644
index 0000000..307abd7
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/evaluators/preeval.test.ts
@@ -0,0 +1,121 @@
+import { join } from 'path';
+import { transformAsync } from '@babel/core';
+import dedent from 'dedent';
+import serializer from '../../__utils__/linaria-snapshot-serializer';
+import type { StrictOptions } from '../../src';
+
+expect.addSnapshotSerializer(serializer);
+
+const options: Partial<StrictOptions> = {
+ displayName: true,
+ evaluate: true,
+};
+
+const transpile = async (input: string) =>
+ (await transformAsync(input, {
+ babelrc: false,
+ presets: [[require.resolve('@linaria/preeval'), options]],
+ plugins: [
+ '@babel/plugin-proposal-class-properties',
+ '@babel/plugin-syntax-jsx',
+ ],
+ filename: join(__dirname, 'app/index.js'),
+ configFile: false,
+ }))!;
+
+it('preserves classNames', async () => {
+ const { code } = await transpile(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ const Component = styled.div\`\`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+});
+
+it('handles locally named import', async () => {
+ const { code } = await transpile(
+ dedent`
+ import { styled as custom } from '@linaria/react';
+
+ const Component = custom.div\`\`;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+});
+
+it('replaces functional component', async () => {
+ const div = '<div>{props.children}</div>';
+ const { code } = await transpile(
+ dedent`
+ import React from 'react';
+
+ const Component = (props) => ${div};
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+});
+
+it('replaces class component', async () => {
+ const div = '<div>{props.children}</div>';
+ const { code } = await transpile(
+ dedent`
+ import React from 'react';
+
+ class Component extends React.PureComponent {
+ render() {
+ return ${div};
+ }
+ }
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+});
+
+it('replaces constant', async () => {
+ const div = '<div>{props.children}</div>';
+ const { code } = await transpile(
+ dedent`
+ import React from 'react';
+
+ const tag = ${div};
+
+ const Component = (props) => tag;
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+});
+
+it('hoists exports', async () => {
+ const { code } = await transpile(
+ dedent`
+ "use strict";
+
+ Object.defineProperty(exports, "__esModule", {
+ value: true
+ });
+ Object.defineProperty(exports, "foo", {
+ enumerable: true,
+ get: function get() {
+ return _foo.foo;
+ }
+ });
+ Object.defineProperty(exports, "bar", {
+ enumerable: true,
+ get: function get() {
+ return _foo.bar;
+ }
+ });
+
+ var _foo = require("./foo");
+ `
+ );
+
+ expect(code).toMatchSnapshot();
+});
diff --git a/@linaria/packages/babel/__tests__/evaluators/shaker.test.ts b/@linaria/packages/babel/__tests__/evaluators/shaker.test.ts
new file mode 100644
index 0000000..1208065
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/evaluators/shaker.test.ts
@@ -0,0 +1,259 @@
+import path from 'path';
+import dedent from 'dedent';
+import type { TransformOptions } from '@babel/core';
+import shake from '../../../shaker/src';
+
+function getFileName() {
+ return path.resolve(__dirname, `../__fixtures__/test.js`);
+}
+
+function _shake(opts?: TransformOptions, only: string[] = ['__linariaPreval']) {
+ return (
+ literal: TemplateStringsArray,
+ ...placeholders: string[]
+ ): [string, Map<string, string[]>] => {
+ const code = dedent(literal, ...placeholders);
+ const [shaken, deps] = shake(
+ getFileName(),
+ {
+ babelOptions: opts || {},
+ displayName: true,
+ evaluate: true,
+ rules: [
+ {
+ action: require('../../../extractor/src').default,
+ },
+ {
+ test: /\/node_modules\//,
+ action: 'ignore',
+ },
+ ],
+ },
+ code,
+ only
+ );
+
+ return [shaken, deps!];
+ };
+}
+
+it('removes all', () => {
+ const [shaken] = _shake()`
+ const { whiteColor: color, anotherColor } = require('…');
+ const a = color || anotherColor;
+ color.green = '#0f0';
+
+ exports.__linariaPreval = [];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('keeps only code which is related to `color`', () => {
+ const [shaken] = _shake()`
+ const { whiteColor: color, anotherColor } = require('…');
+ const wrap = '';
+ const a = color || anotherColor;
+ color.green = '#0f0';
+ module.exports = { color, anotherColor };
+ exports.__linariaPreval = [color];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('keeps only code which is related to `anotherColor`', () => {
+ const [shaken] = _shake()`
+ const { whiteColor: color, anotherColor } = require('…');
+ const a = color || anotherColor;
+ color.green = '#0f0';
+ exports.__linariaPreval = [anotherColor];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('keeps only code which is related to `a`', () => {
+ const [shaken] = _shake()`
+ const { whiteColor: color, anotherColor } = require('…');
+ const a = color || anotherColor;
+ color.green = '#0f0';
+ exports.__linariaPreval = [a];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('shakes imports', () => {
+ const [shaken] = _shake()`
+ import { unrelatedImport } from '…';
+ import { whiteColor as color, anotherColor } from '…';
+ import defaultColor from '…';
+ import anotherDefaultColor from '…';
+ import '…';
+ require('…');
+ export default color;
+ exports.__linariaPreval = [color, defaultColor];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('should keep member expression key', () => {
+ const [shaken] = _shake()`
+ const key = 'blue';
+ const obj = { blue: '#00F' };
+ const blue = obj[key];
+ exports.__linariaPreval = [blue];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('shakes exports', () => {
+ const [shaken] = _shake()`
+ import { whiteColor as color, anotherColor } from '…';
+ export const a = color;
+ export { redColor } from "…";
+ export { anotherColor };
+ exports.__linariaPreval = [a];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('shakes es5 exports', () => {
+ const [shaken] = _shake(undefined, ['redColor', 'greenColor', 'yellowColor'])`
+ "use strict";
+ Object.defineProperty(exports, "__esModule", {
+ value: true
+ });
+ exports.redColor = 'red';
+ exports['yellowColor'] = 'yellow';
+ exports['pinkColor'] = 'pink';
+ Object.defineProperty(exports, "blueColor", {
+ enumerable: true,
+ get: function get() {
+ return 'blue';
+ }
+ });
+ Object.defineProperty(exports, "greenColor", {
+ enumerable: true,
+ get: function get() {
+ return 'green';
+ }
+ });
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+// TODO: this test will be disabled until the shaker is fully implemented
+// eslint-disable-next-line jest/no-disabled-tests
+it.skip('should throw away any side effects', () => {
+ const [shaken] = _shake()`
+ const objects = { key: { fontSize: 12 } };
+ const foo = (k) => {
+ const obj = objects[k];
+ console.log('side effect');
+ return obj;
+ };
+ exports.__linariaPreval = [foo];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('keeps objects as is', () => {
+ const [shaken] = _shake()`
+ const fill1 = (top = 0, left = 0, right = 0, bottom = 0) => ({
+ position: 'absolute',
+ top,
+ right,
+ bottom,
+ left,
+ });
+
+ const fill2 = (top = 0, left = 0, right = 0, bottom = 0) => {
+ return {
+ position: 'absolute',
+ top,
+ right,
+ bottom,
+ left,
+ };
+ };
+
+ exports.__linariaPreval = [fill1, fill2];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('shakes sequence expression', () => {
+ const [shaken] = _shake()`
+ import { external } from '…';
+ const color1 = (external, () => 'blue');
+ let local = '';
+ const color2 = (local = color1(), () => local);
+ exports.__linariaPreval = [color2];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('shakes assignment patterns', () => {
+ const [shaken] = _shake()`
+ const [identifier = 1] = [2];
+ const [{...object} = {}] = [{ a: 1, b: 2 }];
+ const [[...array] = []] = [[1,2,3,4]];
+ const obj = { member: null };
+ ([obj.member = 42] = [1]);
+ exports.__linariaPreval = [identifier, object, array, obj];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('shakes for-in statements', () => {
+ const [shaken] = _shake()`
+ const obj1 = { a: 1, b: 2 };
+ const obj2 = {};
+ for (const key in obj1) {
+ obj2[key] = obj1[key];
+ }
+ exports.__linariaPreval = [obj2];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('keeps reused exports', () => {
+ const [shaken] = _shake()`
+ const bar = function() {
+ return 'hello world';
+ };
+ exports.bar = bar;
+
+ const foo = exports.bar();
+ exports.__linariaPreval = [foo];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
+
+it('keeps only the last assignment of each exported variable', () => {
+ const [shaken] = _shake()`
+ const bar = function() {
+ return 'hello world';
+ };
+
+ exports.bar = "bar";
+ exports.bar = bar;
+
+ const foo = exports.bar();
+ exports.__linariaPreval = [foo];
+ `;
+
+ expect(shaken).toMatchSnapshot();
+});
diff --git a/@linaria/packages/babel/__tests__/module.test.ts b/@linaria/packages/babel/__tests__/module.test.ts
new file mode 100644
index 0000000..1012b93
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/module.test.ts
@@ -0,0 +1,320 @@
+import path from 'path';
+import dedent from 'dedent';
+import * as babel from '@babel/core';
+import { Module } from '../src';
+import type { Evaluator, StrictOptions } from '../src';
+
+beforeEach(() => Module.invalidate());
+
+const evaluator: Evaluator = (filename, options, text) => {
+ const { code } = babel.transformSync(text, {
+ filename: filename,
+ })!;
+ return [code!, null];
+};
+
+function getFileName() {
+ return path.resolve(__dirname, `../__fixtures__/test.js`);
+}
+
+const options: StrictOptions = {
+ displayName: false,
+ evaluate: true,
+ rules: [
+ {
+ action: evaluator,
+ },
+ {
+ test: /\/node_modules\//,
+ action: 'ignore',
+ },
+ ],
+ babelOptions: {},
+};
+
+beforeEach(() => Module.invalidateEvalCache());
+
+it('creates module for JS files', () => {
+ const filename = '/foo/bar/test.js';
+ const mod = new Module(filename, options);
+
+ mod.evaluate('module.exports = () => 42');
+
+ expect(mod.exports()).toBe(42);
+ expect(mod.id).toBe(filename);
+ expect(mod.filename).toBe(filename);
+});
+
+it('requires JS files', () => {
+ const mod = new Module(getFileName(), options);
+
+ mod.evaluate(dedent`
+ const answer = require('./sample-script');
+
+ module.exports = 'The answer is ' + answer;
+ `);
+
+ expect(mod.exports).toBe('The answer is 42');
+});
+
+it('requires JSON files', () => {
+ const mod = new Module(getFileName(), options);
+
+ mod.evaluate(dedent`
+ const data = require('./sample-data.json');
+
+ module.exports = 'Our saviour, ' + data.name;
+ `);
+
+ expect(mod.exports).toBe('Our saviour, Luke Skywalker');
+});
+
+it('imports JS files', () => {
+ const mod = new Module(getFileName(), options);
+
+ mod.evaluate(dedent`
+ import answer from './sample-script';
+
+ export const result = 'The answer is ' + answer;
+ `);
+
+ expect(mod.exports.result).toBe('The answer is 42');
+});
+
+it('imports TypeScript files', () => {
+ const mod = new Module(
+ path.resolve(__dirname, '../__fixtures__/test.ts'),
+ options
+ );
+
+ mod.evaluate(dedent`
+ import answer from './sample-typescript';
+
+ export const result = 'The answer is ' + answer;
+ `);
+
+ expect(mod.exports.result).toBe('The answer is 27');
+});
+
+it('imports JSON files', () => {
+ const mod = new Module(getFileName(), options);
+
+ mod.evaluate(dedent`
+ import data from './sample-data.json';
+
+ const result = 'Our saviour, ' + data.name;
+
+ export default result;
+ `);
+
+ expect(mod.exports.default).toBe('Our saviour, Luke Skywalker');
+});
+
+it('returns module from the cache', () => {
+ /* eslint-disable no-self-compare */
+
+ const filename = getFileName();
+ const mod = new Module(filename, options);
+ const id = './sample-data.json';
+
+ expect(mod.require(id) === mod.require(id)).toBe(true);
+
+ expect(
+ new Module(filename, options).require(id) ===
+ new Module(filename, options).require(id)
+ ).toBe(true);
+});
+
+it('clears modules from the cache', () => {
+ const filename = getFileName();
+ const id = './sample-data.json';
+
+ const result = new Module(filename, options).require(id);
+
+ expect(result === new Module(filename, options).require(id)).toBe(true);
+
+ Module.invalidate();
+
+ expect(result === new Module(filename, options).require(id)).toBe(false);
+});
+
+it('exports the path for non JS/JSON files', () => {
+ const mod = new Module(getFileName(), options);
+
+ expect(mod.require('./sample-asset.png')).toBe('./sample-asset.png');
+});
+
+it('returns module when requiring mocked builtin node modules', () => {
+ const mod = new Module(getFileName(), options);
+
+ expect(mod.require('path')).toBe(require('path'));
+});
+
+it('returns null when requiring empty builtin node modules', () => {
+ const mod = new Module(getFileName(), options);
+
+ expect(mod.require('fs')).toBe(null);
+});
+
+it('throws when requiring unmocked builtin node modules', () => {
+ const mod = new Module(getFileName(), options);
+
+ expect(() => mod.require('perf_hooks')).toThrow(
+ 'Unable to import "perf_hooks". Importing Node builtins is not supported in the sandbox.'
+ );
+});
+
+it('has access to the global object', () => {
+ const mod = new Module(getFileName(), options);
+
+ expect(() =>
+ mod.evaluate(dedent`
+ new global.Set();
+ `)
+ ).not.toThrow();
+});
+
+it("doesn't have access to the process object", () => {
+ const mod = new Module(getFileName(), options);
+
+ expect(() =>
+ mod.evaluate(dedent`
+ module.exports = process.abort();
+ `)
+ ).toThrow('process.abort is not a function');
+});
+
+it('has access to NODE_ENV', () => {
+ const mod = new Module(getFileName(), options);
+
+ mod.evaluate(dedent`
+ module.exports = process.env.NODE_ENV;
+ `);
+
+ expect(mod.exports).toBe(process.env.NODE_ENV);
+});
+
+it('has require.resolve available', () => {
+ const mod = new Module(getFileName(), options);
+
+ mod.evaluate(dedent`
+ module.exports = require.resolve('./sample-script');
+ `);
+
+ expect(mod.exports).toBe(
+ path.resolve(path.dirname(mod.filename), 'sample-script.js')
+ );
+});
+
+it('has require.ensure available', () => {
+ const mod = new Module(getFileName(), options);
+
+ expect(() =>
+ mod.evaluate(dedent`
+ require.ensure(['./sample-script']);
+ `)
+ ).not.toThrow();
+});
+
+it('has __filename available', () => {
+ const mod = new Module(getFileName(), options);
+
+ mod.evaluate(dedent`
+ module.exports = __filename;
+ `);
+
+ expect(mod.exports).toBe(mod.filename);
+});
+
+it('has __dirname available', () => {
+ const mod = new Module(getFileName(), options);
+
+ mod.evaluate(dedent`
+ module.exports = __dirname;
+ `);
+
+ expect(mod.exports).toBe(path.dirname(mod.filename));
+});
+
+it('has setTimeout, clearTimeout available', () => {
+ const mod = new Module(getFileName(), options);
+
+ expect(() =>
+ mod.evaluate(dedent`
+ const x = setTimeout(() => {
+ console.log('test');
+ },0);
+
+ clearTimeout(x);
+ `)
+ ).not.toThrow();
+});
+
+it('has setInterval, clearInterval available', () => {
+ const mod = new Module(getFileName(), options);
+
+ expect(() =>
+ mod.evaluate(dedent`
+ const x = setInterval(() => {
+ console.log('test');
+ }, 1000);
+
+ clearInterval(x);
+ `)
+ ).not.toThrow();
+});
+
+it('has setImmediate, clearImmediate available', () => {
+ const mod = new Module(getFileName(), options);
+
+ expect(() =>
+ mod.evaluate(dedent`
+ const x = setImmediate(() => {
+ console.log('test');
+ });
+
+ clearImmediate(x);
+ `)
+ ).not.toThrow();
+});
+
+it('has global objects available without referencing global', () => {
+ const mod = new Module(getFileName(), options);
+
+ expect(() =>
+ mod.evaluate(dedent`
+ const x = new Set();
+ `)
+ ).not.toThrow();
+});
+
+it('changes resolve behaviour on overriding _resolveFilename', () => {
+ const originalResolveFilename = Module._resolveFilename;
+
+ Module._resolveFilename = (id) => (id === 'foo' ? 'bar' : id);
+
+ const mod = new Module(getFileName(), options);
+
+ mod.evaluate(dedent`
+ module.exports = [
+ require.resolve('foo'),
+ require.resolve('test'),
+ ];
+ `);
+
+ // Restore old behavior
+ Module._resolveFilename = originalResolveFilename;
+
+ expect(mod.exports).toEqual(['bar', 'test']);
+});
+
+it('correctly processes export declarations in strict mode', () => {
+ const filename = '/foo/bar/test.js';
+ const mod = new Module(filename, options);
+
+ mod.evaluate('"use strict"; exports = module.exports = () => 42');
+
+ expect(mod.exports()).toBe(42);
+ expect(mod.id).toBe(filename);
+ expect(mod.filename).toBe(filename);
+});
diff --git a/@linaria/packages/babel/__tests__/transform.test.ts b/@linaria/packages/babel/__tests__/transform.test.ts
new file mode 100644
index 0000000..c830ad9
--- /dev/null
+++ b/@linaria/packages/babel/__tests__/transform.test.ts
@@ -0,0 +1,237 @@
+/* eslint-disable no-template-curly-in-string */
+
+import path from 'path';
+import dedent from 'dedent';
+import transform, { transformUrl } from '../src/transform';
+import evaluator from '../../extractor/src';
+
+const outputFilename = './.linaria-cache/test.css';
+
+const rules = [
+ {
+ test: () => true,
+ action: evaluator,
+ },
+];
+
+describe('transformUrl', () => {
+ type TransformUrlArgs = Parameters<typeof transformUrl>;
+ const dataset: Record<string, TransformUrlArgs> = {
+ '../assets/test.jpg': [
+ './assets/test.jpg',
+ './.linaria-cache/test.css',
+ './test.js',
+ ],
+ '../a/b/test.jpg': [
+ '../a/b/test.jpg',
+ './.linaria-cache/test.css',
+ './a/test.js',
+ ],
+ };
+
+ it('should work with posix paths', () => {
+ for (const result of Object.keys(dataset)) {
+ expect(transformUrl(...dataset[result])).toBe(result);
+ }
+ });
+
+ it('should work with win32 paths', () => {
+ const toWin32 = (p: string) => p.split(path.posix.sep).join(path.win32.sep);
+ const win32Dataset = Object.keys(dataset).reduce(
+ (acc, key) => ({
+ ...acc,
+ [key]: [
+ dataset[key][0],
+ toWin32(dataset[key][1]),
+ toWin32(dataset[key][2]),
+ path.win32,
+ ] as TransformUrlArgs,
+ }),
+ {} as Record<string, TransformUrlArgs>
+ );
+
+ for (const result of Object.keys(win32Dataset)) {
+ expect(transformUrl(...win32Dataset[result])).toBe(result);
+ }
+ });
+});
+
+it('rewrites a relative path in url() declarations', async () => {
+ const { cssText } = await transform(
+ dedent`
+ import { css } from '@linaria/core';
+
+ export const title = css\`
+ background-image: url(./assets/test.jpg);
+ background-image: url("./assets/test.jpg");
+ background-image: url('./assets/test.jpg');
+ \`;
+ `,
+ {
+ filename: './test.js',
+ outputFilename: './.linaria-cache/test.css',
+ pluginOptions: {
+ rules,
+ },
+ }
+ );
+
+ expect(cssText).toMatchSnapshot();
+});
+
+it('rewrites multiple relative paths in url() declarations', async () => {
+ const { cssText } = await transform(
+ dedent`
+ import { css } from '@linaria/core';
+
+ export const title = css\`
+ @font-face {
+ font-family: Test;
+ src: url(./assets/font.woff2) format("woff2"), url(./assets/font.woff) format("woff");
+ }
+ \`;
+ `,
+ {
+ filename: './test.js',
+ outputFilename,
+ pluginOptions: {
+ rules,
+ },
+ }
+ );
+
+ expect(cssText).toMatchSnapshot();
+});
+
+it("doesn't rewrite an absolute path in url() declarations", async () => {
+ const { cssText } = await transform(
+ dedent`
+ import { css } from '@linaria/core';
+
+ export const title = css\`
+ background-image: url(/assets/test.jpg);
+ \`;
+ `,
+ {
+ filename: './test.js',
+ outputFilename,
+ pluginOptions: {
+ rules,
+ },
+ }
+ );
+
+ expect(cssText).toMatchSnapshot();
+});
+
+it('respects passed babel options', async () => {
+ expect.assertions(2);
+
+ expect(() =>
+ transform(
+ dedent`
+ import { css } from '@linaria/core';
+
+ export const error = <jsx />;
+ `,
+ {
+ filename: './test.js',
+ outputFilename,
+ pluginOptions: {
+ rules,
+ babelOptions: {
+ babelrc: false,
+ configFile: false,
+ presets: [['@babel/preset-env', { loose: true }]],
+ },
+ },
+ }
+ )
+ ).toThrow(
+ /Support for the experimental syntax 'jsx' isn't currently enabled/
+ );
+
+ expect(() =>
+ transform(
+ dedent`
+ import { css } from '@linaria/core';
+
+ export const error = <jsx />;
+ export const title = css\`
+ background-image: url(/assets/test.jpg);
+ \`;
+ `,
+ {
+ filename: './test.js',
+ outputFilename,
+ pluginOptions: {
+ rules,
+ babelOptions: {
+ babelrc: false,
+ configFile: false,
+ presets: [
+ ['@babel/preset-env', { loose: true }],
+ '@babel/preset-react',
+ ],
+ },
+ },
+ }
+ )
+ ).not.toThrow('Unexpected token');
+});
+
+it("doesn't throw due to duplicate preset", async () => {
+ expect.assertions(1);
+
+ expect(() =>
+ transform(
+ dedent`
+ import { styled } from '@linaria/react';
+
+ const Title = styled.h1\` color: blue; \`;
+
+ const Article = styled.article\`
+ ${'${Title}'} {
+ font-size: 16px;
+ }
+ \`;
+ `,
+ {
+ filename: './test.js',
+ outputFilename,
+ pluginOptions: {
+ rules,
+ babelOptions: {
+ babelrc: false,
+ configFile: false,
+ presets: [require.resolve('../src')],
+ plugins: [
+ require.resolve('@babel/plugin-transform-modules-commonjs'),
+ ],
+ },
+ },
+ }
+ )
+ ).not.toThrow('Duplicate plugin/preset detected');
+});
+
+it('should return transformed code even when file only contains unused linaria code', async () => {
+ const { code } = await transform(
+ dedent`
+ import { css } from '@linaria/core';
+
+ const title = css\`
+ color: red;
+ \`;
+ `,
+ {
+ filename: './test.js',
+ outputFilename,
+ pluginOptions: {
+ rules,
+ },
+ }
+ );
+
+ expect(code).not.toContain('css`');
+});