2021-09-19
|~7 min read
|1238 words
Snowpack is a new(er) addition to the build tools available on the front-end.
I had the opportunity to start a new project recently and I was trying to get up and running quickly. I’d heard great things about Snowpack, so I thought I’d give it a shot.
Like CRA (create-react-app), Snowpack has a general CLI that can be called without actually needing to install it (using npx
or yarn create
). And, like CRA, Snowpack has a growing library of templates.
To be clear, CRA is still a great project, however, between it being in maintenance mode after 2.0 and the intentional limitations on the project, it no longer appears to be the best solution for production apps today.1
So, what is it like actually getting started with Snowpack?
Well, for my project I wanted to use a few different technologies:
The first two technologies on my list can be handled in one fell swoop using a template:
% npx create-snowpack-app my-project --template @snowpack/app-template-react-typescript
# or
% yarn create snowpack-app my-project --template @snowpack/app-template-react-typescript
I will say this part confused me initially. The syntax for Snowpack CLI to add a a template is @snowpack/app-template-<name>
. All of the templates are included in the create-snowpack-app repo.
Adding SASS to a project requires adding the dependency and updating the snowpack.config.js
:
% yarn add @snowpack/plugin-sass
module.exports = {
plugins: [["@snowpack/plugin-sass"]],
}
In my case, I installed sass
globally so that I could take advantage of the speed increases of not using the pure JS implementation. (Instructions.)
This did mean that my config looked slightly different:
module.exports = {
plugins: [
[
"@snowpack/plugin-sass",
{
native: true,
},
],
],
}
Adding PostCSS is similarly direct:
Install @snowpack/plugin-postcss
,
Install postcss
Install any plugins
Update the snowpack.config.js
// snowpack.config.mjs
export default {
+ plugins: ['@snowpack/plugin-postcss'],
};
Create and add any plugins to postcss.config.js
// postcss.config.js
module.exports = {
plugins: [
// Replace below with your plugins
require("cssnano"),
require("postcss-preset-env"),
],
}
MirageJS is a fantastic way to test your endpoints and iterate quickly.
In order to get it running, first we need to get some dependencies installed:
% yarn add -D miragejs @types/node
In order to get the node types to take, we need to update tsconfig.json
:
{ "types": [, /*... others */ "node"] }
In my case, I was also using environment variables, some of which were injected from the command line and some of which were stored in a local .env file, to determine when to use a Mirage server.
For the latter, Snowpack has a plugin to install, @snowpack/plugin-dotenv:
% yarn add -D @snowpack/plugin-dotenv
And which requires configuration in the snowpack.config.js
:
// snowpack.config.mjs
export default {
plugins: ["@snowpack/plugin-dotenv"],
}
An additional note regarding environment variables in Snowpack. Snowpack does not have access to process.env
. In order to read environment variables then, we need to replace all of the references with import.meta.env
. This, though, is not quite sufficient.
All environment variables in Snowpack must be prefixed with SNOWPACK_PUBLIC
. This is intended to remind authors that these variables will be shared with the world.
So, if you previously had
if (process.env.FOO) {
}
You would now have:
if (import.meta.env.SNOWPACK_PUBLIC_FOO) {
}
Unfortunately, the documentation suggests there’s a magic string that’s accessible __SNOWPACK_ENV__
, which never worked for me.
For typescript, I also added @types/snowpack-env
as a dev dependency.
From here, we could follow a standard setup for MirageJS in React.
I initially thought I’d want/need Jest. After all, it’s a fantastic test runner and I’ve spent a lot of time learning how to configure Jest for Javascript applications.
It made sense to me that that’s where I’d start. But it wasn’t all smooth sailing.
ERROR: /Users/stephen.weiss/code/sandbox/react-snowpack-ts-ds/src/index.tsx: Support for the experimental syntax 'jsx' isn't currently enabled (7:3):
After a few more hiccups like that, I came to realize that even if I got it to work (by adding a Babel config, which also mostly isn’t necessary with Snowpack, etc.), I wasn’t going to get much benefit from it.
In part, this is because
Snowpack currently recommends different tools for pieces that Jest normally handles.
For assertions, I’d be using mocha
and chai
. For helpers, I could use testing-library
. And for a test runner, Snowpack’s recommendation is
@web/test-runner, which the team says is a faster, more reliable alternative with fewer dependencies and which takes advantage of the Snowpack build pipeline.
So, I’m leaving this section here mostly to say it’s not necessary and that hopefully next time I read this before I go down the path of trying to set it up.
Snowpack is a build tool, not necessarily a bundler. Here’s a pretty good guide.
Snowpack recommends two different approaches:
Unfortunately, I don’t think I’ll be able to use esbuild until it’s more mature.
Since I was migrating from TSDX, which uses Rollup by default, I thought I’d just steal the config and tweak it as needed.
This is a project for another day, however.
Getting up and running with Snowpack was amazingly easy.
The biggest issues I ran into were:
On the other hand, once I got up and running, it was mostly smooth sailing.
The Snowpack bundler is much more particular than others I’ve used in the past. Two notable examples:
I was using a library with a peerDependency
on axios
. In the past, I would have gotten console warnings telling me that axios
was missing and that I should install it. Snowpack goes further and forces its installation - refusing to build the app if it’s not present. Normally, I’d love this - except that in this particular instance I knew axios
was mislabeled as a peer dependency. Of course, this also meant I could go back to the library maintainers and try to get this fixed.
The same library also isn’t using ESM, which meant that importing its components was not as straightforward - nor did it match the library’s recommended approach. Fortunately, Snowpack has some great documentation on some of the most common problems. In my case, the solution was to simply import from the root and drill down:
// Recommended... but doesn't work
import { Button as LibButton } from "my-lib"
const { default: Button } = LibButton
// This works though!
import lib from "my-lib"
const { default: Button } = lib.Button
Finally, I still have work when it comes to configuring a production grade bundle, but I know it’s possible. So, I can come back to that when it’s time to actually ship!
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!