@tobiastengler/create-relay-app
Advanced tools
Comparing version 0.0.7 to 1.0.0
import path from "path"; | ||
import { NEXT_SRC_PATH } from "../consts.js"; | ||
import { h } from "../utils/index.js"; | ||
@@ -45,3 +46,3 @@ import { ArgumentBase } from "./ArgumentBase.js"; | ||
if (existingArgs.toolchain === "next") { | ||
srcPath = "./src"; | ||
srcPath = NEXT_SRC_PATH; | ||
} | ||
@@ -48,0 +49,0 @@ const filepath = path.join(srcPath, filename); |
@@ -0,1 +1,2 @@ | ||
import { NEXT_APP_ROOT, APP_ROOT } from "../consts.js"; | ||
import { h } from "../utils/index.js"; | ||
@@ -36,6 +37,6 @@ import { ArgumentBase } from "./ArgumentBase.js"; | ||
if (existingArgs.toolchain === "next") { | ||
return Promise.resolve("./"); | ||
return Promise.resolve(NEXT_APP_ROOT); | ||
} | ||
return Promise.resolve("./src"); | ||
return Promise.resolve(APP_ROOT); | ||
} | ||
} |
@@ -10,3 +10,3 @@ #!/usr/bin/env node | ||
import { getPackageManger, inferPackageManager, } from "./misc/packageManagers/index.js"; | ||
import { GenerateArtifactDirectoryTask, GenerateRelayEnvironmentTask, GenerateGraphQlSchemaFileTask, TaskRunner, ConfigureRelayCompilerTask, Cra_AddBabelMacroTypeDefinitionsTask, InstallNpmDependenciesTask, InstallNpmDevDependenciesTask, Vite_ConfigureVitePluginRelayTask, Next_ConfigureNextCompilerTask, Cra_AddRelayEnvironmentProvider, Vite_AddRelayEnvironmentProvider, Next_AddRelayEnvironmentProvider, ConfigureEolOfArtifactsTask, HTTP_ENDPOINT, WEBSOCKET_ENDPOINT, } from "./tasks/index.js"; | ||
import { GenerateArtifactDirectoryTask, GenerateRelayEnvironmentTask, GenerateGraphQlSchemaFileTask, TaskRunner, ConfigureRelayCompilerTask, Cra_AddBabelMacroTypeDefinitionsTask, InstallNpmDependenciesTask, InstallNpmDevDependenciesTask, Vite_ConfigureVitePluginRelayTask, Next_ConfigureNextCompilerTask, Cra_AddRelayEnvironmentProvider, Vite_AddRelayEnvironmentProvider, Next_AddRelayEnvironmentProvider, ConfigureEolOfArtifactsTask, HTTP_ENDPOINT, WEBSOCKET_ENDPOINT, Next_AddTypeHelpers, } from "./tasks/index.js"; | ||
import { headline, h, importantHeadline, printError } from "./utils/index.js"; | ||
@@ -120,2 +120,3 @@ import { ProjectContext } from "./misc/ProjectContext.js"; | ||
new Next_ConfigureNextCompilerTask(context), | ||
new Next_AddTypeHelpers(context), | ||
new Next_AddRelayEnvironmentProvider(context), | ||
@@ -171,3 +172,3 @@ ]); | ||
// prettier-ignore | ||
console.log("https://github.com/tobias-tengler/create-relay-app/blob/main/docs/next-server-data-fetching.md"); | ||
console.log("https://github.com/tobias-tengler/create-relay-app/blob/main/docs/next-data-fetching.md"); | ||
} | ||
@@ -174,0 +175,0 @@ console.log(); |
export const TS_CONFIG_FILE = "tsconfig.json"; | ||
export const PACKAGE_FILE = "package.json"; | ||
export const APP_ROOT = "./src"; | ||
export const NEXT_APP_ROOT = "./"; | ||
export const NEXT_SRC_PATH = "./src"; | ||
export const TYPESCRIPT_PACKAGE = "typescript"; | ||
@@ -7,2 +10,3 @@ export const BABEL_RELAY_PACKAGE = "babel-plugin-relay"; | ||
export const REACT_RELAY_PACKAGE = "react-relay"; | ||
export const RELAY_RUNTIME_PACKAGE = "relay-runtime"; | ||
export const GRAPHQL_WS_PACKAGE = "graphql-ws"; | ||
@@ -9,0 +13,0 @@ export const VITE_RELAY_PACKAGE = "vite-plugin-relay"; |
import path from "path"; | ||
import { RELAY_ENV } from "../consts.js"; | ||
import { NEXT_SRC_PATH, RELAY_ENV } from "../consts.js"; | ||
export class ProjectContext { | ||
@@ -54,3 +54,3 @@ constructor(env, args, manager, fs) { | ||
if (args.toolchain === "next") { | ||
srcDirectory = "./src"; | ||
srcDirectory = NEXT_SRC_PATH; | ||
} | ||
@@ -57,0 +57,0 @@ const filepath = path.join(srcDirectory, filename); |
@@ -5,3 +5,3 @@ import traverse from "@babel/traverse"; | ||
import { RelativePath } from "../../misc/RelativePath.js"; | ||
import { insertNamedImport, parseAst, printAst } from "../../utils/ast.js"; | ||
import { insertNamedImport, parseAst, printAst, } from "../../utils/ast.js"; | ||
import { h } from "../../utils/cli.js"; | ||
@@ -8,0 +8,0 @@ import { TaskBase, TaskSkippedError } from "../TaskBase.js"; |
@@ -42,6 +42,2 @@ import { TaskBase } from "./TaskBase.js"; | ||
} | ||
if (this.context.is("next")) { | ||
// prettier-ignore | ||
b.addLine(`import type { RecordMap } from "relay-runtime/lib/store/RelayStoreTypes";`); | ||
} | ||
} | ||
@@ -159,11 +155,5 @@ // prettier-ignore | ||
export function initRelayEnvironment(initialRecords?: RecordMap) { | ||
export function initRelayEnvironment() { | ||
const environment = relayEnvironment ?? createRelayEnvironment(); | ||
// If your page has Next.js data fetching methods that use Relay, | ||
// the initial records will get hydrated here. | ||
if (initialRecords) { | ||
environment.getStore().publish(new RecordSource(initialRecords)); | ||
} | ||
// For SSG and SSR always create a new Relay environment. | ||
@@ -183,4 +173,2 @@ if (typeof window === "undefined") { | ||
if (!this.context.args.typescript) { | ||
// Remove Typescript type | ||
initEnv = initEnv.replace("initialRecords?: RecordMap", "initialRecords"); | ||
initEnv = initEnv.replace(": Environment | undefined", ""); | ||
@@ -187,0 +175,0 @@ } |
@@ -15,1 +15,2 @@ export * from "./TaskRunner.js"; | ||
export * from "./next/Next_AddRelayEnvironmentProvider.js"; | ||
export * from "./next/Next_AddTypeHelpers.js"; |
import traverse from "@babel/traverse"; | ||
import path from "path"; | ||
import { REACT_RELAY_PACKAGE, RELAY_ENV_PROVIDER } from "../../consts.js"; | ||
import { REACT_RELAY_PACKAGE, RELAY_ENV_PROVIDER, RELAY_RUNTIME_PACKAGE, } from "../../consts.js"; | ||
import { RelativePath } from "../../misc/RelativePath.js"; | ||
import { insertNamedImport, parseAst, printAst } from "../../utils/ast.js"; | ||
import { astToString, insertNamedImport, insertNamedImports, parseAst, prettifyCode, } from "../../utils/ast.js"; | ||
import { h } from "../../utils/cli.js"; | ||
@@ -10,8 +10,19 @@ import { TaskBase, TaskSkippedError } from "../TaskBase.js"; | ||
import { removeExtension, hasRelayProvider, wrapJsxInRelayProvider, } from "../cra/Cra_AddRelayEnvironmentProvider.js"; | ||
const envCreation = ` | ||
const environment = useMemo( | ||
() => initRelayEnvironment(pageProps.initialRecords), | ||
[pageProps.initialRecords] | ||
); | ||
import { Next_AddTypeHelpers } from "./Next_AddTypeHelpers.js"; | ||
const envCreationAndHydration = ` | ||
const environment = useMemo(initRelayEnvironment, []); | ||
useEffect(() => { | ||
const store = environment.getStore(); | ||
// Hydrate the store. | ||
store.publish(new RecordSource(pageProps.initialRecords)); | ||
// Notify any existing subscribers. | ||
store.notify(); | ||
}, [environment, pageProps.initialRecords]) | ||
`; | ||
const APP_PROPS = "AppProps"; | ||
const RELAY_PAGE_PROPS = "RelayPageProps"; | ||
export class Next_AddRelayEnvironmentProvider extends TaskBase { | ||
@@ -32,3 +43,2 @@ constructor(context) { | ||
const ast = parseAst(code); | ||
const envCreationAst = parseAst(envCreation).program.body[0]; | ||
let providerWrapped = false; | ||
@@ -41,2 +51,3 @@ traverse.default(ast, { | ||
} | ||
const functionReturn = path.parentPath; | ||
const isProviderConfigured = hasRelayProvider(path); | ||
@@ -46,7 +57,33 @@ if (isProviderConfigured) { | ||
} | ||
insertNamedImport(path, "useMemo", "react"); | ||
const relativeImportPath = new RelativePath(mainFile.parentDirectory, removeExtension(this.context.relayEnvFile.abs)); | ||
insertNamedImport(path, "initRelayEnvironment", relativeImportPath.rel); | ||
// Insert the useMemo creating the environment in the function body. | ||
path.parentPath.insertBefore(envCreationAst); | ||
// We need to modify the type of the _app arguments, | ||
// starting with Next 12.3. | ||
if (this.context.args.typescript) { | ||
// Import RelayPageProps. | ||
const relayTypesPath = Next_AddTypeHelpers.getRelayTypesPath(this.context); | ||
const relayTypesImportPath = new RelativePath(mainFile.parentDirectory, removeExtension(relayTypesPath.abs)); | ||
insertNamedImport(path, RELAY_PAGE_PROPS, relayTypesImportPath.rel); | ||
// Change argument of type AppProps to AppProps<RelayPageProps>. | ||
const functionBodyPath = functionReturn.parentPath; | ||
if (!functionBodyPath.isBlockStatement()) { | ||
throw new Error("Expected parentPath to be a block statement."); | ||
} | ||
const functionPath = functionBodyPath.parentPath; | ||
if (!functionPath.isFunctionDeclaration() || | ||
!t.isFunctionDeclaration(functionPath.node)) { | ||
throw new Error("Expected parentPath to be a function declaration."); | ||
} | ||
const appPropsArg = functionPath.node.params[0]; | ||
if (!appPropsArg) { | ||
throw new Error("Expected function to have one argument."); | ||
} | ||
const genericAppProps = t.genericTypeAnnotation(t.identifier(APP_PROPS), t.typeParameterInstantiation([ | ||
t.genericTypeAnnotation(t.identifier(RELAY_PAGE_PROPS)), | ||
])); | ||
appPropsArg.typeAnnotation = t.typeAnnotation(genericAppProps); | ||
} | ||
insertNamedImports(path, ["useMemo", "useEffect"], "react"); | ||
insertNamedImport(path, "RecordSource", RELAY_RUNTIME_PACKAGE); | ||
const relayEnvImportPath = new RelativePath(mainFile.parentDirectory, removeExtension(this.context.relayEnvFile.abs)); | ||
insertNamedImport(path, "initRelayEnvironment", relayEnvImportPath.rel); | ||
functionReturn.addComment("leading", "--MARKER", true); | ||
const envProviderId = t.jsxIdentifier(insertNamedImport(path, RELAY_ENV_PROVIDER, REACT_RELAY_PACKAGE).name); | ||
@@ -61,5 +98,7 @@ wrapJsxInRelayProvider(path, envProviderId, t.identifier("environment")); | ||
} | ||
const updatedCode = printAst(ast, code); | ||
let updatedCode = astToString(ast, code); | ||
updatedCode = updatedCode.replace("//--MARKER", envCreationAndHydration); | ||
updatedCode = prettifyCode(updatedCode); | ||
await this.context.fs.writeToFile(mainFile.abs, updatedCode); | ||
} | ||
} |
@@ -11,4 +11,7 @@ import generate from "@babel/generator"; | ||
} | ||
export function astToString(ast, oldCode) { | ||
return generate.default(ast, { retainLines: true }, oldCode).code; | ||
} | ||
export function printAst(ast, oldCode) { | ||
const newCode = generate.default(ast, { retainLines: true }, oldCode).code; | ||
const newCode = astToString(ast, oldCode); | ||
return prettifyCode(newCode); | ||
@@ -24,12 +27,39 @@ } | ||
export function insertNamedImport(path, importName, packageName) { | ||
const importIdentifier = t.identifier(importName); | ||
return insertNamedImports(path, [importName], packageName)[0]; | ||
} | ||
export function insertNamedImports(path, imports, packageName) { | ||
const program = path.findParent((p) => p.isProgram()); | ||
const existingImport = getNamedImport(program, importName, packageName); | ||
if (!!existingImport) { | ||
return importIdentifier; | ||
const identifiers = []; | ||
const missingImports = []; | ||
for (const namedImport of imports) { | ||
const importIdentifier = t.identifier(namedImport); | ||
const existingImport = getNamedImport(program, namedImport, packageName); | ||
if (!!existingImport) { | ||
identifiers.push(importIdentifier); | ||
continue; | ||
} | ||
missingImports.push(namedImport); | ||
} | ||
const importDeclaration = t.importDeclaration([t.importSpecifier(t.cloneNode(importIdentifier), importIdentifier)], t.stringLiteral(packageName)); | ||
// Insert import at start of file. | ||
program.node.body.unshift(importDeclaration); | ||
return importIdentifier; | ||
console.log({ imports, missingImports, identifiers }); | ||
let importDeclaration; | ||
const isFirstImportFromPackage = missingImports.length === imports.length; | ||
if (isFirstImportFromPackage) { | ||
console.log("create new"); | ||
importDeclaration = t.importDeclaration([], t.stringLiteral(packageName)); | ||
} | ||
else { | ||
console.log("get existing"); | ||
importDeclaration = getImportDeclaration(program, packageName); | ||
} | ||
for (const namedImport of missingImports) { | ||
const importIdentifier = t.identifier(namedImport); | ||
const newImport = t.importSpecifier(t.cloneNode(importIdentifier), importIdentifier); | ||
importDeclaration.specifiers.push(newImport); | ||
identifiers.push(importIdentifier); | ||
} | ||
if (isFirstImportFromPackage) { | ||
// Insert import at start of file. | ||
program.node.body.unshift(importDeclaration); | ||
} | ||
return identifiers; | ||
} | ||
@@ -48,2 +78,5 @@ export function insertDefaultImport(path, importName, packageName) { | ||
} | ||
function getImportDeclaration(path, packageName) { | ||
return path.node.body.find((s) => t.isImportDeclaration(s) && s.source.value === packageName); | ||
} | ||
export function getNamedImport(path, importName, packageName) { | ||
@@ -54,3 +87,3 @@ return path.node.body.find((s) => t.isImportDeclaration(s) && | ||
} | ||
export function getDefaultImport(path, importName, packageName) { | ||
function getDefaultImport(path, importName, packageName) { | ||
return path.node.body.find((s) => t.isImportDeclaration(s) && | ||
@@ -57,0 +90,0 @@ s.source.value === packageName && |
{ | ||
"name": "@tobiastengler/create-relay-app", | ||
"version": "v0.0.7", | ||
"version": "v1.0.0", | ||
"description": "Easy configuration of Relay for existing projects", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/tobias-tengler/create-relay-app#readme", |
@@ -54,3 +54,3 @@ <h1 align="center" style="font-size: 30px;">create-relay-app</h1> | ||
- [Manual steps after running the script](./docs/steps-after-setup.md) | ||
- [Server-side data fetching with Next.js](./docs/next-server-data-fetching.md) | ||
- [Data fetching with Next.js](./docs/next-data-fetching.md) | ||
- [babel-plugin-relay in combination with Create-React-App](./docs/cra-babel-setup.md) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
86235
48
2071
0