diff --git a/.storybook/sourcemaps.stories.js b/.storybook/sourcemaps.stories.js new file mode 100644 index 00000000..97b95947 --- /dev/null +++ b/.storybook/sourcemaps.stories.js @@ -0,0 +1,21 @@ +import {createElement as h} from 'react'; +import {storiesOf} from '@storybook/react'; +const {action} = require('@storybook/addon-actions'); +const {linkTo} = require('@storybook/addon-links'); +const {create} = require('../index'); +const {addon} = require('../addon/rule'); +const {addon: addonSourcemaps} = require('../addon/sourcemaps'); + +const nano = create(); +addon(nano); +addonSourcemaps(nano); +const {rule} = nano; + +const className1 = rule({ + border: '1px solid red' +}, 'RedBorder'); + +storiesOf('Addons/sourcemaps', module) + .add('Default', () => + h('div', {className: className1}, 'Hello world') + ) diff --git a/addon/sourcemaps.js b/addon/sourcemaps.js new file mode 100644 index 00000000..043ad220 --- /dev/null +++ b/addon/sourcemaps.js @@ -0,0 +1,82 @@ +'use strict'; + +var StackTrace = require('stacktrace-js'); +var SourcemapCodec = require('sourcemap-codec'); + +exports.addon = function (renderer) { + var queue = []; + var timeout = null; + var sourceCache = {}; + + function flush () { + timeout = null; + + var sources = []; + var segments = []; + var rules = []; + + for (var i = 0; i < queue.length; i++) { + var item = queue[i]; + + rules.push(item.rule); + segments.push([[0, sources.length, item.lineNumber - 1, 0]]); + sources.push(item.fileName); + } + + queue = []; + + var mappings = SourcemapCodec.encode(segments); + var map = { + version: 3, + sources: sources, + mappings: mappings, + sourcesContent: sources.map(function (source) { + return sourceCache[source]; + }), + }; + + var json = JSON.stringify(map); + var base64 = window.btoa(json); + var css = rules.join('\n') + '\n/*# sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64 + ' */'; + var style = document.createElement('style'); + + style.appendChild(document.createTextNode(css)); + document.head.appendChild(style); + } + + function enqueue (rawCss) { + StackTrace.get({sourceCache: sourceCache}) + .then(function (stackframes) { + var frame = stackframes[4]; + + if (!frame) { + return; + } + + var pos = rawCss.indexOf('{'); + + if (pos < 1) return; + + var selector = rawCss.substr(0, pos).trim(); + + queue.push({ + selector: selector, + rule: rawCss, + fileName: frame.fileName, + lineNumber: frame.lineNumber, + }); + + if (!timeout) { + timeout = setTimeout(flush, 100); + } + // eslint-disable-next-line no-console + }, console.log); + } + + var putRaw = renderer.putRaw; + + renderer.putRaw = function (rawCSS) { + enqueue(rawCSS); + putRaw.apply(null, arguments); + }; +}; diff --git a/package.json b/package.json index 3e09933a..c2bff3fc 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,9 @@ "stylis": "3.5.0", "inline-style-prefixer": "^4.0.0", "rtl-css-js": "^1.9.0", - "css-tree": "^1.0.0-alpha.28" + "css-tree": "^1.0.0-alpha.28", + "stacktrace-js": "^2.0.0", + "sourcemap-codec": "^1.4.1" }, "devDependencies": { "@types/react": "16.3.17",