Undecided yet what bundler to use? We suggest using SvelteKit, or Vite with vite-plugin-svelte.
svelte-loader
A webpack loader for svelte.
Install
npm install --save svelte svelte-loader
Usage
Configure inside your webpack.config.js
:
...
resolve: {
alias: {
svelte: path.resolve('node_modules', 'svelte/src/runtime')
},
extensions: ['.mjs', '.js', '.svelte'],
mainFields: ['svelte', 'browser', '...'],
conditionNames: ['svelte', 'browser', '...'],
},
module: {
rules: [
...
{
test: /\.svelte\.ts$/,
use: [ "svelte-loader", "ts-loader"],
},
{
test: /(?<!\.svelte)\.ts$/,
loader: "ts-loader",
},
{
test: /\.(svelte|svelte\.js)$/,
use: 'svelte-loader'
},
{
test: /node_modules\/svelte\/.*\.mjs$/,
resolve: {
fullySpecified: false
}
}
...
]
}
...
Check out the example project.
resolve.alias
The resolve.alias
option is used to make sure that only one copy of the Svelte runtime is bundled in the app, even if you are npm link
ing in dependencies with their own copy of the svelte
package. Having multiple copies of the internal scheduler in an app, besides being inefficient, can also cause various problems.
resolve.mainFields
Webpack's resolve.mainFields
option determines which fields in package.json
are used to resolve identifiers. If you're using Svelte components installed from npm, you should specify this option so that your app can use the original component source code, rather than consuming the already-compiled version (which is less efficient).
resolve.conditionNames
Webpack's resolve.conditionNames
option determines which fields in the exports
in package.json
are used to resolve identifiers. If you're using Svelte components installed from npm, you should specify this option so that your app can use the original component source code, rather than consuming the already-compiled version (which is less efficient).
If your Svelte components contain <style>
tags, by default the compiler will add JavaScript that injects those styles into the page when the component is rendered. That's not ideal, because it adds weight to your JavaScript, prevents styles from being fetched in parallel with your code, and can even cause CSP violations.
A better option is to extract the CSS into a separate file. Using the emitCss
option as shown below would cause a virtual CSS file to be emitted for each Svelte component. The resulting file is then imported by the component, thus following the standard Webpack compilation flow. Add MiniCssExtractPlugin to the mix to output the css to a separate file.
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
...
module: {
rules: [
...
{
test: /\.(svelte|svelte\.js)$/,
use: {
loader: 'svelte-loader',
options: {
emitCss: true,
},
},
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
url: false,
}
}
]
},
...
]
},
...
plugins: [
new MiniCssExtractPlugin('styles.css'),
...
]
...
Additionally, if you're using multiple entrypoints, you may wish to change new MiniCssExtractPlugin('styles.css')
for new MiniCssExtractPlugin('[name].css')
to generate one CSS file per entrypoint.
Warning: in production, if you have set sideEffects: false
in your package.json
, MiniCssExtractPlugin
has a tendency to drop CSS, regardless of whether it's included in your svelte components.
Alternatively, if you're handling styles in some other way and just want to prevent the CSS being added to your JavaScript bundle, use
...
use: {
loader: 'svelte-loader',
options: {
compilerOptions: {
css: false
}
},
},
...
Source maps
JavaScript source maps are enabled by default, you just have to use an appropriate webpack devtool.
To enable CSS source maps, you'll need to use emitCss
and pass the sourceMap
option to the css-loader
. The above config should look like this:
module.exports = {
...
devtool: "source-map",
...
module: {
rules: [
...
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
sourceMap: true
}
}
]
},
...
]
},
...
plugins: [
new MiniCssExtractPlugin('styles.css'),
...
]
...
};
This should create an additional styles.css.map
file.
Svelte Compiler options
You can specify additional arbitrary compilation options with the compilerOptions
config key, which are passed directly to the underlying Svelte compiler:
...
use: {
loader: 'svelte-loader',
options: {
compilerOptions: {
generate: 'ssr',
}
},
},
...
Using preprocessors like TypeScript
Install svelte-preprocess and add it to the loader options:
const sveltePreprocess = require('svelte-preprocess');
...
use: {
loader: 'svelte-loader',
options: {
preprocess: sveltePreprocess()
},
},
...
Now you can use other languages inside the script and style tags. Make sure to install the respective transpilers and add a lang
tag indicating the language that should be preprocessed. In the case of TypeScript, install typescript
and add lang="ts"
to your script tags.
Hot Reload
Hot Module Reloading is currently not supported for Svelte 5+
This loader supports component-level HMR via the community supported svelte-hmr package. This package serves as a testbed and early access for Svelte HMR, while we figure out how to best include HMR support in the compiler itself (which is tricky to do without unfairly favoring any particular dev tooling). Feedback, suggestion, or help to move HMR forward is welcomed at svelte-hmr (for now).
Configure inside your webpack.config.js
:
const mode = process.env.NODE_ENV || 'development';
const prod = mode === 'production';
module.exports = {
...
module: {
rules: [
...
{
test: /\.(svelte|svelte\.js)$/,
use: {
loader: 'svelte-loader',
options: {
compilerOptions: {
dev: !prod,
},
emitCss: prod,
hotReload: !prod,
hotOptions: {
preserveLocalState: false,
noPreserveStateKey: '@!hmr',
noReload: false,
optimistic: false,
acceptAccessors: true,
acceptNamedExports: true,
}
}
}
}
...
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
...
]
}
You also need to add the HotModuleReplacementPlugin. There are multiple ways to achieve this.
If you're using webpack-dev-server, you can just pass it the hot
option to add the plugin automatically.
Otherwise, you can add it to your webpack config directly:
const webpack = require('webpack');
module.exports = {
...
plugins: [
new webpack.HotModuleReplacementPlugin(),
...
]
}
CSS @import in components
It is advised to inline any css @import
in component's style tag before it hits css-loader
.
This ensures equal css behavior when using HMR with emitCss: false
and production.
Install svelte-preprocess
, postcss
, postcss-import
, postcss-load-config
.
Configure svelte-preprocess
:
const sveltePreprocess = require('svelte-preprocess');
...
module.exports = {
...
module: {
rules: [
...
{
test: /\.(svelte|svelte\.js)$/,
use: {
loader: 'svelte-loader',
options: {
preprocess: sveltePreprocess({
postcss: true
})
}
}
}
...
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
...
]
}
...
Create postcss.config.js
:
module.exports = {
plugins: [
require('postcss-import')
]
}
If you are using autoprefixer for .css
, then it is better to exclude emitted css, because it was already processed with postcss
through svelte-preprocess
before emitting.
...
module: {
rules: [
...
{
test: /\.css$/,
exclude: /svelte\.\d+\.css/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
},
{
test: /\.css$/,
include: /svelte\.\d+\.css/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
},
...
]
},
...
This ensures that global css is being processed with postcss
through webpack rules, and svelte component's css is being processed with postcss
through svelte-preprocess
.
License
MIT