First look at snowpack

Today is another Red Hat Day of Learning. A while ago I heard about snowpack, a new contender for the trusty old webpack to build modern web projects. Today I finally managed to take a quick look at it.

Even with webpack --watch one often needs to wait several seconds up to half a minute with some larger cockpit pages, so the promised split-second builds certainly sound attractive. At first sight it also makes more opinionated choices about sensible defaults, so that one hopefully does not have to write such a wall of boilerplate.

Hello world

I set up a playground repository, followed the getting started tutorial, and had my first working hello world within 15 minutes or so.

Then I quickly moved on to the snowpack React tutorial, updated my playground, and was very pleased – up to that point this Just Works™ without a single line of snowpack configuration! That is certainly a big plus.

$ npm run start
snowpack

  http://localhost:8080 • http://192.168.178.21:8080
  Server started.

▼ Console

[snowpack] Hint: run "snowpack init" to create a project config file. Using defaults...
[snowpack] ! building dependencies...
[snowpack] ✔ dependencies ready! [6.39s]
[snowpack] File changed...

(Note: The real terminal output is colored)

The initial build takes a bit, but as promised, any further change to my code is done in a not-noticeable time, and the browser automatically updates to the new code.

Configuration only came into play when organizing the project directory to put the source code into src/, but one could do that with a three-liner (however, I kept the snowpack init template).

Cockpit page, take #1

A standalone web app is nice, but my roots are in the Cockpit project, so I was eager to see if snowpack works with that at all. Due to the way cockpit works, pages have to be served through cockpit’s own web server, so snowpack’s on-the-fly “dev” server does not work. So for that we need to use the snowpack build mode, with --watch for convenience, and that also worked surprisingly well and straightforward. I added a little demo for using the cockpit.file() API, similar to starter-kit.

Performance in this mode is similarly fast as with the dev server, one only loses the automatic browser refresh. The core idea of snowpack of not creating a bundle, but build and cache every file and npm library separately and let the browser load the modules by itself (every browser can do import these days) still holds.

Adding PatternFly

In cockpit, and in many other Red Hat projects we use the PatternFly web UI toolkit extensively, for getting a consistent, clean, desktop+mobile friendly and accessible design. Here I hit my first wall, as naïvely importing the library causes a lot of 404 errors for CSS and fonts not being found, because snowpack doesn’t copy the assets into the build directory. I am not the first one to run into this, but there is no official solution yet. So I came up with a workaround to salvage at least the CSS, and with this commit I got it working to a “good enough for a demo” and “yes, it uses PatternFly CSS” degree:

patternfly demo

babel.. or not

I thought the next step would have been babel, but it turns out that this is not really needed: snowpack can do JSX transformations natively, and for new projects one should really be able to stop worrying about some ancient crappy IE11 browsers – as long as one sticks to ES6, browsers should be able to just run the code without any transmogrification.

eslint

But eslint is of course important for enforcing clean code and spotting simple errors. So I first added a simple eslint configuration and then integrated it into snowpack, which was again pleasantly easy. It works as expected in watch mode:

▼ Console

[snowpack] ! building dependencies...
[snowpack] File changed...

▼ eslint

  /var/home/martin/tmp/snowpack-playground/src/index.jsx
    11:7  error  Multiple spaces found before ''./index.css''  no-multi-spaces

  ✖ 1 problem (1 error, 0 warnings)
    1 error and 0 warnings potentially fixable with the `--fix` option.

Oops! I drop the superfluous space, and not a second later it’s all good:

▼ Console

[snowpack] ! building dependencies...
[snowpack] ✔ dependencies ready! [16.42s]
[snowpack] File changed... [x2]

▼ eslint

  ✓ Clean (5:17:36 PM)

This is certainly very cool, the same would have taken much longer with webpack.

Cockpit with PatternFly

Next step was to rebase the above “Cockpit hello world” to new master with PatternFly (commit), but here I hit a rather hard wall: snowpack puts the individually built files into paths that follow the npm module names, e.g. ../snowpack-playground/_snowpack/pkg/@patternfly/react-core.js. It turns out that cockpit-ws refuses file paths with @, so that’s a bug that we have to fix in cockpit first. Normally it uses @ to separate remote host channels from the path on the remote side, but this conflicts.

More advanced stuff

The above were more or less the “simple, obvious, and basic” things. Cockpit’s build system is a lot more complex than that, for example we have a webpack plugin for building translation js files from gettext po files, or some “interesting” (read: hackish) rules for adjusting font paths in the built CSS, all of which would need to be ported to snowpack. It does have a reasonable looking plugin API, but I ran out of time for these.

What I still did try however, was to use SASS files. There is an official snowpack sass plugin which uses the dart sass npm module. I tried to turn src/index.css into a proper .scss and adjust the import accordingly, but it seems this went entirely ignored by the build, and I have no idea why. This would need a more in-depth debugging session.

We don’t even care about that in Cockpit, we use our own loader that uses the packaged CLI sass for portability/licensing reasons. So maybe porting that would work better, but I haven’t looked into the snowpack equivalent of a webpack loader at all.

Finally, this is exclusively for development mode – for releases/production we still need to generate bundles, as cockpit-ws does not support HTTP/2 and thus browser performance would suck with lots of little files. For that purpose, snowpack has a webpack module which would be used for production builds only. At this point I have zero experience with it, and how easy/difficult it would be to integrate.

Conclusion

The two main stumbling blocks above (paths with @ and broken sass) certainly seem fixable, and then it is a very interesting alternative for new starter-kit derived projects.

For cockpit itself it seems still a bit immature, and we still have too much old baggage and technical debt to consider a switch now. I think efforts are better spent with optimizing the “webpack watch” workflow and avoiding redundant webpack invocations between CI machines and developers altogether.