Home  >  Article  >  Web Front-end  >  A gentle introduction to SvelteKit for Google Cloud developers

A gentle introduction to SvelteKit for Google Cloud developers

PHPz
PHPzOriginal
2024-07-17 19:39:17608browse

 A gentle introduction to SvelteKit for Google Cloud developers

Introduction

An earlier post in this series (A very gentle introduction to React) introduced readers to the excellent React framework system for developing webapps. SvelteKit is an alternative framework. How does it differ from React and is it any better?

Functionally, I guess there's not that much difference. Most things you can do in React you can do in SvelteKit. And vice-versa. But when you get down to the details, many people feel that SvelteKit has the edge in terms of the ease with which you achieve your "reactive" goals. Svelte means "elegant" - and that's just what it is - a slender, highly adaptable and practical tool.

Personally, I was attracted to SvelteKit because it also tends to push you towards server-side design - ie code that runs on your webapp's Cloud servers rather than in your user's web browser. This is ironic because it was the ease with which you could write and debug client-side code that originally got me hooked on webapp development. But then I discovered how reluctant indexing spiders are to invest effort in "hydrating" client-side code and realised I would just have to put more effort in here (see debugging in SvelteKit, below, to see what's entailed). But there are other reasons why you might consider using server-side code too. Here are a couple:

  • Once you start using third-party services such as Postmark (email despatch) or Paypal (payment collection), you'll realise that it's not a good idea to include their security codes in client-side code. If you can use the "inspector" to view these, so can anyone else. Code that runs server-side is inaccessible.

  • server-side code lives closer to your data and runs faster here than on a client laptop.

SvelteKit makes it easy to play tunes on specifying which bits of your webapp are to run locally and which are to run remotely.

  • In some cases, pages may be entirely server-side rendered - if they contain only static information, Sveltekit will enable you to "pre-render" them. Pre-rendered pages are constructed at build time and downloaded as slabs of pure HTML.
  • Alternatively, they may be entirely client-side rendered.
  • Or yet again, they may run on both. A SvelteKit webapp aiming to deliver optimal response times may initially display just a server-sourced "placeholder" screen to get something, anything, visible (you get great credit with Google's indexing bots here, apparently). This is then "hydrated" by client-side code with information specific to the user-instance.

Let's get down to something a bit more concrete.

Routing in Svelte

Externally, a Sveltekit webapp will look exactly like any classic browser application - a hierarchy of "pages" such as mywebapp/dosomethingwithmyfiles. It's like this because client users expect, and rely on this type of arrangement. But below the surface, a SvelteKit webapp delivers this arrangement in a totally different way to a React webapp. In React these pages are actually all parts of one giant slab of code and requests are routed thither by re-directs operating at the web interface (if that sentence doesn't make any sense to you, have a look at Whats a 'Single-page' webapp?). SvelteKit achieves this by using your project structure to define your page structure. So, if you want to have a mywebapp/dosomethingwithmyfiles page, you need to have a folder named dosomethingwithmyfiles with a +page.svelte file inside it. Once this arrangement is in place, your deployed app delivers a separate physical page for each of its URLs.

Here's a sample source folder structure for a SvelteKit project:

myproject
├───src
│ └───routes
│ └───dosomethingwithmyfiles

Once you've installed SvelteKit (see Svelte for New Developers), this structure will be augmented by a mass of complicated config files and build folders etc. But, for the present, the focus is on the routes folder. This is where you store your page code - and here is where you might start to wonder whether SvelteKit is the right thing for you. Take a tight grip now because this is where things get a bit complicated.

SvelteKit requires you to follow a very strict naming convention for the content of a page folder. Here's a list of the filenames that might see in a dosomethingwithmyfiles folder:

  • dosomethingwithmyfiles/+page.svelte. This file would contain the source for the code that displays the page for URL myproject/dosomethingwithmyfileson the browser screen. Whoah - let that sink in for a moment. When you're working in your VSCode editor on a SvelteKit project with half a dozen different pages, your filebar may display half a dozen tabs all named +page.svelte. Confusing? Yes, I agree.

At first sight, you might feel that this is simply unacceptable. But note that each +page.svelte file is qualified on the editor bar by the name of its folder owner, dosomethingwithmyfiles, or whatever. It's not so difficult to discipline yourself to check for the owner of a +page.svelte before you dive in and start editing. And once you've developed a SvelteKit project or two you'll begin to appreciate the value of the convention in declaring the purpose of the arrangement (as you'll see in a moment there are quite a few variations)

While you're absorbing this shock, let me give you a bit of encouragement. Within a +page.svelte file you might expect to find the same sort of code you'd see in an equivalent React file - a mixture of exotic useState calls to manipulate page state, and JSX to 'react' to this and generate HTML. While a +page.svelte file certainly does the same job, it manages to discard the "exotic" bit and uses plain javascript and pure, undiluted HTMl salted with a sprinkling of special keywords. You may find this refreshing.

Here are a few more standard filenames you might find in a dosomethingwithmyfiles folder:

  • dosomethingwithmyfiles/+page.js, This would contain the source for a file that delivers data to a +page.svelte file (ie, the equivalent of a React useEffect). Code here will run on the server when the page is initially loaded. Subsequently, if the page is re-referenced, the +page.js code runs in the browser with the advantages listed earlier.

     

    Interestingly, if you've suffered in the past from having to "re-program" your javascript brain whenever you switch between writing Web API code to run in the browser and Node.js style to run server-side in a Firebase function you'll be delighted to hear that, in Sveltekit, the Web API version is now perfectly happy to run server-side as well.

     

    Naturally, you'll want to know just how you organise things so that data read by a +page.js file ends up in the associated +page.svelte. Let me say that, for the present, this arrives by SvelteKit magic. The exact mechanism will only become clear once I've described SvelteKit's arrangements for defining "reactive" variables. Hang onto your hat for now.

     

  • dosomethingwithmyfiles/+page.server.js. This is where you would place code that you want to run only on the server (typically for security or performance reasons). As mentioned earlier, you can request that this is pre-rendered and thus constructed at build-time. In this case, performance is simply startling.

     

  • dosomethingwithmyfiles/+layout.svelte. This is where you would place code that sets up those bits of a page common to a whole set of other pages - toolbar headers, for example. A +layout.svelte file applies to every child route and any sibling +page.svelte. You can nest layouts to arbitrary depth. Again, the precise arrangement for inserting the common layout into the recipient pages will be left for later - more Svelte magic.

     

    If a +layout.svelte page needs some data, it can have an attendant +layout.server.js file

     

  • dosomethingwithmyfiles/+server.js. This is where you would place code that you wanted to be available as an "API endpoint" via a parameterised URL such as myProject/dosomethingwithmyfiles?type="pdf". I'll provide more details on this arrangement later.

'Reactive variables' and 'Reactive HTML' in SvelteKit

By 'reactive variables' I mean data items that cause the browser page to re-render when they change. By 'reactive HTML' I mean HTML instrumented to make it respond to these changes.

In React, you'll recall, reactive variables are declared using a useState expression that defines the variables as properties of a state object. The declaration also specifies initial property values and a function to change them.

Here's an example - a React webapp that displays a popup that disappears when you click it:

import React, { useState } from "react";

const [screenState, setScreenState] = useState({popupVisible: true,});

return (
    <div>
        <h1 style={{textAlign: "center"}}
            onClick = {() => {setScreenState({popupVisible: !screenState.popupVisible})}}>
        Main Page - Click to toggle popup
        </h1>

    {screenState.popupVisible && 
        <div 
            style={{ textAlign: "center", marginLeft: "auto", marginRight: "auto", height: "2rem", width: "25rem", backgroundColor: "gainsboro" }}
            onClick = {() => {setScreenState({popupVisible: !screenState.popupVisible})}}>
            <h2> Popup Window - Click to Hide popup</h2>
        </div>  
    }
    </div>
)

In Svelte (I'm now talking about the language as opposed to the framework in which it operates) you might achieve this effect in a src/routes/demo/+page.svelte file by simply declaring popupVisible as a javascript variable

<script>
    let popupVisible = false;
</script>

 <div>
    <h1 style="text-align: center" 
        on:click={() => (popupVisible = !popupVisible)}>
        Main Page - Click to toggle popup
    </h1>

    {#if popupVisible}
        <div
            style="text-align: center; margin-left: auto; margin-right: auto; height: 2rem; width: 25rem; background-color: gainsboro"
            on:click={() => (popupVisible = !popupVisible)}
        >
            <h2>Popup Window - Click to Hide popup</h2>
        </div>
    {/if}
</div>

Here's a summary of the key differences:

  • Svelte uses a standard Javascript let declaration to introduce state variables instead of the strange React useState expression

  • Svelte uses a down to earth #if 'logical expression' keyword to replace the awkward JSX {'logical expression' &&syntax. This makes your code much more readable. Svelte also provides associated else and each keywords.

  • Svelte uses plain CSS to define HTML classes rather than the perplexing JSX style objects (eg {{textAlign: "center"}}).

Note also that the demo/+pagesvelte file defined above will run directly in the browser as /demo. To run the React version you would have to put some code into an associated src/main.jsx file to define the new route.

Inputs: Local Functions, Actions and API endpoints

Keyboard input in React generally uses the following pattern:

const [myState, setMyState] = useState({myProperty: "",});

function handleChange({ target }) {
    setMyState({ ...myState, [target.name]: target.value });
};

return (
    <input name="myProperty"
        value={myState.myProperty}
        onChange={handleChange} />
)

Here, an input labelled as "myProperty" fires a general-purpose handleChange function every time you press a key. In handleChange its value is extracted and applied to the page's state to trigger a re-render.

Svelte thinks this is too complicated and introduces a "bind" keyword to its input syntax. This automatically transmits changes to an associated state variable. A Svelte version of the above thus looks like this:

<script>
    let myProperty = "";
</script>
<input bind:value={myProperty} />

The bind keyword is also used to enable you to create two-way communication between parent and child components. This is a powerful feature.

An interesting feature of Svelte is that it encourages you to use forms and server-side processing for input handling. Thus it's perfectly permissible in Svelte to launch a client-side function like this:

<script>
    let myProperty = "";
    function commitChange() {
        // Use the global myProperty variable to update server storage
    }
</script>

<span>myProperty = </span><input bind:value={myProperty}  />
<button on:click={commitChange}>Commit Change</button>
/>

Svelte docs correctly insist that interactions like this are better handled by forms and server-side processing in a +page.server.js file. Here the validation and submission of the user input can be safely protected from the sort of interference possible in client-based code. Here also, any subsequent processing can be performed with maximum efficiency.

To implement this view, Svelte provide a neat automatic link between a form reading data on a +page.svelte and a function handling the processing of that data in the associated +page.server.js file. Here's an example:

src/routes/login/+page.svelte
<form method="POST">
    <span>myProperty = </span><input name="myProperty">
    <button>Commit Change</button>
</form>

src/routes/login/+page.server.js
export const actions = {
    default: async (event) => {
        // TODO handle the processing for the input read by the form on +page.svelte
    }
};

Note that no Javascript has been used in the form - no "on click" or "on submit", for example. The linkage has been established entirely through "Svelte magic".

In practice, of course, a +page.svelte file is likely to want to be the source of multiple "actions". See Svelte Form Actions for details of how Svelte manages this. (Note that Svelte docs are organised under two URLs: kit.svelte.dev for framework topics like routing and svelte.dev for elements of the language itself)

Finally, to conclude this section, suppose you wanted users to be able to call on the service of an action by referencing it directly through a javascript "fetch" (or, at its simplest by launching a parameterised url via the browser - eg https:// mySite/myPage?param1=3 etc). This is where you would use a +server.js file to create an API "endpoint" function. Firebase users might well use such an arrangement where they had previously used a Firebase function. Not the least advantage of this would be that testing and debugging could be done in the Sveltekit server rather than the Firebase emulator.

Components

  • 1-way bindings

Each +page.svelte file defines a component, and you mark variables declared here as "props" - ie make them accessible to "consumers" of the component - by adding the export keyword to their declarations. So, if you're still wondering how a +page.svelte file gets its data from +page.server.js - this is how it's done. A +page.svelte file wanting to receive "load" data from its +page.server.js (or +page.js) file just needs to put something like the following in its