Build Setup Recipes¶
Using Svelte with other technologies (e.g. PostCSS, SCSS, TypeScript, and Babel)¶
Svelte doesn't build in any opinions on the kind of JavaScript, CSS, or even HTML you write. Therefore, if you want to use any nonstandard syntax, you need to put that into your bundler plugin chain or Svelte's preprocessors. Here we will describe the top usecases for doing this.
First we will discuss Babel. There are two different usecases for Babel with Svelte - with accordingly two separate ways to implement it:
- Transpiling ES6 to ES5: use Babel with bundler plugin
- Using future JS syntax: use Babel with Svelte preprocessor
Transpiling ES6 to ES5 for Legacy Browser (IE11) Support with Babel¶
Svelte outputs modern JavaScript, therefore for legacy browser support, you need to transpile this output back to ES5 (or older). The main strategy people adopt is having 2 builds:
- Modern JS build - transpile Svelte as is
- Legacy browser build - add Babel plugin to bundler, after Svelte plugin.
You can use an environment variable like process.env.IS_LEGACY_BUILD
(the name is arbitrary - Sapper calls it SAPPER_LEGACY_BUILD
) to toggle this behavior between builds.
If you are using Parcel, this should be taken care of by specifying the correct .babelrc
.
If you are using Sapper, this should be correctly set up for you.
If you are using Rollup or Webpack, you need to add the respective Babel plugins.
Rollup
Assuming you want to toggle on and off a modern and a legacy build using a `IS_LEGACY_BUILD` environment variable:// // rollup.config.js
// other imports
import svelte from "rollup-plugin-svelte";
import babel from "rollup-plugin-babel";
const legacy = !!process.env.IS_LEGACY_BUILD;
export default {
// etc...
plugins: [
svelte({
// etc..
}),
// this should come after the Svelte plugin
legacy &&
babel({
extensions: [".js", ".mjs", ".html", ".svelte"],
runtimeHelpers: true,
exclude: ["node_modules/@babel/**"],
presets: [
[
"@babel/preset-env",
{
targets: "> 0.25%, not dead",
},
],
],
plugins: [
"@babel/plugin-syntax-dynamic-import",
[
"@babel/plugin-transform-runtime",
{
useESModules: true,
},
],
],
}),
// ...
],
};
Webpack
_To be written - please contribute!_Using Future JS Syntax in Svelte with Babel¶
The other, less common but still handy usecase for Babel with Svelte is transpiling future syntax inside <script>
tags. For example, the Stage 3 Optional Chaining proposal is popular, but not yet in browsers, and more relevantly, is not yet understood by [Acorn]https://github.com/acornjs/acorn), which is what Svelte uses to parse <script>
tags.
Example usage:
<script>
let foo = {
bar: {
baz: true,
},
};
let sub = foo?.ban?.baz;
</script>
<main>
<h1>Hello {sub}!</h1>
</main>
Here, the fix is to use Svelte's Preprocess feature:
// rollup.config.js
// ...
export default {
// ...
plugins: [
svelte({
// ...
preprocess: {
script: ({ content }) => {
return require("@babel/core").transform(content, {
plugins: ["@babel/plugin-proposal-class-properties"],
});
},
},
}),
// ...
Of course, make sure that @babel/core
and whatever plugins you use are installed.
Alternatively, you might wish to use svelte-preprocess
instead, for its' other features:
import preprocess from "svelte-preprocess";
// ...
preprocess: preprocess({
babel: {
presets: [
[
"@babel/preset-env",
{
loose: true,
// No need for babel to resolve modules
modules: false,
targets: {
// ! Very important. Target es6+
esmodules: true,
},
},
],
],
},
});
// ...
Important note: Don't try to transpile to ES5 here. This would produce unnecessary bloat as you would be transpiling on a per-component basis - sharing Babel helpers is the reason you use bundler plugins instead. Keep it light, only transpile the new stuff you use. This also makes it easy to remove in future.
You may also wish to use new JS syntax inside the JS expressions in your template markup. There is currently no easy way to do that, but watch this issue.
Using TypeScript with Svelte¶
It is a common misconception that Svelte doesn't work with TypeScript yet. Svelte is, of course, written in TypeScript, so the project strongly believes in the value of typing. The part that doesn't have full support yet is IDE-supported typechecking of templates, and there is ongoing work on a langauge server with some pointers from the TypeScript team.
However, using TypeScript inside <script>
tags is supported, and that is likely an important part of how you will be using TypeScript anyway. All of them use Svelte's preprocessor feature:
- You can use
[@babel/preset-typescript](https://babeljs.io/docs/en/babel-preset-typescript)
together with the Babel process described above to strip out types. - You can pass the source code through the
typescript
compiler:
// rollup.config.js
import * as ts from "typescript";
// ...
export default {
// ...
plugins: [
svelte({
// ...
preprocess: {
script: ({ content }) => {
return ts.transpileModule(content, {
compilerOptions: { // up to you }
})
},
},
}),
// ...
- Or instead of writing this all yourself, you can use
svelte-preprocess
, which is what we recommend.
Example using svelte-preprocess
:
// rollup.config.js
import svelte from 'rollup-plugin-svelte';
import autoPreprocess from 'svelte-preprocess'
import { scss, coffeescript, pug } from 'svelte-preprocess'
export default {
...,
plugins: [
svelte({
/**
* Auto preprocess supported languages with
* '<template>'/'external src files' support
**/
preprocess: autoPreprocess({ /* options available https://github.com/kaisermann/svelte-preprocess/#user-content-options */ })
})
]
}
and then you can write your Svelte components with TypeScript:
<script lang="typescript">
// Compatible with Svelte v3...
export const hello: string = 'world';
</script>
<template>
<div>Hello {hello}</div>
</template>
More information from community blogposts:
Using PostCSS/SCSS with Svelte¶
Option 1 - Write your own
This process is actually documented in the Svelte docs, but we'll restate it here with the bundler case:
// rollup.config.js
const sass = require('node-sass');
// ...
export default {
// ...
plugins: [
svelte({
// ...
preprocess: {
style: ({ content, attributes, filename }) => {
// only process <style lang="sass">
if (attributes.lang !== 'sass') return;
const { css, stats } = await new Promise((resolve, reject) => sass.render({
file: filename,
data: content,
includePaths: [
dirname(filename),
],
}, (err, result) => {
if (err) reject(err);
else resolve(result);
}));
// TODO: not sure about this. maybe supposed to just return css.toString() instead
return {
code: css.toString(),
dependencies: stats.includedFiles
};
},
},
}),
// ...
Option 2 - use svelte-preprocess
Setting up svelte-preprocess
can help simplify this:
// rollup.config.js
import svelte from 'rollup-plugin-svelte';
import autoPreprocess from 'svelte-preprocess'
import { scss, coffeescript, pug } from 'svelte-preprocess'
export default {
...,
plugins: [
svelte({
/**
* Auto preprocess supported languages with
* '<template>'/'external src files' support
**/
preprocess: autoPreprocess({ /* options available https://github.com/kaisermann/svelte-preprocess/#user-content-options */ })
})
]
}
and you can write SCSS/SASS in your Svelte template:
<style lang="scss">
$color: red;
div {
color: $color;
}
</style>
The same is true for PostCSS as well, which also applies to any PostCSS plugins, like TailwindCSS (see Tailwind demo here).
Writing Your Own Preprocessors¶
This article references
svelte.preprocess
throughout but you may be more familiar with thepreprocess
option ofsvelte-loader
orrollup-plugin-svelte
. Thispreprocess
option callssvelte.preprocess
internally. The bundler plugin gives you easy access to it, so you don't need to transform your components before compilation manually.
The Svelte compiler expects all components it receives to be valid Svelte syntax. To use compile-to-js or compile-to-css languages, you need to make sure that any non-standard syntax is transformed before Svelte tries to parse it. To enable this Svelte provides a preprocess
method allowing you to transform different parts of the component before it reaches the compiler.
With svelte.preprocess
you have a great deal of flexibility in how you write your components while ensuring that the Svelte compiler receives a plain component.
svelte.preprocess¶
Svelte's preprocess
method expects an object or an array of objects with one or more of markup
, script
, and style
properties, each being a function receiving the source code as an argument. The preprocessors run in this order.
const preprocess = {
markup,
script,
style,
};
In general, preprocessors receive the component source code and must return the transformed source code, either as a string or as an object containing a code
and map
property. The code
property must contain the transformed source code, while the map
property can optionally contain a sourcemap. The sourcemap is currently unused by Svelte.
How to make a pre-processor that makes it possible to use Pug/Jade¶
Pug is an alternative templating language that compiles to html, using whitespace instead of angle brackets:
const pug = require("pug");
const string = "p Hello World!"; // pug template language
const html = pug.render(file);
console.log(html); // <p>Hello World!</p>
Our goal is to write Pug in our Svelte files instead of HTML where we can do all the scoped styling and JS goodness. Let's create an app.svelte
file with this:
<style>
p {
color: red;
}
</style>
p Hello World!
We need to run it through Svelte to generate JavaScript, not HTML:
const svelte = require("svelte/compiler");
const fs = require("fs");
const file = fs.readFileSync("app.svelte", "utf8");
const result = svelte.compile(file);
console.log(result.js.code); // svelte.compile returns both js and sourcemap
But this logs out the wrong thing!
/* generated by Svelte v3.23.0 */
import {
SvelteComponent,
detach,
init,
insert,
noop,
safe_not_equal,
text,
} from "svelte/internal";
function create_fragment(ctx) {
let t;
return {
c() {
t = text("p Hello World!"); // WRONG
},
m(target, anchor) {
insert(target, t, anchor);
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(t);
},
};
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, create_fragment, safe_not_equal, {});
}
}
export default Component;
To do this properly, we need to preprocess the template file. Let's use svelte.preprocess
instead of svelte.compile
:
const result = svelte.preprocess(file, {
markup: ({ content }) => console.log(content),
});
Notice also that the markup
includes the style
tag as well. Some preprocessors might need that, but we don't, so we can strip it out. Finally, we can run the stripped code through pug
:
const pug = require("pug");
const svelte = require("svelte/compiler");
const fs = require("fs");
const file = fs.readFileSync("app.svelte", "utf8");
// https://github.com/sveltejs/svelte/blob/8fc85f0ef6b53ed85e54c129d79270fe577626dc/src/compiler/preprocess/index.ts#L97
const styleRegex = /<!--[^]*?-->|<style(\s[^]*?)?>([^]*?)<\/style>/gi;
const scriptRegex = /<!--[^]*?-->|<script(\s[^]*?)?>([^]*?)<\/script>/gi;
(async function () {
const result = await svelte.preprocess(file, {
// options
markup: ({ content }) => {
let code = content.replace(styleRegex, "").replace(scriptRegex, "");
code = pug.render(code);
// TODO: if still want CSS/JS, still have to append the style/script tags BACK onto `code` for svelte to pick them up
return { code };
},
});
console.log(result); // <p>Hello World!</p>
})();
This looks right. Our final task is to run this through svelte.compile
:
const res = svelte.compile(result.code);
console.log(res.js.code);
And this generates the correct result.
/* generated by Svelte v3.23.0 */
import {
SvelteComponent,
detach,
element,
init,
insert,
noop,
safe_not_equal,
} from "svelte/internal";
function create_fragment(ctx) {
let p;
return {
c() {
p = element("p");
p.textContent = "Hello World!"; // correct!
},
m(target, anchor) {
insert(target, p, anchor);
},
p: noop,
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(p);
},
};
}
class Component extends SvelteComponent {
constructor(options) {
super();
init(this, options, null, create_fragment, safe_not_equal, {});
}
}
export default Component;
The svelte.preprocess
API also offers a dependencies
array you can use to optimize for recompiling when files are changed.
Most of the time, you will be using a bundler plugin rather than using svelte.compile
and svelte.preprocess
directly. These plugins have a slightly different API, but you can still slot in your handwritten preprocessor code inline:
// example rollup.config.js
import * as fs from "fs";
import svelte from "rollup-plugin-svelte";
const pug = require("pug");
const styleRegex = /<!--[^]*?-->|<style(\s[^]*?)?>([^]*?)<\/style>/gi;
const scriptRegex = /<!--[^]*?-->|<script(\s[^]*?)?>([^]*?)<\/script>/gi;
export default {
input: "src/main.js",
output: {
file: "public/bundle.js",
format: "iife",
},
plugins: [
svelte({
// etc
preprocess: {
markup: ({ content }) => {
let code = content.replace(styleRegex, "").replace(scriptRegex, "");
code = pug.render(code);
return { code };
},
},
}),
],
};
If you want to use Pug and HTML markup interchangeably, you may wish to adopt the non-standard but community norm of adding a <template>
tag for non-HTML markup:
<style>
p {
color: red;
}
</style>
<template lang="pug">p Hello World!</template>
This guarantees no ambiguity when you have a Svelte codebase that is a mix of Pug and HTML templates. In order to process this you will have to add some checking and preprocessing:
const pug = require("pug");
const svelte = require("svelte/compiler");
const fs = require("fs");
const file = fs.readFileSync("app.svelte", "utf8");
// https://github.com/sveltejs/svelte/blob/8fc85f0ef6b53ed85e54c129d79270fe577626dc/src/compiler/preprocess/index.ts#L97
const styleRegex = /<!--[^]*?-->|<style(\s[^]*?)?>([^]*?)<\/style>/gi;
const scriptRegex = /<!--[^]*?-->|<script(\s[^]*?)?>([^]*?)<\/script>/gi;
const markupPattern = new RegExp(
`<template([\\s\\S]*?)(?:>([\\s\\S]*)<\\/template>|/>)`
);
(async function () {
const result = await svelte.preprocess(file, {
// options
markup: ({ content }) => {
// lifted from https://github.com/sveltejs/svelte-preprocess/blob/38b32b110b7e81c995da14dc813f002346b9e0af/src/autoProcess.ts#L179
const templateMatch = content.match(markupPattern);
if (!templateMatch) return { code: content };
const [fullMatch, attributesStr, templateCode] = templateMatch;
/** Transform an attribute string into a key-value object */
const attributes = attributesStr
.split(/\s+/)
.filter(Boolean)
.reduce((acc, attr) => {
const [name, value] = attr.split("=");
// istanbul ignore next
acc[name] = value ? value.replace(/['"]/g, "") : true;
return acc;
}, {});
/** Transform the found template code */
let code = content.replace(styleRegex, "").replace(scriptRegex, "");
if (attributes.lang === "pug") {
code = templateCode;
code = pug.render(code);
}
code =
content.slice(0, templateMatch.index) +
code +
content.slice(templateMatch.index + fullMatch.length);
return { code };
},
});
const res = svelte.compile(result.code);
console.log(res.js.code);
})();
This makes the preprocessor only work if <template lang="pug">
is used.