Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: load babel macro dynamically #812

Merged
merged 2 commits into from Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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