From f541db4d8999912a29ef19348a1aaa01f9094043 Mon Sep 17 00:00:00 2001 From: Alec Aivazis Date: Wed, 3 Aug 2022 13:41:11 -0700 Subject: [PATCH] generate component side of ssr fetch --- src/vite/transforms/index.ts | 12 +-- src/vite/transforms/kit.test.ts | 99 +++++++++++++++++++++--- src/vite/transforms/kit.ts | 128 ++++++++++++++++++++++++++++---- 3 files changed, 202 insertions(+), 37 deletions(-) diff --git a/src/vite/transforms/index.ts b/src/vite/transforms/index.ts index 7f36e34c8b..6a14907888 100644 --- a/src/vite/transforms/index.ts +++ b/src/vite/transforms/index.ts @@ -50,14 +50,6 @@ export default async function applyTransforms( return { code: content } } - // we need to apply the changes to the file. we'll do this by printing the mutated - // content as a string and then replacing everything between the appropriate - // script tags. the parser tells us the locations for the different tags so we - // just have to replace the indices it tells us to - const printedInstance = result.script ? (recast.print(result.script).code as string) : '' - - // just copy the instance where it needs to go - return { - code: printedInstance, - } + // print the result + return recast.print(result.script) } diff --git a/src/vite/transforms/kit.test.ts b/src/vite/transforms/kit.test.ts index 6c863083b2..45823c69ce 100644 --- a/src/vite/transforms/kit.test.ts +++ b/src/vite/transforms/kit.test.ts @@ -20,8 +20,22 @@ describe('kit route processor', function () { // make sure we added the right stuff expect(route.component).toMatchInlineSnapshot(` + import { houdini_load } from "./+page.js"; + import { browser } from "$app/env"; + import { getHoudiniContext } from "$houdini/runtime/lib/context"; import { GQL_TestQuery } from "$houdini/stores/TestQuery"; + $: + _houdini_inputs = $$props.data.inputs; + + const _houdini_context_DO_NOT_USE = getHoudiniContext(); + + $: + browser && GQL_TestQuery.fetch({ + context: _houdini_context_DO_NOT_USE, + variables: _houdini_inputs["TestQuery"] + }); + const { data } = query(GQL_TestQuery); @@ -106,17 +120,37 @@ describe('kit route processor', function () { // make sure we added the right stuff expect(route.component).toMatchInlineSnapshot(` - import { GQL_TestQuery2 } from "$houdini/stores/TestQuery2"; - import { GQL_TestQuery1 } from "$houdini/stores/TestQuery1"; + import { houdini_load } from "./+page.js"; + import { browser } from "$app/env"; + import { getHoudiniContext } from "$houdini/runtime/lib/context"; + import { GQL_TestQuery2 } from "$houdini/stores/TestQuery2"; + import { GQL_TestQuery1 } from "$houdini/stores/TestQuery1"; - const { - data: data1 - } = query(GQL_TestQuery1); + $: + _houdini_inputs = $$props.data.inputs; - const { - data: data2 - } = query(GQL_TestQuery2); - `) + const _houdini_context_DO_NOT_USE = getHoudiniContext(); + + $: + browser && GQL_TestQuery1.fetch({ + context: _houdini_context_DO_NOT_USE, + variables: _houdini_inputs["TestQuery1"] + }); + + $: + browser && GQL_TestQuery2.fetch({ + context: _houdini_context_DO_NOT_USE, + variables: _houdini_inputs["TestQuery2"] + }); + + const { + data: data1 + } = query(GQL_TestQuery1); + + const { + data: data2 + } = query(GQL_TestQuery2); + `) expect(route.script).toMatchInlineSnapshot(` import { RequestContext } from "$houdini/runtime/lib/network"; import { GQL_TestQuery2 } from "$houdini/stores/TestQuery2"; @@ -289,8 +323,34 @@ describe('kit route processor', function () { }) expect(route.component).toMatchInlineSnapshot(` + import { houdini_load } from "./+page.js"; + import { browser } from "$app/env"; + import { getHoudiniContext } from "$houdini/runtime/lib/context"; import { GQL_TestQuery } from "$houdini/stores/TestQuery"; + $: + _houdini_inputs = $$props.data.inputs; + + const _houdini_context_DO_NOT_USE = getHoudiniContext(); + + $: + browser && GQL_TestQuery.fetch({ + context: _houdini_context_DO_NOT_USE, + variables: _houdini_inputs["TestQuery"] + }); + + $: + browser && houdini_load[0].fetch({ + context: _houdini_context_DO_NOT_USE, + variables: _houdini_inputs["MyQuery1"] + }); + + $: + browser && houdini_load[1].fetch({ + context: _houdini_context_DO_NOT_USE, + variables: _houdini_inputs["MyQuery2"] + }); + const { data } = query(GQL_TestQuery); @@ -358,9 +418,24 @@ describe('kit route processor', function () { `, }) - expect(route.component).toMatchInlineSnapshot( - `import { GQL_TestQuery } from "$houdini/stores/TestQuery";` - ) + expect(route.component).toMatchInlineSnapshot(` + import { houdini_load } from "./+page.js"; + import { browser } from "$app/env"; + import { getHoudiniContext } from "$houdini/runtime/lib/context"; + + $: + _houdini_inputs = $$props.data.inputs; + + const _houdini_context_DO_NOT_USE = getHoudiniContext(); + + $: + browser && GQL_TestQuery.fetch({ + context: _houdini_context_DO_NOT_USE, + variables: _houdini_inputs["TestQuery"] + }); + + import { GQL_TestQuery } from "$houdini/stores/TestQuery"; + `) expect(route.script).toMatchInlineSnapshot(` import { RequestContext } from "$houdini/runtime/lib/network"; import { GQL_TestQuery } from "$houdini/stores/TestQuery"; diff --git a/src/vite/transforms/kit.ts b/src/vite/transforms/kit.ts index c847c648ae..9c3c6227b8 100644 --- a/src/vite/transforms/kit.ts +++ b/src/vite/transforms/kit.ts @@ -32,8 +32,14 @@ export default async function svelteKitProcessor(config: Config, page: Transform find_page_info(page), ]) - // merge page and inline queries into a single list const queries = inline_queries.concat(page_query ?? []) + for (const [i, target] of (page_info.load ?? []).entries()) { + queries.push({ + name: target.name, + variables: target.variables, + store_id: AST.memberExpression(AST.identifier('houdini_load'), AST.literal(i)), + }) + } // if we are processing a route component (+page.svelte) if (page.config.isRoute(page.filepath)) { @@ -56,24 +62,124 @@ export default async function svelteKitProcessor(config: Config, page: Transform async function process_component({ page, - queries: external_queries, + queries, page_info, }: { page: TransformPage queries: LoadTarget[] page_info: PageScriptInfo }) { + // find the first non import + let insert_index = page.script.body.findIndex( + (statement) => statement.type !== 'ImportDeclaration' + ) + if (insert_index === -1) { + insert_index = 0 + } + + // add an import for the context utility + ensure_imports({ + config: page.config, + script: page.script, + import: ['getHoudiniContext'], + sourceModule: '$houdini/runtime/lib/context', + }) + insert_index++ + + // import the browser chec + ensure_imports({ + config: page.config, + script: page.script, + import: ['browser'], + sourceModule: '$app/env', + }) + insert_index++ + + // if the page contains a defined load, import it + if (page_info.load) { + ensure_imports({ + config: page.config, + script: page.script, + import: ['houdini_load'], + sourceModule: './+page' + (page.config.typescript ? '.ts' : '.js'), + }) + insert_index++ + } + // the first thing we need to do is to define a local variable that // will hold onto the values we get from props + const input_obj = AST.identifier('_houdini_inputs') + page.script.body.splice( + insert_index++, + 0, + // @ts-ignore + AST.labeledStatement( + AST.identifier('$'), + AST.expressionStatement( + AST.assignmentExpression( + '=', + input_obj, + AST.memberExpression( + AST.memberExpression(AST.identifier('$$props'), AST.identifier('data')), + AST.identifier('inputs') + ) + ) + ) + ) + ) + + // create a context handler we can pass to the fetches + const houdini_context = AST.identifier('_houdini_context_DO_NOT_USE') + page.script.body.splice( + insert_index++, + 0, + // @ts-ignore + AST.variableDeclaration('const', [ + AST.variableDeclarator( + houdini_context, + AST.callExpression(AST.identifier('getHoudiniContext'), []) + ), + ]) + ) + + // we need to add the client side fetches for every query that we ran into + page.script.body.splice( + insert_index++, + 0, + // @ts-ignore + ...queries.map((query) => + AST.labeledStatement( + AST.identifier('$'), + AST.expressionStatement( + AST.logicalExpression( + '&&', + AST.identifier('browser'), + AST.callExpression( + AST.memberExpression(query.store_id, AST.identifier('fetch')), + [ + AST.objectExpression([ + AST.objectProperty(AST.identifier('context'), houdini_context), + AST.objectProperty( + AST.identifier('variables'), + AST.memberExpression(input_obj, AST.literal(query.name)) + ), + ]), + ] + ) + ) + ) + ) + ) + ) } function add_load({ page, - queries: external_queries, + queries, page_info, }: { - page: TransformPage queries: LoadTarget[] + page: TransformPage page_info: PageScriptInfo }) { // if there is already a load function defined, don't do anything @@ -83,7 +189,7 @@ function add_load({ // let's verify that we have all of the variable functions we need before we mutate anything let invalid = false - for (const query of (page_info.load ?? []).concat(external_queries)) { + for (const query of queries) { const variable_fn = query_variable_fn(query.name) // if the page doesn't export a function with the correct name, something is wrong if (!page_info.exports.includes(variable_fn) && query.variables) { @@ -118,14 +224,6 @@ could not find required variable function: ${variable_fn}. maybe its not exporte const promise_list = AST.identifier('promises') // build up a list of metadata for every store that we have to load - const load = external_queries - for (const [i, target] of (page_info.load ?? []).entries()) { - load.push({ - name: target.name, - variables: target.variables, - store_id: AST.memberExpression(AST.identifier('houdini_load'), AST.literal(i)), - }) - } const preload_fn = AST.functionDeclaration( AST.identifier('load'), @@ -175,7 +273,7 @@ could not find required variable function: ${variable_fn}. maybe its not exporte let insert_index = 3 // every query that we found needs to be triggered in this function - for (const query of load) { + for (const query of queries) { preload_fn.body.body.splice( insert_index++, 0, @@ -257,7 +355,7 @@ could not find required variable function: ${variable_fn}. maybe its not exporte ]) ) - let args = [request_context, page.config, load, input_obj, resolved_promises] as const + let args = [request_context, page.config, queries, input_obj, resolved_promises] as const // add calls to user before/after load functions if (before_load) {