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

Adds running the puzzmo codebase inside the tests in order to figure out whats slow #22

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

orta
Copy link
Member

@orta orta commented Sep 15, 2024

Re #20

@orta
Copy link
Member Author

orta commented Oct 29, 2024

[sdl-codegen] Creating shared schema files
[sdl-codegen] Creating shared schema files: 433.366ms
[sdl-codegen] Creating shared schema files via tsc
[sdl-codegen] Creating shared schema files via tsc: 48.238ms
[sdl-codegen] Creating shared schema files via structure
[sdl-codegen] Creating shared schema files via structure: 54.231ms

The structures API is the way to go, still high level, almost tsc perf

@orta
Copy link
Member Author

orta commented Oct 29, 2024

So, I'm dropping prettier and switching to the structures API for the next major

@orta
Copy link
Member Author

orta commented Oct 29, 2024

Not looking too accurate in reality, when switching to fully running in structures

[sdl-codegen] Creating shared schema files
[sdl-codegen] Creating shared schema files: 135.916ms
[sdl-codegen] Creating shared return position schema files
[sdl-codegen] Creating shared return position schema files: 294.18ms

@orta
Copy link
Member Author

orta commented Oct 29, 2024

[sdl-codegen] Creating shared schema files
createSharedExternalSchemaFile:types-loop: 5.608ms
createSharedExternalSchemaFile:scalars: 0.038ms
createSharedExternalSchemaFile:write: 235.658ms
createSharedExternalSchemaFile:read: 0.051ms
createSharedExternalSchemaFile: 248.43ms
[sdl-codegen] Creating shared schema files: 248.773ms

Calling

console.time("createSharedExternalSchemaFile:write")
	externalTSFile.set({ statements })
	console.timeEnd("createSharedExternalSchemaFile:write")

is 250ms

@orta
Copy link
Member Author

orta commented Nov 3, 2024

Babel version is looking pretty reasonable

import generator from "@babel/generator"
import parser from "@babel/parser"
import t from "@babel/types"

export const builder = (priorSource: string, opts: {}) => {
  const sourceFile = parser.parse(priorSource, { sourceType: "module", plugins: ["jsx", "typescript"] })

  const setImport = (source: string, opts: { subImports?: string[]; mainImport?: string }) => {
    const imports = sourceFile.program.body.filter((s) => s.type === "ImportDeclaration")

    const existing = imports.find((i) => i.source.value === source)
    if (!existing) {
      const imports = [] as t.ImportSpecifier[]
      if (opts.mainImport) {
        imports.push(t.importDefaultSpecifier(t.identifier(opts.mainImport)))
      }

      if (opts.subImports) {
        imports.push(...opts.subImports.map((si) => t.importSpecifier(t.identifier(si), t.identifier(si))))
      }

      const importDeclaration = t.importDeclaration(imports, t.stringLiteral(source))

      sourceFile.program.body.push(importDeclaration)
      return
    }

    if (!existing.specifiers.find((f) => f.type === "ImportDefaultSpecifier") && opts.mainImport) {
      existing.specifiers.push(t.importDefaultSpecifier(t.identifier(opts.mainImport)))
    }

    if (opts.subImports) {
      const existingImports = existing.specifiers.map((e) => e.local.name)
      const newImports = opts.subImports.filter((si) => !existingImports.includes(si))

      if (newImports.length) {
        existing.specifiers.push(...newImports.map((si) => t.importSpecifier(t.identifier(si), t.identifier(si))))
      }
    }
  }

  const getResult = () => {
    return generator(sourceFile.program).code
  }
  return { setImport, getResult }
}
import { describe, expect, it } from "vitest"

import { builder } from "./tsBuilder"

it("creates a new file", () => {
  const api = builder("", {})

  api.setImport("react-relay", { subImports: ["graphql", "useFragment"] })
  const code = api.getResult()

  expect(code).toMatchInlineSnapshot(`"import { graphql, useFragment } from "react-relay";"`)
})

describe("imports", () => {
  it("adds a new import", () => {
    const api = builder("", {})

    api.setImport("react-relay", { subImports: ["graphql", "useFragment"] })
    const code = api.getResult()

    expect(code).toMatchInlineSnapshot(`"import { graphql, useFragment } from "react-relay";"`)
  })

  it("adds a new import with main import", () => {
    const api = builder("", {})

    api.setImport("react-relay", { mainImport: "relay", subImports: ["graphql", "useFragment"] })
    const code = api.getResult()

    expect(code).toMatchInlineSnapshot(`"import relay, { graphql, useFragment } from "react-relay";"`)
  })

  it("adds a new import with main import and existing imports", () => {
    const api = builder(`import {graphql} from "react-relay"`, {})

    api.setImport("react-relay", { subImports: ["graphql", "useFragment"] })
    api.setImport("react", { subImports: ["useState"], mainImport: "React" })
    const code = api.getResult()

    expect(code).toMatchInlineSnapshot(`
      "import { graphql, useFragment } from "react-relay";
      import React, { useState } from "react";"
    `)
  })
})

@orta
Copy link
Member Author

orta commented Nov 3, 2024

I explored a high level typescript compiler builder API, but the AST is just not built to be edited, things crash unexpectedly

import * as ts from "typescript"

export const builder = (priorSourceFileLocation: string, opts: { host?: ts.CompilerHost; forceClean?: boolean }) => {
  const host = opts.host || ts.createCompilerHost({})
  const tsf = ts.factory

  const program = ts.createProgram([priorSourceFileLocation], { allowJs: true }, host)

  const sourceFile =
    host.fileExists(priorSourceFileLocation) && !opts.forceClean
      ? program.getSourceFile(priorSourceFileLocation)!
      : ts.createSourceFile(priorSourceFileLocation, "", ts.ScriptTarget.ESNext)

  const setImport = (source: string, opts: { subImports?: string[]; mainImport?: string }) => {
    const imports = sourceFile.statements.filter((s) => ts.isImportDeclaration(s))
    const subImports = opts.subImports || []

    const existing = imports.find((s) => (s as ts.ImportDeclaration).moduleSpecifier.getText() === `"${source}"`)
    if (!existing) {
      const importStatement = tsf.createImportDeclaration(
        undefined,
        tsf.createImportClause(
          false,
          opts.mainImport ? tsf.createIdentifier(opts.mainImport) : undefined,
          tsf.createNamedImports(subImports.map((si) => tsf.createImportSpecifier(false, undefined, tsf.createIdentifier(si)))),
        ),
        tsf.createStringLiteral(source),
      )
      sourceFile.statements = [importStatement, ...sourceFile.statements]
      return
    }

    if (!existing.importClause && opts.mainImport) {
      existing.importClause = tsf.createImportClause(false, tsf.createIdentifier(opts.mainImport), undefined)
    }

    if (opts.subImports) {
      const namedImports = existing.importClause?.namedBindings as ts.NamedImports
      const existingImports = namedImports.elements.map((e) => e.getText())
      const newImports = opts.subImports.filter((si) => !existingImports.includes(si))

      if (newImports.length) {
        namedImports.elements = [
          ...namedImports.elements,
          ...newImports.map((si) => tsf.createImportSpecifier(false, undefined, tsf.createIdentifier(si))),
        ]
      }
    }
  }

  const getResult = () => ts.createPrinter().printFile(sourceFile)
  return { setImport, getResult }
}
import { createDefaultMapFromNodeModules, createSystem, createVirtualCompilerHost } from "@typescript/vfs"
import * as ts from "typescript"
import { describe, expect, it } from "vitest"

import { builder } from "./tsBuilder"

const setupHost = () => {
  const fsMap = createDefaultMapFromNodeModules({ target: ts.ScriptTarget.ES2023 })
  fsMap.set("/index.ts", ``)
  const system = createSystem(fsMap)
  return createVirtualCompilerHost(system, { target: ts.ScriptTarget.ESNext }, ts)
}

it("creates a new file", () => {
  const { compilerHost } = setupHost()
  const api = builder("/index.ts", { host: compilerHost })

  api.setImport("react-relay", { subImports: ["graphql", "useFragment"] })
  const code = api.getResult()

  expect(code).toMatchInlineSnapshot(`
    "import { graphql, useFragment } from "react-relay";
    "
  `)
})

describe("imports", () => {
  it("adds a new import", () => {
    const { compilerHost } = setupHost()
    const api = builder("/index.ts", { host: compilerHost })

    api.setImport("react-relay", { subImports: ["graphql", "useFragment"] })
    const code = api.getResult()

    expect(code).toMatchInlineSnapshot(`
            "import { graphql, useFragment } from "react-relay";
            "
        `)
  })

  it("adds a new import with main import", () => {
    const { compilerHost } = setupHost()
    const api = builder("/index.ts", { host: compilerHost })

    api.setImport("react-relay", { subImports: ["graphql", "useFragment"], mainImport: "relay" })
    const code = api.getResult()

    expect(code).toMatchInlineSnapshot(`
            "import relay, { graphql, useFragment } from "react-relay";
            "
        `)
  })

  it("adds a new import with main import and existing imports", () => {
    const { compilerHost } = setupHost()
    const api = builder("/index.ts", { host: compilerHost })

    api.setImport("react-relay", { subImports: ["graphql", "useFragment"] })
    api.setImport("react", { subImports: ["useState"], mainImport: "react" })
    const code = api.getResult()

    expect(code).toMatchInlineSnapshot(`
            "import relay, { graphql, useFragment } from "react-relay";
            import react, { useState } from "react";
            "
        `)
  })
})

Think this might need to be a babel AST instead

@orta
Copy link
Member Author

orta commented Nov 4, 2024

Current builder usage in the Burr templating system:

  const sourceCode = existsSync(filePath) ? readFileSync(filePath, "utf-8") : ``
  const api = builder(sourceCode, {})

  // Imports
  api.setImport("react-relay", { subImports: ["graphql", "useFragment"] })
  api.setImport(`src/__generated__/${singularPascalName}Form_${singularCamelName}.graphql`, {
    subImports: [` ${singularPascalName}Form_${singularCamelName}$key`],
  })
  api.setImport(`src/__generated__/Edit${singularPascalName}PageMutation.graphql`, {
    subImports: [`Update${singularPascalName}Input`],
  })

  // Types
  api.setTypeViaTemplate(
    `type ${singularPascalName}Form_${singularCamelName} = { ${singularCamelName}: ${singularPascalName}Form_${singularCamelName}$key }`,
  )

  api.setTypeViaTemplate(`type EditForm = { onSave: (data: any, id?: string) => void; loading: boolean; error?: Error }`)

  // Main component
  const component = api.addFunction(`${singularPascalName}Form`)
  component.addParam("props", `Props & EditForm`)

  component.addVariableDeclaration(singularCamelName, (prior) => {
    if (prior) return api.updateGraphQLTemplateTag(prior, ".", modelFields)

    const statement = api.parseStatement(
      `useFragment(graphql\`query ${singularPascalName}Form_${singularCamelName} { ${modelFields.join(", ")} } \`)`,
    ) as t.ExpressionStatement

    return statement.expression
  })

  const code = api.getResult()

@orta
Copy link
Member Author

orta commented Nov 4, 2024

I'd like to try move this into something which is a two step process:

  • calling these builder functions creates an intermediary object
  • then this works against the prior AST
  • only augment

want to maybe think in "scopes" instead of real AST terms: "add const in root scope", "add const in this fns scope" etc

Perhaps further down the line this could also have a "from AST" where it reads an existing AST to generate the intermediary object"

Would need to have graphql templates represented in the AST, probably a babel plugin?

@orta
Copy link
Member Author

orta commented Nov 10, 2024

Using the TS builder:

createSharedExternalSchemaFile:types-loop: 50.042ms
createSharedExternalSchemaFile:scalars: 1.454ms
createSharedExternalSchemaFile:write: 0.001ms
createSharedExternalSchemaFile:read: 33.815ms
createSharedExternalSchemaFile: 87.58ms

#24

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant