Photo by Nathan Wolfe on Unsplash

Getting started with reactjs with Snowpack or vitejs

samarjit.samanta
9 min readAug 18, 2020

--

Snowpack is a tool to convert non module code into module format. It also has little dev server which serves the es6 modules (aka esm, es modules) that can be consumed by browser using import X from Ystatements.

The other reason to write this article is to delve deep into configuration so that we do not have to use babel compiler with snowpack. This is the only difference from the official snowpack template project for react.

Finally we will configure postcss for scss compilation.

How is snowpack different from webpack?

In reality not all that much different. webpack with 900 npm packages try to bundle (concatenate) all js files into 1 bundle.js file, without which browsers will be expecting you to write 100s of<script> tags for each and every the js files of your project in order to load them into client’s browser. Yay! webpack enables only one <script> tag line in your index.html. With snowpack its just 197 npm packages 👍, it does NOT even bundle all js files into 1 js file, but you have to write only one <script> tag just like webpack to import the index.js file in index.html, yay!!. Wait, how does the remaining files reach client’s browser? Glad you asked. Thanks to js modules, browsers are now smart enough to understand the import statements and fetch those files dynamically into the browser. Downside is your browser makes 100s of http calls to download all those files, unless you have heard of HTTP/2 etc. To overcome this snowpack encourages you to use webpack for production builds 🤪.

How is vite different from snowpack?

For me they are very similar. Snowpack does little bit more like converting normal react.js library into esm module and put it in web_modules folder. Vite expects a react.js esm module to begin with, so in package.json you will see “@pika/react”: “16.13.1”.

Esbuild

ESbuild is the compiler used by snowpack. Interestingly esbuild can compile jsx, yay! no need for babel with all the jsx transpiler configurations.

Lets see

snop/src/reactesbuildtest.jsx
import React from "react";
const App = () => <div className="app">hello world</div>;
export default App;

snop>node_modules\.bin\esbuild src\reactesbuildtest.jsx --outdir=build

output: snop/build/reactesbuildtest.js
import React from "react";
const App = () => /* @__PURE__ */ React.createElement("div", {
className: "app"
}, "hello world");
export default App;

Looks cool? It is not very powerful through, like if you are trying to import import './App.css'; It wont work. Possibly importing other stuff like svg etc wont work either. This is where different loaders are required to deal with certain file types to convert them into javascript which then can be imported. Even webpack calls them loaders ‘style-loader’, ‘css-loader’ those kind of things. Some of these loaders are provided by snowpack eg the css loader, others can be added up through plugins like scss/sass files can be loaded using postcss.

Snowpack

Lets start creating a basic application. Start by creating the package.json as below. You can skip esbuild as it is a transitive dependency of snowpack.

folder structure
snop/package.json
{
"name": "snowpack-sample",
"scripts": {
"start": "snowpack dev",
"build": "snowpack build"
},
"dependencies": {
"react": "^16.13.0",
"react-dom": "^16.13.0"
},
"devDependencies": {
"esbuild": "^0.6.25",
"snowpack": "^2.6.4"
}
}

After you run npm install. You will get 197 node packages installed.

Lets quickly create the jsx files.

snop/src/index.jsx (Note the jsx extension, otherwise esbuild will not interpret jsx syntax)import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
ReactDOM.render(<App />, document.getElementById('root'));// Hot Module Replacement (HMR) - Remove this snippet to remove HMR.
// Learn more: https://www.snowpack.dev/#hot-module-replacement
if (import.meta.hot) {
import.meta.hot.accept();
}
snop/src/App.jsx (Regular react)
import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<img src={logo} className="App-logo" width="400px"/> <br/>
Hello World - Hot module replacement is enabled
</div>
)
}
export default App;
App.css // You can add your own css
logo.svg // You can add your own svg

Next you can created snowpack.config.js. This is very basic without any plugins. There is a lot of documentation about what each of these mount commands means in snowpack website.

snop/src/snowpack.config.js for snowpack@2.6.4
module.exports = {
"scripts": {
"mount:public": "mount public --to /",
"mount:src": "mount src --to /_dist_",
"mount:web_modules": "mount $WEB_MODULES --to /web_modules",
},
"plugins": []
}
Running cmd
$npm run build
Snowpack.\build Building your application... mount:public............[DONE] mount public --to /
mount:src...............[DONE] mount src --to /_dist_
build:.js,.jsx,.ts,.tsx.[DONE] (default) esbuild
bundle:*................[SKIP]
▼ bundle:*"plugins": ["@snowpack/plugin-webpack"]Connect a bundler plugin to optimize your build for production.
Set "devOptions.bundle" configuration to false to remove this message.
▼ Console[log] ! optimizing dependencies...
[log]
⦿ web_modules/ size gzip brotli
├─ react-dom.js 125.59 KB 39.45 KB 34.58 KB
└─ react.js 0.17 KB 0.12 KB 0.1 KB
⦿ web_modules/common/ (Shared)
└─ index-3e0c732a.js 9.84 KB 3.77 KB 3.37 KB
▶ Build Complete!Done in 7.40s.
Output of $npm run build

One interesting thing to note here is the web_modules folder. This contains all the npm packages compiled into es modules format. These can be now directly imported by browsers using esm import statements.

<body>
<script type='module'>
import React from 'react';
console.log(React);
</script>
</body>

Another interesting thing that happens is all the jsx files are rewritten using proper paths to vendor npm packages from web_modules.

Compiled: build/_dist_/index.js

Note the react, react-dom paths are rewritten with web_modules paths. CSS files converted into a javascript files which can then be loaded as a regular esm.

Index html should be clear by now, from the script tag to import script should be _dist_/index.js. Just let the browser know it is a module.

<!DOCTYPE html>
<html lang="en">
<head>
<title>Snowpack App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/_dist_/index.js"></script>
</body>
</html>

Running is simple

Running server
$cd build
$ npx http-server
$ open browser with
http://localhost:8080 // react app should run
Running snowpack server
$ npm start
snowpackhttp://localhost:8080http://192.168.254.39:8080
Server started in 51ms.
$ open browser with http://localhost:8080 // react app should run

Almost done. Let me write a few more lines about upgraded config 2.8.0

snop/src/snowpack.config.js for version 2.8.0 
module.exports = {
mount: { // configuration is simplified
public: '/',
src: '/_dist_',
web_modules: '/web_modules'
},
"plugins": []
}

The same command can be used to build.

$ npm run build or $npx snowpack build> snowpack-sample@1.5.0 build 
> snowpack build
[snowpack] ! building source…
[snowpack] ✔ build complete [0.32s]
[snowpack] ! installing dependencies…
[snowpack] ✔ install complete [1.13s]
[snowpack] ! minifying javascript...
[snowpack] ✔ minification complete [0.35s]
[snowpack] ▶ Build Complete!

Since the newer version has less verbose output during build, I purposefully elaborated with older version so that some of the inner workings are clear.

index.js with hmr injected
App.js

Additionally builds files i.e. minified concatenated files can be generated through webpack plugins @snowpack/plugin-webpack.

Sass/scss

Sass/scss support is something I am not entirely satisfied, it looks more like a workaround. But atleast we have a workaround. I would like to import './App.scss'; directly instead today we have to convert to css as a separate process and then inside jsx file we need to import css.

$ npm intall sasssnop/snowpack.config.js
module.exports = {
mount: {
public: '/',
src: '/_dist_',
web_modules: '/web_modules'
},
"plugins": [
["@snowpack/plugin-run-script", {"cmd": "sass src:src --no-source-map", "watch": "$1 --watch"}]
]
}
rename snop/src/App.css to snop/src/App.scsssnop/src/App.jsx
import './App.css'; // no changes here, note no scss
$ npm start -- additional output of sass --
▼ sass
Compiled src\App.scss to src\App.css.
Sass is watching for changes. Press Ctrl-C to stop.
Compiled src\app.scss to src\App.css.
Compiled src\app.scss to src\App.css.

The App.css is autogenerated now. I don’t see any other problem, the app.jsx imports css so its better to have the css in src folder. That said I would still like to see snowpack team to create a loader for sass so that we do not have to go through this intermediate step of generating css in src folder. Another problem I found out was that the plugin @snowpack/plugin-run-script keeps running behind the scenes even after killing the current build server. (This is a bug at least in windows 10, and should be fixed by snowpack team in future).

Vite

I wont go a lot of deep in the details but these are the configs used in vite. Note the @pika/react esm react module is required to being with since vite cannot compile normal commonjs or umd react into es modules. So snowpack has the upperhand here 👍. But more and more we are seeing esm build available for npm packages along with commonjs in dists. But its good to have competing technologies. Here is official version of their starter project.

C:\vite\package.json
{
"name": "vite-react-starter",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"dependencies": {
"@pika/react": "^16.13.1",
"@pika/react-dom": "^16.13.1"
},
"devDependencies": {
"vite": "^1.0.0-rc.1",
"vite-plugin-react": "^3.0.0"
}
}
npm install will installs 202 npm packages. Comparable to snowpackC:\vite\vite.config.js
// @ts-check
const reactPlugin = require('vite-plugin-react')
/**
* @type { import('vite').UserConfig }
*/
const config = {
jsx: 'react',
plugins: [reactPlugin]
}
module.exports = configC:\vite\index.html
<!DOCTYPE html>
<html>
<head>
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>
Other files index.jsx, App.jsx, App.css will remain same$ npm run build (This does bundle everything together)> vite-react-starter@0.0.0 build
> vite build
vite v1.0.0-rc.4
[write] dist\_assets\index.70394685.js 266.81kb, brotli: 68.12kb
[write] dist\_assets\style.343661e4.css 0.76kb, brotli: 0.40kb
[write] dist\_assets\logo.cdc95cf0.svg 2.61kb
[write] dist\index.html 0.35kb, brotli: 0.14kb
Build completed in 9.25s.
$ npm run dev (This creates es modules)> vite-react-starter@0.0.0 dev
> vite
vite v1.0.0-rc.4Dev server running at:
> Network: http://0.0.0.0:3000/
> Local: http://localhost:3000/
[vite:hmr] /src/App.jsx hot updated due to change in /src/App.jsx.
[vite:hmr] /src/App.jsx hot updated due to change in /src/App.jsx.

Vite dev mode starts up a dev server, and serves everything up using es modules. It adds a small reloader in the html and each js files also has a small reloader code attached. So the hot module replacement [HMR] part is different from snowpack. Vite’s index.js does not have any special code for reloading. Vite adds sourcemaps which is a plus 👍.

Vite build by default has a bundler (powered by rollup) it combined all files into a single index.[hash].js. Snowpack also does the build but delegates to webpack via plugin @snowpack/plugin-webpack.

index.js — shows @modules which is similar to web_modules, also note sourcemaps
App.jsx — shows @react-refresh and headers injected for hot reloading

In real world

I converted one of my projects into snowpack and it seems to be running just fine. These are the libs I am using. I might get rid of the postcss portions and go directly with sass.

"dependencies": {
"bootstrap": "4.5.0",
"dayjs": "^1.8.27",
"jquery": "3.5.1",
"lodash": "4.17.15",
"materialize-css": "^1.0.0",
"node-sass": "^4.14.1",
"popper.js": "1.16.1",
"postcss-cli": "^7.1.1",
"postcss-import": "^12.0.1",
"postcss-scss": "^2.1.1",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-easy-swipe": "0.0.18",
"react-router-dom": "^5.2.0",
"react-swipeable": "5.5.1",
"react-use-websocket": "2.0.1",
"redux": "4.0.5",
"src": "^1.1.2"
},
"devDependencies": {
"snowpack": "^2.6.4",
"@csstools/postcss-sass": "^4.0.0"
}

Conclusion

Snowpack/Vite is a new way of using esmodules for running without bundling client side javascript. They both approach hot reloading in slightly different approach. But at the end experience is same. In production probably it makes more sense even today to minify concatenate js files into one file or intelligently split code into logical splits with tools like webpack. But there are areas where esmodules might be beneficial.

I can think of immediate applications where dynamic imports are required. Webpack does split and creates dynamic imports points. But imagine the you are trying to load a dynamic plugin from a different application which need not necessarily share code base with the parent webapp.

Think of microfrontends where each section is a different webapp not sharing code base. These are the really helpful if esmodules can be imported directly in browser and those modules will act as if part of same application.

--

--

samarjit.samanta

Developer in UI, reactjs, angularjs, backend nodejs, java, springboot. Tinkerer of electronics. Loves to do everything hands on.