Skip to content
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

Document how to use adapter-cloudflare with svelte-kit dev #2966

Closed
jaymakes11 opened this issue Dec 2, 2021 · 26 comments
Closed

Document how to use adapter-cloudflare with svelte-kit dev #2966

jaymakes11 opened this issue Dec 2, 2021 · 26 comments

Comments

@jaymakes11
Copy link

Describe the problem

I can't figure out how to use svelte-kit dev and https://github.com/cloudflare/wrangler2 together while developing locally. This is necessary for sites that utilize Cloudflare's KV store and durable objects.

Is this currently possible?

Describe the proposed solution

If this is currently possible, let's get it documented.

Alternatives considered

I've tried this:

 npx wrangler@beta pages dev .svelte-kit/cloudflare/ --port 3131 -- npx svelte-kit dev --port 3131

The problem with this is that file changes do not update what's being served, nor do they update in the browser. Sveltekit (via Vite) is serving things from memory (as I understand it), but wrangler (beta) is serving the built site that lives in .svelte-kit/cloudflare/.

Importance

i cannot use SvelteKit without it

Additional Information

Note: Earlier I created a discussion on this and I've also reached out in the Discord. There's been no response to either. Hence the reason I'm creating this issue for it. Without this ability, I'm blocked on using Sveltekit.

@jaymakes11 jaymakes11 changed the title Please document how to use cloudflare-adapter with svelte-kit dev Document how to use cloudflare-adapter with svelte-kit dev Dec 2, 2021
@benmccann benmccann changed the title Document how to use cloudflare-adapter with svelte-kit dev Document how to use adapter-cloudflare with svelte-kit dev Dec 2, 2021
@mehmetcansahin
Copy link

ssr doesn't work for the same reason.

@jaymakes11
Copy link
Author

I'm curious if anyone is currently using the adapter-cloudflare with any type of local development setup that's working for them. I'm not worried if requires any type of hack, just something that works for now until this is officially supported/documented.

@lukeed Could you comment on whether or not there's any way (that you know of) for this to currently work? (i.e. is any type of local development setup possible with adapter-cloudflare?)

@lukeed
Copy link
Member

lukeed commented Dec 9, 2021

Use svelte-kit on its own for local development. If you need to make use of bindings (eg, KV), then setup miniflare and connect it to the .svelte-kit/cloudflare/_worker.js output file.

wrangler2 is in heavy beta development & currently has a very closed/limited configuration window. Wrangler (1.x) could be used for wrangler dev but would also need a type=javascript and custom build setup so that it watches the same .svelte-kit/cloudflare/_worker.js file for reload. You'd be running npm run dev and wrangler dev at the same time... awkward & not worth the effort IMO. Miniflare is the better investment.

@jaymakes11
Copy link
Author

Thanks for the quick and helpful reply, @lukeed.

If you need to make use of bindings (eg, KV), then setup miniflare and connect it to the .svelte-kit/cloudflare/_worker.js output file.

Yes, this is the issue (my project requires KV, DO, etc). It's not clear to me how your suggestion here would work, however, since svelte-kit dev (via Vite) serves everything from memory (as I understand things), rather than building statically on each file change (to .svelte-kit/). How would miniflare pick up file changes from src/* files?

It would be super helpful if you could share any example code (or point to any repositories) that use miniflare with a Sveltekit project using adapter-cloudflare.

@jaymakes11
Copy link
Author

Also, it seems that miniflare only supports Workers Sites (i.e. it does not support Cloudflare Pages).

CleanShot 2021-12-09 at 05 23 12@2x

For Cloudflare Pages projects, the miniflare developer is directing people to Wrangler v2.

@lukeed
Copy link
Member

lukeed commented Dec 9, 2021

It ended up being straightforward with wrangler@beta (wrangler2). There's a proxy-command mode that I wasn't aware of. Here's the necessary changes: lukeed/pages-fullstack@ebdee7e

@jaymakes11
Copy link
Author

It ended up being straightforward with wrangler@beta (wrangler2). There's a proxy-command mode that I wasn't aware of. Here's the necessary changes: lukeed/pages-fullstack@ebdee7e

Hmm... I'm seeing that this approach only serves the output of the built site (i.e. the result of running svelte-kit build), not the content of src/*. So when making a change to a file, the change is not updated in the browser. Am I missing something obvious here?

@lukeed
Copy link
Member

lukeed commented Dec 9, 2021

Thanks for the callout. I added a miniflare-based local server here: lukeed/pages-fullstack@6e53dce

It runs svelte-kit build when any src/** file changes. This means it's definitely nowhere near as fast as npm run dev for development, but it still works & reflects source changes. Personally, I'd continue using npm run dev for raw development speed & then drop into npm run watch whenever I want to check for Workers-runtime compatibility, guaranteed by Miniflare.

@jaymakes11
Copy link
Author

@lukeed thanks for the miniflare-based local server. It's a helpful step in the right direction, however, it's unclear to me how to use KV with it.

From this section of the Adapter Cloudflare docs, it seems that platform.env should be available in Svelte hooks and endpoints, however I'm seeing that it's always undefined. That section of the README states that this is only available in production builds. It's unclear to me if it should be available via the miniflare-based local server approach. I've tried testing production builds, as well, but it's still undefined.

A full-stack example that uses KV with a workable local development setup would be super helpful.

@harlantwood
Copy link

I'm seeing the same behavior -- actually platform is undefined in development, never mind platform.env.

The best idea I've seen for local dev on a sveltekit project that uses KV is (far from ideally) swapping out with Redis when in local dev; sample code here: https://stackoverflow.com/a/69067406/1533892

@vhscom
Copy link

vhscom commented Mar 28, 2022

I ended up creating a watch command as @lukeed mentioned earlier but as @jaymakes11 pointed out it's not suitable for development purposes, and I don't believe it's working for integration testing either as I am hitting this issue:

From this section of the Adapter Cloudflare docs, it seems that platform.env should be available in Svelte hooks and endpoints, however I'm seeing that it's always undefined.

I expected platform.env to be populated with at least something using wrangler2 with the following wrangler.toml contents:

[env.dev]
vars = { API_HOST_URI = "https://domain.example", ENDPOINT_PATH = "/path/to/endpoint" }

But the platform.env does not include the environment variables API_HOST_URI nor ENDPOINT_PATH when I run:

wrangler pages dev --env dev .svelte-kit/cloudflare -- pnpm run dev

Has anyone gotten this working? If so, could you please share a link to a working example? Thanks!

@NaNorNull
Copy link

I was able to get the local KV working with pages functions. Here is a hacky example that syncs the Cloudflare Pages Function KV to the local dev KV. It looks like the local values are stored in .mf directory in the root of the project.

https://github.com/NaNorNull/Cloudflare-Pages-Functions-With-KV-and-SvelteKit

npx wrangler@beta pages dev .svelte-kit\cloudflare\ -k kvName

@Rich-Harris
Copy link
Member

This is one facet of two larger issues — closing this in favour of those:

@juliancox
Copy link

Just in case anyone arriving here is, like me, looking for a quick solution that still allows to accessing a KV dev environment and still use sveltekit dev command with support for ssr etc, here's my quick hack:

Create a new brand new worker with a binding to a KV store (I'm calling mine 'KV'). The worker provides basic support for List, Get, Put and Delete on the store through the HTTP method and path...

addEventListener("fetch", event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  if (request.headers.get('Access-Key') !== ACCESS_KEY) {
    return new Response('Forbidden',{ status: 403 })
  }
  let data = null;
  const url = new URL(request.url);
  const key = url.pathname.substring(1);
  const params = url.searchParams;
  if (request.method === 'DELETE') {
    if (key) {
      data = await KV.delete(key);
    } else {
      data = {error: 'No key provided to delete'}
    }
  } else if (request.method === 'POST') {
    if (key) {
      data = await request.json()
      await KV.put(key, data);
    } else {
      data = {error: 'No key provided for value'}
    }
  } else { //assuming get method
    if (key) {
        data = await KV.get(key);
    } else {
      const prefix = params.get('prefix')
      data = await KV.list({prefix});
    }
  }
  const json = JSON.stringify(data, null, 2);
     return new Response(json, {
      headers: {
        'content-type': 'application/json;charset=UTF-8',
      },
    })
}

This worker also needs an ACCESS_KEY=yoursecret environment variable which provides basic protection against external use but you could add more robust limiting via whitelisted IP etc.
Publish that worker and copy the URL used to call it.

In your sveltekit repo add a kv.js file into your lib directory this falls back to your dev KV store if no platform object is found...

const devKV = function() {
    const url = import.meta.env.VITE_KVDEV_URL;
    const key = import.meta.env.VITE_KVDEV_ACCESS_KEY;

    const get = async function (key) {
        const response = await fetch(`${url}${encodeURIComponent(key)}`,{headers: {'Access-Key': key}});
        return await response.json();
    }

    const put = async function (key, value) {
        const response = await fetch(`${url}${encodeURIComponent(key)}`, {
            headers: {'Access-Key': key},
            method: 'POST',
            body: JSON.stringify(value)
        });
        return await response.json();
    }

    const list = async function ({ prefix }) {
        const response = await fetch(`${url}?prefix=${prefix ? encodeURIComponent(prefix) : ''}`,{headers: {'Access-Key': key}})
        return await response.json();
    }

    const del = async function (key, value) {
        const response = await fetch(`${url}${encodeURIComponent(key)}`, {
            headers: {'Access-Key': key},
            method: 'DELETE',
        });
        return await response.json();
    }

    return {
        get,
        put,
        list,
        delete: del
    }
}

export const kv = function kv(platform) {
   return  platform && platform.env ? platform.env.KV : devKV();
}

NB: This assumes that the binding for your dev KV and sveltekit pages KV is the same (in this example they are both bound as 'KV').

Add environment variables for the worker url and the shared ACCESS_KEY secret in your .env file (for the code above include a trailing forward slash in the URL):

VITE_KVDEV_ACCESS_KEY=yoursecret
VITE_KVDEV_URL=https://worker-name.your-org.workers.dev/

Then whenever you need to use the store import kv.js and instantiate with the platform object...


import {kv} from '$lib/kv.js';

export async function get({ request, platform }) {
    const KV = kv(platform)
    const details = await KV.list({})
    const items = details.keys;
    // etc
  }  

@RicardoViteriR
Copy link

Hi @juliancox , I tried your solution and the fallback works very well for local development. However, I am having difficulties once I deploy to Cloudflare Pages. The platform.env[KVNameSpace] key exists but the value is empty. Did you run across this issue?

@juliancox
Copy link

juliancox commented Jun 13, 2022 via email

@juliancox
Copy link

juliancox commented Jun 13, 2022 via email

@chientrm
Copy link
Contributor

In your app.d.ts

declare namespace App {
	interface Locals {
		STUDENTS: {
			get(key: string): Promise<string | null>;
			put(key: string, value: string): Promise<void>;
			list(): Promise<{ keys: { name: string }[] }>;
			delete(key: string): Promise<void>;
		};
	}
	interface Platform {
		env: { STUDENTS: KVNamespace };
	}
}

In hooks.ts

import type { Handle } from '@sveltejs/kit';

const students: Record<string, string> = {};

export const handle: Handle = async ({ event, resolve }) => {
	event.locals.STUDENTS = event.platform?.env.STUDENTS ?? {
		get: (key: string) => students[key],
		put: (key: string, value: string) => (students[key] = value),
		list: () => ({ keys: Object.keys(students).map((name) => ({ name })) }),
		delete: (key: string) => delete students[key]
	};
	return resolve(event);
};

In your endpoints create.ts

import type { RequestHandler } from './__types/create';

export const post: RequestHandler = async ({ locals, request }) => {
	const formData = await request.formData();
        const id = formData.get('id');
	const student = {
		name: formData.get('name'),
		age: formData.get('age')
	};
	await locals.STUDENTS.put(id, JSON.stringify(student));
	return { status: 302, headers: { Location: `/students/${id}` } };
};

@kalepail
Copy link

Putting this here in case it's helpful for someone. I've been using this repo as my boilerplate specifically for development via thanks to miniflare.
https://github.com/tyvdh/test-kit

@bbigras
Copy link

bbigras commented Jun 30, 2022

Putting this here in case it's helpful for someone. I've been using this repo as my boilerplate specifically for development via thanks to miniflare.
https://github.com/tyvdh/test-kit

Is miniflare usable for cloudflare pages? I thought we needed to use wrangler 2.

@kalepail
Copy link

kalepail commented Jun 30, 2022

Well miniflare is for development and even wrangler 2 uses miniflare in the background during development so it's a moot point.

When actually deploying to cloudflare pages you use the adapter-cloudflare keeping in mind that if you have bindings you'll need to add those manually in the dashboard for now (hoping they add wrangler.toml support for cloudflare pages soon)

@RicardoViteriR
Copy link

RicardoViteriR commented Sep 20, 2022

I tried @tyvdh repo but my installation was braking once I uploaded to Cloudflare Pages.

Here is my solution based on @tyvdh to access a KV store in Cloudflare:

Install miniflare npm i -D miniflare

In app.d.ts add your KVNamespace. In my case, it is called ASIMED_KV:

/// <reference types="@sveltejs/kit" />

// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare namespace App {
	interface Platform {
		env?: {
			ASIMED_KV: KVNamespace;
		};
	}
}

The Namespace has to match to your binding to a KV under your Cloudflare Pages project, in the Functions menu and Bindings

Next, setup a helper function to instantiate miniflare to use when in local development:

miniflare.ts

import { Miniflare, Log, LogLevel } from 'miniflare';
import { dev } from '$app/environment';

export const fallBackPlatformToMiniFlareInDev = async (_platform: App.Platform) => {
	if (!dev) return _platform;

	if (_platform) return _platform;
	const mf = new Miniflare({
		log: new Log(LogLevel.INFO),
		kvPersist: './kv-data', // Use filebase or in memory store
		kvNamespaces: ['ASIMED_KV'], //Declare array with NameSpaces
		globalAsyncIO: true,
		globalTimers: true,
		globalRandom: true,

		script: `
		addEventListener("fetch", (event) => {
			event.waitUntil(Promise.resolve(event.request.url));
			event.respondWith(new Response(event.request.headers.get("X-Message")));
		});
		addEventListener("scheduled", (event) => {
			event.waitUntil(Promise.resolve(event.scheduledTime));
		});
		`
	});

	// await mf.dispatchFetch('https://host.tld');

	const env = await mf.getBindings();

	const platform: App.Platform = { env };

	return platform;
};

Create hooks.server.ts in the scr directory, add a handler function and conditionally import the miniflare helper function. This happens to be important because if it is not conditionally loaded the Cloudflare Pages build will fail as core NodeJS libraries are not available:

import { dev } from '$app/environment';
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
	if (dev) {
		const { fallBackPlatformToMiniFlareInDev } = await import('$lib/clients/miniflare');
		event.platform = await fallBackPlatformToMiniFlareInDev(event.platform);
	}
	return resolve(event);
};

Finally, test it on an endpoint, in my case /api/kv-test/+server.ts:

import type { RequestHandler } from '@sveltejs/kit';
import { v4 as uuidv4 } from 'uuid';
import { json } from '@sveltejs/kit';

export const GET: RequestHandler = async ({ request, platform }) => {
	const ASIMED = platform.env.ASIMED_KV; //from NameSpace
	await ASIMED.put(String(uuidv4()), JSON.stringify(new Date()), {
		metadata: { someMetadataKey: 'someMetadataValue' }
	});

	return json(await ASIMED.list());
};

@GeoffreyBooth
Copy link

In Miniflare 3, the API has changed: mf.getBindings is not a function (see https://miniflare.dev/get-started/migrating). Is there a way to achieve the approach shown in #2966 (comment) using Miniflare 3?

@chientrm
Copy link
Contributor

Use this package cf-workers-proxy to start a worker proxy server when dev locally.

@jahir9991
Copy link

Use this package cf-workers-proxy to start a worker proxy server when dev locally.

this repo saved my day

@RicardoViteriR
Copy link

In Miniflare 3, the API has changed: mf.getBindings is not a function (see https://miniflare.dev/get-started/migrating). Is there a way to achieve the approach shown in #2966 (comment) using Miniflare 3?

Hi @GeoffreyBooth, the Cloudflare team has added support back to the getBindgins function in PR 639, the implementation is via proxy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests