Creating an Elm Project with Snowpack
Mickey
Posted on November 28, 2020
Intro
There is a new emerging trend of a development setup in the frontend world. As browsers now natively support esm modules, we don't need to use bundlers like Webpack, Parcel or Rollup.
Projects like Snowpack, Vite (for Vue), Svite (for Svelte) and recently SvelteKit (official one for Svelte) are starting to gain attention.
By skipping the bundling stage, the browser is immediately updated with the changes in the source files, without having to wait for re-bundling.
Snowpack
Described in its own words as:
... a lightning-fast frontend build tool, designed for the modern web.
Snowpack has an out-of-the-box support for TypeScript, JSX, CSS Modules and more. It also can be extended with plugins, and there is a plugin for Elm: Snowpack Elm Plugin by Marc Walter. Thank you Marc!
Today I would like to share a setup of Snowpack with Elm and TailwindCSS - a very popular utility-first CSS framework.
Project Setup
Let's create our project from scratch, as it is described in the documentation' "Starting a New Project"
mkdir elm-snowpack
cd elm-snowpack
npm init -y
npm install snowpack --save-dev
Those will perform several things:
- create
elm-snowpack
folder - cd into it
- create
package.json
with predefined fields without prompting us (the-y
or--yes
) - install Snowpack version
3.x.y
.
Now let's add index.html
to the root of our project with the following content:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Elm with Snowpack" />
<title>Elm with Snowpack</title>
</head>
<body>
<h1>Welcome to Snowpack!</h1>
</body>
</html>
and add a start
script entry to the package.json
file:
"scripts": {
"start": "snowpack dev",
"test": "echo \"Error: no test specified\" && exit 1"
},
with that ready let's start the Snowpack development server:
npm run start
We see some Snowpack's output:
The browser opens automatically (if it will not - open it manually and navigate to http://localhost:8080
).
And we see our beautiful application:
Following the documentation, let's add an index.js
and index.css
files.
Create both those files in the root of our project:
index.js
console.log('Hello World!');
index.css
body {
font-family: sans-serif;
}
And update index.html
to include them:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Elm with Snowpack" />
<!-- add index.css -->
<link rel="stylesheet" type="text/css" href="/index.css" />
<title>Elm with Snowpack</title>
</head>
<body>
<h1>Welcome to Snowpack!</h1>
<!-- add index.js -->
<script type="module" src="/index.js"></script>
</body>
</html>
After saving all the files, the browser is updated with our changes.
Reorganizing
Having all the files in the root of our project is not practical. Usually source files reside in src
folder. So let's reorganize our files.
Stop the Snowpack development server. Create an src
folder and move index.js
and index.css
there. Then create public
folder and move index.html
there.
elm-snowpack/
node_modules/
public/
index.html
src/
index.css
index.js
package.json
package-lock.json
Since we have moved our files around, we need to inform Snowpack where to find them. Snowpack can be configured from the command line or by adding snowpack.config.js
to the project folder. So let's add this file in the root of our project and add the default content from the documentation:
module.exports = {
plugins: [
/* ... */
],
installOptions: {
/* ... */
},
devOptions: {
/* ... */
},
buildOptions: {
/* ... */
},
proxy: {
/* ... */
},
mount: {
/* ... */
},
alias: {
/* ... */
},
};
This file can also be added automatically by Snowpack:
npx snowpack init
The entry we are interested in is mount
and the example setting in the documentation is exactly suits our setup:
// ...omitted
{
"mount": {
"src": "/dist",
"public": "/"
}
}
// ...omitted
You can look for the examples of how this mapping works in the documentation, but basically it maps requests of /dist/*
to serve files under src/*
(index.js
, index.css
) and requests of /*
to serve files under public/*
(index.html
).
According to this mapping our index.html
will be server from /public
folder when requested as /
and it's good, but our source files under src/
folder should be requested from /dist
. So let's change their URIs in the index.html
:
<link rel="stylesheet" type="text/css" href="/dist/index.css" />
<script type="module" src="/dist/index.js"></script>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Elm with Snowpack" />
<!-- update URI to index.css -->
<link rel="stylesheet" type="text/css" href="/dist/index.css" />
<title>Elm with Snowpack</title>
</head>
<body>
<h1>Welcome to Snowpack!</h1>
<!-- update URI to index.js -->
<script type="module" src="/dist/index.js"></script>
</body>
</html>
Starting our development server should show our application as before.
Adding Elm Support
Great, we have an empty application running and the changes that we make are propagated to the browser with lightning speed!
Let's add Elm support now.
As I mentioned in the beginning - There is a Snowpack Elm Plugin by Marc Walter.
We need to install it (as a dev dependency using -D
flag):
npm install -D snowpack-plugin-elm
And add it to the plugins
entry in the snowpack.config.js
:
module.exports = {
// ...
plugins: ['snowpack-plugin-elm'],
// ...
}
as it is described in the plugin's repo.
Good, now let's add src/Main.elm
with the mandatory "Hello World!":
module Main exposing (main)
import Html exposing (Html)
main =
Html.text "Hello, World!"
and import our Elm application in src/index.js
:
import Elm from './Main.elm';
console.log(Elm);
console.log('Elm and Tailwind with Snowpack');
If we run our application now (or it was already running) we'll see an error message that, very rightfully, tells us that we didn't initialize an Elm project with elm.json
file:
So let's do exactly that:
elm init
Click Enter
to agree with the prompt. This will create the elm.json
file.
Running the application again shows no errors. And we see in the console that the Elm application is indeed created:
The wiring of the Elm application is described in the guide's JavaScript Interop/Embedding in HTML. We can copy the code from there to our src/index.js
(I have deleted all the console.log
):
import Elm from './Main.elm';
const app = Elm.Main.init({
node: document.getElementById('app'),
});
We also need to update our index.html
file to have a placeholder for our Elm application:
<div id="app"></div>
So now our index.html
looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Elm with Snowpack" />
<link rel="stylesheet" type="text/css" href="/dist/index.css" />
<title>Elm with Snowpack</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/dist/index.js"></script>
</body>
</html>
After saving the file the browser is instantly updated and shows the "Hello, World!" - our application is working!
Try editing the text and saving it. The browser updates really fast!
Adding Tailwind CSS
Now let's add a Tailwind CSS to our project. We partially follow the Installing Tailwind CSS as a PostCSS plugin as it described in the documentation, only instead of postcss
we'll install postcss-cli
, that Snowpack needs.
npm install -D tailwindcss autoprefixer postcss-cli
The default presets of Tailwind CSS are absolutely sufficient to start playing with prototyping components/design. But in order to further customize Tailwind CSS we will need a configuration file - tailwind.config.js
. So as an optional step we can create it by running:
npx tailwindcss init
Let's replace the content of our src/index.css
with the standard TailwindCSS presets:
@tailwind base;
@tailwind components;
@tailwind utilities;
Let's also create postcss.config.js
file:
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
],
};
We tell PostCSS to use tailwindcss
and autoprefixer
(that is another plugin that add browser specific prefixes for the relevant CSS rules).
Good, now we need to tell Snowpack to execute the PostCSS when needed. Following the official guide on PostCSS, update snowpack.config.js
, plugins
entry:
plugins: [
[
'@snowpack/plugin-build-script',
{ cmd: 'postcss', input: ['.css'], output: ['.css'] },
],
'snowpack-plugin-elm',
],
And that should enable TailwindCSS in our application.
NOTE: There is also an official way to setup TailwindCSS with Snowpack, but it did not work for me.
I took a chunk of user card HTML from the Tailwind CSS website's (version 1.9) landing page and pasted it in our public/index.html
. In addition, I have updated the avatar
image with an online placeholder and added shadow-lg mx-10
to the <div>
in order to see the shadow and for the card not to spread all the way to the edges of the window:
<!-- ... -->
<body>
<div id="app"></div>
<div class="md:flex bg-white rounded-lg p-6 shadow-lg m-10">
<img
class="h-16 w-16 md:h-24 md:w-24 rounded-full mx-auto md:mx-0 md:mr-6"
src="https://i.pravatar.cc/100?img=1"
/>
<div class="text-center md:text-left">
<h2 class="text-lg">Erin Lindford</h2>
<div class="text-purple-500">Product Engineer</div>
<div class="text-gray-600">erinlindford@example.com</div>
<div class="text-gray-600">(555) 765-4321</div>
</div>
</div>
<!-- ... -->
And after the page updates, we see Tailwind CSS in action:
This style is responsive. Try narrowing the browser's window and see the card changes:
We can re-create this HTML in our Elm application and delete the raw HTML:
module Main exposing (main)
import Browser
import Html exposing (Html, div, h2, img, text)
import Html.Attributes exposing (class, src)
type alias User =
{ fullName : String
, position : String
, email : String
, phone : String
, avatar : String
}
erinLindford : User
erinLindford =
{ fullName = "Erin Lindford"
, position = "Product Engineer"
, email = "erinlindford@example.com"
, phone = "(555) 765-4321"
, avatar = "https://i.pravatar.cc/100?img=1"
}
userCardView : User -> Html msg
userCardView user =
div [ class "md:flex bg-white rounded-lg p-6 shadow-lg m-10" ]
[ img
[ class "h-16 w-16 md:h-24 md:w-24 rounded-full mx-auto md:mx-0 md:mr-6"
, src user.avatar
]
[]
, div
[ class "text-center md:text-left" ]
[ h2 [ class "text-lg" ] [ text user.fullName ]
, div [ class "text-purple-500" ] [ text user.position ]
, div [ class "text-gray-600" ] [ text user.email ]
, div [ class "text-gray-600" ] [ text user.phone ]
]
]
update : msg -> User -> User
update _ model =
model
view : User -> Html msg
view model =
userCardView model
main : Program () User msg
main =
Browser.sandbox { init = erinLindford, update = update, view = view }
Delete the raw HTML from index.html
and refresh the page - the result is the same as before.
And at last we have a nice development setup with Snowpack, Elm and TailwindCSS.
Although the step-by-step tutorial may seem long, it's pretty quick to re-create this setup.
Hope you enjoyed creating our small project and find this useful.
Thank you for reading.
Posted on November 28, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.