UNPKG

@tobiastengler/create-relay-app

Version:

Easy configuration of Relay for existing projects

85 lines (84 loc) 3.61 kB
import traverse from "@babel/traverse"; import path from "path"; import { REACT_RELAY_PACKAGE, RELAY_ENV, RELAY_ENV_PROVIDER } from "../../consts.js"; import { RelativePath } from "../../misc/RelativePath.js"; import { insertNamedImport, parseAst, printAst } from "../../utils/ast.js"; import { bold } from "../../utils/cli.js"; import { TaskBase, TaskSkippedError } from "../TaskBase.js"; import t from "@babel/types"; export class Cra_AddRelayEnvironmentProvider extends TaskBase { constructor(context) { super(); this.context = context; this.message = "Add " + RELAY_ENV_PROVIDER; } isEnabled() { return this.context.is("cra"); } async run() { const mainFilename = "index" + (this.context.args.typescript ? ".tsx" : ".js"); const mainFile = this.context.env.rel(path.join("src", mainFilename)); this.updateMessage(this.message + " in " + bold(mainFile.rel)); const code = await this.context.fs.readFromFile(mainFile.abs); const ast = parseAst(code); configureRelayProviderInReactDomRender(ast, mainFile, this.context.relayEnvFile); const updatedCode = printAst(ast, code); await this.context.fs.writeToFile(mainFile.abs, updatedCode); } } export function hasRelayProvider(jsxPath) { let isProviderConfigured = false; jsxPath.traverse({ JSXOpeningElement: (path) => { if (isProviderConfigured) { return; } if (t.isJSXIdentifier(path.node.name) && path.node.name.name === RELAY_ENV_PROVIDER) { isProviderConfigured = true; path.skip(); return; } }, }); return isProviderConfigured; } export function configureRelayProviderInReactDomRender(ast, currentFile, relayEnvFile) { let providerWrapped = false; traverse.default(ast, { JSXElement: (path) => { if (providerWrapped) { return; } const parent = path.parentPath.node; // Check if it's the top JSX in ReactDOM.render(...) if (!t.isCallExpression(parent) || !t.isMemberExpression(parent.callee) || !t.isIdentifier(parent.callee.property) || parent.callee.property.name !== "render") { return; } const isProviderConfigured = hasRelayProvider(path); if (isProviderConfigured) { throw new TaskSkippedError("Already added"); } const relativeRelayImport = new RelativePath(currentFile.parentDirectory, removeExtension(relayEnvFile.abs)); const envId = insertNamedImport(path, RELAY_ENV, relativeRelayImport.rel); const envProviderId = t.jsxIdentifier(insertNamedImport(path, RELAY_ENV_PROVIDER, REACT_RELAY_PACKAGE).name); wrapJsxInRelayProvider(path, envProviderId, envId); providerWrapped = true; path.skip(); }, }); if (!providerWrapped) { throw new Error("Could not find JSX being passed to ReactDOM.render"); } } export function wrapJsxInRelayProvider(jsxPath, envProviderId, envId) { // Wrap JSX into RelayEnvironmentProvider. jsxPath.replaceWith(t.jsxElement(t.jsxOpeningElement(envProviderId, [ t.jsxAttribute(t.jsxIdentifier("environment"), t.jsxExpressionContainer(envId)), ]), t.jsxClosingElement(envProviderId), [jsxPath.node])); } export function removeExtension(filename) { return filename.substring(0, filename.lastIndexOf(".")) || filename; }