Spaces:
Sleeping
Sleeping
| import { | |
| createEmotionMacro, | |
| transformers as vanillaTransformers | |
| } from './emotion-macro' | |
| import { createStyledMacro, styledTransformer } from './styled-macro' | |
| import coreMacro, { | |
| transformers as coreTransformers, | |
| transformCsslessArrayExpression, | |
| transformCsslessObjectExpression | |
| } from './core-macro' | |
| import { getStyledOptions, createTransformerMacro } from './utils' | |
| const getCssExport = (reexported, importSource, mapping) => { | |
| const cssExport = Object.keys(mapping).find(localExportName => { | |
| const [packageName, exportName] = mapping[localExportName].canonicalImport | |
| return packageName === '@emotion/react' && exportName === 'css' | |
| }) | |
| if (!cssExport) { | |
| throw new Error( | |
| `You have specified that '${importSource}' re-exports '${reexported}' from '@emotion/react' but it doesn't also re-export 'css' from '@emotion/react', 'css' is necessary for certain optimisations, please re-export it from '${importSource}'` | |
| ) | |
| } | |
| return cssExport | |
| } | |
| let webStyledMacro = createStyledMacro({ | |
| importSource: '@emotion/styled/base', | |
| originalImportSource: '@emotion/styled', | |
| isWeb: true | |
| }) | |
| let nativeStyledMacro = createStyledMacro({ | |
| importSource: '@emotion/native', | |
| originalImportSource: '@emotion/native', | |
| isWeb: false | |
| }) | |
| let primitivesStyledMacro = createStyledMacro({ | |
| importSource: '@emotion/primitives', | |
| originalImportSource: '@emotion/primitives', | |
| isWeb: false | |
| }) | |
| let vanillaEmotionMacro = createEmotionMacro('@emotion/css') | |
| let transformersSource = { | |
| '@emotion/css': vanillaTransformers, | |
| '@emotion/react': coreTransformers, | |
| '@emotion/styled': { | |
| default: [ | |
| styledTransformer, | |
| { styledBaseImport: ['@emotion/styled/base', 'default'], isWeb: true } | |
| ] | |
| }, | |
| '@emotion/primitives': { | |
| default: [styledTransformer, { isWeb: false }] | |
| }, | |
| '@emotion/native': { | |
| default: [styledTransformer, { isWeb: false }] | |
| } | |
| } | |
| export const macros = { | |
| core: coreMacro, | |
| nativeStyled: nativeStyledMacro, | |
| primitivesStyled: primitivesStyledMacro, | |
| webStyled: webStyledMacro, | |
| vanillaEmotion: vanillaEmotionMacro | |
| } | |
| /* | |
| export type BabelPath = any | |
| export type EmotionBabelPluginPass = any | |
| */ | |
| const AUTO_LABEL_VALUES = ['dev-only', 'never', 'always'] | |
| export default function (babel, options) { | |
| if ( | |
| options.autoLabel !== undefined && | |
| !AUTO_LABEL_VALUES.includes(options.autoLabel) | |
| ) { | |
| throw new Error( | |
| `The 'autoLabel' option must be undefined, or one of the following: ${AUTO_LABEL_VALUES.map( | |
| s => `"${s}"` | |
| ).join(', ')}` | |
| ) | |
| } | |
| let t = babel.types | |
| return { | |
| name: '@emotion', | |
| // https://github.com/babel/babel/blob/0c97749e0fe8ad845b902e0b23a24b308b0bf05d/packages/babel-plugin-syntax-jsx/src/index.ts#L9-L18 | |
| manipulateOptions(opts, parserOpts) { | |
| const { plugins } = parserOpts | |
| if ( | |
| plugins.some(p => { | |
| const plugin = Array.isArray(p) ? p[0] : p | |
| return plugin === 'typescript' || plugin === 'jsx' | |
| }) | |
| ) { | |
| return | |
| } | |
| plugins.push('jsx') | |
| }, | |
| visitor: { | |
| ImportDeclaration(path, state) { | |
| const macro = state.pluginMacros[path.node.source.value] | |
| // most of this is from https://github.com/kentcdodds/babel-plugin-macros/blob/main/src/index.js | |
| if (macro === 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() {} | |
| }) | |
| macro({ | |
| path, | |
| references: referencePathsByImportName, | |
| state, | |
| babel, | |
| isEmotionCall: true, | |
| isBabelMacrosCall: true | |
| }) | |
| }, | |
| Program(path, state) { | |
| let macros = {} | |
| let jsxReactImports /*: Array<{ | |
| importSource: string, | |
| export: string, | |
| cssExport: string | |
| }> */ = [ | |
| { importSource: '@emotion/react', export: 'jsx', cssExport: 'css' } | |
| ] | |
| state.jsxReactImport = jsxReactImports[0] | |
| Object.keys(state.opts.importMap || {}).forEach(importSource => { | |
| let value = state.opts.importMap[importSource] | |
| let transformers = {} | |
| Object.keys(value).forEach(localExportName => { | |
| let { canonicalImport, ...options } = value[localExportName] | |
| let [packageName, exportName] = canonicalImport | |
| if (packageName === '@emotion/react' && exportName === 'jsx') { | |
| jsxReactImports.push({ | |
| importSource, | |
| export: localExportName, | |
| cssExport: getCssExport('jsx', importSource, value) | |
| }) | |
| return | |
| } | |
| let packageTransformers = transformersSource[packageName] | |
| if (packageTransformers === undefined) { | |
| throw new Error( | |
| `There is no transformer for the export '${exportName}' in '${packageName}'` | |
| ) | |
| } | |
| let extraOptions | |
| if (packageName === '@emotion/react' && exportName === 'Global') { | |
| // this option is not supposed to be set in importMap | |
| extraOptions = { | |
| cssExport: getCssExport('Global', importSource, value) | |
| } | |
| } else if ( | |
| packageName === '@emotion/styled' && | |
| exportName === 'default' | |
| ) { | |
| // this is supposed to override defaultOptions value | |
| // and let correct value to be set if coming in options | |
| extraOptions = { | |
| styledBaseImport: undefined | |
| } | |
| } | |
| let [exportTransformer, defaultOptions] = Array.isArray( | |
| packageTransformers[exportName] | |
| ) | |
| ? packageTransformers[exportName] | |
| : [packageTransformers[exportName]] | |
| transformers[localExportName] = [ | |
| exportTransformer, | |
| { | |
| ...defaultOptions, | |
| ...extraOptions, | |
| ...options | |
| } | |
| ] | |
| }) | |
| macros[importSource] = createTransformerMacro(transformers, { | |
| importSource | |
| }) | |
| }) | |
| state.pluginMacros = { | |
| '@emotion/styled': webStyledMacro, | |
| '@emotion/react': coreMacro, | |
| '@emotion/primitives': primitivesStyledMacro, | |
| '@emotion/native': nativeStyledMacro, | |
| '@emotion/css': vanillaEmotionMacro, | |
| ...macros | |
| } | |
| for (const node of path.node.body) { | |
| if (t.isImportDeclaration(node)) { | |
| let jsxReactImport = jsxReactImports.find( | |
| thing => | |
| node.source.value === thing.importSource && | |
| node.specifiers.some( | |
| x => | |
| t.isImportSpecifier(x) && x.imported.name === thing.export | |
| ) | |
| ) | |
| if (jsxReactImport) { | |
| state.jsxReactImport = jsxReactImport | |
| break | |
| } | |
| } | |
| } | |
| if (state.opts.cssPropOptimization === false) { | |
| state.transformCssProp = false | |
| } else { | |
| state.transformCssProp = true | |
| } | |
| 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)) { | |
| if (t.isArrayExpression(path.node.value.expression)) { | |
| transformCsslessArrayExpression({ | |
| state, | |
| babel, | |
| path | |
| }) | |
| } else if (t.isObjectExpression(path.node.value.expression)) { | |
| transformCsslessObjectExpression({ | |
| state, | |
| babel, | |
| path, | |
| cssImport: state.jsxReactImport | |
| }) | |
| } | |
| } | |
| }, | |
| 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) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |