From b69a2a9a6501c47790e0966811631b159801690f Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Sat, 13 May 2017 16:20:58 +0200 Subject: [PATCH 01/10] Add support for plugins --- readme.md | 34 +++++++++++++++++ src/_utils.js | 26 +++++++++++++ src/babel-external.js | 16 ++++++-- src/babel.js | 14 +++++-- test/fixtures/plugins/another-plugin.js | 1 + test/fixtures/plugins/invalid-plugin.js | 1 + test/fixtures/plugins/plugin.js | 1 + test/plugins.js | 51 +++++++++++++++++++++++++ 8 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 test/fixtures/plugins/another-plugin.js create mode 100644 test/fixtures/plugins/invalid-plugin.js create mode 100644 test/fixtures/plugins/plugin.js create mode 100644 test/plugins.js diff --git a/readme.md b/readme.md index 7cff3195..4f0aeb8c 100644 --- a/readme.md +++ b/readme.md @@ -48,6 +48,7 @@ export default () => ( - Runtime size of just **2kb** (gzipped, from 6kb) - Complete isolation: Selectors, animations, keyframes - Built-in CSS vendor prefixing +- CSS Preprocessing via Plugins - Very fast, minimal and efficient transpilation (see below) - High-performance runtime-CSS-injection when not server-rendering - Future-proof: Equivalent to server-renderable "Shadow CSS" @@ -113,6 +114,39 @@ module.exports = `div { color: green; }` // module.exports = { styles: `div { color: green; }` } ``` +### CSS Preprocessing via Plugins + +Styles can be preprocessed via plugins. + +Plugins are regular JavaScript modules that export a simple function with the following signature: + +```js +(css: string) => string +``` + +Basically they accept a CSS string in input, optionally modify it and finally return it. + +Plugins make it possible to use popular preprocessors like SASS, Less, Stylus, PostCSS or apply custom transformations to the styles. + +To register a plugin add an option `plugins` for `styled-jsx/babel` to your `.babelrc`. `plugins` must be an array of module names or *full* paths for local plugins. + +```json +{ + "plugins": [ + [ + "styled-jsx/babel", + { "plugins": ["my-styled-jsx-plugin-package", "/full/path/to/local/plugin"] } + ] + ] +} +``` + +In order to resolve local plugins paths you can use NodeJS' [require.resolve](https://nodejs.org/api/globals.html#globals_require_resolve). + +Plugins are applied in definition order left to right before styles are scoped. + +N.B. when applying the plugins styled-jsx replaces template literals expressions with placeholders e.g. `%%styledjsxexpression_ExprNumber%%` because otherwise CSS parsers would get invalid CSS. + ### Targeting The Root Notice that the parent `
` above also gets a `data-jsx` attribute. We do this so that diff --git a/src/_utils.js b/src/_utils.js index e33467e8..b2c887a3 100644 --- a/src/_utils.js +++ b/src/_utils.js @@ -258,3 +258,29 @@ export const addSourceMaps = (code, generator, filename) => convert.fromObject(generator).toComment({ multiline: true }), `/*@ sourceURL=${filename} */` ].join('\n') +) + +export const combinePlugins = plugins => { + if (!plugins) { + return css => css + } + + if (!Array.isArray(plugins) || plugins.some(p => typeof p !== 'string')) { + throw new Error('`plugins` must be an array of plugins names') + } + + const loadedPlugins = plugins.map(plugin => { + // eslint-disable-next-line import/no-dynamic-require + const p = require(plugin) + return (p && p.default) || p + }) + + loadedPlugins.forEach((plugin, i) => { + const type = typeof plugin + if (type !== 'function') { + throw new Error(`Expected plugin ${plugins[i]} to be a function but instead got ${type}`) + } + }) + + return loadedPlugins.reduce((plugin, nextPlugin) => css => nextPlugin(plugin(css))) +} diff --git a/src/babel-external.js b/src/babel-external.js index 14aecac9..9e450838 100644 --- a/src/babel-external.js +++ b/src/babel-external.js @@ -9,9 +9,11 @@ import { makeStyledJsxCss, isValidCss, makeSourceMapGenerator, - addSourceMaps + addSourceMaps, + combinePlugins } from './_utils' +let plugins const getCss = (path, validate = false) => { if (!path.isTemplateLiteral() && !path.isStringLiteral()) { return @@ -40,7 +42,7 @@ const getStyledJsx = (css, opts, path) => { const offset = path.get('loc').node.start compiledCss = [/* global */ '', prefix].map(prefix => addSourceMaps( - transform(prefix, css.modified || css, { + transform(prefix, opts.plugins(css.modified || css), { generator, offset, filename @@ -51,7 +53,7 @@ const getStyledJsx = (css, opts, path) => { ) } else { compiledCss = ['', prefix].map(prefix => - transform(prefix, css.modified || css) + transform(prefix, opts.plugins(css.modified || css)) ) } globalCss = compiledCss[0] @@ -158,13 +160,19 @@ const callVisitor = (visitor, path, state) => { validate: state.opts.validate || opts.validate, sourceMaps: opts.sourceMaps, sourceFileName: opts.sourceFileName, - file + file, + plugins }) } export default function() { return { visitor: { + Program(path, state) { + if (!plugins) { + plugins = combinePlugins(state.opts.plugins) + } + }, ExportDefaultDeclaration(path, state) { callVisitor(exportDefaultDeclarationVisitor, path, state) }, diff --git a/src/babel.js b/src/babel.js index c90c55af..acd4bafa 100644 --- a/src/babel.js +++ b/src/babel.js @@ -20,7 +20,8 @@ import { validateExpression, generateAttribute, makeSourceMapGenerator, - addSourceMaps + addSourceMaps, + combinePlugins } from './_utils' import { @@ -29,6 +30,7 @@ import { MARKUP_ATTRIBUTE_EXTERNAL } from './_constants' +let plugins const getPrefix = id => `[${MARKUP_ATTRIBUTE}="${id}"]` const callExternalVisitor = (visitor, path, state) => { const { file } = state @@ -37,7 +39,8 @@ const callExternalVisitor = (visitor, path, state) => { validate: true, sourceMaps: opts.sourceMaps, sourceFileName: opts.sourceFileName, - file + file, + plugins }) } @@ -301,7 +304,7 @@ export default function({ types: t }) { transformedCss = addSourceMaps( transform( isGlobal ? '' : getPrefix(state.jsxId), - css.modified || css, + plugins(css.modified || css), { generator, offset: loc.start, @@ -314,7 +317,7 @@ export default function({ types: t }) { } else { transformedCss = transform( isGlobal ? '' : getPrefix(state.jsxId), - css.modified || css + plugins(css.modified || css) ) } @@ -334,6 +337,9 @@ export default function({ types: t }) { state.ignoreClosing = null state.file.hasJSXStyle = false state.imports = [] + if (!plugins) { + plugins = combinePlugins(state.opts.plugins) + } }, exit({ node, scope }, state) { if (!(state.file.hasJSXStyle && !scope.hasBinding(STYLE_COMPONENT))) { diff --git a/test/fixtures/plugins/another-plugin.js b/test/fixtures/plugins/another-plugin.js new file mode 100644 index 00000000..14c586f0 --- /dev/null +++ b/test/fixtures/plugins/another-plugin.js @@ -0,0 +1 @@ +export default css => css.replace(/[\{\}]/g, '@') diff --git a/test/fixtures/plugins/invalid-plugin.js b/test/fixtures/plugins/invalid-plugin.js new file mode 100644 index 00000000..aa3b6937 --- /dev/null +++ b/test/fixtures/plugins/invalid-plugin.js @@ -0,0 +1 @@ +export default 'invalid' diff --git a/test/fixtures/plugins/plugin.js b/test/fixtures/plugins/plugin.js new file mode 100644 index 00000000..ca989db6 --- /dev/null +++ b/test/fixtures/plugins/plugin.js @@ -0,0 +1 @@ +export default css => `TEST (${css}) EOTEST` diff --git a/test/plugins.js b/test/plugins.js new file mode 100644 index 00000000..702db37a --- /dev/null +++ b/test/plugins.js @@ -0,0 +1,51 @@ +// Packages +import test from 'ava' + +// Ours +import {combinePlugins} from '../src/_utils' +import testPlugin1 from './fixtures/plugins/plugin' +import testPlugin2 from './fixtures/plugins/another-plugin' + +test('combinePlugins returns an identity function when plugins is undefined', t => { + const test = 'test' + const plugins = combinePlugins() + t.is(plugins(test), test) +}) + +test('combinePlugins throws if plugins is not an array', t => { + t.throws(() => { + combinePlugins(2) + }) +}) + +test('combinePlugins throws if plugins is not an array of strings', t => { + t.throws(() => { + combinePlugins(['test', 2]) + }) +}) + +test('combinePlugins throws if loaded plugins are not functions', t => { + t.throws(() => { + combinePlugins([ + require.resolve('./fixtures/plugins/plugin'), + require.resolve('./fixtures/plugins/invalid-plugin') + ]) + }) +}) + +test('combinePlugins works with a single plugin', t => { + const plugins = combinePlugins([ + require.resolve('./fixtures/plugins/plugin') + ]) + + t.is(testPlugin1('test'), plugins('test')) +}) + +test('combinePlugins applies plugins left to right', t => { + const plugins = combinePlugins([ + require.resolve('./fixtures/plugins/plugin'), + require.resolve('./fixtures/plugins/another-plugin') + ]) + + t.is(testPlugin2(testPlugin1('test')), plugins('test')) +}) From 56b647d75b0186a0b74eaf97df9b6e4a4087173f Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Sat, 13 May 2017 16:49:53 +0200 Subject: [PATCH 02/10] Add transformation test --- test/fixtures/plugins/another-plugin.js | 2 +- test/fixtures/with-plugins.js | 10 ++++++++++ test/fixtures/with-plugins.out.js | 9 +++++++++ test/plugins.js | 21 +++++++++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/with-plugins.js create mode 100644 test/fixtures/with-plugins.out.js diff --git a/test/fixtures/plugins/another-plugin.js b/test/fixtures/plugins/another-plugin.js index 14c586f0..c90c5381 100644 --- a/test/fixtures/plugins/another-plugin.js +++ b/test/fixtures/plugins/another-plugin.js @@ -1 +1 @@ -export default css => css.replace(/[\{\}]/g, '@') +export default css => css.replace(/div/g, 'span') diff --git a/test/fixtures/with-plugins.js b/test/fixtures/with-plugins.js new file mode 100644 index 00000000..29f8ace3 --- /dev/null +++ b/test/fixtures/with-plugins.js @@ -0,0 +1,10 @@ +import styles from './styles' +const color = 'red' + +export default () => ( +
+

test

+ + +
+) diff --git a/test/fixtures/with-plugins.out.js b/test/fixtures/with-plugins.out.js new file mode 100644 index 00000000..4b5c861a --- /dev/null +++ b/test/fixtures/with-plugins.out.js @@ -0,0 +1,9 @@ +import _JSXStyle from 'styled-jsx/style'; +import styles from './styles'; +const color = 'red'; + +export default (() =>
+

test

+ <_JSXStyle styleId={4216192053} css={`span.${color}[data-jsx="4216192053"] {color: ${otherColor}}`} /> + <_JSXStyle styleId={styles.__scopedHash} css={styles.__scoped} /> +
); diff --git a/test/plugins.js b/test/plugins.js index 702db37a..fcdbb66e 100644 --- a/test/plugins.js +++ b/test/plugins.js @@ -2,10 +2,25 @@ import test from 'ava' // Ours +import babelPlugin from '../src/babel' import {combinePlugins} from '../src/_utils' +import _transform from './_transform' +import read from './_read' import testPlugin1 from './fixtures/plugins/plugin' import testPlugin2 from './fixtures/plugins/another-plugin' +const transform = (file, opts = {}) => ( + _transform(file, { + plugins: [ + [ + babelPlugin, + {plugins: [require.resolve('./fixtures/plugins/another-plugin')]} + ] + ], + ...opts + }) +) + test('combinePlugins returns an identity function when plugins is undefined', t => { const test = 'test' const plugins = combinePlugins() @@ -49,3 +64,9 @@ test('combinePlugins applies plugins left to right', t => { t.is(testPlugin2(testPlugin1('test')), plugins('test')) }) + +test('applies plugins', async t => { + const {code} = await transform('./fixtures/with-plugins.js') + const out = await read('./fixtures/with-plugins.out.js') + t.is(code, out.trim()) +}) From 4611d687b43c172d485c4ceb699f4ab682f41dba Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Sat, 13 May 2017 20:40:43 +0200 Subject: [PATCH 03/10] remove extra loop --- src/_utils.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/_utils.js b/src/_utils.js index b2c887a3..5bd95bdd 100644 --- a/src/_utils.js +++ b/src/_utils.js @@ -269,18 +269,19 @@ export const combinePlugins = plugins => { throw new Error('`plugins` must be an array of plugins names') } - const loadedPlugins = plugins.map(plugin => { - // eslint-disable-next-line import/no-dynamic-require - const p = require(plugin) - return (p && p.default) || p - }) - - loadedPlugins.forEach((plugin, i) => { - const type = typeof plugin - if (type !== 'function') { - throw new Error(`Expected plugin ${plugins[i]} to be a function but instead got ${type}`) - } - }) + return plugins + .map((plugin, i) => { + // eslint-disable-next-line import/no-dynamic-require + let p = require(plugin) + if (p.default) { + p = p.default + } - return loadedPlugins.reduce((plugin, nextPlugin) => css => nextPlugin(plugin(css))) + const type = typeof p + if (type !== 'function') { + throw new Error(`Expected plugin ${plugins[i]} to be a function but instead got ${type}`) + } + return p + }) + .reduce((plugin, nextPlugin) => css => nextPlugin(plugin(css))) } From afed145a5f50c22030d6411ab56d152649d381e3 Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Sat, 27 May 2017 18:55:07 +0200 Subject: [PATCH 04/10] Add missing parenthesis --- src/_utils.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/_utils.js b/src/_utils.js index 5bd95bdd..b842d1ab 100644 --- a/src/_utils.js +++ b/src/_utils.js @@ -258,7 +258,6 @@ export const addSourceMaps = (code, generator, filename) => convert.fromObject(generator).toComment({ multiline: true }), `/*@ sourceURL=${filename} */` ].join('\n') -) export const combinePlugins = plugins => { if (!plugins) { @@ -279,7 +278,9 @@ export const combinePlugins = plugins => { const type = typeof p if (type !== 'function') { - throw new Error(`Expected plugin ${plugins[i]} to be a function but instead got ${type}`) + throw new Error( + `Expected plugin ${plugins[i]} to be a function but instead got ${type}` + ) } return p }) From 83ae63738462c272cc0c1c08f01213562f275933 Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Sat, 27 May 2017 20:47:05 +0200 Subject: [PATCH 05/10] Add plugin options support --- src/_utils.js | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/_utils.js b/src/_utils.js index b842d1ab..ea0d3a2a 100644 --- a/src/_utils.js +++ b/src/_utils.js @@ -264,12 +264,25 @@ export const combinePlugins = plugins => { return css => css } - if (!Array.isArray(plugins) || plugins.some(p => typeof p !== 'string')) { - throw new Error('`plugins` must be an array of plugins names') + if ( + !Array.isArray(plugins) || + plugins.some(p => !Array.isArray(p) && typeof p !== 'string') + ) { + throw new Error( + '`plugins` must be an array of plugins names (string) or an array `[plugin-name, {options}]`' + ) } + const env = typeof window === 'undefined' ? 'compile' : 'runtime' + return plugins .map((plugin, i) => { + let options = {} + if (Array.isArray(plugin)) { + plugin = plugin[0] + options = plugin[1] || {} + } + // eslint-disable-next-line import/no-dynamic-require let p = require(plugin) if (p.default) { @@ -282,7 +295,17 @@ export const combinePlugins = plugins => { `Expected plugin ${plugins[i]} to be a function but instead got ${type}` ) } - return p + return { + plugin: p, + settings: { + env, + options + } + } }) - .reduce((plugin, nextPlugin) => css => nextPlugin(plugin(css))) + .reduce( + (current, next) => css => + next.plugin(current ? current(css) : css, next.settings), + null + ) } From 0a21257aa14238c80d2b7592babd067a18a13de7 Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Sun, 28 May 2017 08:36:48 +0200 Subject: [PATCH 06/10] Add plugins options --- readme.md | 43 +++++++++++++++++++++++++++++- src/_utils.js | 8 +++--- src/babel-external.js | 2 +- src/babel.js | 10 ++++--- test/__snapshots__/index.js.snap | 18 +++++++++++++ test/__snapshots__/plugins.js.snap | 13 +++++++++ test/fixtures/plugins/options.js | 1 + test/index.js | 9 ++++++- test/plugins.js | 30 +++++++++++++-------- 9 files changed, 112 insertions(+), 22 deletions(-) create mode 100644 test/__snapshots__/plugins.js.snap create mode 100644 test/fixtures/plugins/options.js diff --git a/readme.md b/readme.md index 4f0aeb8c..f66f740a 100644 --- a/readme.md +++ b/readme.md @@ -121,7 +121,7 @@ Styles can be preprocessed via plugins. Plugins are regular JavaScript modules that export a simple function with the following signature: ```js -(css: string) => string +(css: string, settings: Object) => string ``` Basically they accept a CSS string in input, optionally modify it and finally return it. @@ -147,6 +147,47 @@ Plugins are applied in definition order left to right before styles are scoped. N.B. when applying the plugins styled-jsx replaces template literals expressions with placeholders e.g. `%%styledjsxexpression_ExprNumber%%` because otherwise CSS parsers would get invalid CSS. +#### Plugin options and settings + +Users can set plugin options by registering a plugin as an array that contains +the plugin path and an options object. + +```json +{ + "plugins": [ + [ + "styled-jsx/babel", + { + "plugins": [ + ["my-styled-jsx-plugin-package", { "exampleOption": true }] + ], + "sourceMaps": true + } + ] + ] +} +``` + +Each plugin receives a `settings` object as second argument which contains +the babel and user options: + +```js +export default (css, settings) => { /* ... */ } +``` + +The `settings` object has the following shape: + +```js +{ + sourceMaps: true, + + // user options + options: { + exampleOption: true + } +} +``` + ### Targeting The Root Notice that the parent `
` above also gets a `data-jsx` attribute. We do this so that diff --git a/src/_utils.js b/src/_utils.js index ea0d3a2a..06e4bcee 100644 --- a/src/_utils.js +++ b/src/_utils.js @@ -259,7 +259,7 @@ export const addSourceMaps = (code, generator, filename) => `/*@ sourceURL=${filename} */` ].join('\n') -export const combinePlugins = plugins => { +export const combinePlugins = (plugins, opts) => { if (!plugins) { return css => css } @@ -273,14 +273,12 @@ export const combinePlugins = plugins => { ) } - const env = typeof window === 'undefined' ? 'compile' : 'runtime' - return plugins .map((plugin, i) => { let options = {} if (Array.isArray(plugin)) { - plugin = plugin[0] options = plugin[1] || {} + plugin = plugin[0] } // eslint-disable-next-line import/no-dynamic-require @@ -298,7 +296,7 @@ export const combinePlugins = plugins => { return { plugin: p, settings: { - env, + ...opts, options } } diff --git a/src/babel-external.js b/src/babel-external.js index 9e450838..c9b9626f 100644 --- a/src/babel-external.js +++ b/src/babel-external.js @@ -158,7 +158,7 @@ const callVisitor = (visitor, path, state) => { const { opts } = file visitor(path, { validate: state.opts.validate || opts.validate, - sourceMaps: opts.sourceMaps, + sourceMaps: state.opts.sourceMaps || opts.sourceMaps, sourceFileName: opts.sourceFileName, file, plugins diff --git a/src/babel.js b/src/babel.js index acd4bafa..8830304b 100644 --- a/src/babel.js +++ b/src/babel.js @@ -37,7 +37,7 @@ const callExternalVisitor = (visitor, path, state) => { const { opts } = file visitor(path, { validate: true, - sourceMaps: opts.sourceMaps, + sourceMaps: state.opts.sourceMaps || opts.sourceMaps, sourceFileName: opts.sourceFileName, file, plugins @@ -295,7 +295,9 @@ export default function({ types: t }) { // We replace styles with the function call const [id, css, loc] = state.styles.shift() - const useSourceMaps = Boolean(state.file.opts.sourceMaps) + const useSourceMaps = Boolean( + state.opts.sourceMaps || state.file.opts.sourceMaps + ) let transformedCss if (useSourceMaps) { @@ -338,7 +340,9 @@ export default function({ types: t }) { state.file.hasJSXStyle = false state.imports = [] if (!plugins) { - plugins = combinePlugins(state.opts.plugins) + plugins = combinePlugins(state.opts.plugins, { + sourceMaps: state.opts.sourceMaps || state.file.opts.sourceMaps + }) } }, exit({ node, scope }, state) { diff --git a/test/__snapshots__/index.js.snap b/test/__snapshots__/index.js.snap index 3c632c15..9884ce39 100644 --- a/test/__snapshots__/index.js.snap +++ b/test/__snapshots__/index.js.snap @@ -1,5 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`generates source maps (babel options) 1`] = ` +"import _JSXStyle from 'styled-jsx/style'; +export default (() =>
+

test

+

woot

+ <_JSXStyle styleId={188072295} css={\\"p[data-jsx=\\\\\\"188072295\\\\\\"]{color:red}\\\\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJzb3VyY2UtbWFwcy5qcyIsInNvdXJjZXNDb250ZW50IjpbXX0= */\\\\n/*@ sourceURL=source-maps.js */\\"} /> +
);" +`; + +exports[`generates source maps (plugin options) 1`] = ` +"import _JSXStyle from 'styled-jsx/style'; +export default (() =>
+

test

+

woot

+ <_JSXStyle styleId={188072295} css={\\"p[data-jsx=\\\\\\"188072295\\\\\\"]{color:red}\\\\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJzb3VyY2UtbWFwcy5qcyIsInNvdXJjZXNDb250ZW50IjpbXX0= */\\\\n/*@ sourceURL=source-maps.js */\\"} /> +
);" +`; + exports[`generates source maps 1`] = ` "import _JSXStyle from 'styled-jsx/style'; export default (() =>
diff --git a/test/__snapshots__/plugins.js.snap b/test/__snapshots__/plugins.js.snap new file mode 100644 index 00000000..d71c9954 --- /dev/null +++ b/test/__snapshots__/plugins.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`applies plugins 1`] = ` +"import _JSXStyle from 'styled-jsx/style'; +import styles from './styles'; +const color = 'red'; + +export default (() =>
+

test

+ <_JSXStyle styleId={4216192053} css={\`span[data-jsx=\\"4216192053\\"].\${color}[data-jsx=\\"4216192053\\"]{color:\${otherColor}}\`} /> + <_JSXStyle styleId={styles.__scopedHash} css={styles.__scoped} /> +
);" +`; diff --git a/test/fixtures/plugins/options.js b/test/fixtures/plugins/options.js new file mode 100644 index 00000000..d0c4376d --- /dev/null +++ b/test/fixtures/plugins/options.js @@ -0,0 +1 @@ +export default (css, settings) => settings.options.test diff --git a/test/index.js b/test/index.js index 905a9b77..52668f5c 100644 --- a/test/index.js +++ b/test/index.js @@ -40,13 +40,20 @@ test('works with global styles', async t => { t.snapshot(code) }) -test('generates source maps', async t => { +test('generates source maps (babel options)', async t => { const { code } = await transform('./fixtures/source-maps.js', { sourceMaps: true }) t.snapshot(code) }) +test('generates source maps (plugin options)', async t => { + const { code } = await _transform('./fixtures/source-maps.js', { + plugins: [[plugin, { sourceMaps: true }]] + }) + t.snapshot(code) +}) + test('mixed global and scoped', async t => { const { code } = await transform('./fixtures/mixed-global-scoped.js') t.snapshot(code) diff --git a/test/plugins.js b/test/plugins.js index fcdbb66e..09e9956e 100644 --- a/test/plugins.js +++ b/test/plugins.js @@ -3,23 +3,21 @@ import test from 'ava' // Ours import babelPlugin from '../src/babel' -import {combinePlugins} from '../src/_utils' +import { combinePlugins } from '../src/_utils' import _transform from './_transform' -import read from './_read' import testPlugin1 from './fixtures/plugins/plugin' import testPlugin2 from './fixtures/plugins/another-plugin' -const transform = (file, opts = {}) => ( +const transform = (file, opts = {}) => _transform(file, { plugins: [ [ babelPlugin, - {plugins: [require.resolve('./fixtures/plugins/another-plugin')]} + { plugins: [require.resolve('./fixtures/plugins/another-plugin')] } ] ], ...opts }) -) test('combinePlugins returns an identity function when plugins is undefined', t => { const test = 'test' @@ -49,13 +47,24 @@ test('combinePlugins throws if loaded plugins are not functions', t => { }) test('combinePlugins works with a single plugin', t => { - const plugins = combinePlugins([ - require.resolve('./fixtures/plugins/plugin') - ]) + const plugins = combinePlugins([require.resolve('./fixtures/plugins/plugin')]) t.is(testPlugin1('test'), plugins('test')) }) +test('combinePlugins works with options', t => { + const expectedOption = 'my-test' + const plugins = combinePlugins([ + [ + require.resolve('./fixtures/plugins/options'), + { + test: expectedOption + } + ] + ]) + t.is(plugins(''), expectedOption) +}) + test('combinePlugins applies plugins left to right', t => { const plugins = combinePlugins([ require.resolve('./fixtures/plugins/plugin'), @@ -66,7 +75,6 @@ test('combinePlugins applies plugins left to right', t => { }) test('applies plugins', async t => { - const {code} = await transform('./fixtures/with-plugins.js') - const out = await read('./fixtures/with-plugins.out.js') - t.is(code, out.trim()) + const { code } = await transform('./fixtures/with-plugins.js') + t.snapshot(code) }) From 2fdb857676c7c9a5cffb78f5029ed289081b6f3e Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Sun, 28 May 2017 09:49:24 +0200 Subject: [PATCH 07/10] Improve readability of the combinePlugins reducer --- src/_utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_utils.js b/src/_utils.js index 06e4bcee..c18d8f7c 100644 --- a/src/_utils.js +++ b/src/_utils.js @@ -302,8 +302,8 @@ export const combinePlugins = (plugins, opts) => { } }) .reduce( - (current, next) => css => - next.plugin(current ? current(css) : css, next.settings), + (previous, { plugin, settings }) => css => + plugin(previous ? previous(css) : css, settings), null ) } From e7327b93ffa426c3004a3557ed1515bfbca6953e Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Sun, 28 May 2017 11:45:18 +0200 Subject: [PATCH 08/10] Make vendor prefixing configurable --- readme.md | 9 ++++++++- src/babel-external.js | 18 ++++++++++++------ src/babel.js | 14 ++++++++++---- src/lib/style-transform.js | 3 +++ 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index f66f740a..f2c2119c 100644 --- a/readme.md +++ b/readme.md @@ -42,6 +42,11 @@ export default () => ( ) ``` +### Options + +* `sourceMaps` generates source maps (default: `false`) +* `vendorPrefix` turn on/off automatic vendor prefixing (default: `true`) + ## Features - Full CSS support, no tradeoffs in power @@ -172,14 +177,16 @@ Each plugin receives a `settings` object as second argument which contains the babel and user options: ```js -export default (css, settings) => { /* ... */ } +(css, settings) => { /* ... */ } ``` The `settings` object has the following shape: ```js { + // babel options sourceMaps: true, + vendorPrefix: true, // user options options: { diff --git a/src/babel-external.js b/src/babel-external.js index c9b9626f..8d8de5ea 100644 --- a/src/babel-external.js +++ b/src/babel-external.js @@ -26,7 +26,6 @@ const getCss = (path, validate = false) => { } const getStyledJsx = (css, opts, path) => { - const useSourceMaps = Boolean(opts.sourceMaps) const commonHash = hash(css.modified || css) const globalHash = `1${commonHash}` const scopedHash = `2${commonHash}` @@ -36,7 +35,7 @@ const getStyledJsx = (css, opts, path) => { const prefix = `[${MARKUP_ATTRIBUTE_EXTERNAL}~="${scopedHash}"]` const isTemplateLiteral = Boolean(css.modified) - if (useSourceMaps) { + if (opts.sourceMaps) { const generator = makeSourceMapGenerator(opts.file) const filename = opts.sourceFileName const offset = path.get('loc').node.start @@ -45,7 +44,8 @@ const getStyledJsx = (css, opts, path) => { transform(prefix, opts.plugins(css.modified || css), { generator, offset, - filename + filename, + vendorPrefix: opts.vendorPrefix }), generator, filename @@ -53,7 +53,9 @@ const getStyledJsx = (css, opts, path) => { ) } else { compiledCss = ['', prefix].map(prefix => - transform(prefix, opts.plugins(css.modified || css)) + transform(prefix, opts.plugins(css.modified || css), { + vendorPrefix: opts.vendorPrefix + }) ) } globalCss = compiledCss[0] @@ -161,7 +163,8 @@ const callVisitor = (visitor, path, state) => { sourceMaps: state.opts.sourceMaps || opts.sourceMaps, sourceFileName: opts.sourceFileName, file, - plugins + plugins, + vendorPrefix: state.opts.vendorPrefix }) } @@ -170,7 +173,10 @@ export default function() { visitor: { Program(path, state) { if (!plugins) { - plugins = combinePlugins(state.opts.plugins) + plugins = combinePlugins(state.opts.plugins, { + sourceMaps: state.opts.sourceMaps || state.file.opts.sourceMaps, + vendorPrefix: state.opts.vendorPrefix || true + }) } }, ExportDefaultDeclaration(path, state) { diff --git a/src/babel.js b/src/babel.js index 8830304b..937aac24 100644 --- a/src/babel.js +++ b/src/babel.js @@ -40,7 +40,8 @@ const callExternalVisitor = (visitor, path, state) => { sourceMaps: state.opts.sourceMaps || opts.sourceMaps, sourceFileName: opts.sourceFileName, file, - plugins + plugins, + vendorPrefix: state.opts.vendorPrefix }) } @@ -310,7 +311,8 @@ export default function({ types: t }) { { generator, offset: loc.start, - filename + filename, + vendorPrefix: state.opts.vendorPrefix } ), generator, @@ -319,7 +321,10 @@ export default function({ types: t }) { } else { transformedCss = transform( isGlobal ? '' : getPrefix(state.jsxId), - plugins(css.modified || css) + plugins(css.modified || css), + { + vendorPrefix: state.opts.vendorPrefix + } ) } @@ -341,7 +346,8 @@ export default function({ types: t }) { state.imports = [] if (!plugins) { plugins = combinePlugins(state.opts.plugins, { - sourceMaps: state.opts.sourceMaps || state.file.opts.sourceMaps + sourceMaps: state.opts.sourceMaps || state.file.opts.sourceMaps, + vendorPrefix: state.opts.vendorPrefix || true }) } }, diff --git a/src/lib/style-transform.js b/src/lib/style-transform.js index 16a542d0..32012c58 100644 --- a/src/lib/style-transform.js +++ b/src/lib/style-transform.js @@ -82,6 +82,9 @@ function transform(prefix, styles, settings = {}) { generator = settings.generator offset = settings.offset filename = settings.filename + stylis.set({ + prefix: typeof settings.vendorPrefix === 'boolean' ? settings.vendorPrefix : true + }) return stylis(prefix, styles) } From df90cc59be0b7b3ce74f4fd858c7396f1630e004 Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Sun, 28 May 2017 14:19:55 +0200 Subject: [PATCH 09/10] fix default vendrPrefix logic --- src/babel-external.js | 7 +++++-- src/babel.js | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/babel-external.js b/src/babel-external.js index 8d8de5ea..c4bcc1b4 100644 --- a/src/babel-external.js +++ b/src/babel-external.js @@ -173,9 +173,12 @@ export default function() { visitor: { Program(path, state) { if (!plugins) { + const { sourceMaps, vendorPrefix } = state.opts plugins = combinePlugins(state.opts.plugins, { - sourceMaps: state.opts.sourceMaps || state.file.opts.sourceMaps, - vendorPrefix: state.opts.vendorPrefix || true + sourceMaps: sourceMaps || state.file.opts.sourceMaps, + vendorPrefix: typeof vendorPrefix === 'boolean' + ? vendorPrefix + : true }) } }, diff --git a/src/babel.js b/src/babel.js index 937aac24..d4c8e37a 100644 --- a/src/babel.js +++ b/src/babel.js @@ -345,9 +345,12 @@ export default function({ types: t }) { state.file.hasJSXStyle = false state.imports = [] if (!plugins) { + const { sourceMaps, vendorPrefix } = state.opts plugins = combinePlugins(state.opts.plugins, { - sourceMaps: state.opts.sourceMaps || state.file.opts.sourceMaps, - vendorPrefix: state.opts.vendorPrefix || true + sourceMaps: sourceMaps || state.file.opts.sourceMaps, + vendorPrefix: typeof vendorPrefix === 'boolean' + ? vendorPrefix + : true }) } }, From dbe522a61b01d27b8ffaf0b5b8c4ded0af3079d6 Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Sun, 4 Jun 2017 12:31:29 +0200 Subject: [PATCH 10/10] Update test --- src/lib/style-transform.js | 4 +++- test/__snapshots__/index.js.snap | 4 ++-- test/__snapshots__/plugins.js.snap | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/style-transform.js b/src/lib/style-transform.js index 32012c58..679cea8e 100644 --- a/src/lib/style-transform.js +++ b/src/lib/style-transform.js @@ -83,7 +83,9 @@ function transform(prefix, styles, settings = {}) { offset = settings.offset filename = settings.filename stylis.set({ - prefix: typeof settings.vendorPrefix === 'boolean' ? settings.vendorPrefix : true + prefix: typeof settings.vendorPrefix === 'boolean' + ? settings.vendorPrefix + : true }) return stylis(prefix, styles) diff --git a/test/__snapshots__/index.js.snap b/test/__snapshots__/index.js.snap index 9884ce39..2040f3aa 100644 --- a/test/__snapshots__/index.js.snap +++ b/test/__snapshots__/index.js.snap @@ -5,7 +5,7 @@ exports[`generates source maps (babel options) 1`] = ` export default (() =>

test

woot

- <_JSXStyle styleId={188072295} css={\\"p[data-jsx=\\\\\\"188072295\\\\\\"]{color:red}\\\\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJzb3VyY2UtbWFwcy5qcyIsInNvdXJjZXNDb250ZW50IjpbXX0= */\\\\n/*@ sourceURL=source-maps.js */\\"} /> + <_JSXStyle styleId={188072295} css={\\"p[data-jsx=\\\\\\"188072295\\\\\\"]{color:red}\\\\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNvdXJjZS1tYXBzLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUlnQixBQUNjLFdBQUMiLCJmaWxlIjoic291cmNlLW1hcHMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCAoKSA9PiAoXG4gIDxkaXY+XG4gICAgPHA+dGVzdDwvcD5cbiAgICA8cD53b290PC9wPlxuICAgIDxzdHlsZSBqc3g+eydwIHsgY29sb3I6IHJlZCB9J308L3N0eWxlPlxuICA8L2Rpdj5cbilcbiJdfQ== */\\\\n/*@ sourceURL=source-maps.js */\\"} />
);" `; @@ -14,7 +14,7 @@ exports[`generates source maps (plugin options) 1`] = ` export default (() =>

test

woot

- <_JSXStyle styleId={188072295} css={\\"p[data-jsx=\\\\\\"188072295\\\\\\"]{color:red}\\\\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJzb3VyY2UtbWFwcy5qcyIsInNvdXJjZXNDb250ZW50IjpbXX0= */\\\\n/*@ sourceURL=source-maps.js */\\"} /> + <_JSXStyle styleId={188072295} css={\\"p[data-jsx=\\\\\\"188072295\\\\\\"]{color:red}\\\\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNvdXJjZS1tYXBzLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUlnQixBQUNjLFdBQUMiLCJmaWxlIjoic291cmNlLW1hcHMuanMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCAoKSA9PiAoXG4gIDxkaXY+XG4gICAgPHA+dGVzdDwvcD5cbiAgICA8cD53b290PC9wPlxuICAgIDxzdHlsZSBqc3g+eydwIHsgY29sb3I6IHJlZCB9J308L3N0eWxlPlxuICA8L2Rpdj5cbilcbiJdfQ== */\\\\n/*@ sourceURL=source-maps.js */\\"} />
);" `; diff --git a/test/__snapshots__/plugins.js.snap b/test/__snapshots__/plugins.js.snap index d71c9954..d99457c3 100644 --- a/test/__snapshots__/plugins.js.snap +++ b/test/__snapshots__/plugins.js.snap @@ -7,7 +7,7 @@ const color = 'red'; export default (() =>

test

- <_JSXStyle styleId={4216192053} css={\`span[data-jsx=\\"4216192053\\"].\${color}[data-jsx=\\"4216192053\\"]{color:\${otherColor}}\`} /> + <_JSXStyle styleId={4216192053} css={\`span.\${color}[data-jsx=\\"4216192053\\"]{color:\${otherColor}}\`} /> <_JSXStyle styleId={styles.__scopedHash} css={styles.__scoped} />
);" `;