-
Notifications
You must be signed in to change notification settings - Fork 0
Adds running the puzzmo codebase inside the tests in order to figure out whats slow #22
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