Skip to content

Commit

Permalink
Generate plugin runtimes before collecting documents (#1223)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlecAivazis committed Oct 27, 2023
1 parent 1fa2392 commit ae73932
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 80 deletions.
5 changes: 5 additions & 0 deletions .changeset/many-hounds-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'houdini': patch
---

Plugin runtimes are now generated before documents are collected
2 changes: 1 addition & 1 deletion packages/houdini-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,4 @@
},
"main": "./build/plugin-cjs/index.js",
"types": "./build/plugin/index.d.ts"
}
}
2 changes: 1 addition & 1 deletion packages/houdini-svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@
},
"main": "./build/plugin-cjs/index.js",
"types": "./build/plugin/index.d.ts"
}
}
2 changes: 1 addition & 1 deletion packages/houdini/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,4 @@
"bin": "./build/cmd-esm/index.js",
"main": "./build/lib-cjs/index.js",
"types": "./build/lib/index.d.ts"
}
}
163 changes: 89 additions & 74 deletions packages/houdini/src/codegen/generators/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,11 @@ import { generatePluginIndex } from './pluginIndex'
import { injectConfig } from './runtimeConfig'

export default async function runtimeGenerator(config: Config, docs: Document[]) {
const importStatement =
config.module === 'commonjs'
? importDefaultFrom
: (where: string, as: string) => `import ${as} from '${where}'`

const exportStatement =
config.module === 'commonjs' ? exportDefault : (as: string) => `export default ${as}`

const exportStar =
config.module === 'commonjs'
? exportStarFrom
: (where: string) => `export * from '${where}'`
const {
importStatement,
exportDefaultStatement: exportStatement,
exportStarStatement: exportStar,
} = moduleStatments(config)

// copy the appropriate runtime first so we can generate files over it
await Promise.all([
Expand Down Expand Up @@ -47,83 +40,105 @@ ${exportStatement('config')}
content
) => injectPlugins({ config, content, importStatement, exportStatement }),
}),
...config.plugins
.filter((plugin) => plugin.includeRuntime)
.map((plugin) =>
generatePluginRuntime({
config,
docs,
plugin,
importStatement,
exportDefaultStatement: exportStatement,
exportStarStatement: exportStar,
})
),
transformPluginRuntimes({ config, docs }),
generatePluginIndex({ config, exportStatement: exportStar }),
])

await generateGraphqlReturnTypes(config, docs)
}

async function generatePluginRuntime({
config,
docs,
plugin,
importStatement,
exportDefaultStatement,
exportStarStatement,
}: {
config: Config
docs: Document[]
plugin: Config['plugins'][number]
importStatement: (where: string, as: string) => string
exportDefaultStatement: (val: string) => string
exportStarStatement: (val: string) => string
}) {
if (houdini_mode.is_testing || !plugin.includeRuntime) {
return
}
export async function generatePluginRuntimes({ config }: { config: Config }) {
await Promise.all(
config.plugins
.filter((plugin) => plugin.includeRuntime)
.map(async (plugin) => {
if (houdini_mode.is_testing || !plugin.includeRuntime) {
return
}

// a plugin has told us to include a runtime then the path is relative to the plugin file
const runtime_path = path.join(
path.dirname(plugin.filepath),
typeof plugin.includeRuntime === 'string'
? plugin.includeRuntime
: plugin.includeRuntime[config.module]
)

try {
await fs.stat(runtime_path)
} catch {
throw new HoudiniError({
message: 'Cannot find runtime to generate for ' + plugin.name,
description: 'Maybe it was bundled?',
})
}

// a plugin has told us to include a runtime then the path is relative to the plugin file
const runtime_path = path.join(
path.dirname(plugin.filepath),
typeof plugin.includeRuntime === 'string'
? plugin.includeRuntime
: plugin.includeRuntime[config.module]
// copy the runtime
const pluginDir = config.pluginRuntimeDirectory(plugin.name)

await fs.mkdirp(pluginDir)
await fs.recursiveCopy(runtime_path, pluginDir)
})
)
}

try {
await fs.stat(runtime_path)
} catch {
throw new HoudiniError({
message: 'Cannot find runtime to generate for ' + plugin.name,
description: 'Maybe it was bundled?',
})
}
async function transformPluginRuntimes({ config, docs }: { config: Config; docs: Document[] }) {
const { importStatement, exportDefaultStatement, exportStarStatement } = moduleStatments(config)

// copy the runtime
const pluginDir = config.pluginRuntimeDirectory(plugin.name)
let transformMap = plugin.transformRuntime ?? {}
if (transformMap && typeof transformMap === 'function') {
transformMap = transformMap(docs, { config })
}
await Promise.all(
config.plugins
.filter((plugin) => plugin.includeRuntime)
.map(async (plugin) => {
// the transform map holds a map of files to transform functions
let transformMap = plugin.transformRuntime ?? {}
if (transformMap && typeof transformMap === 'function') {
transformMap = transformMap(docs, { config })
}

// the keys of the transform map are the files we have to transform
for (const [target, transform] of Object.entries(transformMap)) {
// the path to the file we're transforming
const targetPath = path.join(config.pluginRuntimeDirectory(plugin.name), target)

// read the file
const content = await fs.readFile(targetPath)
if (!content) {
return
}

await fs.mkdirp(pluginDir)
await fs.recursiveCopy(
runtime_path,
pluginDir,
Object.fromEntries(
Object.entries(transformMap).map(([key, value]) => [
path.join(runtime_path, key),
(content) =>
value({
// transform the file
const transformed = transform({
config,
content,
importStatement,
exportDefaultStatement: exportDefaultStatement,
exportStarStatement: exportStarStatement,
}),
])
)
})

// write the file back out
await fs.writeFile(targetPath, transformed)
}
})
)
}

function moduleStatments(config: Config) {
const importStatement =
config.module === 'commonjs'
? importDefaultFrom
: (where: string, as: string) => `import ${as} from '${where}'`

const exportDefaultStatement =
config.module === 'commonjs' ? exportDefault : (as: string) => `export default ${as}`

const exportStarStatement =
config.module === 'commonjs'
? exportStarFrom
: (where: string) => `export * from '${where}'`

return {
importStatement,
exportDefaultStatement,
exportStarStatement,
}
}
5 changes: 5 additions & 0 deletions packages/houdini/src/codegen/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ import type { Config, PluginHooks, Document, LogLevels } from '../lib'
import { runPipeline as run, LogLevel, find_graphql, parseJS, HoudiniError, fs, path } from '../lib'
import { ArtifactKind, type ArtifactKinds } from '../runtime/lib/types'
import * as generators from './generators'
import { generatePluginRuntimes } from './generators/runtime'
import * as transforms from './transforms'
import * as validators from './validators'

// the main entry point of the compile script
export default async function compile(config: Config) {
// before we collect the documents, we need to generate the plugin runtimes
// so that they can include documents in the user's project
await generatePluginRuntimes({ config })

// grab the graphql documents
const documents = await collectDocuments(config)

Expand Down
16 changes: 16 additions & 0 deletions packages/houdini/src/lib/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,19 @@ test(`Files that should not be included`, async () => {
expect(config.includeFile(path.join(process.cwd(), 'src/routes/test?'))).toBe(false)
expect(config.includeFile(path.join(process.cwd(), 'src/rou?tes/page.nop?s?e'))).toBe(false)
})

test('Config.include includes plugin runtimes', () => {
const config = testConfig()

config.plugins = [
{
name: 'test-plugin',
filepath: '',
includeRuntime: 'foo',
},
]

// make sure we are including the plugin runtime
const includePath = path.relative(config.projectRoot, config.pluginDirectory('test-plugin'))
expect(config.include.some((path) => path.includes(includePath))).toBeTruthy()
})
21 changes: 19 additions & 2 deletions packages/houdini/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,25 @@ export class Config {
this.plugins.flatMap((plugin) => plugin.extensions ?? [])
)

// any file of a valid extension in src is good enough
return [`src/**/*{${extensions.join(',')}}`]
// by default, any file of a valid extension in src is good enough
const include = [`src/**/*{${extensions.join(',')}}`]

// if any of the plugins specify included runtimes then their paths might have
// documents
for (const plugin of this.plugins) {
// skip plugins that dont' include runtimes
if (!plugin.includeRuntime) {
continue
}

// the include path is relative to root of the vite project
const includePath = path.relative(this.projectRoot, this.pluginDirectory(plugin.name))

// add the plugin's directory to the include pile
include.push(`${includePath}/**/*{${extensions.join(',')}}`)
}

return include
}

pluginConfig<ConfigType extends {}>(name: string): ConfigType {
Expand Down
7 changes: 7 additions & 0 deletions packages/houdini/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ export type PluginHooks = {

includeRuntime?: string | { esm: string; commonjs: string }

/**
* A relative path from the file exporting your plugin to a runtime that can be
* added to the project before generating artifacts. This is useful for plugins
* that want to add third-party documents to the user's application.
*/
staticRuntime?: string | { esm: string; commonjs: string }

/**
* Transform the plugin's runtime while houdini is copying it .
* You must have passed a value to includeRuntime for this hook to matter.
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-svelte-global-stores/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@
},
"main": "./build/plugin-cjs/index.js",
"types": "./build/plugin/index.d.ts"
}
}

0 comments on commit ae73932

Please sign in to comment.