7 minutes
Svelte starter with Typescript, Tailwindcss, routing & lazy loaded routes - IE11 compatible
1. Prerequisites:
- node (used here v14.15.0)
2. Project initialisation with Typescript
Init the project (named here svelte-project-starter) using degit:
npx degit sveltejs/template svelte-project-starter
Navigate inside the directory of the newly created project:
cd ./svelte-project-starter
Convert the project to Typescript:
node scripts/setupTypeScript.js
Install the node modules:
npm i
(Optional) Add Prettier config:
It’s useful to first install prettier-plugin-svelte:
npm i -D prettier-plugin-svelte
The .prettierrc file:
{
"printWidth": 120,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"semi": false,
"bracketSpacing": true,
"trailingComma": "es5",
"svelteSortOrder": "scripts-markup-styles"
}
The .prettierignore file:
rollup.config.js
tailwind.config.js
package.json
package-lock.json
tslint.json
tsconfig.json
public/build
.github/
You should now be able to succesfuly run the app in development mode at localhost:5000:
npm run dev
3. Add tailwindcss
In order to stay compatible with IE 11, we will use tailwindcss@1.9.x.
Tailwind css depends on postcss@8, and we will install autoprefixer & postcss-nesting
to make development more pleasurable.
npm i -D tailwindcss@1.9.x autoprefixer postcss-nesting postcss@8
Tailwindcss configuration
The tailwind.config.js file:
const production = !process.env.ROLLUP_WATCH;
module.exports = {
future: {
purgeLayersByDefault: true,
removeDeprecatedGapUtilities: true,
},
plugins: [],
purge: {
content: [
'./src/**/*.svelte',
// may also want to include base index.html
],
enabled: production // disable purge in dev
}
}
Update the Rollup configuration
Add the postcss config in the sveltePreprocess plugin:
The rollup.config.js file:
// ...
export default {
// ...
plugins: [
// ...
svelte({
// ...
preprocess: sveltePreprocess({
// ...
sourceMap: !production,
postcss: {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
require('postcss-nesting'),
],
},
})
})
]
}
Import Tailwindcss in the components
Next, we need to import tailwindcss somewhere in our app code.
This is best done in the root component of the app, in order to have access to the CSS classes in every component the app uses.
In the ./src/App.svelte file:
<script lang="ts">
</script>
<style global lang="postcss">
/* purgecss start ignore */
@tailwind base;
@tailwind components;
/* purgecss end ignore */
@tailwind utilities;
</style>
<main>
<h1 class="text-xl text-red-200">Svelte, Typescript, Tailwindcss</h1>
</main>
Test everything works
Now, if you run npm run dev one more time, the compilation should
be successful, and the heading should appear as a red text, with the size
of 1.25rem.
Test that the CSS purging works in production mode
In development mode, the size of the css bundle exceeds 1M.
However, if everything in the setup is correct, the size in production should be much smaller because every unused CSS class and asset from tailwindcss will be purged from the final build.
For example, at the time of this writing, after I created a production build
with npm run build, the size of the files found in ./public/build were:
| File | Size |
|---|---|
| bundle.css | 3.6K |
| bundle.js | 2.4K |
Note: The production build can be served with npm start.
4. Add IE 11 compatibility
Add babel to transpile the code to ES5
Install the required packages:
npm i -D @babel/core @babel/preset-env @babel/plugin-transform-runtime \
@babel/plugin-syntax-dynamic-import @rollup/plugin-babel
We also need to install core-js for ES6 features in ES5:
npm i -D core-js
Update the Rollup configuration to use babel
The rollup.config.js file:
// ...
import babel from '@rollup/plugin-babel';
// ...
export default {
// ...
plugins: [
// ...
// for IE 11 compatibility
babel({
extensions: ['.js', '.mjs', '.html', '.svelte'],
babelHelpers: 'runtime',
exclude: ['node_modules/@babel/**', 'node_modules/core-js/**'],
presets: [
[
'@babel/preset-env',
{
targets: '> 0.25%, not dead',
useBuiltIns: 'usage',
corejs: 3,
}
]
],
plugins: [
'@babel/plugin-syntax-dynamic-import',
[
'@babel/plugin-transform-runtime',
{
useESModules: true
}
]
]
}),
]
}
Test everything works
If everything went as intended, if you run npm run dev again, you now should
be able to see the app running without errors in a IE11 browser.
How the bundle size was affected
After running npm run build for a production build, the sizes of the
generated files are as follows:
| File | Size |
|---|---|
| bundle.css | 3.6K |
| bundle.js | 46K |
Although the bundle.js size increased dramatically, it is still way better than
the other popular frameworks like Angular, React or Vue. I guess this
is the price we have to pay for having to support IE11.
5. Add routing
Install
For the routing we are going to use the declarative svelte-routing library:
npm i -D svelte-routing
Create some page components
Let’s create some demo pages (Svelte components):
The ./src/page/Home.svelte file:
<h1 class="text-xl">Home Page</h1>
The ./src/page/About.svelte file:
<h1 class="text-xl">About Page</h1>
Define the router viewport and routes
The ./src/App.svelte file will look like this:
<script lang="ts">
import { Router, Link, Route } from 'svelte-routing'
import Home from './page/Home.svelte'
import About from './page/About.svelte'
export let url = ''
</script>
<main>
<h1 class="text-xl text-red-500">Svelte, Typescript, Tailwindcss</h1>
<Router ulr={url}>
<!-- Navigation bar with links to the different pages -->
<nav>
<Link to="home">Home</Link>
<Link to="about">About</Link>
</nav>
<!-- Viewport where the routes are linked to the component pages -->
<div>
<Route path="home" component={Home} />
<Route path="about" component={About} />
</div>
</Router>
</main>
<!-- The style content remains unchanged -->
<style global lang="postcss">
// ...
</style>
Update the npm scripts to redirect to index.html on 404
To allow direct navigation by putting the url in the browser (for example
trying to navigate directly to http://localhost:5000/home), we need to
tell svelte server (sirv) to redirect to index.html when the requested
page is not found.
We do this by updating the start script in package.json:
{
// ...
"scripts": {
// ...
"start": "sirv public --single"
}
}
Important note: This will only work for serving the project after building it
with npm run build.
I don’t have a solution for npm run dev yet, but I will update this article once
I’ll find it.
Test everything works
If everything went as intended, if you run npm run build and npm start you should
be able to see the Home and About links and to navigate to them.
How the bundle size was affected
| File | Size |
|---|---|
| bundle.css | 3.6K |
| bundle.js | 64K |
6. Add lazy loading (bundle chunking with ES native modules)
Create a component that we will lazy load from one of the previous defined routes:
The component ./src/component/LazyLoaded.svelte:
<section>
<h1>Lazy loaded component!</h1>
</section>
Update the About page to lazy load the component
Lazy load the component using ES dynamic imports.
The updated ./src/page/About.svelte:
<script lang="ts">
const LazyLoadedP = import('../component/LazyLoaded.svelte')
.then(({ default: C }) => C)
</script>
<h1 class="text-xl">About Page</h1>
{#await LazyLoadedP}
...Loading lazy loaded component
{:then LazyLoaded}
<LazyLoaded />
{/await}
Update rollup configuration and index.html
Update the rollup.config.js file as follows:
// ...
export default {
// ...
output: [
// The order counts! Otherwise, we get an error at build time!
{
sourcemap: true,
format: 'esm',
name: 'app',
dir: 'public/build'
},
{
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/main.iife.js',
// this is important, otherwise we get an error:
// can't use IIFE format with dynamic imports!
inlineDynamicImports: true,
},
],
}
Update the ./public/index.html file as follows:
<!DOCTYPE html>
<!-- ... -->
<head>
<!-- ... -->
<!-- We indicate that the file type is module (it contains ES import statements) -->
<script defer type="module" src="/build/main.js"></script>
<!-- the only browsers that will load this file are -->
<!-- the ones which don't support ES modules -->
<!-- nomodule indicates to modern browser that this file should not be loaded -->
<script nomodule src="build/main.iife.js"></script>
</head>
Test everything works
To test everything works as intended, run npm run build and then npm start. When you
navigate to the app in a browser that supports ES modules, in the network tab you
should see that already the browser loaded two JS files: main.js and another chunk main-[hash].js
Now, keeping the network tab open, go to the About page. In the network tab, another
request for the LazyLoaded-[hash].js should appear. This is the JS file for our
LazyLoaded.svelte component.
Now, if you open the app in IE 11 everything should work as it previously did.
The only difference will be that the file loaded by IE 11 will be called
main.iife.js instead of bundle.js. IE 11 will also load the main.js file,
but this should not interfere.
How the bundle size was affected
Files loaded at startup (modern browsers):
| File | Size |
|---|---|
| bundle.css | 3.6K |
| main.js | 79B |
| main.[hash].js | 66K |
Module files:
| File | Size |
|---|---|
| LazyLoaded.[hash].js | 0.9K |
IE 11 files:
| File | Size |
|---|---|
| bundle.css | 3.6K |
| main.iife.js | 67K |
7. Conclusion
If you followed the steps, now you should have a completly working project starter in Svelte, with routing, TailwindCSS setup and lazy loaded modules, that also works on IE 11.