243 lines
7.6 KiB
JavaScript
243 lines
7.6 KiB
JavaScript
// @flow
|
|
import { createEmotionMacro } from './emotion-macro'
|
|
import { createStyledMacro } from './styled-macro'
|
|
import cssMacro, { transformCssCallExpression } from './css-macro'
|
|
import { addNamed } from '@babel/helper-module-imports'
|
|
import nodePath from 'path'
|
|
import { getSourceMap, getStyledOptions } from './utils'
|
|
|
|
let webStyledMacro = createStyledMacro({
|
|
importPath: '@emotion/styled-base',
|
|
originalImportPath: '@emotion/styled',
|
|
isWeb: true
|
|
})
|
|
let nativeStyledMacro = createStyledMacro({
|
|
importPath: '@emotion/native',
|
|
originalImportPath: '@emotion/native',
|
|
isWeb: false
|
|
})
|
|
let primitivesStyledMacro = createStyledMacro({
|
|
importPath: '@emotion/primitives',
|
|
originalImportPath: '@emotion/primitives',
|
|
isWeb: false
|
|
})
|
|
|
|
export const macros = {
|
|
createEmotionMacro,
|
|
css: cssMacro,
|
|
createStyledMacro
|
|
}
|
|
|
|
export type BabelPath = any
|
|
|
|
export type EmotionBabelPluginPass = any
|
|
|
|
let emotionCoreMacroThatsNotARealMacro = ({ references, state, babel }) => {
|
|
Object.keys(references).forEach(refKey => {
|
|
if (refKey === 'css') {
|
|
references[refKey].forEach(path => {
|
|
transformCssCallExpression({ babel, state, path: path.parentPath })
|
|
})
|
|
}
|
|
})
|
|
}
|
|
emotionCoreMacroThatsNotARealMacro.keepImport = true
|
|
|
|
function getAbsolutePath(instancePath: string, rootPath: string) {
|
|
if (instancePath.charAt(0) === '.') {
|
|
let absoluteInstancePath = nodePath.resolve(rootPath, instancePath)
|
|
return absoluteInstancePath
|
|
}
|
|
return false
|
|
}
|
|
|
|
function getInstancePathToCompare(instancePath: string, rootPath: string) {
|
|
let absolutePath = getAbsolutePath(instancePath, rootPath)
|
|
if (absolutePath === false) {
|
|
return instancePath
|
|
}
|
|
return absolutePath
|
|
}
|
|
|
|
export default function(babel: *) {
|
|
let t = babel.types
|
|
return {
|
|
name: 'emotion',
|
|
inherits: require('babel-plugin-syntax-jsx'),
|
|
visitor: {
|
|
ImportDeclaration(path: *, state: *) {
|
|
const hasFilepath =
|
|
path.hub.file.opts.filename &&
|
|
path.hub.file.opts.filename !== 'unknown'
|
|
let dirname = hasFilepath
|
|
? nodePath.dirname(path.hub.file.opts.filename)
|
|
: ''
|
|
|
|
if (
|
|
!state.pluginMacros[path.node.source.value] &&
|
|
state.emotionInstancePaths.indexOf(
|
|
getInstancePathToCompare(path.node.source.value, dirname)
|
|
) !== -1
|
|
) {
|
|
state.pluginMacros[path.node.source.value] = createEmotionMacro(
|
|
path.node.source.value
|
|
)
|
|
}
|
|
let pluginMacros = state.pluginMacros
|
|
// most of this is from https://github.com/kentcdodds/babel-plugin-macros/blob/master/src/index.js
|
|
if (pluginMacros[path.node.source.value] === undefined) {
|
|
return
|
|
}
|
|
if (t.isImportNamespaceSpecifier(path.node.specifiers[0])) {
|
|
return
|
|
}
|
|
const imports = path.node.specifiers.map(s => ({
|
|
localName: s.local.name,
|
|
importedName:
|
|
s.type === 'ImportDefaultSpecifier' ? 'default' : s.imported.name
|
|
}))
|
|
let shouldExit = false
|
|
let hasReferences = false
|
|
const referencePathsByImportName = imports.reduce(
|
|
(byName, { importedName, localName }) => {
|
|
let binding = path.scope.getBinding(localName)
|
|
if (!binding) {
|
|
shouldExit = true
|
|
return byName
|
|
}
|
|
byName[importedName] = binding.referencePaths
|
|
hasReferences =
|
|
hasReferences || Boolean(byName[importedName].length)
|
|
return byName
|
|
},
|
|
{}
|
|
)
|
|
if (!hasReferences || shouldExit) {
|
|
return
|
|
}
|
|
/**
|
|
* Other plugins that run before babel-plugin-macros might use path.replace, where a path is
|
|
* put into its own replacement. Apparently babel does not update the scope after such
|
|
* an operation. As a remedy, the whole scope is traversed again with an empty "Identifier"
|
|
* visitor - this makes the problem go away.
|
|
*
|
|
* See: https://github.com/kentcdodds/import-all.macro/issues/7
|
|
*/
|
|
state.file.scope.path.traverse({
|
|
Identifier() {}
|
|
})
|
|
|
|
pluginMacros[path.node.source.value]({
|
|
references: referencePathsByImportName,
|
|
state,
|
|
babel,
|
|
isBabelMacrosCall: true,
|
|
isEmotionCall: true
|
|
})
|
|
if (!pluginMacros[path.node.source.value].keepImport) {
|
|
path.remove()
|
|
}
|
|
},
|
|
Program(path: *, state: *) {
|
|
state.emotionInstancePaths = (state.opts.instances || []).map(
|
|
instancePath => getInstancePathToCompare(instancePath, process.cwd())
|
|
)
|
|
state.pluginMacros = {
|
|
'@emotion/css': cssMacro,
|
|
'@emotion/styled': webStyledMacro,
|
|
'@emotion/core': emotionCoreMacroThatsNotARealMacro,
|
|
'@emotion/primitives': primitivesStyledMacro,
|
|
'@emotion/native': nativeStyledMacro,
|
|
emotion: createEmotionMacro('emotion')
|
|
}
|
|
if (state.opts.cssPropOptimization === undefined) {
|
|
for (const node of path.node.body) {
|
|
if (
|
|
t.isImportDeclaration(node) &&
|
|
node.source.value === '@emotion/core' &&
|
|
node.specifiers.some(
|
|
x => t.isImportSpecifier(x) && x.imported.name === 'jsx'
|
|
)
|
|
) {
|
|
state.transformCssProp = true
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
state.transformCssProp = state.opts.cssPropOptimization
|
|
}
|
|
|
|
if (state.opts.sourceMap === false) {
|
|
state.emotionSourceMap = false
|
|
} else {
|
|
state.emotionSourceMap = true
|
|
}
|
|
},
|
|
JSXAttribute(path: *, state: *) {
|
|
if (path.node.name.name !== 'css' || !state.transformCssProp) {
|
|
return
|
|
}
|
|
|
|
if (
|
|
t.isJSXExpressionContainer(path.node.value) &&
|
|
(t.isObjectExpression(path.node.value.expression) ||
|
|
t.isArrayExpression(path.node.value.expression))
|
|
) {
|
|
let expressionPath = path.get('value.expression')
|
|
let sourceMap =
|
|
state.emotionSourceMap && path.node.loc !== undefined
|
|
? getSourceMap(path.node.loc.start, state)
|
|
: ''
|
|
|
|
expressionPath.replaceWith(
|
|
t.callExpression(
|
|
// the name of this identifier doesn't really matter at all
|
|
// it'll never appear in generated code
|
|
t.identifier('___shouldNeverAppearCSS'),
|
|
[path.node.value.expression]
|
|
)
|
|
)
|
|
|
|
transformCssCallExpression({
|
|
babel,
|
|
state,
|
|
path: expressionPath,
|
|
sourceMap
|
|
})
|
|
if (t.isCallExpression(expressionPath)) {
|
|
if (!state.cssIdentifier) {
|
|
state.cssIdentifier = addNamed(path, 'css', '@emotion/core', {
|
|
nameHint: 'css'
|
|
})
|
|
}
|
|
|
|
expressionPath
|
|
.get('callee')
|
|
.replaceWith(t.cloneDeep(state.cssIdentifier))
|
|
}
|
|
}
|
|
},
|
|
CallExpression: {
|
|
exit(path: BabelPath, state: EmotionBabelPluginPass) {
|
|
try {
|
|
if (
|
|
path.node.callee &&
|
|
path.node.callee.property &&
|
|
path.node.callee.property.name === 'withComponent'
|
|
) {
|
|
switch (path.node.arguments.length) {
|
|
case 1:
|
|
case 2: {
|
|
path.node.arguments[1] = getStyledOptions(t, path, state)
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
throw path.buildCodeFrameError(e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|