diff --git a/README.md b/README.md index d2c99a5..c83a47c 100644 --- a/README.md +++ b/README.md @@ -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'] } ``` diff --git a/package.json b/package.json index 9c9f64a..e34171a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/index.ts b/src/index.ts index c22b86b..38c956f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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() @@ -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. , ) - 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' - // - type = declaration.init.value - } else if (t.isTemplateLiteral(declaration.init)) { - // const Comp = `value${var}` - // - 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') - // - 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' - // - type = [declaration.init.consequent, declaration.init.alternate] - .map((o) => (t.isStringLiteral(o) ? o.value : '\\w*')) - .join('|') - } else { - // const Comp = var - // - type = '\\w+' + // Parse identifiers (e.g. , ) + 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' + // + type = declaration.init.value + } else if (t.isTemplateLiteral(declaration.init)) { + // const Comp = `value${var}` + // + 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') + // + 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' + // + type = [declaration.init.consequent, declaration.init.alternate] + .map((o) => (t.isStringLiteral(o) ? o.value : '\\w*')) + .join('|') + } else { + // const Comp = var + // + 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 + } +}) diff --git a/yarn.lock b/yarn.lock index 429a401..fd02c88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -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" @@ -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"