Skip to content

Commit

Permalink
Add Source Maps 🗺 (#320)
Browse files Browse the repository at this point in the history
* 🗺

* remove sheet check

* This should be working but its not.

* Revert pretty css changes

* still not working.

* still not working.

* still not working.

* Its working!

* Remove changes to site

* Add snapshot

* Simplify source map creation

* Fix package.json

* Update snapshots

* Make the runtime smaller

* Move regex into process.env.NODE_ENV !== 'production' check

* Remove __emotion_source_map

* Update snapshots and fix regex

* Remove all the boilerplate in the App component of the site

* comment out unused examples

* Add checks for loc's existance and add a test for the css prop

* Abstract away source map stuff

* Don't output tagged template literals because the tagged template literal transform includes two copies of the strings

* Increase sheet.js coverage

- fix undefined showing up as source map

* Remove console log

* Add snapshots

* Update snapshots

* Test the illegal rule

* Add source maps to objects calls of keyframes, injectGlobal and fontFace

* Add pure comments to object call syntax

* Simplify babel macro and add pure comments to object call syntax in babel macro

* Add more tests for StyleSheet

* Add docs for source maps

* Add source-maps to site

* Update docs

* Add gif

* Update docs
  • Loading branch information
Kye Hohenberger authored Sep 24, 2017
1 parent baf9b95 commit c34a46d
Show file tree
Hide file tree
Showing 33 changed files with 783 additions and 415 deletions.
48 changes: 48 additions & 0 deletions docs/source-maps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## Source Maps

**babel plugin required**

emotion supports source maps for styles authored in javascript.

<div style='max-height: 480px;'>

![source-map-demo](https://user-images.githubusercontent.com/662750/30778580-78fbeae4-a096-11e7-82e1-120b6984e875.gif)

</div>

Required For Source Maps:
1. `babel-plugin-emotion` must be in your Babel setup. [[documentation]](https://github.com/emotion-js/emotion/blob/master/docs/install.md)
2. `process.env.NODE_ENV` must be any value except `"production"`

---

**We do not advise using sourceMaps in production. The source maps can add significant size to your bundle.**

**Babel setup**

**.babelrc**
```json
{
"plugins": [
["emotion", { "sourceMap": true }]
]
}
```

**Recommended Setup**

Use [Babel's `env` property](https://babeljs.io/docs/usage/babelrc/#env-option) and only set source maps in your development environment.

**.babelrc**
```json
{
"env": {
"production": {
"plugins": [["emotion", { "sourceMap": false }]]
},
"development": {
"plugins": [["emotion", { "sourceMap": true }]]
}
}
}
```
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
"watch": "lerna run --parallel watch",
"bootstrap": "lerna bootstrap"
},
"dependencies": {},
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.24.1",
Expand Down
22 changes: 9 additions & 13 deletions packages/babel-plugin-emotion/package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
{
"name": "babel-plugin-emotion",
"version": "8.0.2-3",
"description": "A recommended babel preprocessing plugin for emotion, the The Next Generation of CSS-in-JS.",
"description":
"A recommended babel preprocessing plugin for emotion, the The Next Generation of CSS-in-JS.",
"main": "lib/index.js",
"files": [
"src",
"lib"
],
"files": ["src", "lib"],
"scripts": {
"build": "npm-run-all clean babel",
"babel": "babel src -d lib",
"watch": "babel src -d lib --watch",
"clean": "rimraf lib"
},
"dependencies": {
"babel-generator": "^6.26.0",
"convert-source-map": "^1.5.0",
"source-map": "^0.5.7",
"babel-macros": "^1.0.2",
"babel-plugin-syntax-jsx": "^6.18.0",
"emotion-utils": "^8.0.2-3",
Expand All @@ -27,14 +28,9 @@
"author": "Kye Hohenberger",
"homepage": "https://github.com/tkh44/emotion#readme",
"license": "MIT",
"repository": "https://github.com/tkh44/emotion/tree/master/packages/babel-plugin-emotion",
"keywords": [
"styles",
"emotion",
"react",
"css",
"css-in-js"
],
"repository":
"https://github.com/tkh44/emotion/tree/master/packages/babel-plugin-emotion",
"keywords": ["styles", "emotion", "react", "css", "css-in-js"],
"bugs": {
"url": "https://github.com/tkh44/emotion/issues"
}
Expand Down
44 changes: 22 additions & 22 deletions packages/babel-plugin-emotion/src/ast-object.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
const interleave = (strings, interpolations) =>
interpolations.reduce(
(array, interp, i) => array.concat(interp, strings[i + 1]),
[strings[0]]
)

export default class ASTObject {
props: Array<any>
expressions: Array<any>
Expand Down Expand Up @@ -27,41 +33,35 @@ export default class ASTObject {
replacePlaceholdersWithExpressions(matches: any[], str: string) {
const { expressions, t } = this
if (expressions.length === 0) {
return t.templateLiteral(
[t.templateElement({ cooked: str, raw: str })],
[]
)
if (str === '') {
return []
}
return [t.stringLiteral(str)]
}
const templateElements = []
const templateExpressions = []
const strings = []
const finalExpressions = []
let cursor = 0

matches.forEach(({ value, p1, index }, i) => {
const preMatch = str.substring(cursor, index)
cursor = cursor + preMatch.length + value.length
if (preMatch) {
templateElements.push(
t.templateElement({ raw: preMatch, cooked: preMatch })
)
strings.push(t.stringLiteral(preMatch))
} else if (i === 0) {
templateElements.push(t.templateElement({ raw: '', cooked: '' }))
strings.push(t.stringLiteral(''))
}

templateExpressions.push(expressions[p1])
finalExpressions.push(expressions[p1])
if (i === matches.length - 1) {
templateElements.push(
t.templateElement(
{
raw: str.substring(index + value.length),
cooked: str.substring(index + value.length)
},
true
)
)
strings.push(t.stringLiteral(str.substring(index + value.length)))
}
})
return t.templateLiteral(templateElements, templateExpressions)

return interleave(strings, finalExpressions).filter(
node => node.value !== ''
)
}
toTemplateLiteral() {
toExpressions() {
return this.replacePlaceholdersWithExpressions(
this.getDynamicMatches(this.stringSrc),
this.stringSrc
Expand Down
78 changes: 51 additions & 27 deletions packages/babel-plugin-emotion/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import {
createRawStringFromTemplateLiteral,
minify
} from './babel-utils'

import { hashString, Stylis } from 'emotion-utils'
import { addSourceMaps } from './source-map'

import cssProps from './css-prop'
import ASTObject from './ast-object'

Expand All @@ -34,7 +37,7 @@ export function replaceCssWithCallExpression(
staticCSSSelectorCreator = (name, hash) => `.${name}-${hash}`
) {
try {
const { hash, src } = createRawStringFromTemplateLiteral(path.node.quasi)
let { hash, src } = createRawStringFromTemplateLiteral(path.node.quasi)
const name = getName(getIdentifierName(path, t), 'css')

if (state.extractStatic && !path.node.quasi.expressions.length) {
Expand All @@ -52,12 +55,20 @@ export function replaceCssWithCallExpression(
if (!removePath) {
path.addComment('leading', '#__PURE__')
}
path.node.quasi = new ASTObject(
minify(src),
path.node.quasi.expressions,
t
).toTemplateLiteral()
path.node.tag = identifier
if (state.opts.sourceMap === true && path.node.quasi.loc !== undefined) {
src = src + addSourceMaps(path.node.quasi.loc.start, state)
}

return path.replaceWith(
t.callExpression(
identifier,
new ASTObject(
minify(src),
path.node.quasi.expressions,
t
).toExpressions()
)
)
} catch (e) {
if (path) {
throw path.buildCodeFrameError(e)
Expand Down Expand Up @@ -128,12 +139,6 @@ const getComponentId = (state, prefix: string = 'css') => {
return `${prefix}-${getFileHash(state)}${getNextId(state)}`
}

const interleave = (strings, interpolations) =>
interpolations.reduce(
(array, interp, i) => array.concat(interp, strings[i + 1]),
[strings[0]]
)

export function buildStyledCallExpression(identifier, tag, path, state, t) {
const identifierName = getIdentifierName(path, t)

Expand Down Expand Up @@ -164,21 +169,13 @@ export function buildStyledCallExpression(identifier, tag, path, state, t) {
)
}

const { src } = createRawStringFromTemplateLiteral(path.node.quasi)
let { src } = createRawStringFromTemplateLiteral(path.node.quasi)

path.addComment('leading', '#__PURE__')

const templateLiteral = new ASTObject(
minify(src),
path.node.quasi.expressions,
t
).toTemplateLiteral()

const values = interleave(
templateLiteral.quasis.map(node => t.stringLiteral(node.value.cooked)),
path.node.quasi.expressions
).filter(node => node.value !== '')

if (state.opts.sourceMap === true && path.node.quasi.loc !== undefined) {
src = src + addSourceMaps(path.node.quasi.loc.start, state)
}
return t.callExpression(
t.callExpression(identifier, [
tag,
Expand All @@ -191,14 +188,21 @@ export function buildStyledCallExpression(identifier, tag, path, state, t) {
)
])
]),
values
new ASTObject(minify(src), path.node.quasi.expressions, t).toExpressions()
)
}

export function buildStyledObjectCallExpression(path, state, identifier, t) {
const tag = t.isCallExpression(path.node.callee)
? path.node.callee.arguments[0]
: t.stringLiteral(path.node.callee.property.name)

let args = path.node.arguments
if (state.opts.sourceMap === true && path.node.loc !== undefined) {
args.push(t.stringLiteral(addSourceMaps(path.node.loc.start, state)))
}
path.addComment('leading', '#__PURE__')

return t.callExpression(
t.callExpression(identifier, [
tag,
Expand All @@ -211,7 +215,7 @@ export function buildStyledObjectCallExpression(path, state, identifier, t) {
)
])
]),
path.node.arguments
args
)
}

Expand Down Expand Up @@ -312,6 +316,26 @@ export default function(babel) {
return
}
try {
if (t.isIdentifier(path.node.callee)) {
switch (path.node.callee.name) {
case state.importedNames.css:
case state.importedNames.keyframes: {
path.addComment('leading', '#__PURE__')
}
// eslint-disable-next-line no-fallthrough
case state.importedNames.injectGlobal:
case state.importedNames.fontFace:
if (
state.opts.sourceMap === true &&
path.node.loc !== undefined
) {
path.node.arguments.push(
t.stringLiteral(addSourceMaps(path.node.loc.start, state))
)
}
}
}

if (
(t.isCallExpression(path.node.callee) &&
path.node.callee.callee.name === state.importedNames.styled) ||
Expand Down
3 changes: 1 addition & 2 deletions packages/babel-plugin-emotion/src/macro-styled.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ module.exports = createMacro(macro)

function macro(options) {
const { references, state, babel: { types: t } } = options
if (!state.inline) state.inline = true
let referencesWithoutDefault = references
if (references.default) {
referencesWithoutDefault = omit(references, key => key !== 'default')
references.default.forEach(styledReference => {
references.default.reverse().forEach(styledReference => {
const path = styledReference.parentPath.parentPath
const runtimeNode = buildMacroRuntimeNode(
styledReference,
Expand Down
Loading

0 comments on commit c34a46d

Please sign in to comment.