-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
Conversation
The structures API is the way to go, still high level, almost tsc perf |
So, I'm dropping prettier and switching to the structures API for the next major |
Not looking too accurate in reality, when switching to fully running in structures
|
Calling
is 250ms |
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";"
`)
})
}) |
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 |
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() |
I'd like to try move this into something which is a two step process:
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? |
Using the TS builder:
|
Re #20