I took a detour to try out the Smelte/Tailwind CSS UI gadgetry. I found lots to like about it, but it’s not the right thing for me right now. Read on if you’re interested in the detour. If you’re trying to follow how I got to my end state, go here instead to see how I set things up for vanilla Tailwind.

Adding Smelte and Tailwind CSS

I’m not a web designer, and I have no ambitions of becoming one. So I don’t want to break any new design ground. Google’s Material Design components for the web seem to align well with current user expectations, while giving me a library with most widgets I am likely to need right now. Smelte gives me most of those components in easily consumable svelte components while also pulling in Tailwind CSS in a way that makes it easy to use for anything I want to add that they haven’t already built. And none of it seems very heavy. So in order to get started with my own front-end pages, first I add smelte:

npm install smelte

Then I integrate it with the rollup.config.js that degit generated for me earlier:

const smelte=require("smelte/rollup-plugin-smelte");

plugins=[ 
  // ...svelte plugins, 
  smelte({ 
    purge: production,
    output: "public/global.css", // it defaults to static/global.css which is probably what you expect in Sapper 
    postcss: [], // Your PostCSS plugins
    whitelist: [], // Array of classnames whitelisted from purging
    whitelistPatterns: [], // Same as above, but list of regexes
    tailwind: { 
      colors: { 
        primary: "#b027b0",
        secondary: "#009688",
        error: "#f44336",
        success: "#4caf50",
        alert: "#ff9800",
        blue: "#2196f3",
        dark: "#212121" 
      }, // Object of colors to generate a palette from, and then all the utility classes
      darkMode: true, 
    }, 
    // Any other props will be applied on top of default Smelte tailwind.config.js
  }),
]

per the smelte manual.

I also added

import "smelte/src/tailwind.css";

to the top of main.js and

	<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500|Material+Icons&display=swap" rel="stylesheet" />

to the top of index.html for the time being. When I clean things up to host them, I should make sure to have the fonts locally instead of leaning on Google’s CDN.

Use the Grid

To complete my front-end setup, I added the following to a new file that I called grid.css inside public because I find it easy to understand and it fits my application well.

.wrapper {
  display: grid;
  grid-template-columns:
    1fr
    min(120ch, 100%)
    1fr;
}
.wrapper > * {
  grid-column: 2;
}
.full-bleed {
  width: 100%;
  grid-column: 1 / 4;
}
.pseudo-full-bleed {
  width: 100%;
  grid-column: 1 / 4;
  /* constrain the width for very-large monitors */
  max-width: 1500px;
  /* center the element */
  margin-left: auto;
  margin-right: auto;
}

(Taken from this article)

Then I added a link to it in index.html:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset='utf-8'>
	<meta name='viewport' content='width=device-width,initial-scale=1'>

	<title>Stock Toolkit</title>

	<link rel='icon' type='image/png' href='/favicon.png'>
	<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500|Material+Icons&display=swap" rel="stylesheet" />
	<link rel='stylesheet' href='/global.css'>
	<link rel='stylesheet' href='/build/bundle.css'>
	<link rel="stylesheet" href="/grid.css">

	<script defer src='/build/bundle.js'></script>
</head>

<body>
</body>
</html>

Displaying the Basic Stock List

To see if things were working, I used the DataTable component from Smelte and used Svelte’s onMount callback to fetch data for all stocks from the backend:

<script>
    import {onMount} from 'svelte';
    import { DataTable } from "smelte";

    export let backend_base_url = "/"
	let stocks = [];
	let stocks_loading = true;

	async function load_stock_data() {
	    stocks_loading = true;
	    var stock_url = backend_base_url + "stocks";
	    const api_response = await fetch(stock_url);
	    stocks = await api_response.json();
	    stocks_loading = false;
    }

    onMount(async () => load_stock_data());
</script>

<main class="wrapper">
    <h3><i class="material-icons">show_chart</i> Stock Toolkit</h3>
    <DataTable
            data={stocks}
            loading="{stocks_loading}"
            columns={[
                {label: "Ticker", field: "symbol", headerRemove: "justify-end", remove: "text-right",},
                {label: "Close", field: "price", headerRemove: "justify-end", remove: "text-right",},
                {label: "P/E", field: "forward_pe", headerRemove: "justify-end", remove: "text-right",},
                {label: "Dividend Yield", field: "dividend_yield", headerRemove: "justify-end", remove: "text-right",},
                {label: "50-day MA", field: "ma50", headerRemove: "justify-end", remove: "text-right",},
                {label: "200-day MA", field: "ma200", headerRemove: "justify-end", remove: "text-right",},
            ]}
            pagination={false}
    />
</main>

I was a little surprised how easily that worked; I had previously tried svelte with some other UI widgets, and it was significantly more verbose (though still both more compact and easier to read than the jquery implementation). This seems to cry out for having the API access factored into its own package sometime soon.

Unfortunately, adding the ability to select rows is currently significantly less intuitive for me. I don’t yet understand the smelte widget set well enough to modify it the way I want to, and this exercise has expanded beyond the time box I set for it. I’m going to set this aside and kick the tires on some other components for now. I’d like to return here because the Tailwind CSS integration is quite appealing and the component set is nice. I’m stashing this on the smelte-datagrid branch for easy return once I’m more comfortable with svelte.

Update: After further expanding my timebox, spending a day tire-kicking and working through some tutorials with Tailwind CSS, I still wanted to use Smelte. It didn’t go especially well, but I plan to try it again once I’ve lived with vanilla Tailwind for a little while. See this post for a little more explanation and details of how I’ve got it set up.