From 8ce43306c239176d90a4f0a71fc310427ec50ce0 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Thu, 19 Oct 2017 17:47:01 +0200 Subject: [PATCH] feat: support template literals in object properties and jsx props fixes #134, #138 --- babel.js | 1 - .../__utils__/transpile.js | 2 +- .../__snapshots__/errors.spec.js.snap | 51 +++++++- .../__snapshots__/preval-extract.spec.js.snap | 2 +- ...ap => with-common-js-imports.spec.js.snap} | 0 ...c.js.snap => with-es-imports.spec.js.snap} | 0 .../preval-extract/development.spec.js | 4 +- .../preval-extract/errors.spec.js | 4 +- .../preval-extract/preval-extract.spec.js | 19 +-- ...spec.js => with-common-js-imports.spec.js} | 24 ++-- ...xports.spec.js => with-es-imports.spec.js} | 8 +- .../with-function-calls.spec.js | 38 +++--- ...-function-declarations-expressions.spec.js | 118 ++++++++--------- .../preval-extract/with-plain-objects.spec.js | 12 +- .../preval-extract/with-properties.spec.js | 44 +++++++ .../__snapshots__/index.spec.js.snap | 86 ------------- src/babel/hoist-css/__tests__/index.spec.js | 119 ------------------ src/babel/hoist-css/index.js | 68 ---------- .../__snapshots__/index.spec.js.snap | 8 ++ .../preval-extract/__tests__/index.spec.js | 89 ++++++++++++- .../__tests__/prevalStyles.spec.js | 6 +- .../__tests__/resolveSource.spec.js | 16 +-- .../__tests__/validators.spec.js | 20 --- src/babel/preval-extract/index.js | 49 +++++++- src/babel/preval-extract/prevalStyles.js | 15 +-- src/babel/preval-extract/resolveSource.js | 10 +- src/babel/preval-extract/validators.js | 11 -- src/babel/rewire-imports/index.js | 7 +- src/babel/types.js | 9 ++ 29 files changed, 373 insertions(+), 467 deletions(-) rename src/babel/__integration-tests__/preval-extract/__snapshots__/{with-common-js-exports.spec.js.snap => with-common-js-imports.spec.js.snap} (100%) rename src/babel/__integration-tests__/preval-extract/__snapshots__/{with-es-exports.spec.js.snap => with-es-imports.spec.js.snap} (100%) rename src/babel/__integration-tests__/preval-extract/{with-common-js-exports.spec.js => with-common-js-imports.spec.js} (63%) rename src/babel/__integration-tests__/preval-extract/{with-es-exports.spec.js => with-es-imports.spec.js} (87%) create mode 100644 src/babel/__integration-tests__/preval-extract/with-properties.spec.js delete mode 100644 src/babel/hoist-css/__tests__/__snapshots__/index.spec.js.snap delete mode 100644 src/babel/hoist-css/__tests__/index.spec.js delete mode 100644 src/babel/hoist-css/index.js create mode 100644 src/babel/preval-extract/__tests__/__snapshots__/index.spec.js.snap diff --git a/babel.js b/babel.js index 85b7316f0..5f36cea6f 100644 --- a/babel.js +++ b/babel.js @@ -3,7 +3,6 @@ module.exports = function linariaBabelPreset(context, opts = {}) { return { plugins: [ - [require('./build/babel/hoist-css').default, opts], [require('./build/babel/preval-extract').default, opts], [require('./build/babel/rewire-imports').default, opts], ], diff --git a/src/babel/__integration-tests__/__utils__/transpile.js b/src/babel/__integration-tests__/__utils__/transpile.js index 6fbbf024f..9d25380bc 100644 --- a/src/babel/__integration-tests__/__utils__/transpile.js +++ b/src/babel/__integration-tests__/__utils__/transpile.js @@ -20,7 +20,7 @@ function transpile(source, options = { pluginOptions: {}, babelOptions: {} }) { `, Object.assign( { - presets: ['es2015', 'stage-2'], + presets: ['es2015', 'stage-2', 'react'], plugins: [[PREVAL_PLUGIN_PATH, options.pluginOptions]], sourceMaps: false, }, diff --git a/src/babel/__integration-tests__/preval-extract/__snapshots__/errors.spec.js.snap b/src/babel/__integration-tests__/preval-extract/__snapshots__/errors.spec.js.snap index e327d6926..1e845c62d 100644 --- a/src/babel/__integration-tests__/preval-extract/__snapshots__/errors.spec.js.snap +++ b/src/babel/__integration-tests__/preval-extract/__snapshots__/errors.spec.js.snap @@ -1,5 +1,54 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`preval-extract babel plugin errors should throw error if "css" tagged template literal is not assigned to a variable 1`] = ` +Command failed: node <>/src/babel/__integration-tests__/__utils__/transpile.js '"css\`\\n font-size: 3em;\\n\`;"' '{"pluginOptions":{"cache":false,"extract":false},"babelOptions":{"filename":"test.js"}}' +<>/node_modules/babel-core/lib/transformation/file/index.js:590 + throw err; + ^ + +SyntaxError: test.js: Couldn't determine the class name for CSS template literal. Ensure that it's either: +- Assigned to a variable +- Is an object property +- Is a prop in a JSX element + 1 | import css from '<>/build/css.js'; + 2 | +> 3 | css\` + | ^ + 4 | font-size: 3em; + 5 | \`; + at File.buildCodeFrameError (<>/node_modules/babel-core/lib/transformation/file/index.js:427:15) + at NodePath.buildCodeFrameError (<>/node_modules/babel-traverse/lib/path/index.js:140:26) + at PluginPass.TaggedTemplateExpression (<>/build/babel/preval-extract/index.js:105:26) + at newFn (<>/node_modules/babel-traverse/lib/visitors.js:276:21) + at NodePath._call (<>/node_modules/babel-traverse/lib/path/context.js:76:18) + at NodePath.call (<>/node_modules/babel-traverse/lib/path/context.js:48:17) + at NodePath.visit (<>/node_modules/babel-traverse/lib/path/context.js:105:12) + at TraversalContext.visitQueue (<>/node_modules/babel-traverse/lib/context.js:150:16) + at TraversalContext.visitSingle (<>/node_modules/babel-traverse/lib/context.js:108:19) + at TraversalContext.visit (<>/node_modules/babel-traverse/lib/context.js:192:19) + +`; + +exports[`preval-extract babel plugin errors should throw error if "css.named" is not called with classname 1`] = ` +Command failed: node <>/src/babel/__integration-tests__/__utils__/transpile.js '"css.named\`\\n font-size: 3em;\\n\`;"' '{"pluginOptions":{"cache":false,"extract":false},"babelOptions":{"filename":"test.js"}}' +<>/node_modules/babel-core/lib/transformation/file/index.js:590 + throw err; + ^ + +Error: test.js: Linaria's \`css.named\` must be called with a class name + at isLinariaTaggedTemplate (<>/build/babel/preval-extract/validators.js:22:11) + at PluginPass.TaggedTemplateExpression (<>/build/babel/preval-extract/index.js:88:72) + at newFn (<>/node_modules/babel-traverse/lib/visitors.js:276:21) + at NodePath._call (<>/node_modules/babel-traverse/lib/path/context.js:76:18) + at NodePath.call (<>/node_modules/babel-traverse/lib/path/context.js:48:17) + at NodePath.visit (<>/node_modules/babel-traverse/lib/path/context.js:105:12) + at TraversalContext.visitQueue (<>/node_modules/babel-traverse/lib/context.js:150:16) + at TraversalContext.visitSingle (<>/node_modules/babel-traverse/lib/context.js:108:19) + at TraversalContext.visit (<>/node_modules/babel-traverse/lib/context.js:192:19) + at Function.traverse.node (<>/node_modules/babel-traverse/lib/index.js:114:17) + +`; + exports[`preval-extract babel plugin errors should throw error if the id was not found 1`] = ` Command failed: node <>/src/babel/__integration-tests__/__utils__/transpile.js '"const title = css\`\\n width: \${document.width};\\n\`;"' '{"pluginOptions":{"cache":false,"extract":false},"babelOptions":{"filename":"test.js"}}' <>/node_modules/babel-core/lib/transformation/file/index.js:590 @@ -17,7 +66,7 @@ SyntaxError: test.js: Linaria css evaluation error: 5 | \`; at File.buildCodeFrameError (<>/node_modules/babel-core/lib/transformation/file/index.js:427:15) at NodePath.buildCodeFrameError (<>/node_modules/babel-traverse/lib/path/index.js:140:26) - at resolveSource (<>/build/babel/preval-extract/resolveSource.js:33:16) + at resolveSource (<>/build/babel/preval-extract/resolveSource.js:31:16) at Object.Identifier (<>/build/babel/preval-extract/index.js:46:48) at NodePath._call (<>/node_modules/babel-traverse/lib/path/context.js:76:18) at NodePath.call (<>/node_modules/babel-traverse/lib/path/context.js:48:17) diff --git a/src/babel/__integration-tests__/preval-extract/__snapshots__/preval-extract.spec.js.snap b/src/babel/__integration-tests__/preval-extract/__snapshots__/preval-extract.spec.js.snap index 5ae52e0f0..39147f710 100644 --- a/src/babel/__integration-tests__/preval-extract/__snapshots__/preval-extract.spec.js.snap +++ b/src/babel/__integration-tests__/preval-extract/__snapshots__/preval-extract.spec.js.snap @@ -28,7 +28,7 @@ import css from '<>/build/css.js'; const size = 3; let color = "#ffffff"; -/*linaria-output*/const header = "header__3r30ar"; +const header = /*linaria-output*/"header__3r30ar"; `; exports[`preval-extract babel plugin should preval css with classname from another prevaled css 1`] = ` diff --git a/src/babel/__integration-tests__/preval-extract/__snapshots__/with-common-js-exports.spec.js.snap b/src/babel/__integration-tests__/preval-extract/__snapshots__/with-common-js-imports.spec.js.snap similarity index 100% rename from src/babel/__integration-tests__/preval-extract/__snapshots__/with-common-js-exports.spec.js.snap rename to src/babel/__integration-tests__/preval-extract/__snapshots__/with-common-js-imports.spec.js.snap diff --git a/src/babel/__integration-tests__/preval-extract/__snapshots__/with-es-exports.spec.js.snap b/src/babel/__integration-tests__/preval-extract/__snapshots__/with-es-imports.spec.js.snap similarity index 100% rename from src/babel/__integration-tests__/preval-extract/__snapshots__/with-es-exports.spec.js.snap rename to src/babel/__integration-tests__/preval-extract/__snapshots__/with-es-imports.spec.js.snap diff --git a/src/babel/__integration-tests__/preval-extract/development.spec.js b/src/babel/__integration-tests__/preval-extract/development.spec.js index bfb8e52bc..fa91d15d4 100644 --- a/src/babel/__integration-tests__/preval-extract/development.spec.js +++ b/src/babel/__integration-tests__/preval-extract/development.spec.js @@ -33,10 +33,10 @@ describe('preval-extract babel plugin', () => { { filename: path.join(process.cwd(), 'test.js') } ); - const classnameWithSlugFromContent = /header = '(header__[a-z0-9]+)'/g.exec( + const classnameWithSlugFromContent = /header = \/\*.+\*\/'(header__[a-z0-9]+)'/g.exec( codeWithSlugFromContent ); - const classnameWithSlugFromFilename = /header = '(header__[a-z0-9]+)'/g.exec( + const classnameWithSlugFromFilename = /header = \/\*.+\*\/'(header__[a-z0-9]+)'/g.exec( codeWithSlugFromFilename ); diff --git a/src/babel/__integration-tests__/preval-extract/errors.spec.js b/src/babel/__integration-tests__/preval-extract/errors.spec.js index 2c1ca6c7a..4e33ca14d 100644 --- a/src/babel/__integration-tests__/preval-extract/errors.spec.js +++ b/src/babel/__integration-tests__/preval-extract/errors.spec.js @@ -21,7 +21,7 @@ describe('preval-extract babel plugin errors', () => { font-size: 3em; \`; `); - }).toThrow(); + }).toThrowErrorMatchingSnapshot(); }); it('should throw error if "css.named" is not called with classname', () => { @@ -31,7 +31,7 @@ describe('preval-extract babel plugin errors', () => { font-size: 3em; \`; `); - }).toThrow(); + }).toThrowErrorMatchingSnapshot(); }); it('should throw error if the id was not found', () => { diff --git a/src/babel/__integration-tests__/preval-extract/preval-extract.spec.js b/src/babel/__integration-tests__/preval-extract/preval-extract.spec.js index f8e2099a0..02cc00073 100644 --- a/src/babel/__integration-tests__/preval-extract/preval-extract.spec.js +++ b/src/babel/__integration-tests__/preval-extract/preval-extract.spec.js @@ -48,7 +48,7 @@ describe('preval-extract babel plugin', () => { \`; `); - const match = /header = '(header__[a-z0-9]+)'/g.exec(code); + const match = /header = \/\*.+\*\/'(header__[a-z0-9]+)'/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 3em'); @@ -62,7 +62,7 @@ describe('preval-extract babel plugin', () => { \`; `); - const match = /header = "(my-header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(my-header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 3em'); @@ -80,8 +80,8 @@ describe('preval-extract babel plugin', () => { \`; `); - const headerMatch = /header = '(header__[a-z0-9]+)'/g.exec(code); - const bodyMatch = /body = '(body__[a-z0-9]+)'/g.exec(code); + const headerMatch = /header = \/\*.+\*\/'(header__[a-z0-9]+)'/g.exec(code); + const bodyMatch = /body = \/\*.+\*\/'(body__[a-z0-9]+)'/g.exec(code); expect(headerMatch).not.toBeNull(); expect(bodyMatch).not.toBeNull(); const headerStyles = getCSSForClassName(headerMatch[1]); @@ -97,7 +97,6 @@ describe('preval-extract babel plugin', () => { const defaults = { fontSize: "3em", }; - function render() { const header = css\` font-size: ${'${defaults.fontSize}'}; @@ -105,7 +104,7 @@ describe('preval-extract babel plugin', () => { } `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 3em'); @@ -131,7 +130,7 @@ describe('preval-extract babel plugin', () => { } ); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 3em'); @@ -154,10 +153,12 @@ describe('preval-extract babel plugin', () => { \`; `); - const titleMatch = /title = '(title__[a-z0-9]+)'/g.exec(code); + const titleMatch = /title = \/\*.+\*\/'(title__[a-z0-9]+)'/g.exec(code); expect(titleMatch).not.toBeNull(); - const containerMatch = /container = '(container__[a-z0-9]+)'/g.exec(code); + const containerMatch = /container = \/\*.+\*\/'(container__[a-z0-9]+)'/g.exec( + code + ); expect(containerMatch).not.toBeNull(); const css = getCSSForClassName(containerMatch[1]); diff --git a/src/babel/__integration-tests__/preval-extract/with-common-js-exports.spec.js b/src/babel/__integration-tests__/preval-extract/with-common-js-imports.spec.js similarity index 63% rename from src/babel/__integration-tests__/preval-extract/with-common-js-exports.spec.js rename to src/babel/__integration-tests__/preval-extract/with-common-js-imports.spec.js index e8397ccec..08909cbec 100644 --- a/src/babel/__integration-tests__/preval-extract/with-common-js-exports.spec.js +++ b/src/babel/__integration-tests__/preval-extract/with-common-js-imports.spec.js @@ -15,14 +15,14 @@ describe('preval-extract babel plugin with commonjs imports', () => { it('should preval imported constants ', () => { const { code, getCSSForClassName } = transpile(dedent` - const constants = require("./src/babel/__integration-tests__/__fixtures__/commonjs/constants.js"); + const constants = require("./src/babel/__integration-tests__/__fixtures__/commonjs/constants.js"); - const header = css\` - font-size: ${'${constants.fontSize}'}; - \`; - `); + const header = css\` + font-size: ${'${constants.fontSize}'}; + \`; + `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 14px'); @@ -31,14 +31,14 @@ describe('preval-extract babel plugin with commonjs imports', () => { it('should preval imported constants with destructurization', () => { const { code, getCSSForClassName } = transpile(dedent` - const { fontSize } = require("./src/babel/__integration-tests__/__fixtures__/commonjs/constants.js"); + const { fontSize } = require("./src/babel/__integration-tests__/__fixtures__/commonjs/constants.js"); - const header = css\` - font-size: ${'${fontSize}'}; - \`; - `); + const header = css\` + font-size: ${'${fontSize}'}; + \`; + `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 14px'); diff --git a/src/babel/__integration-tests__/preval-extract/with-es-exports.spec.js b/src/babel/__integration-tests__/preval-extract/with-es-imports.spec.js similarity index 87% rename from src/babel/__integration-tests__/preval-extract/with-es-exports.spec.js rename to src/babel/__integration-tests__/preval-extract/with-es-imports.spec.js index 7efddd964..593b7d3a8 100644 --- a/src/babel/__integration-tests__/preval-extract/with-es-exports.spec.js +++ b/src/babel/__integration-tests__/preval-extract/with-es-imports.spec.js @@ -22,7 +22,7 @@ describe('preval-extract babel plugin with ES imports', () => { \`; `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 14px'); @@ -42,8 +42,8 @@ describe('preval-extract babel plugin with ES imports', () => { \`; `); - const headerMatch = /header = "(header__[a-z0-9]+)"/g.exec(code); - const bodyMatch = /body = "(body__[a-z0-9]+)"/g.exec(code); + const headerMatch = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); + const bodyMatch = /body = \/\*.+\*\/"(body__[a-z0-9]+)"/g.exec(code); expect(headerMatch).not.toBeNull(); expect(bodyMatch).not.toBeNull(); const headerStyles = getCSSForClassName(headerMatch[1]); @@ -63,7 +63,7 @@ describe('preval-extract babel plugin with ES imports', () => { \`; `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 28px'); diff --git a/src/babel/__integration-tests__/preval-extract/with-function-calls.spec.js b/src/babel/__integration-tests__/preval-extract/with-function-calls.spec.js index 46f7e8e99..4c547ba4d 100644 --- a/src/babel/__integration-tests__/preval-extract/with-function-calls.spec.js +++ b/src/babel/__integration-tests__/preval-extract/with-function-calls.spec.js @@ -15,15 +15,15 @@ describe('preval-extract babel plugin with function calls', () => { it('should preval with function call inside an expression', () => { const { code, getCSSForClassName } = transpile(dedent` - const constants = require("./src/babel/__integration-tests__/__fixtures__/commonjs/constants.js"); - const utils = require("./src/babel/__integration-tests__/__fixtures__/commonjs/utils.js"); + const constants = require("./src/babel/__integration-tests__/__fixtures__/commonjs/constants.js"); + const utils = require("./src/babel/__integration-tests__/__fixtures__/commonjs/utils.js"); - const header = css\` - font-size: ${'${utils.multiply(constants.unitless.fontSize)}'}px; - \`; - `); + const header = css\` + font-size: ${'${utils.multiply(constants.unitless.fontSize)}'}px; + \`; + `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 28px'); @@ -32,21 +32,21 @@ describe('preval-extract babel plugin with function calls', () => { it('should preval multiple function calls inside an expression', () => { const { code, getCSSForClassName } = transpile(dedent` - const constants = require("./src/babel/__integration-tests__/__fixtures__/commonjs/constants.js"); - const utils = require("./src/babel/__integration-tests__/__fixtures__/commonjs/utils.js"); + const constants = require("./src/babel/__integration-tests__/__fixtures__/commonjs/constants.js"); + const utils = require("./src/babel/__integration-tests__/__fixtures__/commonjs/utils.js"); - function compose(...fns) { - return value => fns.reduce((prev, fn) => { - return fn(prev); - }, value); - } + function compose(...fns) { + return value => fns.reduce((prev, fn) => { + return fn(prev); + }, value); + } - const header = css\` - font-size: ${'${compose(utils.multiply, utils.add5)(constants.unitless.fontSize)}'}px; - \`; - `); + const header = css\` + font-size: ${'${compose(utils.multiply, utils.add5)(constants.unitless.fontSize)}'}px; + \`; + `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 33px'); diff --git a/src/babel/__integration-tests__/preval-extract/with-function-declarations-expressions.spec.js b/src/babel/__integration-tests__/preval-extract/with-function-declarations-expressions.spec.js index 7d6b3cfbc..2e24bfa94 100644 --- a/src/babel/__integration-tests__/preval-extract/with-function-declarations-expressions.spec.js +++ b/src/babel/__integration-tests__/preval-extract/with-function-declarations-expressions.spec.js @@ -15,18 +15,18 @@ describe('preval-extract babel plugin with function delcarations/expressions', ( it('should preval with function declaration', () => { const { code, getCSSForClassName } = transpile(dedent` - function getConstants() { - return { - fontSize: "14px", - }; - } - - const header = css\` - font-size: ${'${getConstants().fontSize}'}; - \`; - `); - - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + function getConstants() { + return { + fontSize: "14px", + }; + } + + const header = css\` + font-size: ${'${getConstants().fontSize}'}; + \`; + `); + + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 14px'); @@ -35,18 +35,18 @@ describe('preval-extract babel plugin with function delcarations/expressions', ( it('should preval with function expression', () => { const { code, getCSSForClassName } = transpile(dedent` - const getConstants = function getConstants() { - return { - fontSize: "14px", - }; - } - - const header = css\` - font-size: ${'${getConstants().fontSize}'}; - \`; - `); - - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const getConstants = function getConstants() { + return { + fontSize: "14px", + }; + } + + const header = css\` + font-size: ${'${getConstants().fontSize}'}; + \`; + `); + + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 14px'); @@ -55,16 +55,16 @@ describe('preval-extract babel plugin with function delcarations/expressions', ( it('should preval with arrow function', () => { const { code, getCSSForClassName } = transpile(dedent` - const getConstants = () => ({ - fontSize: "14px", - }); + const getConstants = () => ({ + fontSize: "14px", + }); - const header = css\` - font-size: ${'${getConstants().fontSize}'}; - \`; - `); + const header = css\` + font-size: ${'${getConstants().fontSize}'}; + \`; + `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 14px'); @@ -73,15 +73,15 @@ describe('preval-extract babel plugin with function delcarations/expressions', ( it('should preval function with flat/shallow external ids', () => { const { code, getCSSForClassName } = transpile(dedent` - const defaults = { fontSize: "14px" }; - const getConstants = () => Object.assign({}, defaults); + const defaults = { fontSize: "14px" }; + const getConstants = () => Object.assign({}, defaults); - const header = css\` - font-size: ${'${getConstants().fontSize}'}; - \`; - `); + const header = css\` + font-size: ${'${getConstants().fontSize}'}; + \`; + `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 14px'); @@ -90,16 +90,16 @@ describe('preval-extract babel plugin with function delcarations/expressions', ( it('should preval function with nested/deep external ids', () => { const { code, getCSSForClassName } = transpile(dedent` - const base = { color: "#ffffff", fontSize: "15px" }; - const defaults = { fontSize: "14px", ...base }; - const getConstants = () => ({ ...defaults }); + const base = { color: "#ffffff", fontSize: "15px" }; + const defaults = { fontSize: "14px", ...base }; + const getConstants = () => ({ ...defaults }); - const header = css\` - font-size: ${'${getConstants().fontSize}'}; - \`; - `); + const header = css\` + font-size: ${'${getConstants().fontSize}'}; + \`; + `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 15px'); @@ -108,21 +108,21 @@ describe('preval-extract babel plugin with function delcarations/expressions', ( it('should preval function with multiple nested/deep external ids', () => { const { code, getCSSForClassName } = transpile(dedent` - function multiply(value, by) { - return value * by; - } + function multiply(value, by) { + return value * by; + } - const bg = { background: "none" }; - const base = { color: "#ffffff", fontSize: multiply(14, 2) + "px", ...bg }; - const defaults = { fontSize: "14px", ...base, ...bg }; - const getConstants = () => ({ ...defaults }); + const bg = { background: "none" }; + const base = { color: "#ffffff", fontSize: multiply(14, 2) + "px", ...bg }; + const defaults = { fontSize: "14px", ...base, ...bg }; + const getConstants = () => ({ ...defaults }); - const header = css\` - font-size: ${'${getConstants().fontSize}'}; - \`; - `); + const header = css\` + font-size: ${'${getConstants().fontSize}'}; + \`; + `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 28px'); diff --git a/src/babel/__integration-tests__/preval-extract/with-plain-objects.spec.js b/src/babel/__integration-tests__/preval-extract/with-plain-objects.spec.js index 62ae0529a..9a16dda5c 100644 --- a/src/babel/__integration-tests__/preval-extract/with-plain-objects.spec.js +++ b/src/babel/__integration-tests__/preval-extract/with-plain-objects.spec.js @@ -24,7 +24,7 @@ describe('preval-extract babel plugin with plain objects', () => { \`; `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 3em'); @@ -48,7 +48,7 @@ describe('preval-extract babel plugin with plain objects', () => { \`; `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 3em'); @@ -70,7 +70,7 @@ describe('preval-extract babel plugin with plain objects', () => { \`; `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 3em'); @@ -92,7 +92,7 @@ describe('preval-extract babel plugin with plain objects', () => { \`; `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 3em'); @@ -114,7 +114,7 @@ describe('preval-extract babel plugin with plain objects', () => { \`; `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 3em'); @@ -134,7 +134,7 @@ describe('preval-extract babel plugin with plain objects', () => { \`; `); - const match = /header = "(header__[a-z0-9]+)"/g.exec(code); + const match = /header = \/\*.+\*\/"(header__[a-z0-9]+)"/g.exec(code); expect(match).not.toBeNull(); const css = getCSSForClassName(match[1]); expect(css).toMatch('font-size: 3em'); diff --git a/src/babel/__integration-tests__/preval-extract/with-properties.spec.js b/src/babel/__integration-tests__/preval-extract/with-properties.spec.js new file mode 100644 index 000000000..c06e2916b --- /dev/null +++ b/src/babel/__integration-tests__/preval-extract/with-properties.spec.js @@ -0,0 +1,44 @@ +/* eslint-disable no-template-curly-in-string */ +/* @flow */ + +import dedent from 'dedent'; +import { transpile } from '../__utils__/exec'; + +describe('with object and jsx properties', () => { + it('should preval with object properties', () => { + const { code, getCSSForClassName } = transpile(dedent` + const styles = { + header: css\` + font-size: 3em; + \`, + }; + `); + + const match = /header: \/\*.+\*\/'(header__[a-z0-9]+)'/g.exec(code); + expect(match).not.toBeNull(); + const css = getCSSForClassName(match[1]); + expect(css).toMatch('font-size: 3em'); + }); + + it('should preval with JSX props', () => { + const { code, getCSSForClassName } = transpile(dedent` +
+ `); + + const match = /className: \/\*.+\*\/'(article__[a-z0-9]+)'/g.exec(code); + expect(match).not.toBeNull(); + const css = getCSSForClassName(match[1]); + expect(css).toMatch('font-size: 3em'); + }); + + it('should preval with spreading in JSX', () => { + const { code, getCSSForClassName } = transpile(dedent` +
+ `); + + const match = /styles\( \/\*.+\*\/'(article__[a-z0-9]+)'\)/g.exec(code); + expect(match).not.toBeNull(); + const css = getCSSForClassName(match[1]); + expect(css).toMatch('font-size: 3em'); + }); +}); diff --git a/src/babel/hoist-css/__tests__/__snapshots__/index.spec.js.snap b/src/babel/hoist-css/__tests__/__snapshots__/index.spec.js.snap deleted file mode 100644 index a8b50ad1f..000000000 --- a/src/babel/hoist-css/__tests__/__snapshots__/index.spec.js.snap +++ /dev/null @@ -1,86 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`hoist-css babel plugin should hoist CSS from JSX from class 1`] = ` -" -function MyComponent() { - return React.createElement(\\"article\\", { - \\"class\\": _article - }); -} - -var _article = css\`color: \${colors.red};\`;" -`; - -exports[`hoist-css babel plugin should hoist CSS from JSX from className 1`] = ` -" -function MyComponent() { - return React.createElement(\\"article\\", { - className: _article - }); -} - -var _article = css\`color: \${colors.red};\`;" -`; - -exports[`hoist-css babel plugin should hoist CSS from JSX from styles 1`] = ` -"var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -function MyComponent() { - return React.createElement(\\"article\\", _extends({ - id: \\"post\\" - }, styles(_article, _article2))); -} - -var _article = css\`color: \${colors.red};\`; - -var _article2 = css\`font-size: 12px;\`;" -`; - -exports[`hoist-css babel plugin should hoist named CSS from JSX from styles 1`] = ` -"var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -function MyComponent() { - return React.createElement(\\"article\\", _extends({ - id: \\"post\\" - }, styles(_article, _article2))); -} - -var _article = css.named('test')\`color: \${colors.red};\`; - -var _article2 = css\`font-size: 12px;\`;" -`; - -exports[`hoist-css babel plugin should noop if no styles or class present 1`] = ` -" -function MyComponent() { - return React.createElement(\\"article\\", { id: \\"post\\" }); -}" -`; - -exports[`hoist-css babel plugin should not affect other template literals in class 1`] = ` -" -function MyComponent() { - React.createElement(\\"article\\", { - \\"class\\": \`color: \${colors.red};\` - }); -}" -`; - -exports[`hoist-css babel plugin should not affect other template literals in className 1`] = ` -" -function MyComponent() { - React.createElement(\\"article\\", { - className: sass\`color: \${colors.red};\` - }); -}" -`; - -exports[`hoist-css babel plugin should not affect other template literals in styles 1`] = ` -"var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - -function MyComponent() { - React.createElement(\\"article\\", _extends({ - id: \\"post\\" - }, styles(sass\`color: \${colors.red};\`, \`font-size: 12px;\`))); -}" -`; diff --git a/src/babel/hoist-css/__tests__/index.spec.js b/src/babel/hoist-css/__tests__/index.spec.js deleted file mode 100644 index 7c8966fcf..000000000 --- a/src/babel/hoist-css/__tests__/index.spec.js +++ /dev/null @@ -1,119 +0,0 @@ -/* eslint-disable no-template-curly-in-string */ -/* @flow */ - -import * as babel from 'babel-core'; - -function transpile(source) { - return babel.transform(source, { - presets: ['react'], - plugins: [require.resolve('../index')], - babelrc: false, - }); -} - -describe('hoist-css babel plugin', () => { - it('should hoist CSS from JSX from styles', () => { - const { code } = transpile(` - function MyComponent() { - return ( -
- ); - } - `); - - expect(code).toMatchSnapshot(); - }); - - it('should hoist named CSS from JSX from styles', () => { - const { code } = transpile(` - function MyComponent() { - return ( -
- ); - } - `); - - expect(code).toMatchSnapshot(); - }); - - it('should not affect other template literals in styles', () => { - const { code } = transpile(` - function MyComponent() { -
- } - `); - - expect(code).toMatchSnapshot(); - }); - - it('should hoist CSS from JSX from className', () => { - const { code } = transpile(` - function MyComponent() { - return ( -
- ); - } - `); - - expect(code).toMatchSnapshot(); - }); - - it('should not affect other template literals in className', () => { - const { code } = transpile(` - function MyComponent() { -
- } - `); - - expect(code).toMatchSnapshot(); - }); - - it('should hoist CSS from JSX from class', () => { - const { code } = transpile(` - function MyComponent() { - return ( -
- ); - } - `); - - expect(code).toMatchSnapshot(); - }); - - it('should not affect other template literals in class', () => { - const { code } = transpile(` - function MyComponent() { -
- } - `); - - expect(code).toMatchSnapshot(); - }); - - it('should noop if no styles or class present', () => { - const { code } = transpile(` - function MyComponent() { - return
; - } - `); - - expect(code).toMatchSnapshot(); - }); -}); diff --git a/src/babel/hoist-css/index.js b/src/babel/hoist-css/index.js deleted file mode 100644 index 0a1649c29..000000000 --- a/src/babel/hoist-css/index.js +++ /dev/null @@ -1,68 +0,0 @@ -/* @flow */ - -import type { BabelTypes, NodePath } from '../types'; - -type State = { - programPath: NodePath<*>, -}; - -const transformTaggedTemplate = ( - t: BabelTypes, - path: NodePath<*>, - state: State, - expression: * -) => { - if ( - t.isTaggedTemplateExpression(expression) && - ((t.isIdentifier(expression.tag) && expression.tag.name === 'css') || - (t.isCallExpression(expression.tag) && - t.isMemberExpression(expression.tag.callee) && - expression.tag.callee.object.name === 'css' && - expression.tag.callee.property.name === 'named')) - ) { - const className = path.scope.generateUidIdentifier(path.node.name.name); - state.programPath.node.body.push( - t.variableDeclaration('var', [ - t.variableDeclarator(t.identifier(className.name), expression), - ]) - ); - return className; - } - return expression; -}; - -export default function({ types: t }: { types: BabelTypes }) { - return { - visitor: { - Program: { - enter(path: NodePath<*>, state: State) { - state.programPath = path; - }, - }, - JSXOpeningElement(path: NodePath<*>, state: State) { - path.node.attributes.forEach(attr => { - if ( - t.isJSXSpreadAttribute(attr) && - t.isCallExpression(attr.argument) && - attr.argument.callee.name === 'styles' - ) { - attr.argument.arguments = attr.argument.arguments.map(arg => - transformTaggedTemplate(t, path, state, arg) - ); - } else if ( - t.isJSXIdentifier(attr.name) && - t.isJSXExpressionContainer(attr.value) && - (attr.name.name === 'class' || attr.name.name === 'className') - ) { - attr.value.expression = transformTaggedTemplate( - t, - path, - state, - attr.value.expression - ); - } - }); - }, - }, - }; -} diff --git a/src/babel/preval-extract/__tests__/__snapshots__/index.spec.js.snap b/src/babel/preval-extract/__tests__/__snapshots__/index.spec.js.snap new file mode 100644 index 000000000..97208213c --- /dev/null +++ b/src/babel/preval-extract/__tests__/__snapshots__/index.spec.js.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`preval-extract/index TaggedTemplateExpression should not process template if checks do not pass 1`] = ` +"Couldn't determine the class name for CSS template literal. Ensure that it's either: +- Assigned to a variable +- Is an object property +- Is a prop in a JSX element" +`; diff --git a/src/babel/preval-extract/__tests__/index.spec.js b/src/babel/preval-extract/__tests__/index.spec.js index 93ea6fa6a..1859fba07 100644 --- a/src/babel/preval-extract/__tests__/index.spec.js +++ b/src/babel/preval-extract/__tests__/index.spec.js @@ -157,7 +157,6 @@ describe('preval-extract/index', () => { visitor.TaggedTemplateExpression( { - type: 'TaggedTemplateExpression', node: { type: 'TaggedTemplateExpression', tag: { @@ -177,7 +176,6 @@ describe('preval-extract/index', () => { expect(() => { visitor.TaggedTemplateExpression( { - type: 'TaggedTemplateExpression', node: { type: 'TaggedTemplateExpression', tag: { @@ -188,13 +186,15 @@ describe('preval-extract/index', () => { parentPath: { isVariableDeclarator: () => false, }, + findParent: () => null, + buildCodeFrameError: text => new Error(text), }, { skipFile: false, foundLinariaTaggedLiterals: false, } ); - }).toThrowError(); + }).toThrowErrorMatchingSnapshot(); }); it('should collect requirements and call prevalStyles', () => { @@ -202,7 +202,6 @@ describe('preval-extract/index', () => { visitor.TaggedTemplateExpression( { - type: 'TaggedTemplateExpression', node: { type: 'TaggedTemplateExpression', tag: { @@ -213,7 +212,88 @@ describe('preval-extract/index', () => { parentPath: { isVariableDeclarator: () => true, }, + parent: { + id: { + name: 'test', + }, + }, + traverse: () => {}, + findParent: () => null, + replaceWith: () => {}, + }, + { + skipFile: false, + foundLinariaTaggedLiterals: false, + } + ); + + expect(prevalStyles).toHaveBeenCalled(); + expect(prevalStyles.mock.calls[0][1]).toBe('test'); + + const propertyParent = { + node: { + type: 'ObjectProperty', + key: { + type: 'Identifier', + name: 'foo', + }, + }, + isObjectProperty: () => true, + isJSXOpeningElement: () => false, + }; + + visitor.TaggedTemplateExpression( + { + node: { + type: 'TaggedTemplateExpression', + tag: { + type: 'Identifier', + name: 'css', + }, + }, + parentPath: { + isVariableDeclarator: () => false, + }, + traverse: () => {}, + findParent: check => (check(propertyParent) ? propertyParent : null), + replaceWith: () => {}, + }, + { + skipFile: false, + foundLinariaTaggedLiterals: false, + } + ); + + expect(prevalStyles).toHaveBeenCalled(); + expect(prevalStyles.mock.calls[1][1]).toBe('foo'); + + const jsxParent = { + node: { + type: 'JSXOpeningElement', + name: { + type: 'JSXIdentifier', + name: 'article', + }, + }, + isObjectProperty: () => false, + isJSXOpeningElement: () => true, + }; + + visitor.TaggedTemplateExpression( + { + node: { + type: 'TaggedTemplateExpression', + tag: { + type: 'Identifier', + name: 'css', + }, + }, + parentPath: { + isVariableDeclarator: () => false, + }, traverse: () => {}, + findParent: check => (check(jsxParent) ? jsxParent : null), + replaceWith: () => {}, }, { skipFile: false, @@ -222,6 +302,7 @@ describe('preval-extract/index', () => { ); expect(prevalStyles).toHaveBeenCalled(); + expect(prevalStyles.mock.calls[2][1]).toBe('article'); }); }); diff --git a/src/babel/preval-extract/__tests__/prevalStyles.spec.js b/src/babel/preval-extract/__tests__/prevalStyles.spec.js index 2658ef196..70e044dd8 100644 --- a/src/babel/preval-extract/__tests__/prevalStyles.spec.js +++ b/src/babel/preval-extract/__tests__/prevalStyles.spec.js @@ -38,12 +38,8 @@ function runAssertions(expectedReplacement) { }, }; - prevalStyles(babel, path, { filename: 'test.js' }, []); + prevalStyles(babel, 'header', path, { filename: 'test.js' }, []); - expect(path.parentPath.node.leadingComments).toEqual([ - { type: 'CommentBlock', value: 'linaria-output' }, - ]); - expect(path.parentPath.node.init.value).toEqual('header__abc123'); expect(getReplacement).toHaveBeenCalled(); expect(getReplacement.mock.calls[0][0][0].code).toMatch(expectedReplacement); expect(clearLocalModulesFromCache).toHaveBeenCalled(); diff --git a/src/babel/preval-extract/__tests__/resolveSource.spec.js b/src/babel/preval-extract/__tests__/resolveSource.spec.js index 83777aee3..ff3680b46 100644 --- a/src/babel/preval-extract/__tests__/resolveSource.spec.js +++ b/src/babel/preval-extract/__tests__/resolveSource.spec.js @@ -61,7 +61,7 @@ describe('preval-extract/resolveSource', () => { kind, path: { getSource: () => 'source', - parentPath: { isVariableDeclaration: () => false }, + isVariableDeclarator: () => false, node: { loc: { start: { line: 0, column: 0 } }, }, @@ -79,12 +79,7 @@ describe('preval-extract/resolveSource', () => { kind: 'const', path: { getSource: () => 'source', - parentPath: { - isVariableDeclaration: () => true, - node: { - leadingComments: [{ value: 'linaria-output' }], - }, - }, + isVariableDeclarator: () => true, node: { type: 'VariableDeclarator', id: { @@ -94,13 +89,14 @@ describe('preval-extract/resolveSource', () => { init: { type: 'StringLiteral', value: 'value', + leadingComments: [{ value: 'linaria-output' }], }, loc: { start: { line: 0, column: 0 } }, }, }, }, { - code: 'const test = "value";', + code: 'const test = /*linaria-output*/"value";', loc: { line: 0, column: 0 }, } ); @@ -110,9 +106,7 @@ describe('preval-extract/resolveSource', () => { kind: 'const', path: { getSource: () => '', - parentPath: { - isVariableDeclaration: () => false, - }, + isVariableDeclarator: () => false, node: { loc: { start: { line: 0, column: 0 } }, }, diff --git a/src/babel/preval-extract/__tests__/validators.spec.js b/src/babel/preval-extract/__tests__/validators.spec.js index cdcca1317..a4bf3e91f 100644 --- a/src/babel/preval-extract/__tests__/validators.spec.js +++ b/src/babel/preval-extract/__tests__/validators.spec.js @@ -1,7 +1,6 @@ import { types } from 'babel-core'; import { isLinariaTaggedTemplate, - ensureTagIsAssignedToAVariable, shouldTraverseExternalIds, isExcluded, } from '../validators'; @@ -111,25 +110,6 @@ describe('preval-extract/validators', () => { }); }); - describe('ensureTagIsAssignedToAVariable', () => { - it('should throw error if tag is not assigned to a variable', () => { - expect(() => { - ensureTagIsAssignedToAVariable({ - parentPath: { isVariableDeclarator: () => false }, - buildCodeFrameError: msg => new Error(msg), - }); - }).toThrowError(); - }); - - it('should not throw anything if tag is assigned to a variable', () => { - expect(() => { - ensureTagIsAssignedToAVariable({ - parentPath: { isVariableDeclarator: () => true }, - }); - }).not.toThrowError(); - }); - }); - describe('shouldTraverseExternalIds', () => { it('should return false if path is a import specifier', () => { expect( diff --git a/src/babel/preval-extract/index.js b/src/babel/preval-extract/index.js index f1615cf11..0a798be82 100644 --- a/src/babel/preval-extract/index.js +++ b/src/babel/preval-extract/index.js @@ -13,7 +13,6 @@ import type { import { shouldTraverseExternalIds, isLinariaTaggedTemplate, - ensureTagIsAssignedToAVariable, isExcluded, } from './validators'; import { getSelfBinding } from './utils'; @@ -67,11 +66,11 @@ export default (babel: BabelCore) => { enter(path: NodePath<*>, state: State) { state.skipFile = // $FlowFixMe - path.container.tokens.findIndex( + path.container.tokens.some( token => token.type === 'CommentBlock' && token.value.includes('linaria-preval') - ) > -1; + ); state.foundLinariaTaggedLiterals = false; state.filename = state.file.opts.filename; }, @@ -90,17 +89,57 @@ export default (babel: BabelCore) => { state: State ) { if (!state.skipFile && isLinariaTaggedTemplate(types, path)) { - ensureTagIsAssignedToAVariable(path); + let title; + + if (path.parentPath.isVariableDeclarator()) { + title = path.parent.id.name; + } else { + const parent = path.findParent( + p => p.isObjectProperty() || p.isJSXOpeningElement() + ); + + if (parent) { + if (parent.isJSXOpeningElement()) { + title = parent.node.name.name; + } else { + title = parent.node.key.name; + } + } else { + throw path.buildCodeFrameError( + "Couldn't determine the class name for CSS template literal. Ensure that it's either:\n" + + '- Assigned to a variable\n' + + '- Is an object property\n' + + '- Is a prop in a JSX element' + ); + } + } state.foundLinariaTaggedLiterals = true; const requirements: RequirementSource[] = []; + path.traverse(cssTaggedTemplateRequirementsVisitor, { requirements, types, }); - prevalStyles(babel, path, state, requirements); + const className = prevalStyles( + babel, + title, + path, + state, + requirements + ); + + const leadingComments = [ + { + type: 'CommentBlock', + value: 'linaria-output', + }, + ]; + + path.replaceWith(className); + path.node.leadingComments = leadingComments; } }, }, diff --git a/src/babel/preval-extract/prevalStyles.js b/src/babel/preval-extract/prevalStyles.js index 10fb396da..13096fb44 100644 --- a/src/babel/preval-extract/prevalStyles.js +++ b/src/babel/preval-extract/prevalStyles.js @@ -32,13 +32,13 @@ import { export default function( babel: BabelCore, + title: string, path: NodePath< BabelTaggedTemplateExpression >, state: State, requirements: RequirementSource[] ) { - const title = path.parent.id.name; const env = process.env.NODE_ENV || process.env.BABEL_ENV; const replacement = getReplacement([ @@ -62,16 +62,5 @@ export default function( resolve(state.filename) ); - path.parentPath.node.init = babel.types.stringLiteral(className); - - const variableDeclarationPath = path.findParent( - babel.types.isVariableDeclaration - ); - - variableDeclarationPath.node.leadingComments = [ - { - type: 'CommentBlock', - value: 'linaria-output', - }, - ]; + return babel.types.stringLiteral(className); } diff --git a/src/babel/preval-extract/resolveSource.js b/src/babel/preval-extract/resolveSource.js index 868354372..b83e5ac9c 100644 --- a/src/babel/preval-extract/resolveSource.js +++ b/src/babel/preval-extract/resolveSource.js @@ -21,14 +21,12 @@ function getSourceForVariableDeclarationFromAst( } function isLinariaOutput(path: NodePath>) { - const parent = path.parentPath; - return ( - parent.isVariableDeclaration() && - parent.node.leadingComments && - parent.node.leadingComments.findIndex( + path.isVariableDeclarator() && + path.node.init.leadingComments && + path.node.init.leadingComments.some( comment => comment.value === 'linaria-output' - ) > -1 + ) ); } diff --git a/src/babel/preval-extract/validators.js b/src/babel/preval-extract/validators.js index cdf815d54..f169f6503 100644 --- a/src/babel/preval-extract/validators.js +++ b/src/babel/preval-extract/validators.js @@ -38,17 +38,6 @@ export function isLinariaTaggedTemplate( return false; } -export function ensureTagIsAssignedToAVariable( - path: NodePath> -) { - const parent = path.parentPath; - if (!parent.isVariableDeclarator()) { - throw path.buildCodeFrameError( - "Linaria's template literals must be assigned to a variable" - ); - } -} - export function shouldTraverseExternalIds(path: NodePath) { if (path.isImportDefaultSpecifier() || path.isImportSpecifier()) { return false; diff --git a/src/babel/rewire-imports/index.js b/src/babel/rewire-imports/index.js index 746007942..69b8e3a21 100644 --- a/src/babel/rewire-imports/index.js +++ b/src/babel/rewire-imports/index.js @@ -25,8 +25,11 @@ export default ({ types }: { types: BabelTypes }) => ({ enter(path: NodePath, state: State) { state.shouldSkip = // $FlowFixMe - path.container.comments.length && - path.container.comments[0].value.includes('linaria-preval'); + path.container.tokens.some( + token => + token.type === 'CommentBlock' && + token.value.includes('linaria-preval') + ); }, }, ImportDeclaration(path: NodePath, state: State) { diff --git a/src/babel/types.js b/src/babel/types.js index d033f7488..901fb11ea 100644 --- a/src/babel/types.js +++ b/src/babel/types.js @@ -5,7 +5,9 @@ export type NodePath = { loc: { start: { line: number, column: number }, }, + leadingComments: Array, }, + replaceWith: (path: any) => void, parent: Object, parentPath: NodePath<*>, scope: { @@ -18,6 +20,8 @@ export type NodePath = { isImportDefaultSpecifier: () => boolean, isImportSpecifier: () => boolean, isVariableDeclaration: () => boolean, + isObjectProperty: () => boolean, + isJSXOpeningElement: () => boolean, getSource: () => string, buildCodeFrameError: (message: string) => Error, traverse: (visitor: { [key: string]: Function }, thisArgs?: any) => void, @@ -33,6 +37,11 @@ export type BabelCore = { types: BabelTypes, }; +export type BabelCommentBlock = { + type: string, + value: string, +}; + export type BabelObjectExpression = { properties: any[], type: string,