Skip to content

Commit

Permalink
Query outside routes (#95)
Browse files Browse the repository at this point in the history
* split  up module from framework config

* move test config defn to bottom of file

* tests pass

* internal api change

* fixed build errors for init command

* dry up config file generator

* incorporate recent init changes

* dry preprocessor tests

* cleanup

* rename cache internals

* distinguish routes from components

* wrap non-route queries in a component-only wrapper

* first pass at firing a query on mount

* added comment

* better handling of null variable function

* add tests for component queries inside routes in a bare svelte app

* cleanup typedefs

* remove lastKnownVariables and fix lasting issue

* deep-equal is not esm compatible

* fix bug causing stale variables to be used when updating a store's data

* remove unused config

* tests pass

* remove unused import in example

* move variable computation into a separate reactive statement

* document loading state

* update example config file

* add section for bare svelte apps and spa mode
  • Loading branch information
AlecAivazis authored Jun 16, 2021
1 parent 205adc2 commit 1897cb1
Show file tree
Hide file tree
Showing 29 changed files with 17,888 additions and 588 deletions.
60 changes: 55 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ for the generation of an incredibly lean GraphQL abstraction for your applicatio
1. [Configuring Your Application](#configuring-your-application)
1. [Sapper](#sapper)
1. [SvelteKit](#sveltekit)
1. [Svelte](#svelte)
1. [Running the Compiler](#running-the-compiler)
1. [Fetching Data](#fetching-data)
1. [Query variables and page data](#query-variables-and-page-data)
1. [Loading State](#loading-state)
1. [Refetching Data](#refetching-data)
1. [What about load?](#what-about-load)
1. [Fragments](#fragments)
Expand Down Expand Up @@ -165,6 +167,13 @@ If you have updated your schema on the server, you can pull down the most recent
npx houdini generate --pull-schema
```

### Svelte

If you are working on an application that isn't using SvelteKit or Sapper, you have to configure the
compiler and preprocessor to generate the correct logic by setting the `framework` field in your
config file to `"svelte"`. You should also use this setting if you are building a SvelteKit application
in SPA mode.

## 🚀  Fetching Data

Grabbing data from your API is done with the `query` function:
Expand All @@ -189,11 +198,6 @@ Grabbing data from your API is done with the `query` function:
{/each}
```

Please note that since `query` desugars into a `load` function (see [below](#what-about-load)) you
can only use it inside of a page or layout. This will be addressed soon. Until then, you will need
to only use `query` inside of components defined under `/src/routes`.


### Query variables and page data

At the moment, query variables are declared as a function in the module context of your component.
Expand Down Expand Up @@ -241,6 +245,52 @@ modified example from the [demo](./example):
{/each}
```

### Loading State

The methods used for tracking the loading state of your queries changes depending
on the context of your component. For queries that live in routes (ie, in
`/src/routes/...`), the actual query happens in a `load` function as described
in [What about load?](#what-about-load). Because of this, the best way to track
if your query is loading is to use the
[navigating store](https://kit.svelte.dev/docs#modules-$app-stores) exported from `$app/stores`:

```svelte
// src/routes/index.svelte
<script>
import { query } from '$houdini'
import { navigating } from '$app/stores'
const { data } = query(...)
</script>
{#if $navigating}
loading...
{:else}
data is loaded!
{/if}
```

However, since queries inside of non-route components (ie, ones that are not defined in `/src/routes/...`)
do not get hoisted to a `load` function, the recommended practice to is use the store returned from
the result of query:

```svelte
// src/components/MyComponent.svelte
<script>
import { query } from '$houdini'
const { data, loading } = query(...)
</script>
{#if $loading}
loading...
{:else}
data is loaded!
{/if}
```

### Refetching Data

Refetching data is done with the `refetch` function provided from the result of a query:
Expand Down
3 changes: 2 additions & 1 deletion example/houdini.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'path'
export default {
schemaPath: path.resolve('./schema/schema.gql'),
sourceGlob: 'src/**/*.svelte',
mode: 'kit',
framework: 'kit',
module: 'esm',
apiUrl: 'http://localhost:4000/graphql',
}
116 changes: 74 additions & 42 deletions packages/houdini-common/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,36 @@ export type ConfigFile = {
schemaPath?: string
schema?: string
quiet?: boolean
verifyHash?: boolean
apiUrl?: string
// an old config file could specify mode instead of framework and module
mode?: 'kit' | 'sapper'
framework?: 'kit' | 'sapper' | 'svelte'
module?: 'esm' | 'commonjs'
}

// a place to hold conventions and magic strings
export class Config {
filepath: string
rootDir: string
projectRoot: string
schema: graphql.GraphQLSchema
apiUrl?: string
schemaPath?: string
sourceGlob: string
quiet: boolean
verifyHash: boolean
mode: string = 'sapper'
framework: 'sapper' | 'kit' | 'svelte' = 'sapper'
module: 'commonjs' | 'esm' = 'commonjs'

constructor({
schema,
schemaPath,
sourceGlob,
apiUrl,
quiet = false,
verifyHash,
filepath,
mode = 'sapper',
framework = 'sapper',
module = 'commonjs',
mode,
}: ConfigFile & { filepath: string }) {
// make sure we got some kind of schema
if (!schema && !schemaPath) {
Expand All @@ -53,23 +57,47 @@ export class Config {
)
}

// if we were given a mode instead of framework/module
if (mode) {
if (!quiet) {
// warn the user
console.warn('Encountered deprecated config value: mode')
console.warn(
'This parameter will be removed in a future version. Please update your config with the following values:'
)
}
if (mode === 'sapper') {
if (!quiet) {
console.warn(JSON.stringify({ framework: 'sapper', module: 'commonjs' }))
}
framework = 'sapper'
module = 'commonjs'
} else {
if (!quiet) {
console.warn(JSON.stringify({ framework: 'kit', module: 'esm' }))
}
framework = 'kit'
module = 'esm'
}
}

// save the values we were given
this.schemaPath = schemaPath
this.apiUrl = apiUrl
this.filepath = filepath
this.sourceGlob = sourceGlob
this.quiet = quiet
this.verifyHash = typeof verifyHash === 'undefined' ? true : verifyHash
this.mode = mode
this.framework = framework
this.module = module
this.projectRoot = path.dirname(filepath)

// if we are building a sapper project, we want to put the runtime in
// src/node_modules so that we can access @sapper/app and interact
// with the application stores directly
const rootDir = path.dirname(filepath)
this.rootDir =
mode === 'sapper'
? path.join(rootDir, 'src', 'node_modules', '$houdini')
: path.join(rootDir, '$houdini')
framework === 'sapper'
? path.join(this.projectRoot, 'src', 'node_modules', '$houdini')
: path.join(this.projectRoot, '$houdini')
}

/*
Expand Down Expand Up @@ -288,8 +316,39 @@ export class Config {
throw new Error('Could not find connection name from fragment: ' + fragmentName)
}
}
// a place to store the current configuration
let _config: Config

export function testConfig(config: {} = {}) {
// get the project's current configuration
export async function getConfig(): Promise<Config> {
if (_config) {
return _config
}

// load the config file
const configPath = path.join(process.cwd(), 'houdini.config.js')

// on windows, we need to prepend the right protocol before we
// can import from an absolute path
let importPath = configPath
if (os.platform() === 'win32') {
importPath = 'file:///' + importPath
}

const imported = await import(importPath)

// if this is wrapped in a default, use it
const config = imported.default || imported

// add the filepath and save the result
_config = new Config({
...config,
filepath: configPath,
})
return _config
}

export function testConfig(config: Partial<ConfigFile> = {}) {
return new Config({
filepath: path.join(process.cwd(), 'config.cjs'),
sourceGlob: '123',
Expand Down Expand Up @@ -357,39 +416,12 @@ export function testConfig(config: {} = {}) {
cat: Cat
}
`,
framework: 'sapper',
quiet: true,
...config,
})
}

// a place to store the current configuration
let _config: Config

// get the project's current configuration
export async function getConfig(): Promise<Config> {
if (_config) {
return _config
}

// load the config file
const configPath = path.join(process.cwd(), 'houdini.config.js')

// on windows, we need to prepend the right protocol before we
// can import from an absolute path
let importPath = configPath
if (os.platform() === 'win32') {
importPath = 'file:///' + importPath
}

const imported = await import(importPath)

// if this is wrapped in a default, use it
const config = imported.default || imported

// add the filepath and save the result
_config = new Config({
...config,
filepath: configPath,
})
return _config
type Partial<T> = {
[P in keyof T]?: T[P]
}
36 changes: 2 additions & 34 deletions packages/houdini-preprocess/src/transforms/fragment.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// external imports
import * as svelte from 'svelte/compiler'
// local imports
import fragmentProcessor from './fragment'
import { testConfig } from 'houdini-common'
import { preprocessorTest } from '../utils'
import '../../../../jest.setup'

describe('fragment preprocessor', function () {
Expand All @@ -20,7 +18,7 @@ describe('fragment preprocessor', function () {
`)

// make sure we added the right stuff
expect(doc.instance.content).toMatchInlineSnapshot(`
expect(doc.instance?.content).toMatchInlineSnapshot(`
import _TestFragmentArtifact from "$houdini/artifacts/TestFragment";
let reference;
Expand All @@ -31,33 +29,3 @@ describe('fragment preprocessor', function () {
`)
})
})

async function preprocessorTest(content: string) {
const schema = `
type User {
id: ID!
}
`

// parse the document
const parsed = svelte.parse(content)

// build up the document we'll pass to the processor
const config = testConfig({ schema, verifyHash: false })

const doc = {
instance: parsed.instance,
module: parsed.module,
config,
dependencies: [],
filename: 'base.svelte',
}

// @ts-ignore
// run the source through the processor
await fragmentProcessor(config, doc)

// invoke the test
return doc
}
40 changes: 2 additions & 38 deletions packages/houdini-preprocess/src/transforms/mutation.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// external imports
import * as svelte from 'svelte/compiler'
// local imports
import mutationProcessor from './mutation'
import { testConfig } from 'houdini-common'
import '../../../../jest.setup'
import { preprocessorTest } from '../utils'

describe('mutation preprocessor', function () {
test('happy path', async function () {
Expand All @@ -22,7 +19,7 @@ describe('mutation preprocessor', function () {
`)

// make sure we added the right stuff
expect(doc.instance.content).toMatchInlineSnapshot(`
expect(doc.instance?.content).toMatchInlineSnapshot(`
import _AddUserArtifact from "$houdini/artifacts/AddUser";
import { mutation } from "$houdini";
Expand All @@ -33,36 +30,3 @@ describe('mutation preprocessor', function () {
`)
})
})

async function preprocessorTest(content: string) {
const schema = `
type User {
id: ID!
}
type Mutation {
addUser: User!
}
`

// parse the document
const parsed = svelte.parse(content)

// build up the document we'll pass to the processor
const config = testConfig({ schema, verifyHash: false })

const doc = {
instance: parsed.instance,
module: parsed.module,
config,
dependencies: [],
filename: 'base.svelte',
}

// @ts-ignore
// run the source through the processor
await mutationProcessor(config, doc)

// invoke the test
return doc
}
Loading

0 comments on commit 1897cb1

Please sign in to comment.