From e76150ab5d7521738b90832b30c5c48adc591560 Mon Sep 17 00:00:00 2001 From: Andrey Sidorov Date: Mon, 12 Dec 2016 00:49:40 +1100 Subject: [PATCH 1/8] initial support for sourcemaps generation --- lib/style-transform.js | 65 ++++++++++++++++++++++++++++++++---------- package.json | 3 +- src/babel.js | 40 ++++++++++++++++++++++++-- 3 files changed, 89 insertions(+), 19 deletions(-) diff --git a/lib/style-transform.js b/lib/style-transform.js index 977d96dc..8ba779ea 100644 --- a/lib/style-transform.js +++ b/lib/style-transform.js @@ -1,17 +1,17 @@ -// based on Stylis (MIT) +// based on Stylis (MIT) // modified by Guillermo Rauch to add support for custom // attributes instead of custom selector prefix /*! * - * __ ___ + * __ ___ * _____/ /___ __/ (_)____ * / ___/ __/ / / / / / ___/ - * (__ ) /_/ /_/ / / (__ ) - * /____/\__/\__, /_/_/____/ - * /____/ - * + * (__ ) /_/ /_/ / / (__ ) + * /____/\__/\__, /_/_/____/ + * /____/ + * * stylis is a small css compiler - * + * * @licence MIT */ (function (factory) { @@ -36,23 +36,39 @@ * css compiler * * @example compiler('.class1', 'css...', false); - * + * * @param {string} id to use for data attributes * @param {string} styles * @return {string} */ - function stylis (id, styles) { + function stylis (id, styles, generator, start, fileName) { var suffix = '[data-jsx="' + id +'"]'; var output = ''; var line = ''; var len = styles.length; var i = 0; + var lineNumber = start ? start.line : 0; + var columnNumber = start ? start.column : 0; + + generator && generator.addMapping({ + generated: { + line: 1, + column: 0 + }, + source: fileName, + original: start + }) // parse + compile while (i < len) { var code = styles.charCodeAt(i); + if (code === 10) { + lineNumber++; + columnNumber = 0; + } + // {, }, ; characters if (code === 123 || code === 125 || code === 59) { line += styles[i]; @@ -75,6 +91,7 @@ // @keyframe/@root, `k` or @root, `r` character if (second === 107 || second === 114) { i++; + columnNumber++; if (second == 107) { // @keyframes @@ -88,7 +105,12 @@ while (i < len) { var char = styles[i++]; + columnNumber++; var _code = char.charCodeAt(0); + if (_code === 10) { + lineNumber++; + columnNumber = 0; + } // not `\t`, `\r`, `\n` characters if (_code !== 9 && _code !== 13 && _code !== 10) { @@ -98,12 +120,12 @@ if (close === 1) { break; } - // current block tag is close tag + // current block tag is close tag else { close = 1; } } - // { character + // { character else if (_code === 123) { // current block tag is open close = 0; @@ -115,7 +137,7 @@ // vendor prefix transform properties within keyframes and @root blocks line = line.replace(regSpaces, '').replace(regPrefix, '-webkit-$1-moz-$1-ms-$1$1'); - + if (second === 107) { // vendor prefix keyframes blocks line = '@-webkit-'+line+'}'+'@-moz-'+line+'}@'+line+'}'; @@ -150,7 +172,7 @@ // vendor prefix line = '-webkit-' + line + '-moz-' + line + line; } - // transforms & transitions: t, r, a + // transforms & transitions: t, r, a // hyphens: h, y, p // user-select: u, s, r, s else if ( @@ -275,16 +297,29 @@ } } + generator && generator.addMapping({ + generated: { + line: 1, + column: output.length + }, + source: fileName, + original: { + line: lineNumber, + column: columnNumber + } + }) + output += line; line = ''; - } + } // not `\t`, `\r`, `\n` characters else if (code !== 9 && code !== 13 && code !== 10) { line += styles[i]; } // next character - i++; + i++; + columnNumber++; } return output; diff --git a/package.json b/package.json index a1c75a87..752a202b 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "dependencies": { "babel-plugin-syntax-jsx": "^6.18.0", "object.entries": "^1.0.4", - "string-hash": "^1.1.1" + "string-hash": "^1.1.1", + "source-map": "^0.5.6" }, "devDependencies": { "ava": "^0.17.0", diff --git a/src/babel.js b/src/babel.js index c760733c..ecadcbee 100644 --- a/src/babel.js +++ b/src/babel.js @@ -1,6 +1,9 @@ // Packages import jsx from 'babel-plugin-syntax-jsx' import hash from 'string-hash' +import { SourceMapGenerator } from 'source-map' +import { relative } from 'path' +import convert from 'convert-source-map' // Ours import transform from '../lib/style-transform' @@ -110,7 +113,8 @@ export default function ({types: t}) { state.styles.push([ styleId, - styleText + styleText, + expression.loc ]) } @@ -126,12 +130,42 @@ export default function ({types: t}) { if (el.name && el.name.name === 'style') { // we replace styles with the function call - const [id, css] = state.styles.shift() + const [id, css, loc] = state.styles.shift() const skipTransform = el.attributes.some(attr => ( attr.name.name === GLOBAL_ATTRIBUTE )) + // TODO: use plugin param option? + const useSourceMaps = process.env.NODE_ENV !== 'production' + let transformedCss = css + + if (!skipTransform) { + if (useSourceMaps) { + // TODO: better way to get reative path to use as sourcemap name? + const filename = relative(process.cwd(), state.file.log.filename) + // have it relative to babelrc? + const generator = new SourceMapGenerator({ + file: filename + // TODO: pass sourceRoot via plugin option? + // sourceRoot: "/" + }); + // TODO: better way to get current untransformed file source? + // I think it's already set somewhere + const input = require('fs').readFileSync(filename, 'utf-8') + generator.setSourceContent(filename, input) + transformedCss = [ + transform(id, css, generator, loc.start, filename), + convert + .fromObject(generator) + .toComment({multiline: true}), + `/*@ sourceURL=${filename} */` + ].join('\n'); + } else { + transformedCss = transform(id, css) + } + } + path.replaceWith( t.JSXElement( t.JSXOpeningElement( @@ -139,7 +173,7 @@ export default function ({types: t}) { [ t.JSXAttribute( t.JSXIdentifier(STYLE_COMPONENT_CSS), - t.JSXExpressionContainer(t.stringLiteral(skipTransform ? css : transform(id, css))) + t.JSXExpressionContainer(t.stringLiteral(transformedCss)) ) ], true From f0f9d88158ef15a7c135bc2acf1d7cd42dbd0b08 Mon Sep 17 00:00:00 2001 From: Andrey Sidorov Date: Mon, 12 Dec 2016 12:06:33 +1100 Subject: [PATCH 2/8] use source from babel state instead of re-reading file --- src/babel.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/babel.js b/src/babel.js index ecadcbee..61dee524 100644 --- a/src/babel.js +++ b/src/babel.js @@ -150,10 +150,7 @@ export default function ({types: t}) { // TODO: pass sourceRoot via plugin option? // sourceRoot: "/" }); - // TODO: better way to get current untransformed file source? - // I think it's already set somewhere - const input = require('fs').readFileSync(filename, 'utf-8') - generator.setSourceContent(filename, input) + generator.setSourceContent(filename, state.file.code) transformedCss = [ transform(id, css, generator, loc.start, filename), convert From cbd7059595dcc0811044aa5c825321fd52242ba8 Mon Sep 17 00:00:00 2001 From: Andrey Sidorov Date: Mon, 12 Dec 2016 12:23:02 +1100 Subject: [PATCH 3/8] fix linter errors --- src/babel.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/babel.js b/src/babel.js index 61dee524..a3c0221b 100644 --- a/src/babel.js +++ b/src/babel.js @@ -1,3 +1,5 @@ +import {relative} from 'path' + // Packages import jsx from 'babel-plugin-syntax-jsx' import hash from 'string-hash' @@ -146,10 +148,10 @@ export default function ({types: t}) { const filename = relative(process.cwd(), state.file.log.filename) // have it relative to babelrc? const generator = new SourceMapGenerator({ - file: filename - // TODO: pass sourceRoot via plugin option? - // sourceRoot: "/" - }); + file: filename + // TODO: pass sourceRoot via plugin option? + // sourceRoot: "/" + }) generator.setSourceContent(filename, state.file.code) transformedCss = [ transform(id, css, generator, loc.start, filename), @@ -157,7 +159,7 @@ export default function ({types: t}) { .fromObject(generator) .toComment({multiline: true}), `/*@ sourceURL=${filename} */` - ].join('\n'); + ].join('\n') } else { transformedCss = transform(id, css) } From fd320042ca1473d105a06a626e5106cec8e3aafd Mon Sep 17 00:00:00 2001 From: Andrey Sidorov Date: Mon, 12 Dec 2016 12:23:40 +1100 Subject: [PATCH 4/8] update fixtures .out files to contain generated sourcemaps --- test/fixtures/class.out.js | 4 ++-- test/fixtures/stateless.out.js | 4 ++-- test/fixtures/whitespace.out.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/fixtures/class.out.js b/test/fixtures/class.out.js index f28dafff..49801382 100644 --- a/test/fixtures/class.out.js +++ b/test/fixtures/class.out.js @@ -39,8 +39,8 @@ var _class = function () { "test" ), React.createElement(_style2.default, { - css: "p[data-jsx=\"1891769468\"] {color: red;}", - "data-jsx": "1891769468" + css: "p[data-jsx=\"1544381438\"] {color: red;}", + "data-jsx": "1544381438" }) ); } diff --git a/test/fixtures/stateless.out.js b/test/fixtures/stateless.out.js index 5266fabe..0d444397 100644 --- a/test/fixtures/stateless.out.js +++ b/test/fixtures/stateless.out.js @@ -38,8 +38,8 @@ exports.default = function () { 'woot' ), React.createElement(_style2.default, { - css: 'p[data-jsx="188072295"] {color: red }', - 'data-jsx': '188072295' + css: 'p[data-jsx="4271158759"] {color: red }', + 'data-jsx': '4271158759' }) ); }; diff --git a/test/fixtures/whitespace.out.js b/test/fixtures/whitespace.out.js index 5266fabe..0d444397 100644 --- a/test/fixtures/whitespace.out.js +++ b/test/fixtures/whitespace.out.js @@ -38,8 +38,8 @@ exports.default = function () { 'woot' ), React.createElement(_style2.default, { - css: 'p[data-jsx="188072295"] {color: red }', - 'data-jsx': '188072295' + css: 'p[data-jsx="4271158759"] {color: red }', + 'data-jsx': '4271158759' }) ); }; From 5080946e79da1b88d1350b7cf4a931efe4c277b3 Mon Sep 17 00:00:00 2001 From: Andrey Sidorov Date: Tue, 13 Dec 2016 11:04:38 +1100 Subject: [PATCH 5/8] use sourceMaps,sourceFileName and sourceRoot from babel opts --- src/babel.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/babel.js b/src/babel.js index a3c0221b..dc890018 100644 --- a/src/babel.js +++ b/src/babel.js @@ -138,19 +138,15 @@ export default function ({types: t}) { attr.name.name === GLOBAL_ATTRIBUTE )) - // TODO: use plugin param option? - const useSourceMaps = process.env.NODE_ENV !== 'production' + const useSourceMaps = !!state.file.opts.sourceMaps let transformedCss = css if (!skipTransform) { if (useSourceMaps) { - // TODO: better way to get reative path to use as sourcemap name? - const filename = relative(process.cwd(), state.file.log.filename) - // have it relative to babelrc? + const filename = state.file.opts.sourceFileName const generator = new SourceMapGenerator({ - file: filename - // TODO: pass sourceRoot via plugin option? - // sourceRoot: "/" + file: filename, + sourceRoot: state.file.opts.sourceRoot }) generator.setSourceContent(filename, state.file.code) transformedCss = [ From 08900547a33a35d496debc46647776c63d3d5e21 Mon Sep 17 00:00:00 2001 From: Andrey Sidorov Date: Tue, 13 Dec 2016 11:37:29 +1100 Subject: [PATCH 6/8] fix lint errors --- src/babel.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/babel.js b/src/babel.js index dc890018..431cc3c7 100644 --- a/src/babel.js +++ b/src/babel.js @@ -1,10 +1,7 @@ -import {relative} from 'path' - // Packages import jsx from 'babel-plugin-syntax-jsx' import hash from 'string-hash' -import { SourceMapGenerator } from 'source-map' -import { relative } from 'path' +import {SourceMapGenerator} from 'source-map' import convert from 'convert-source-map' // Ours @@ -138,7 +135,7 @@ export default function ({types: t}) { attr.name.name === GLOBAL_ATTRIBUTE )) - const useSourceMaps = !!state.file.opts.sourceMaps + const useSourceMaps = Boolean(state.file.opts.sourceMaps) let transformedCss = css if (!skipTransform) { From c43a1e0bd3592caf31af37dc3239f8a10f647670 Mon Sep 17 00:00:00 2001 From: Andrey Sidorov Date: Tue, 13 Dec 2016 11:37:42 +1100 Subject: [PATCH 7/8] add missing dependency --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 752a202b..897b8475 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,10 @@ ], "dependencies": { "babel-plugin-syntax-jsx": "^6.18.0", + "convert-source-map": "^1.3.0", "object.entries": "^1.0.4", - "string-hash": "^1.1.1", - "source-map": "^0.5.6" + "source-map": "^0.5.6", + "string-hash": "^1.1.1" }, "devDependencies": { "ava": "^0.17.0", From a913d7c5a300afb27d89ba8c7f94fa6fc1ce1ac6 Mon Sep 17 00:00:00 2001 From: Andrey Sidorov Date: Tue, 13 Dec 2016 11:37:58 +1100 Subject: [PATCH 8/8] update test fixtures --- test/fixtures/class.out.js | 4 ++-- test/fixtures/stateless.out.js | 4 ++-- test/fixtures/whitespace.out.js | 4 ++-- test/index.js | 1 + 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/test/fixtures/class.out.js b/test/fixtures/class.out.js index 49801382..c2f2869d 100644 --- a/test/fixtures/class.out.js +++ b/test/fixtures/class.out.js @@ -39,8 +39,8 @@ var _class = function () { "test" ), React.createElement(_style2.default, { - css: "p[data-jsx=\"1544381438\"] {color: red;}", - "data-jsx": "1544381438" + css: "p[data-jsx=\"1891769468\"] {color: red;}\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImNsYXNzLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQU1vQixBQUNQLDBCQUNVLFdBQ1oiLCJmaWxlIjoiY2xhc3MuanMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCBjbGFzcyB7XG5cbiAgcmVuZGVyICgpIHtcbiAgICByZXR1cm4gKFxuICAgICAgPGRpdj5cbiAgICAgICAgPHA+dGVzdDwvcD5cbiAgICAgICAgPHN0eWxlIGpzeD57YFxuICAgICAgICAgIHAge1xuICAgICAgICAgICAgY29sb3I6IHJlZDtcbiAgICAgICAgICB9XG4gICAgICAgIGB9PC9zdHlsZT5cbiAgICAgIDwvZGl2PlxuICAgIClcbiAgfVxuXG59XG4iXX0= */\n/*@ sourceURL=class.js */", + "data-jsx": "1891769468" }) ); } diff --git a/test/fixtures/stateless.out.js b/test/fixtures/stateless.out.js index 0d444397..9acb1bf5 100644 --- a/test/fixtures/stateless.out.js +++ b/test/fixtures/stateless.out.js @@ -38,8 +38,8 @@ exports.default = function () { 'woot' ), React.createElement(_style2.default, { - css: 'p[data-jsx="4271158759"] {color: red }', - 'data-jsx': '4271158759' + css: 'p[data-jsx="188072295"] {color: red }\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0YXRlbGVzcy5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFLZ0IsQUFBRSx5QkFBYSIsImZpbGUiOiJzdGF0ZWxlc3MuanMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgZGVmYXVsdCAoKSA9PiAoXG4gIDxkaXY+XG4gICAgPHA+dGVzdDwvcD5cbiAgICA8cD53b290PC9wPlxuICAgIDxwPndvb3Q8L3A+XG4gICAgPHN0eWxlIGpzeD57J3AgeyBjb2xvcjogcmVkIH0nfTwvc3R5bGU+XG4gIDwvZGl2PlxuKVxuIl19 */\n/*@ sourceURL=stateless.js */', + 'data-jsx': '188072295' }) ); }; diff --git a/test/fixtures/whitespace.out.js b/test/fixtures/whitespace.out.js index 0d444397..f8ef5dc7 100644 --- a/test/fixtures/whitespace.out.js +++ b/test/fixtures/whitespace.out.js @@ -38,8 +38,8 @@ exports.default = function () { 'woot' ), React.createElement(_style2.default, { - css: 'p[data-jsx="4271158759"] {color: red }', - 'data-jsx': '4271158759' + css: 'p[data-jsx="188072295"] {color: red }\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndoaXRlc3BhY2UuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBT1EsQUFBRSx5QkFBYSIsImZpbGUiOiJ3aGl0ZXNwYWNlLmpzIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgKCkgPT4gKFxuICA8ZGl2PlxuICAgIDxwPnRlc3Q8L3A+XG4gICAgPHA+d29vdDwvcD5cbiAgICA8cD53b290PC9wPlxuICAgIDxzdHlsZSBqc3g+XG4gICAgICB7XG4gICAgICAgICdwIHsgY29sb3I6IHJlZCB9J1xuICAgICAgfVxuICAgIDwvc3R5bGU+XG4gIDwvZGl2PlxuKVxuXG4iXX0= */\n/*@ sourceURL=whitespace.js */', + 'data-jsx': '188072295' }) ); }; diff --git a/test/index.js b/test/index.js index e2844baa..8aaf2767 100644 --- a/test/index.js +++ b/test/index.js @@ -12,6 +12,7 @@ import read from './_read' const transform = file => ( new Promise((resolve, reject) => { transformFile(path.resolve(__dirname, file), { + sourceMap: true, plugins: [ 'transform-runtime', plugin