Skip to content

Commit

Permalink
fix: export plugin as function (#13)
Browse files Browse the repository at this point in the history
* fix: export plugin as function instead of object

* wip: add @babel/helper-plugin-utils to assert Babel version 7 on older versions

* docs: fix readme
  • Loading branch information
umar-ahmed authored Feb 10, 2024
1 parent da262da commit 45670a7
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 91 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ npm install --save-dev @react-three/babel
```js
// .babelrc or babel.config.js
{
plugins: ['@react-three/babel']
plugins: ['module:@react-three/babel']
}
```
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@
"@react-three/fiber": ">=6",
"three": "*"
},
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
},
"devDependencies": {
"@babel/core": "^7.21.8",
"@react-three/fiber": "^8.13.0",
"@types/babel__core": "^7.20.0",
"@types/babel__helper-plugin-utils": "^7.10.3",
"@types/node": "^20.1.7",
"@types/three": "^0.152.0",
"three": "^0.152.2",
Expand Down
184 changes: 94 additions & 90 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type NodePath, type PluginObj, types as t } from '@babel/core'
import { type NodePath, types as t } from '@babel/core'
import { declare } from "@babel/helper-plugin-utils";
import * as THREE from 'three'

const imports = new Map<string, t.ImportSpecifier>()
Expand Down Expand Up @@ -45,105 +46,108 @@ let lastImport: NodePath | null = null
* })
* ```
*/
export default {
manipulateOptions(_, options) {
options.plugins.push('jsx')
},
post() {
imports.clear()
lastImport = null
},
visitor: {
CallExpression(path) {
// Remove extend(THREE) from Canvas and user-land
if (
t.isIdentifier(path.node.callee) &&
path.node.callee.name === 'extend' &&
t.isIdentifier(path.node.arguments[0]) &&
path.node.arguments[0].name === 'THREE'
) {
path.remove()
}
export default declare((api) => {
api.assertVersion(7)
return {
manipulateOptions(_, options) {
options.plugins.push('jsx')
},
ImportDeclaration(importPath) {
lastImport = importPath
post() {
imports.clear()
lastImport = null
},
JSXOpeningElement(path) {
// Don't include non-React JSX
// https://github.com/facebook/jsx/issues/13
const { name } = path.node
if (t.isJSXNamespacedName(name)) return
visitor: {
CallExpression(path) {
// Remove extend(THREE) from Canvas and user-land
if (
t.isIdentifier(path.node.callee) &&
path.node.callee.name === 'extend' &&
t.isIdentifier(path.node.arguments[0]) &&
path.node.arguments[0].name === 'THREE'
) {
path.remove()
}
},
ImportDeclaration(importPath) {
lastImport = importPath
},
JSXOpeningElement(path) {
// Don't include non-React JSX
// https://github.com/facebook/jsx/issues/13
const { name } = path.node
if (t.isJSXNamespacedName(name)) return

// Parse identifiers (e.g. <mesh />, <animated.mesh />)
let type = 'property' in name ? name.property.name : name.name
const declaration = path.scope.getBinding(type)?.path.node
if (t.isVariableDeclarator(declaration)) {
if (t.isStringLiteral(declaration.init)) {
// const Comp = 'value'
// <Comp />
type = declaration.init.value
} else if (t.isTemplateLiteral(declaration.init)) {
// const Comp = `value${var}`
// <Comp />
type = declaration.init.quasis.map((n) => n.value.cooked).join('\\w*')
} else if (t.isBinaryExpression(declaration.init) || t.isAssignmentExpression(declaration.init)) {
// const Comp = 'left' + 'right' || (left += 'right')
// <Comp />
type = [declaration.init.left, declaration.init.right]
.map((o) => (t.isStringLiteral(o) ? o.value : '\\w*'))
.join('')
} else if (t.isConditionalExpression(declaration.init)) {
// const Comp = test ? 'consequent' : 'alternate'
// <Comp />
type = [declaration.init.consequent, declaration.init.alternate]
.map((o) => (t.isStringLiteral(o) ? o.value : '\\w*'))
.join('|')
} else {
// const Comp = var
// <Comp />
type = '\\w+'
// Parse identifiers (e.g. <mesh />, <animated.mesh />)
let type = 'property' in name ? name.property.name : name.name
const declaration = path.scope.getBinding(type)?.path.node
if (t.isVariableDeclarator(declaration)) {
if (t.isStringLiteral(declaration.init)) {
// const Comp = 'value'
// <Comp />
type = declaration.init.value
} else if (t.isTemplateLiteral(declaration.init)) {
// const Comp = `value${var}`
// <Comp />
type = declaration.init.quasis.map((n) => n.value.cooked).join('\\w*')
} else if (t.isBinaryExpression(declaration.init) || t.isAssignmentExpression(declaration.init)) {
// const Comp = 'left' + 'right' || (left += 'right')
// <Comp />
type = [declaration.init.left, declaration.init.right]
.map((o) => (t.isStringLiteral(o) ? o.value : '\\w*'))
.join('')
} else if (t.isConditionalExpression(declaration.init)) {
// const Comp = test ? 'consequent' : 'alternate'
// <Comp />
type = [declaration.init.consequent, declaration.init.alternate]
.map((o) => (t.isStringLiteral(o) ? o.value : '\\w*'))
.join('|')
} else {
// const Comp = var
// <Comp />
type = '\\w+'
}
}
}

// Test type pattern
const pattern = new RegExp(`^${type}$`)
for (const className in THREE) {
const type = className.replace(/^[A-Z]/, (c) => c.toLowerCase())
if (!imports.has(className) && pattern.test(type)) {
const local = path.scope.generateUidIdentifier(className)
imports.set(className, t.importSpecifier(local, t.identifier(className)))
// Test type pattern
const pattern = new RegExp(`^${type}$`)
for (const className in THREE) {
const type = className.replace(/^[A-Z]/, (c) => c.toLowerCase())
if (!imports.has(className) && pattern.test(type)) {
const local = path.scope.generateUidIdentifier(className)
imports.set(className, t.importSpecifier(local, t.identifier(className)))
}
}
}
},
Program: {
exit(path) {
// Only mutate JSX
if (!imports.size) return
},
Program: {
exit(path) {
// Only mutate JSX
if (!imports.size) return

// Flatten three.js imports
const THREEImports = t.importDeclaration([...imports.values()], t.stringLiteral('three'))
// Flatten three.js imports
const THREEImports = t.importDeclaration([...imports.values()], t.stringLiteral('three'))

// Import extend and call it
const extendImport = t.importSpecifier(path.scope.generateUidIdentifier('extend'), t.identifier('extend'))
const extendCall = t.expressionStatement(
t.callExpression(extendImport.local, [
t.objectExpression(
(THREEImports.specifiers as t.ImportSpecifier[]).map((importSpecifier) =>
t.objectProperty(importSpecifier.imported, importSpecifier.local, false, true),
// Import extend and call it
const extendImport = t.importSpecifier(path.scope.generateUidIdentifier('extend'), t.identifier('extend'))
const extendCall = t.expressionStatement(
t.callExpression(extendImport.local, [
t.objectExpression(
(THREEImports.specifiers as t.ImportSpecifier[]).map((importSpecifier) =>
t.objectProperty(importSpecifier.imported, importSpecifier.local, false, true),
),
),
),
]),
)
]),
)

// Flatten R3F imports
const R3FExports = t.importDeclaration([extendImport], t.stringLiteral('@react-three/fiber'))
// Flatten R3F imports
const R3FExports = t.importDeclaration([extendImport], t.stringLiteral('@react-three/fiber'))

// Add statements in reverse order; imports must be top-level
for (const node of [extendCall, R3FExports, THREEImports]) {
if (lastImport) lastImport.insertAfter(node)
else path.unshiftContainer('body', node)
}
// Add statements in reverse order; imports must be top-level
for (const node of [extendCall, R3FExports, THREEImports]) {
if (lastImport) lastImport.insertAfter(node)
else path.unshiftContainer('body', node)
}
},
},
},
},
} satisfies PluginObj
}
})
23 changes: 23 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@
"@babel/traverse" "^7.21.5"
"@babel/types" "^7.21.5"

"@babel/helper-plugin-utils@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295"
integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==

"@babel/helper-simple-access@^7.21.5":
version "7.21.5"
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee"
Expand Down Expand Up @@ -364,6 +369,17 @@
resolved "https://registry.yarnpkg.com/@tweenjs/tween.js/-/tween.js-18.6.4.tgz#40a3d0a93647124872dec8e0fd1bd5926695b6ca"
integrity sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==

"@types/babel__core@*":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==
dependencies:
"@babel/parser" "^7.20.7"
"@babel/types" "^7.20.7"
"@types/babel__generator" "*"
"@types/babel__template" "*"
"@types/babel__traverse" "*"

"@types/babel__core@^7.20.0":
version "7.20.0"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891"
Expand All @@ -382,6 +398,13 @@
dependencies:
"@babel/types" "^7.0.0"

"@types/babel__helper-plugin-utils@^7.10.3":
version "7.10.3"
resolved "https://registry.yarnpkg.com/@types/babel__helper-plugin-utils/-/babel__helper-plugin-utils-7.10.3.tgz#d2240d11dd7a24624e47e9b3f8a790839d7e90d0"
integrity sha512-FcLBBPXInqKfULB2nvOBskQPcnSMZ0s1Y2q76u9H1NPPWaLcTeq38xBeKfF/RBUECK333qeaqRdYoPSwW7rTNQ==
dependencies:
"@types/babel__core" "*"

"@types/babel__template@*":
version "7.4.1"
resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969"
Expand Down

0 comments on commit 45670a7

Please sign in to comment.