Skip to content

Commit

Permalink
fix: load babel macro dynamically (#812)
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi committed Sep 5, 2022
1 parent 2a31e73 commit 067fcd7
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 192 deletions.
8 changes: 4 additions & 4 deletions package.json
Expand Up @@ -23,9 +23,9 @@
],
"typings": "./index.d.ts",
"scripts": {
"build": "rm -rf dist && rm -rf out && yarn build-babel && yarn build-webpack && yarn build-index",
"build-babel": "bunchee src/babel.js -f cjs --runtime node -e react -e babel-plugin-macros -o dist/babel/index.js",
"build-webpack": "bunchee src/webpack.js -f cjs --runtime node -e react -e babel-plugin-macros -o dist/webpack/index.js",
"build-babel": "bunchee src/babel.js -f cjs -e babel-plugin-macros --runtime node -o dist/babel/index.js",
"build": "rm -rf dist && rm -rf out && yarn build-webpack && yarn build-index && yarn build-babel",
"build-webpack": "bunchee src/webpack.js -f cjs --runtime node -o dist/webpack/index.js",
"build-index": "bunchee src/index.js -f cjs --runtime node -o dist/index/index.js",
"test": "ava",
"lint": "eslint ./src",
Expand Down Expand Up @@ -79,7 +79,7 @@
"@babel/types": "7.15.0",
"ava": "4.3.1",
"babel-plugin-macros": "2.8.0",
"bunchee": "2.0.3",
"bunchee": "2.0.4",
"convert-source-map": "1.7.0",
"eslint": "7.32.0",
"eslint-config-prettier": "4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion src/babel.js
Expand Up @@ -20,7 +20,7 @@ import { default as babelMacro } from './macro'
import { default as babelTest } from './babel-test'

export function macro() {
return babelMacro
return babelMacro(require('babel-plugin-macros'))
}

export function test() {
Expand Down
199 changes: 101 additions & 98 deletions src/macro.js
@@ -1,121 +1,124 @@
import { createMacro, MacroError } from 'babel-plugin-macros'
import { processTaggedTemplateExpression } from './babel-external'
import {
setStateOptions,
createReactComponentImportDeclaration
} from './_utils'
import { STYLE_COMPONENT } from './_constants'

export default createMacro(styledJsxMacro)
export default ({ createMacro, MacroError }) => {
return createMacro(styledJsxMacro)

function styledJsxMacro({ references, state }) {
setStateOptions(state)
function styledJsxMacro({ references, state }) {
setStateOptions(state)

// Holds a reference to all the lines where strings are tagged using the `css` tag name.
// We print a warning at the end of the macro in case there is any reference to css,
// because `css` is generally used as default import name for 'styled-jsx/css'.
// People who want to migrate from this macro to pure styled-jsx might have name conflicts issues.
const cssReferences = []
// Holds a reference to all the lines where strings are tagged using the `css` tag name.
// We print a warning at the end of the macro in case there is any reference to css,
// because `css` is generally used as default import name for 'styled-jsx/css'.
// People who want to migrate from this macro to pure styled-jsx might have name conflicts issues.
const cssReferences = []

// references looks like this
// {
// default: [path, path],
// resolve: [path],
// }
Object.keys(references).forEach(refName => {
// Enforce `resolve` as named import so people
// can only import { resolve } from 'styled-jsx/macro'
// or an alias of it eg. { resolve as foo }
if (refName !== 'default' && refName !== 'resolve') {
throw new MacroError(
`Imported an invalid named import: ${refName}. Please import: resolve`
)
}
// references looks like this
// {
// default: [path, path],
// resolve: [path],
// }
Object.keys(references).forEach(refName => {
// Enforce `resolve` as named import so people
// can only import { resolve } from 'styled-jsx/macro'
// or an alias of it eg. { resolve as foo }
if (refName !== 'default' && refName !== 'resolve') {
throw new MacroError(
`Imported an invalid named import: ${refName}. Please import: resolve`
)
}

// Start processing the references for refName
references[refName].forEach(path => {
// We grab the parent path. Eg.
// path -> css
// path.parenPath -> css`div { color: red }`
let templateExpression = path.parentPath
// Start processing the references for refName
references[refName].forEach(path => {
// We grab the parent path. Eg.
// path -> css
// path.parenPath -> css`div { color: red }`
let templateExpression = path.parentPath

// templateExpression member expression?
// path -> css
// path.parentPath -> css.resolve
if (templateExpression.isMemberExpression()) {
// grab .resolve
const tagPropertyName = templateExpression.get('property').node.name
// Member expressions are only valid on default imports
// eg. import css from 'styled-jsx/macro'
if (refName !== 'default') {
throw new MacroError(
`Can't use named import ${path.node.name} as a member expression: ${
path.node.name
}.${tagPropertyName}\`div { color: red }\` Please use it directly: ${
path.node.name
}\`div { color: red }\``
)
}
// templateExpression member expression?
// path -> css
// path.parentPath -> css.resolve
if (templateExpression.isMemberExpression()) {
// grab .resolve
const tagPropertyName = templateExpression.get('property').node.name
// Member expressions are only valid on default imports
// eg. import css from 'styled-jsx/macro'
if (refName !== 'default') {
throw new MacroError(
`Can't use named import ${
path.node.name
} as a member expression: ${
path.node.name
}.${tagPropertyName}\`div { color: red }\` Please use it directly: ${
path.node.name
}\`div { color: red }\``
)
}

// Otherwise enforce `css.resolve`
if (tagPropertyName !== 'resolve') {
throw new MacroError(
`Using an invalid tag: ${tagPropertyName}. Please use ${
templateExpression.get('object').node.name
}.resolve`
)
}
// Otherwise enforce `css.resolve`
if (tagPropertyName !== 'resolve') {
throw new MacroError(
`Using an invalid tag: ${tagPropertyName}. Please use ${
templateExpression.get('object').node.name
}.resolve`
)
}

// Grab the TaggedTemplateExpression
// i.e. css.resolve`div { color: red }`
templateExpression = templateExpression.parentPath
} else {
if (refName === 'default') {
const { name } = path.node
throw new MacroError(
`Can't use default import directly eg. ${name}\`div { color: red }\`. Please use ${name}.resolve\`div { color: red }\` instead.`
)
}
// Grab the TaggedTemplateExpression
// i.e. css.resolve`div { color: red }`
templateExpression = templateExpression.parentPath
} else {
if (refName === 'default') {
const { name } = path.node
throw new MacroError(
`Can't use default import directly eg. ${name}\`div { color: red }\`. Please use ${name}.resolve\`div { color: red }\` instead.`
)
}

if (path.node.name === 'css') {
// If the path node name is `css` we push it to the references above to emit a warning later.
cssReferences.push(path.node.loc.start.line)
if (path.node.name === 'css') {
// If the path node name is `css` we push it to the references above to emit a warning later.
cssReferences.push(path.node.loc.start.line)
}
}
}

if (!state.styleComponentImportName) {
const programPath = path.findParent(p => p.isProgram())
state.styleComponentImportName = programPath.scope.generateUidIdentifier(
STYLE_COMPONENT
).name
const importDeclaration = createReactComponentImportDeclaration(state)
programPath.unshiftContainer('body', importDeclaration)
}
if (!state.styleComponentImportName) {
const programPath = path.findParent(p => p.isProgram())
state.styleComponentImportName = programPath.scope.generateUidIdentifier(
STYLE_COMPONENT
).name
const importDeclaration = createReactComponentImportDeclaration(state)
programPath.unshiftContainer('body', importDeclaration)
}

// Finally transform the path :)
processTaggedTemplateExpression({
type: 'resolve',
path: templateExpression,
file: state.file,
splitRules:
typeof state.opts.optimizeForSpeed === 'boolean'
? state.opts.optimizeForSpeed
: process.env.NODE_ENV === 'production',
plugins: state.plugins,
vendorPrefixes: state.opts.vendorPrefixes,
sourceMaps: state.opts.sourceMaps,
styleComponentImportName: state.styleComponentImportName
// Finally transform the path :)
processTaggedTemplateExpression({
type: 'resolve',
path: templateExpression,
file: state.file,
splitRules:
typeof state.opts.optimizeForSpeed === 'boolean'
? state.opts.optimizeForSpeed
: process.env.NODE_ENV === 'production',
plugins: state.plugins,
vendorPrefixes: state.opts.vendorPrefixes,
sourceMaps: state.opts.sourceMaps,
styleComponentImportName: state.styleComponentImportName
})
})
})
})

if (cssReferences.length > 0) {
console.warn(
`styled-jsx - Warning - We detected that you named your tag as \`css\` at lines: ${cssReferences.join(
', '
)}.\n` +
'This tag name is usually used as default import name for `styled-jsx/css`.\n' +
'Porting macro code to pure styled-jsx in the future might be a bit problematic.'
)
if (cssReferences.length > 0) {
console.warn(
`styled-jsx - Warning - We detected that you named your tag as \`css\` at lines: ${cssReferences.join(
', '
)}.\n` +
'This tag name is usually used as default import name for `styled-jsx/css`.\n' +
'Porting macro code to pure styled-jsx in the future might be a bit problematic.'
)
}
}
}
2 changes: 1 addition & 1 deletion test/fixtures/macro.js
@@ -1,4 +1,4 @@
import css, { resolve } from '../../src/macro'
import css, { resolve } from '../../test/helpers/babel-test.macro'

const { className, styles } = resolve`
div { color: red }
Expand Down
5 changes: 5 additions & 0 deletions test/helpers/babel-test.macro.js
@@ -0,0 +1,5 @@
import { macro } from '../../src/babel'

const m = macro()
console.log('m', m)
export default m
16 changes: 8 additions & 8 deletions test/macro.js
Expand Up @@ -27,7 +27,7 @@ test('transpiles correctly', async t => {
test('throws when using the default export directly', async t => {
const { message } = await t.throwsAsync(() =>
transformSource(`
import css from './src/macro'
import css from './test/helpers/babel-test.macro'
css\`div { color: red }\`
`)
Expand All @@ -39,7 +39,7 @@ test('throws when using the default export directly', async t => {
test('throws when using the default export directly and it is not called css', async t => {
const { message } = await t.throwsAsync(() =>
transformSource(`
import foo from './src/macro'
import foo from './test/helpers/babel-test.macro'
foo\`div { color: red }\`
`)
Expand All @@ -51,7 +51,7 @@ test('throws when using the default export directly and it is not called css', a
test('throws when using the default export directly and it is not called resolve', async t => {
const { message } = await t.throwsAsync(() =>
transformSource(`
import resolve from './src/macro'
import resolve from './test/helpers/babel-test.macro'
resolve\`div { color: red }\`
`)
Expand All @@ -63,7 +63,7 @@ test('throws when using the default export directly and it is not called resolve
test('throws when using an invalid method from the default export', async t => {
const { message } = await t.throwsAsync(() =>
transformSource(`
import css from './src/macro'
import css from './test/helpers/babel-test.macro'
css.foo\`div { color: red }\`
`)
Expand All @@ -75,7 +75,7 @@ test('throws when using an invalid method from the default export', async t => {
test('throws when using a named import different than resolve', async t => {
const { message } = await t.throwsAsync(() =>
transformSource(`
import { foo } from './src/macro'
import { foo } from './test/helpers/babel-test.macro'
foo\`div { color: red }\`
`)
Expand All @@ -87,7 +87,7 @@ test('throws when using a named import different than resolve', async t => {
test('throws when using a named import as a member expression', async t => {
const { message } = await t.throwsAsync(() =>
transformSource(`
import { resolve } from './src/macro'
import { resolve } from './test/helpers/babel-test.macro'
resolve.foo\`div { color: red }\`
`)
Expand All @@ -98,7 +98,7 @@ test('throws when using a named import as a member expression', async t => {

test('can alias the named import', async t => {
const { code } = await transformSource(`
import { resolve as foo } from './src/macro'
import { resolve as foo } from './test/helpers/babel-test.macro'
foo\`div { color: red }\`
`)
Expand All @@ -107,7 +107,7 @@ test('can alias the named import', async t => {

test('injects JSXStyle for nested scope', async t => {
const { code } = await transformSource(`
import { resolve } from './src/macro'
import { resolve } from './test/helpers/babel-test.macro'
function test() {
resolve\`div { color: red }\`
Expand Down

0 comments on commit 067fcd7

Please sign in to comment.