Example using TypeScript, Preact, and snowpack, a new ESM bundler. Uses [Sass][sass] for styling.
npm i
npm start
It’ll run a multi-page app at localhost:5000
.
Note: changes to .tsx
& .scss
files will re-build automatically, but
changes to src/index.html
will require a restart of the dev server
Type handling is the difficult part of using TypeScript with snowpack, since browsers don’t support it. The core concepts of pulling this off are:
- Having a separate
src/
anddist/
folders - Using this fix in
tsconfig.json
(discussion):"compilerOptions": { "paths": { "/web_modules/*.js": [ "node_modules/@types/*", "node_modules/*", "web_modules/*.js", ] } }
The first point is a rather obvious one: since TypeScript isn’t supported by browsers, we’ll have to keep our source code separate from our compile directory. That’s easy enough, but when you compile it, now you have a lot of these in your code:
import preact from "preact";
A browser doesn’t know what to do with that! That’s not a proper path, and
it’s also missing the .js
extension. However, if you try modifying your
TypeScript to:
import preact from "./web_modules/preact.js";
Now TypeScript can’t resolve those modules or their types! However, by aliasing these imports a special way, we can do something clever:
"compilerOptions": {
"paths": {
"/web_modules/*.js": [
"node_modules/@types/*",
"node_modules/*",
"web_modules/*.js",
]
}
}
This lets us do the following in our project (we’ll always need to do this for all web_modules):
import preact from "/web_modules/preact.js";
Let’s break down what this tells TypeScript:
/web_modules/*.js
tells TypeScript to take any import matching this pattern and treat it differently (“this pattern” meaning any import that starts with a/
, then hasweb_modules
, then anything (*
), then ends in.js
).- How should TypeScript treat them differently? Well, it should try and find the
*
in any of the following folders:- First, try
node_modules/@types/*
(for the modules that have external types) - Next, try
node_modules/*
to see if the original library has types - Lastly, try
web_modules/*
to see if snowpack ported the types over.
- First, try
Usually, the types will be found somewhere within node_modules
and
TypeScript is happy.
But wait—what about browsers? Well that’s the best part! If we look at our output code:
import preact from "/web_modules/preact.js";
That /web_modules/preact.js
resolves to be an actual path to our ESM on our
server (assuming web_modules
was installed at the root)! So the alias we
were using for TypeScript also happened to be a valid path the browser
understands.
If you need to install web_modules
somewhere else other than the root, then
you can change the --dest
of snowpack as well as the option under
"paths"
in tsconfig.json
as-needed.
If you’re using ESLint, you’ll have to add a few more rules to keep it happy (you may already be familiar with this if you’ve used TypeScript & JSX before):
{
"rules": {
"import/extensions": "off", // we need this for browsers
"import/no-absolute-path": "off", // we also need this
"import/no-unresolved": "off", // TypeScript got this
"react/jsx-filename-extension": [
"error",
{ "extensions": [".js", ".jsx", ".tsx"] }
]
}
}