Using Svelte and ASP.NET Together

· 845 words · 4 minute read

I heard from the kind folks over on the C# discord that using Razor or Blazor for web facing applications is a bad idea. Apparently neither are really ready for the task; something to do with performance issues on both counts?

I had been learning razor because I want to build a web application – just learning, you know? – and it's a pretty sweet templating language that lets you inline C# straight into the web page. Given what I was told, I asked about what I should use instead and someone mentioned Svelte.

So I've started looking into Svelte, and I have to say it is rather pleasant so far.

On that note, I'd like to run a mono-repo with my front end and back end in the same repo. I've found a few tutorials that should what the boilerplate should look like.

Who Hosts Whom?

Maybe its "whom hosts who"? Anyway, we've got two build tools to contend with, dotnet and npm. I've found two tutorials, the first hosts npm in dotnet, that is the dotnet build tool kicks off the npm and rollup build tools; and the second does the opposite.

I'm going to do the former because dotnet is my preferred build orchestrator. For now. Maybe I'll change my mind?

[0] is the tutorial I've followed up to a point. It ends with integrating Svelte into Razor pages and I don't want to do that. I want to serve a whole front end that is only Svelte. So we'll follow this up to a point.

package.json

{
	"name": "HnClone",
	"version": "0.1.0",
	"scripts": {
		"build": "rollup -c rollup.config.js"
	},
	"devDependencies": {
		"@rollup/plugin-node-resolve": "^15.2.3",
		"rollup": "^4.6.1",
		"rollup-plugin-svelte": "^7.1.6",
		"svelte": "^4.2.8"
	}
}

Oh, yeah. I'm writing a clone of Hacker News [1]. :D

Make sure to check for the latest version of the packages above. A simple npm view {package} version [2] will get you what you need.

SvelteApp/

Create a folder called SvelteApp and put the following in there.

SvelteApp/main.js

import App from "./App.svelte"

const app = new App({
    target: document.body,
    props: {
        name: "Title"
    }
})

export default app;

App.svelte

<svelte:options tag="svelte-app" />
<script>
    export let name;
    export let id;
</script>

<main>
    <h1 id="{id}">Hello {name}!</h1>
    More text.
</main>

<style>
    h1 {
        font-size: 5em;
    }
</style>

SvelteApp/index.html

It took me a bit to sort out how to get anything served. Apparently we need either a static index.html that we can work from (this smells like a Single Page App – SPA) OR we need to pre-render the HTML during build time. I don't think anyone has sorted out server side rendering (SSR) with ASP.NET and Svelte yet.

<!doctype html>
<html>
<head>
    <meta charset='utf8'>
    <meta name='viewport' content='width=device-width'>

    <title>Svelte app</title>

    <link rel='icon' type='image/png' href='favicon.ico'>
    <link rel='stylesheet' href='lib/bootstrap/dist/css/bootstrap.css'>
</head>

<body>
<script src='js/bundle.js'></script>
<script src="/_framework/aspnetcore-browser-refresh.js"></script>
</body>
</html>

I borrowed this from [2].

rollup.config.mjs

import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';

export default {
    // This `main.js` file we wrote
    input: 'SvelteApp/main.js',
    output: {
        // The destination for our bundled JavaScript
        file: 'wwwroot/js/bundle.js',
        // Our bundle will be an Immediately-Invoked Function Expression
        format: 'iife',
        // The IIFE return value will be assigned into a variable called `app`
        name: 'app',
    },
    plugins: [
        svelte({
            // Tell the svelte plugin where our svelte files are located
            include: 'SvelteApp/**/*.svelte',
            emitCss: false,
            compilerOptions: {
                customElement: true,
                // we'll extract any component CSS out into
                // a separate file - better for performance
                css: "external"
            }
        }),
        // Tell any third-party plugins that we're building for the browser
        resolve({ browser: true }),
    ]
};

*.csproj

We add the following lines to the project's .csproj file.

First we register npm as part of our build process.

    <Target Name="Rollup" BeforeTargets="Build">
        <Exec Command="npm run build"/>
    </Target>

Next, I wanted to auto-rebuild while using dotnet watch run, especially after I've made changes to the Svelte side of things. I had to reference [3],[4] to get this done. dotnet has an outstanding issue in [4] that's been open since 2021 where all of the files and folders in a project are watched for changes. Normally this isn't a huge deal, but looking at the output of dotnet watch --list kinda hurt.

The first incantation clears the watch list of everything.

The Include and Exclude fields repopulate and filter the watch list.

  <ItemGroup>
      <Content Update="@(Content)" Watch="false"/>
      <Watch
              Include="**\*.cs;**\*.js;**\*.json;**\*.html;**\*.svelte;"
              Exclude="node_modules\**\*;wwwroot\**\*;bin\**\*;obj\**\*;"
      />

We need to move our static index.html file into wwwroot. I decided to do this so that I could exclude the wwwroot directory from the watch list. I don't know how I feel about this yet. I might undo it and put the Svelte app in the wwwroot dir. Or something. I'm not sure yet.

      <Content Update="SvelteApp\index.html">
          <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
          <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
          <Watch>false</Watch>
      </Content>
  </ItemGroup>

  <Target Name="MoveStaticFiles" AfterTargets="AfterBuild">
      <Copy SourceFiles="SvelteApp/index.html" DestinationFolder="wwwroot"/>
  </Target>

Serving Svelte Pages

Now we should be able to run dotnet watch run and everything should work.

Source Code

Here's the code for this. It isn't "clean", but it should get you going in the right direction.

https://gitlab.com/comalice/svelte-dotnet-8