1.0.3 • Published 6 months ago

full-client-server-sveltekit v1.0.3

Weekly downloads
-
License
-
Repository
github
Last release
6 months ago

full-client-server-sveltekit

Background

Inspired by blitz.js I wanted to able to write my entire code in one file. Without thinking much about server apis and other server separation also I wanted learn to use ast so I created this.

This uses websocket to allow pretty much any data type to be shared to and from the server but it is not very optimized right now, also I'm a physics student so can't work on this very much.

How to use

First install it by npm i full-client-server-sveltekit

then change your vite config preferably ts

import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import WebSockets from "@carlosv2/adapter-node-ws/plugin";
import {serverBrowserSync} from "full-client-server-sveltekit/plugin";

export default defineConfig({
	plugins: [
		sveltekit(), 
		WebSockets(),
		serverBrowserSync(),
	],
	server: {
		hmr: { port: 3000 } // any port that is not the port that the development server runs on
    }
});

change your svelte config as follows

import adapter from "@carlosv2/adapter-node-ws/adapter";
import { vitePreprocess } from '@sveltejs/kit/vite';
import path from "path"

/** @type {import('@sveltejs/kit').Config} */
const config = {
	// Consult https://kit.svelte.dev/docs/integrations#preprocessors
	// for more information about preprocessors
	preprocess: vitePreprocess(),
	
	kit: {
		// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
		// If your environment is not supported or you settled on a specific environment, switch out the adapter.
		// See https://kit.svelte.dev/docs/adapters for more information about adapters.
		adapter: adapter()
	}
};

export default config;

add a hooks.server

change it's content to the follows

import handleWS from "$lib/ws"
export const handleWs = handleWS((wsEvents) => {
});

This library adds the ws.js file for you with jsdoc typing. And it is added in the lib folder.

And to make a part run in the server from the browser you can try the given example

<!-- src/routes/+page.svelte -->
<script lang="ts">
    import node from "full-client-server-sveltekit"
    import { say } from "server:/routes/toBeImport"
    import WebSocket from "server:npm:ws";
    
    class A {
        c() {
            console.log(this.b)
        }
        a() {
            console.log("a", this.b)
        }
        constructor(public b: number) {
            
        }
    }
    const AInstance = new A(1)
    node(() => {
        say()
        console.log(WebSocket)
        console.log("hello")
    })
    function fn() {
        console.log("client")
        return "to server"
    }
    let hello = "hello server"
    let bigInt = 100n
    const constant = "constant"
    $: setTimeout(() => console.log("update", bigInt), 10)
    let a = node(async () => {
        (await import("./toBeImport")).say()
        console.log(hello)
        console.log(constant)
        hello = "hello client"
        console.log(await fn())
        console.log(AInstance.c())
        console.log(AInstance.a())
        console.log("hello after fn")
        console.log(bigInt)
        bigInt = 12n
        console.log(bigInt)
        return "to client"
    })
    let counter = 0
</script>

<h1>Welcome to your library project</h1>
<p>Create your package using @sveltejs/package and preview/showcase your work with SvelteKit</p>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
{hello}
{#await a}
    hello
{:then a} 
    <h1>{a}</h1>
{/await}

<button on:click={function () {
    console.log(counter)
    node(() => {
        counter = counter + 1
        console.log(counter)
        console.warn("this works again")
        return {
            a: Promise.resolve("hello"),
        }
    }).then(async (e) => {
        console.log(await e.a)
    })
}}>
    increment {counter}
</button>

Also you will have to add the toBeImport file in src/routes, that will run on the server only.

here the node function in ssr directly calls the function in the node call on browser it transpiles to nodeCall(id, [...dependencies], (...updats) => ...(dependencies = updates)) where id is the file name followed by - which is not 1, 2, 3 for some reason, but it goes like 80, 80 84, I don't understand what went wrong

Also the server:/ import, imports from src/ that is server:/routes/toBeImport becomes /routes/toBeImport. also server:npm: imports npm packages as server only. In the browser, the import is done through a virtual file, which imports the file through a node call. Also if you import a function and use it as a function, it will not call it as a value but call it similarly to nodeCall, so any in place value like a string or and options object won't be exposed to the browser. You still should'n do backend query outside of the node function.

Also the ws.js file should become something like

import WSEvents from "full-client-server-sveltekit/ws-events";
import { serialize, deserialize } from "full-client-server-sveltekit";
/** @typedef {import("ws").WebSocketServer} WebSocketServer */


/**
* @param {(wse: import("full-client-server-sveltekit/ws-events").WSEventHandler) => any} cb
* @return {(wse: WebSocketServer) => void}
*/
export default function handleWs(cb) {
    return function handleWse(wse) {
        wse.on("connection", ws => {
            /** @typedef {Record<string, Record<string, any>>} CacheType */
            /** @type {CacheType} */
            let data = {
                cache: {}
            }
            ws.onclose = function () {
                delete data.cache
            }
            
            const wsEvents = WSEvents(ws);
            
            wsEvents.on("__internal_full_client_server_import__/routes/toBeImport?=,say=say", /** 
            * @this CacheType
            * @param {string} str
            */ async function (str) {
                let [id, update] = deserialize(
                    str, 
                    "front", 
                    wsEvents,
                    this.cache
                );
                let caller = async () => await import("/home/mav/repos/full-client-server-sveltekit/src/routes/toBeImport")

                const result = await caller();
                update();
                wsEvents.emit(`__internal_full_client_server_import__/routes/toBeImport?=,say=say-${id}`, serialize(
                    result, 
                    "back", 
                    wsEvents,
                    this.cache
                ));
            }.bind(data));
        

            wsEvents.on("__internal_full_client_server_import__ws?=WebSocket,", /** 
            * @this CacheType
            * @param {string} str
            */ async function (str) {
                let [id, update] = deserialize(
                    str, 
                    "front", 
                    wsEvents,
                    this.cache
                );
                let caller = async () => await import("ws")

                const result = await caller();
                update();
                wsEvents.emit(`__internal_full_client_server_import__ws?=WebSocket,-${id}`, serialize(
                    result, 
                    "back", 
                    wsEvents,
                    this.cache
                ));
            }.bind(data));
        

            wsEvents.on("/home/mav/repos/full-client-server-sveltekit/src/routes/+page.svelte-0", /** 
            * @this CacheType
            * @param {string} str
            */ async function (str) {
                let [id, update] = deserialize(
                    str, 
                    "front", 
                    wsEvents,
                    this.cache
                );
                const { say: say } = await import("/home/mav/repos/full-client-server-sveltekit/src/routes/toBeImport");
                const { default: WebSocket } = await import("ws");
                let caller = () => {
               		say();
               		console.log(WebSocket);
               		console.log("hello");
               	}

                const result = await caller();
                update();
                wsEvents.emit(`/home/mav/repos/full-client-server-sveltekit/src/routes/+page.svelte-0-${id}`, serialize(
                    result, 
                    "back", 
                    wsEvents,
                    this.cache
                ));
            }.bind(data));
        

            wsEvents.on("/home/mav/repos/full-client-server-sveltekit/src/routes/+page.svelte-1", /** 
            * @this CacheType
            * @param {string} str
            */ async function (str) {
                let [id, hello, constant, $$invalidate, fn, AInstance, bigInt, update] = deserialize(
                    str, 
                    "front", 
                    wsEvents,
                    this.cache
                );
                let caller = async () => {
               		(await import("/home/mav/repos/full-client-server-sveltekit/src/routes/toBeImport")).say();
               		console.log(hello);
               		console.log(constant);
               		$$invalidate(0, hello = "hello client");
               		console.log(await fn());
               		console.log(AInstance.c());
               		console.log(AInstance.a());
               		console.log("hello after fn");
               		console.log(bigInt);
               		$$invalidate(3, bigInt = 12n);
               		console.log(bigInt);
               		return "to client";
               	}

                const result = await caller();
                update(hello, constant, $$invalidate, fn, AInstance, bigInt);
                wsEvents.emit(`/home/mav/repos/full-client-server-sveltekit/src/routes/+page.svelte-1-${id}`, serialize(
                    result, 
                    "back", 
                    wsEvents,
                    this.cache
                ));
            }.bind(data));
        

            wsEvents.on("/home/mav/repos/full-client-server-sveltekit/src/routes/+page.svelte-2", /** 
            * @this CacheType
            * @param {string} str
            */ async function (str) {
                let [id, $$invalidate, counter, a, update] = deserialize(
                    str, 
                    "front", 
                    wsEvents,
                    this.cache
                );
                let caller = () => {
               			$$invalidate(1, counter = counter + 1);
               			console.log(counter);
               			console.warn("this works again");
               			return { a: Promise.resolve("hello") };
               		}

                const result = await caller();
                update($$invalidate, counter, a);
                wsEvents.emit(`/home/mav/repos/full-client-server-sveltekit/src/routes/+page.svelte-2-${id}`, serialize(
                    result, 
                    "back", 
                    wsEvents,
                    this.cache
                ));
            }.bind(data));
        
            cb(wsEvents);
    
        })
    }
    
};

Then it should work as if everything is done synchronously but the console logs in the node function will run in the server and will appear in your terminal

Also you can import the wse to import the ws event instance which lets you emit events which can be handles on the handleWS hook

Also it takes class instances as normal object normally.

You can make it able to serialize classes by giving it a serializer by using another exported function it's signature is as follows addSerializerDeserializer(class, {serialize(class instance): "JSON.stringifiable object", deserialize("JSON.stringifiable object"): "class instance"}) this should be added to hook folder that is imported on both server and browser or somewhere else where the code runs before any and all code. Note that this is currently experimental and may not work as expected.

For a better example you may look into this example.

there are other imports which are used internally and I won't explain here.

Currently there is no options parameter unfortunatly, I'll implement that after version 1. Also I'm hoping to add a way define your own method of two way data sending, rather than using my defined way of doing the two way data sending, just to be a little more flexible, on the expense of added minute complexity.

Use this on your own discretion don't blame me for any valnearabilities it introduces I made this in over all 60 (trough out a few months due to lack of time as a physics major in college, about 60% of the mvp of this repo was done on the last 2 days due to summer vecation) hours and also I'm newly 18 at 2023 so don't expect much from me.

I hope you have fun with this.

1.0.3

6 months ago

1.0.3-b

6 months ago

1.0.3-a

6 months ago

1.0.3-c

6 months ago

1.0.2

9 months ago

1.0.1

9 months ago

1.0.0

9 months ago

1.0.0-next-b

9 months ago

1.0.0-next-c

9 months ago

1.0.0-next-d

9 months ago

1.0.0-next-e

9 months ago

0.2.3-d

9 months ago

0.2.3-c

9 months ago

0.2.3-b

9 months ago

0.2.3-a

9 months ago

1.0.0-next-a

9 months ago

0.2.2-e

9 months ago

0.2.2-d

9 months ago

0.2.2-c

9 months ago

0.2.2-b

9 months ago

1.0.0-e

9 months ago

1.0.0-d

9 months ago

1.0.0-g

9 months ago

1.0.0-f

9 months ago

1.0.0-a

9 months ago

0.2.3

9 months ago

1.0.0-c

9 months ago

1.0.0-b

9 months ago

0.0.6-e

10 months ago

0.0.6-d

10 months ago

0.0.6-c

10 months ago

0.0.3-f

10 months ago

0.0.3-a

10 months ago

0.0.2-b

10 months ago

0.0.2-a

10 months ago

0.0.6-b

10 months ago

0.0.3-e

10 months ago

0.0.2-f

10 months ago

0.0.6-a

10 months ago

0.0.5-b

10 months ago

0.0.3-d

10 months ago

0.0.2-e

10 months ago

0.0.5-a

10 months ago

0.0.3-c

10 months ago

0.0.2-d

10 months ago

0.0.4-a

10 months ago

0.0.3-b

10 months ago

0.0.2-c

10 months ago

0.1.0

10 months ago

0.2.1

10 months ago

0.0.3

10 months ago

0.2.2-a

9 months ago

0.2.1-b

10 months ago

0.2.0

10 months ago

0.1.1

10 months ago

0.0.2

10 months ago

0.2.1-a

10 months ago

0.0.5

10 months ago

0.2.2

10 months ago

0.0.4

10 months ago

0.0.6

10 months ago

0.0.1-g

12 months ago

0.0.1-f

12 months ago

0.0.1-e

12 months ago

0.0.1-d

12 months ago

0.0.1-c

12 months ago

0.0.1-b

12 months ago

0.0.1-a

12 months ago

0.0.1

12 months ago