Introducing a collectively owned and operated digital solutions team
#webdev #coop #code #smallbiz
Introducing a collectively owned and operated digital solutions team
#webdev #coop #code #smallbiz
Learn how to create a custom event handler for #Svelte components written in #TypeScript/#ts that listens for clicks outside of itself 🤯
I’m lazy and you probably are too. This post talks about a few easy-to-use tools that even the laziest of developers can use to ensure their application is accessible to all.
#a11y #accessibility #automation
I was approached to write a book about #SvelteKit because I had written about it here. This post outlines my experience writing a technical book, from start to finish.
Today is the day! You can now buy my new book, SvelteKit Up and Running.
Visit https://sveltekitbook.dev to get your copy!
#svelte #SvelteKit #book
In an effort to promote my upcoming book, I created the website https://sveltekitbook.dev. It has all of the information one may want to know before purchasing a copy. Of course, the book is about #SvelteKit so I had to build the site using #SvelteKit. I decided to take the opportunity to learn the latest technology web developers can’t shut up about; #TailwindCSS.
On this website, I normally only write about solutions I’ve come up with to problems I’ve encountered (pertaining to web development). I recognize that’s not entirely authentic because I can’t solve every problem I come across. Sometimes, I’ll come across an issue where the solution isn’t immediately clear to me. Sometimes, it festers for long enough that I’m driven to take drastic action. Sometimes, I’m forced to write an entire post about it. This is post about one of those festering, unsolved problems.
It started while working on the Svelte Work:Rest Timer component I wrote about a few months ago. You see, the timer was intended to be a static Single Page Application (SPA)/Progressive Web Application (PWA) without a back-end server component. That is, once served to the client and installed via the browser PWA installation process, it would be usable on mobile devices whether or not they had an internet connection. I thought surely, this will be easy enough for me to tackle since I’ve already built something similar with helth app. The only major difference being that this timer application would utilize the SvelteKit static site adapter instead of the Node.js adapter.
The Problem
During development, everything ran smoothly. It wasn’t until deployment to production that I noticed issues.
SvelteKit uses Vite to deliver that buttery-smooth development experience, and Vite uses esbuild to bundle an application during development. However, when building the production version of an application, Vite uses Rollup to bundle the app. I believe this discrepancy; coupled with my obvious lack of understanding of the underlying technologies behind SvelteKit (Vite, esbuild, and Rollup), is why I ran into any issues in the first place.
When I attempted to deploy my static files to a static host, I noticed the app was missing several key features, like some of the actual code that made the logic in the application run and the CSS included in each component. After checking the network requests in my developer console, I noticed the development version was making 3-5 more requests than my production build. Why?
When deployed, the production build was very broken.
Troubleshooting
Since I had built the application to be static, it was safe to prerender the HTML on the one and only page. Any two users should see identical HTML, CSS, and JS on the site when they open it for the first time. To accomplish this, I simply needed to add the following code to the file src/routes/+layout.js:
export const prerender = true;
export const ssr = false;
I figured since there wouldn’t be a back-end component of the application, it would be fine to disable Server-Side Rendering (SSR) while I was at it. My thought was that this would force SvelteKit to prerender and it would skip generating any server-side code for that route.
I discovered that if I removed the line that disabled SSR, I would get a different index.html located at .svelte-kit/output/prerendered/pages/index.html.
Left: index.html included in “build/”Right: index.html included in “.sveltekit-output/prerendered/pages/” when option that disables SSR is removed
This file contained links to resources that were not being loaded in my production version of the app. This file was also not included in my production build folder (build/). I don’t fully understand why disabling SSR and enabling prerendering generates a different version of the index.html file. I also don’t understand why the version of index.html I needed wasn’t included in my build directory. For now, I’ve simply overwritten the file build/index.html with the file .svelte-kit/output/prerendered/pages/index.html and the application works as expected.
If you or someone you know has some insight as to why this is, please drop a comment below, reach out to me via my contact form, or message me on various social media platforms. I would love to understand what I assume I’m doing wrong.
I was recently approached by a friend to build an app. Now, I’ve been cornered and pitched far too many half-brained apps in my day but it is refreshing to hear a good pitch. This pitch was for a timer, but not just any timer or stopwatch app that comes preinstalled on your phone. Rather, it was a workout timer that would also time your rests. As someone who often takes too long to rest between sets, I thought this sounded like a decent idea. I was told “nothing like this exists!” but a quick search of the web proves that false; it’s definitely been done. But I could do it as a Svelte component! And it’d be a fun coding challenge!
The Setup
If you’ve read any recent posts of mine, you know I’ve been working with SvelteKit lately. To get started with SvelteKit, I ran a few commands in the project directory:
npm create svelte@latest timer
cd timer
npm install
npm run dev
From there, I created the file /src/routes/+page.svelte as the entry point page for the app. I also created the file /src/lib/timer.svelte which is where all of the magic happens and included it in the aforementioned +page.svelte like so:
+page.svelte
<script>
import Timer from ‘$lib/timer.svelte’;
</script>
<div class=’content’>
<Timer />
</div>
<style>
div.content {
text-align: center;
width: 100%;
background-color: #130d0d;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
color: #d9d9d7;
}
</style>
$lib is an alias to /src/lib/ that is supported by SvelteKit out of the box.
The Markup
Most timers have a display of the running time, a start button, a stop button, and reset button so I went ahead and included those right away in /src/lib/timer.svelte. I also gave it some rough styling so it wasn’t awful to look at while testing. The basic structure of the component looks something like this:
<script>…</script>
<h1>Work+Rest Timer</h1>
<div class=’time’>{hr}:{min}:{s}.{ms}</div>
<div class=’controls’>
<button class=’start’ on:click={start}>{running ? `rest` : `start`}</button>
<button class=’stop’ on:click={stop}>stop</button>
<button class=’reset’ on:click={reset}>reset</button>
</div>
<style>
h1 {
text-align: center;
}
.time {
margin: 1.5rem;
font-size: 4rem;
}
.controls {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr;
}
button {
padding: 1.1rem 1.4rem;
margin: 1.1rem;
font-size: 1.5rem;
border: none;
background-color: #496bf0;
color: white;
transition: all .4s ease-in-out;
cursor: pointer;
grid-column: 3 / 4;
grid-row: 2 / 3;
}
button:hover {
background-color: #2047df;
}
button.stop {
background-color: #df3d2c;
grid-column: 2 / 3;
grid-row: 2 / 3;
}
button.stop:hover {
background-color: #a81303;
}
button.start {
background-color: #408e2a;
grid-column: 2 / 4;
grid-row: 1 / 2;
}
button.start:hover {
background-color: #245e14;
}
</style>
Yup, that’s a timer alright.
Eagle eyed readers likely noticed that there are some variables used in the markup that were not declared in the script tag. Lets get to that part now.
The Logic
This timer needs to be able to start, stop, reset, and display the running time. It also needs a “rest” feature which counts down to zero from whenever the “rest” button was clicked. To prevent users from starting another timer, I decided to turn the start button into the rest button while the timer is running. A quick rundown of how this ought to work:
clicking “start” starts the timer (duh)
the time since start is displayed in hours, minutes, seconds, and milliseconds
after clicking “start,” the “start” button becomes the “rest” button
clicking the “rest” button will reverse the time, counting down to zero
when the timer reaches zero, it will start counting up again until stopped or until “rest” is clicked again
clicking “stop” will stop the time
clicking reset will set hours, minutes, seconds, and milliseconds back to zero
That all seems fairly straightforward, right? Oh, but dear reader, you don’t know this (how could you have?) but when it comes to code, I hate programming anything related to time. And I’m really bad at math.
So why did I think this was a good idea? 🤔 Who knows? Maybe I just hate myself.
Side note: When working with computers and time, there’s all sorts of problems that can arise. It’s a lot easier to do math with time when you convert time to a regular number.
About those missing variables…
<script>
let interval = null;
let running = false;
let elapsed = 0;
let oldElapsed = 0;
$: ms = pad3(0);
$: s = pad2(0);
$: min = pad2(0);
$: hr = pad2(0);
// pad2/3 exist to format the numbers with leading zeros
const pad2 = (number) => `00${number}`.slice(-2);
const pad3 = (number) => `000${number}`.slice(-3);
const start = () => {
…
}
const stop = () => {
…
}
const reset = () => {
…
}
</script>
You’ll notice some that weren’t mentioned the markup; you can ignore those for now. The times (hr, min, s, ms) are all declared as reactive because I want the timer display to update as the those values change. Now all of this keeps the component from throwing errors but it doesn’t do anything. To make it do the things, let’s look at start() first.
const start = () => {
if(!running) {
countUp();
}
else {
countDown();
}
}
There! That was easy. Lets move on to the stop() and reset() functions:
const stop = () => {
clearInterval(interval);
oldElapsed = elapsed;
}
const reset = () => {
s = min = hr = pad2(0);
ms = pad3(0);
elapsed = oldElapsed = 0;
running = false;
clearInterval(interval);
}
Wait a minute, this doesn’t make any sense without looking at what’s actually going on in start(). Let’s back up. Here’s countUp() which is called by start() when running is false.
const countUp = () => {
let startTime = Date.now();
running = true;
interval = setInterval(() => {
elapsed = Date.now() – startTime + oldElapsed;
ms = pad3(elapsed);
s = pad2(Math.floor(elapsed / 1000) % 60);
min = pad2(Math.floor(elapsed / 60000) % 60);
hr = pad2(Math.floor(elapsed / 3600000) % 60);
});
}
The secret sauce here is setInterval(). Since it has not been provided the optional second argument, it loops on the function passed to it every millisecond. In that loop, the time since the counter began counting upwards is calculated by subtracting the start time from the current time. Then, each “section” of the timer display is calculated based on that elapsed time (and padded with extra leading zeros). Since the interval is assigned to the variable interval, I can clear it later on in stop() and reset() to stop the code.
Now for the part that I spent way too long on; countDown().
const countDown = () => {
stop();
const end = Date.now() + elapsed;
interval = setInterval(() => {
elapsed = end – Date.now();
ms = pad3(elapsed);
s = pad2(Math.floor(elapsed / 1000) % 60);
min = pad2(Math.floor(elapsed / 60000) % 60);
hr = pad2(Math.floor(elapsed / 3600000) % 60);
if(elapsed <= 0) {
clearInterval(interval);
reset();
countUp();
}
});
}
The first thing I want to do here is stop the timer where it’s at. That way, I can clear the value inside the interval variable. Once that’s done, the end time of the new timer needs to be calculated. That’s done by taking the amount of time elapsed and adding it to the current time. Once the end time has been calculated, the loop that runs every millisecond begins, where the amount of time elapsed is recalculated by subtracting the current time from the end time. It is then all shown in the timer display, and when the amount of elapsed time reaches zero, the interval is cleared, values are all reset to zero, and the count back up begins again.
If you were confused during any of that, that’s fine. You’re probably a sane, well adjusted human being that gets along perfectly well in social interactions. Congratulations 🥳!
All Together Now!
<script>
let interval = null;
let running = false;
let elapsed = 0;
let oldElapsed = 0;
$: ms = pad3(0);
$: s = pad2(0);
$: min = pad2(0);
$: hr = pad2(0);
const pad2 = (number) => `00${number}`.slice(-2);
const pad3 = (number) => `000${number}`.slice(-3);
const countUp = () => {
let startTime = Date.now();
running = true;
interval = setInterval(() => {
elapsed = Date.now() – startTime + oldElapsed;
ms = pad3(elapsed);
s = pad2(Math.floor(elapsed / 1000) % 60);
min = pad2(Math.floor(elapsed / 60000) % 60);
hr = pad2(Math.floor(elapsed / 3600000) % 24);
});
}
const countDown = () => {
stop();
const end = Date.now() + elapsed;
interval = setInterval(() => {
elapsed = end – Date.now();
ms = pad3(elapsed);
s = pad2(Math.floor(elapsed / 1000) % 60);
min = pad2(Math.floor(elapsed / 60000) % 60);
hr = pad2(Math.floor(elapsed / 3600000) % 24);
if(elapsed <= 0) {
clearInterval(interval);
reset();
countUp();
}
});
}
const start = () => {
if(!running) {
countUp();
}
else {
countDown();
}
}
const stop = () => {
clearInterval(interval);
oldElapsed = elapsed;
}
const reset = () => {
s = min = hr = pad2(0);
ms = pad3(0);
elapsed = oldElapsed = 0;
running = false;
clearInterval(interval);
}
</script>
I put together a repository of this code so you can read it in all of its glory there. If you follow the development, you’ll see new features like “beeps”, fun colors, and buttons that allow you to multiply the rest time by a factor of 1.5, 2, or even 3! If you like tracking calories as well as working out, then keep a watchful eye out for this new feature coming to your favorite health tracking application.
I’ve been writing on this blog for nearly 9 years and I’ve learned so much since I started. The style and the content have come a long ways and I cringe every time I read old posts thoroughly enjoy seeing how I’ve grown as a developer. I’ve interacted with people that I never would have had the chance to otherwise. It’s been a wonderful learning experience.
While my intentions have always been for this site to exist as a sort of journal/wiki/knowledgebase/playground, I’ve always secretly wanted to become a billionaire tech influencer. And now you can help me achieve that goal by buying my merchandise!
BUY! BUY! BUY!
By purchasing merchandise from my shop, you can support this site financially, by giving me real money that you’ve earned for your “hard work.” While donations are always appreciated, I understand that you may want something in return; something tangible, something you can see and smell, something to keep you comfortable while you cry yourself to sleep. And since nobody actually donates to strangers on the internet, I opened a shop.
All of the designs are completely original and there are many, many more to come. The pricing is affordable for all budgets and will only expand with more options. Be sure to check posts here often by following the social media channels or the RSS feed. There may just be coupon codes hidden in future posts 😉.
So if you’re ready to showcase the fact that you know what HTML is and like the look of monospaced fonts, then you should go checkout the new closingtags merch shop. Once you’ve got the closingtags swag (closingswag 🤔), be prepared to have people you barely know ask if you “work with computers” or to tell you about their genius new app idea.
Don’t forget to buy, buy, buy!
EDIT: The proper method for including a global CSS files is to import it inside the root +layout.svelte file. Doing so will alert Vite to the asset which leads to HMR reflecting changes in the browser whenever the CSS file is updated. The method outlined below will not showcase the same behavior and will require you to restart your development server to reflect CSS changes.
I’ve been playing around with Svelte and SvelteKit recently. So far, I’m a fan but one thing that bothers me is how styles are managed. See, way back in the day when I wanted to build a website, I would create the style of that website in a single file called a Cascading Style Sheet (CSS). If I so chose, I could create multiple style sheets and include them all easily in the header of my website like so:
<link rel=’stylesheet’ href=’public/global.css’>
<link rel=’stylesheet’ href=’public/reset.css’>
But Svelte does things differently. Each Svelte component has its own styles. All of the styles created in that component will only ever apply to markup inside that component (unless specified with :global on the style rule). It’s a great feature because it keeps everything compartmentalized. If I see something that doesn’t look right, I know I can open the component and go to the <style> section at the bottom. Whereas CSS files can quickly become unweildly, making it difficult to track down the correct rule.
But there are times when I would like some rules to apply across the board. For instance, CSS resets. Or what about when I want to apply font styles? And sizes of headers? Doing this in each and every component would be a gigantic pain so instead, I would prefer to include one global style sheet for use throughout the application, and then tweak each component as needed. Sounds simple, right?
Well there’s a catch. Of course there is, I wouldn’t be writing about this if there wasn’t a catch (or would I?). When previewing my application with yarn dev / npm run dev, any styles included the aformentioned “old school way” way will work fine. But when I build that application to prepare it for my production environment via yarn build / npm run build, I notice the style is not included. What gives?
During the build process, I came across this error:
404 asset not found, wtf?
After a lot of digging through Github comment threads, I’ve found that Vite; the tooling used by SvelteKit to build and compile, doesn’t process the app.html file. All good, no big whoop dawg! I can just create a file in my routes called __layout.svelte and import my CSS there.
<script>
import ‘../../static/global.css’;
</script>
Although, that path is ugly to look at. And what if I don’t want that file? I don’t know, maybe I have hangups about extraneous files in my projects, cluttering up my valuable mind space 🙃.
Anyways, it turns out there is an option to get Vite to process the global.css from within the app.html. It looks like so:
<link rel=’stylesheet’ href=’%sveltekit.assets%/global.css’>
<link rel=”icon” href=”%sveltekit.assets%/favicon.png” />
See, Vite does actually process the app.html file but it only creates the links to those assets if it sees the %svelte% keyword. The best part about this method is that my app.html file will be processed accordingly with Vite and the assets will be included. Plus, I can keep that valuable clutter out of my project (and headspace!).
SvelteKit is still in development and has a long ways to go, but it’s great to see some different ideas being incorporated into the front-end framework race. It’s also a fun tool to build with and sometimes, we could use a little fun while building.