Svelte: Document a `How To` on using Svelte with Babel

Created on 10 Aug 2019  ·  18Comments  ·  Source: sveltejs/svelte

Is your feature request related to a problem? Please describe.

It's a common request to be able to serve a website to users with older browsers, like IE11 or iOS Safari 9.

This requires all sorts of polyfilling & transpilation happening behind the scenes.

However, there's a few gotchas in getting Babel, the de facto tool for transpiling modern code into digestable ES5, to work together with Rollup/Webpack and Svelte. Would be nice, if there was a well documented, visibly linked and thoroughly explained starting point to fork from. Or at least something to copy-paste from.

For example, this golden nugget of information was hidden in a comment inside a closed issue:

Also notice that at least for rollup-plugin-babel, you will have to explicitly configure it to handle Svelte's components as they are recognized from their original extension. So add something like extensions: ['.js', '.html', '.svelte'] when enabling Babel.

Trying to search the Svelte website for "Babel" gets exactly 0 results. Which is stupid.

Describe the solution you'd like
I'd like a clear how-to on the Svelte website with copy-paste config examples on getting Babel to run with Svelte, so people could spend their time doing actual work or being outdoors ☀️

How important is this feature to you?
It's not very important to myself anymore, as I already sacrificed a fair amount of my own time to fiddle together a working setup. However, I believe it is useful for the general public, and _very easy_ to implement.

guides

Most helpful comment

Just sharing my solution for getting Babel working for anyone struggling with this:

In rollup.config.js:

babel({
  extensions: ['.js', '.mjs', '.html', '.svelte'],
  include: ['src/**', 'node_modules/svelte/**'],
}),

In babel.config.js:

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
        corejs: 3,
      },
    ],
  ],
}

Note that I'm using babel.config.js, and NOT .babelrc. From what I understand, the files in the Svelte package need to be run through Babel as well, which is why the following not-obvious things need to be configured:

  • The .mjs extension needs to be included in Babel, as some files in the Svelte package use that extension. Note that while Babel does include .mjs files by default, we need to explicitly include it anyway since we need to provide our own custom list of extensions that includes .svelte.
  • node_modules/svelte/** needs to be specifically included in Babel along with your source files if you don't want Babel transpiling the rest of your node_modules. You can't just do a blanket ignore on node_modules like you normally would.
  • Babel MUST be configured through babel.config.js, rollup-plugin-babel, or babel-loader, as from what I understand, Babel does not support transpiling anything in node_modules if you configure it using .babelrc.

All 18 comments

In the official website, you've got instructions that will clone a template into your machine, and I don't recall any gotcha with the configuration(either webpack or rollup) other than the fact that you have to turn off HMR with webpack because it's broken.

If you would like to see my configuration, here it is(webpack):

}
// ...
    module: {
        rules: [
            { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' },
            { test: /\.svelte$/, exclude: /node_modules/, loader: 'svelte-loader', options: { css: false } },
            { test: /\.less$/, use: [ExtractCssChunks.loader, 'css-loader', 'less-loader'] }
        ]
    },
// ...

I omitted the irrelevant parts.

+1 Would love to see a correct way to include transpilation and polyfills with rollup and svelte

Just sharing my solution for getting Babel working for anyone struggling with this:

In rollup.config.js:

babel({
  extensions: ['.js', '.mjs', '.html', '.svelte'],
  include: ['src/**', 'node_modules/svelte/**'],
}),

In babel.config.js:

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
        corejs: 3,
      },
    ],
  ],
}

Note that I'm using babel.config.js, and NOT .babelrc. From what I understand, the files in the Svelte package need to be run through Babel as well, which is why the following not-obvious things need to be configured:

  • The .mjs extension needs to be included in Babel, as some files in the Svelte package use that extension. Note that while Babel does include .mjs files by default, we need to explicitly include it anyway since we need to provide our own custom list of extensions that includes .svelte.
  • node_modules/svelte/** needs to be specifically included in Babel along with your source files if you don't want Babel transpiling the rest of your node_modules. You can't just do a blanket ignore on node_modules like you normally would.
  • Babel MUST be configured through babel.config.js, rollup-plugin-babel, or babel-loader, as from what I understand, Babel does not support transpiling anything in node_modules if you configure it using .babelrc.

After setting up babel, i'm now getting this error

bundle-es2015.js?v=1571843170772:1 Uncaught ReferenceError: _classCallCheck is not defined

cant find any thing that talks about this.

edit
Looks like rollup tries to do the unresolved dependencies and the runtime regeneratory and helpers arent being compiled in..

@northkode do you have the "@babel/runtime" dependencies installed ?

I just had the same problem than you with : _classCallCheck is not defined

After installing "@babel/runtime" to my svelte project, it worked.

@damienMellet can you share a snippet ? I am struggling with it too.

I'm using babel with svelte and sapper. Here are snippets of my package.json and rollup.config.js file if it's helpful...

{
  "name": "svelte-app",
  "version": "2.0.0",
  "browserslist": [
    "defaults"
  ],
  "devDependencies": {
    "@ampproject/rollup-plugin-closure-compiler": "^0.12.1",
    "@babel/core": "^7.6.4",
    "@babel/plugin-proposal-object-rest-spread": "^7.6.2",
    "@babel/plugin-syntax-dynamic-import": "^7.2.0",
    "@babel/plugin-transform-runtime": "^7.6.2",
    "@babel/preset-env": "^7.6.3",
    "@material/typography": "^3.1.0",
    "@rollup/plugin-alias": "^2.2.0",
    "@smui/button": "^1.0.0-beta.17",
    "@smui/common": "^1.0.0-beta.15",
    "@smui/data-table": "^1.0.0-beta.17",
    "@smui/dialog": "^1.0.0-beta.17",
    "@smui/form-field": "^1.0.0-beta.17",
    "@smui/icon-button": "^1.0.0-beta.17",
    "@smui/linear-progress": "^1.0.0-beta.17",
    "@smui/paper": "^1.0.0-beta.17",
    "@smui/radio": "^1.0.0-beta.17",
    "@smui/select": "^1.0.0-beta.17",
    "@smui/textfield": "^1.0.0-beta.17",
    "autoprefixer": "^9.7.1",
    "eslint": "^6.6.0",
    "eslint-config-airbnb": "^18.0.1",
    "eslint-config-prettier": "^6.5.0",
    "eslint-plugin-import": "^2.18.2",
    "eslint-plugin-jsx-a11y": "^6.2.3",
    "eslint-plugin-prettier": "^3.1.1",
    "node-sass": "^4.13.0",
    "npm-run-all": "^4.1.5",
    "postcss": "^7.0.21",
    "prettier": "^1.18.2",
    "prettier-plugin-svelte": "^0.7.0",
    "rollup": "^1.12.0",
    "rollup-plugin-alias": "^2.2.0",
    "rollup-plugin-babel": "^4.3.3",
    "rollup-plugin-commonjs": "^10.0.0",
    "rollup-plugin-livereload": "^1.0.0",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-postcss": "^2.0.3",
    "rollup-plugin-replace": "^2.2.0",
    "rollup-plugin-svelte": "^5.0.3",
    "rollup-plugin-terser": "^5.1.2",
    "sirv-cli": "^0.4.4",
    "svelte": "^3.0.0",
    "svelte-inspect": "^0.1.0",
    "svelte-select": "^3.1.1"
  },
  "dependencies": {},
  "scripts": {...}
}
import babel from 'rollup-plugin-babel';
import svelte from 'rollup-plugin-svelte';
import alias from 'rollup-plugin-alias';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import postcss from 'rollup-plugin-postcss';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import replace from 'rollup-plugin-replace';
import pkg from './package.json';

const production = !process.env.ROLLUP_WATCH;
const name = pkg.name
  .replace(/^(@\S+\/)?(svelte-)?(\S+)/, '$3')
  .replace(/^\w/, m => m.toUpperCase())
  .replace(/-\w/g, m => m[1].toUpperCase());

const jsName = name + '.min.js';
const cssName = name + '.min.css';

export default {
  input: './src/main.js',
  output: {
    sourcemap: !production,
    format: 'iife',
    name: name,
    file: 'dist/' + jsName
  },
  plugins: [
    alias({
      resolve: ['.js', '.mjs', '.html', '.svelte']
    }),
    svelte({
      dev: !production,
      // we'll extract any component CSS out into
      // a separate file — better for performance
      css: css => {
        css.write('dist/' + cssName);
      },
      emitCss: true
    }),
    replace({
      'process.env.NODE_ENV': !production
        ? JSON.stringify('development')
        : JSON.stringify('production')
    }),
    babel({
      extensions: ['.js', '.mjs', '.html', '.svelte'],
      include: [
        'src/**',
        'src/services/**',
        'node_modules/svelte/**',
        'node_modules/svelte-select/**'
      ],
      exclude: ['node_modules/@babel/**'],
      runtimeHelpers: true
    }),
    resolve({
      browser: true,
      dedupe: importee =>
        importee === 'svelte' || importee.startsWith('svelte/')
    }),
    commonjs({
      include: ['node_modules/**']
    }),
    postcss({
      extract: true,
      minimize: production,
      use: [
        [
          'sass',
          {
            includePaths: ['./src/theme', './node_modules']
          }
        ]
      ]
    }),
    // Watch the `public` directory and refresh the
    // browser on changes when not in production
    !production &&
      livereload({
        watch: 'dist'
      }),

    // If we're building for production (npm run build
    // instead of npm run dev), minify
    // @todo look into closure.
    production && terser()
  ],
  watch: {
    clearScreen: false
  }
};

@ayZagen Here is my config package.json :

{
  "name": "svelte-app",
  "version": "1.0.0",
  "devDependencies": {
    "@babel/core": "^7.7.2",
    "@babel/plugin-syntax-dynamic-import": "^7.2.0",
    "@babel/plugin-transform-runtime": "^7.6.2",
    "@babel/preset-env": "^7.7.1",
    "@babel/runtime": "^7.7.2",
    "node-sass": "^4.13.0",
    "rollup": "^1.12.0",
    "rollup-plugin-babel": "^4.3.3",
    "rollup-plugin-commonjs": "^10.0.0",
    "rollup-plugin-livereload": "^1.0.0",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-svelte": "^5.0.3",
    "rollup-plugin-terser": "^5.1.2",
    "svelte": "^3.0.0",
    "svelte-preprocess-sass": "^0.2.0"
  },
  "dependencies": {
    "core-js": "^3.4.1",
    "sirv-cli": "^0.4.4"
  },
  "scripts": {
    "build": "rollup -c",
    "dev": "rollup -c -w",
    "start": "sirv public --single",
    "start:dev": "sirv public --single --dev"
  }
}

and rollup.config.js

import svelte from 'rollup-plugin-svelte';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import rollup_start_dev from './rollup_start_dev';
import { sass } from 'svelte-preprocess-sass';
import babel from 'rollup-plugin-babel';

const production = !process.env.ROLLUP_WATCH;

export default {
    input: 'src/main.js',
    output: {
        sourcemap: true,
        format: 'iife',
        name: 'app',
        file: 'public/bundle.js'
    },
    plugins: [
        svelte({
            // enable run-time checks when not in production
            dev: !production,
            // we'll extract any component CSS out into
            // a separate file — better for performance
            css: css => {
                css.write('public/bundle.css');
            },
            preprocess: {
        style: sass(),
      },
        }),

        // If you have external dependencies installed from
        // npm, you'll most likely need these plugins. In
        // some cases you'll need additional configuration —
        // consult the documentation for details:
        // https://github.com/rollup/rollup-plugin-commonjs
        resolve({
            browser: true,
            dedupe: importee => importee === 'svelte' || importee.startsWith('svelte/')
        }),
        commonjs(),

        // In dev mode, call `npm run start:dev` once
        // the bundle has been generated
        !production && rollup_start_dev,

        // Watch the `public` directory and refresh the
        // browser on changes when not in production
        !production && livereload('public'),

        //compile to old es5 for ie11
        babel({
            extensions: [ '.js', '.mjs', '.html', '.svelte' ],
            runtimeHelpers: true,
            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: false
                    }
                ]
            ]
        }),
        // If we're building for production (npm run build
        // instead of npm run dev), minify
        production && terser()
    ],
    watch: {
        clearScreen: false
    }
};

@caschbre @damienMellet Thanks a lot! I was using webpack so I am posting here my configs in case anyone struggles with it too. These configs are also configured to transpile typescript and svelte with babel too.

//babel-config.js
module.exports = function(api) {
    api.cache(true);

    const presets = [
        ["@babel/preset-env", {
            useBuiltIns: "entry", // or "entry"
            corejs: 2,
        }],
        "@babel/typescript",
    ];
    const plugins = [
        "@babel/proposal-class-properties",
        "@babel/proposal-object-rest-spread",
        "@babel/plugin-proposal-optional-chaining",
    ];

    return {
        comments: true,
        ignore: [/[\/\\]core-js/, /@babel[\/\\]runtime/],
        presets,
        plugins,
    };
};
//webpack.config.js
const path = require("path");
const webpack = require("webpack");

const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const sveltePreprocess = require("svelte-preprocess");
const TerserPlugin = require("terser-webpack-plugin");

const mode = process.env.NODE_ENV || "development";

module.exports = {
    mode,
    entry: "./src/index.ts",
    output: {
        library: "MyLibrary",
        libraryTarget: "umd",
        libraryExport: "default",
        path: path.resolve(__dirname, "dist"),
        filename: "bundle.min.js",
        umdNamedDefine: true
    },
    resolve: {
       // here is the deal breaker.
       //mjs extension must be before js to use correct svelte lib files.
        extensions: [".mjs", ".ts", ".tsx", ".js", ".json", ".svelte"],
    },
    optimization: {
        minimize: true,
        minimizer: [
            new TerserPlugin({
                terserOptions: {
                    output: {
                        comments: false, // in case of magic comments
                    },
                },
                extractComments: false,
            }),
        ],
    },
    module: {
        rules: [
            {
                test: /\.(js|ts|mjs|svelte)$/,
                use: {
                    loader: 'babel-loader'
                },
            },
            {
                test: /\.svelte$/,
                exclude: /node_modules/,
                use: {
                    loader: "svelte-loader",
                    options: {
                        emitCss: false, // I would like to keep my css in js so If you want to have separate files change this to be true
                        hotReload: true,
                        preprocess: {
                            style: sveltePreprocess({}).style
                        },
                    },
                },
            },
            {
                test: /\.(sass|scss)$/,
                use: ["style-loader", "css-loader", "postcss-loader", "sass-loader"],
            },
        ],
    },
    plugins: [
        new CleanWebpackPlugin()
    ]
};

// I haven't tried building declaration files 
//tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "declaration": true,
    "outDir": "dist/types",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true
  }
}

Just sharing my solution for getting Babel working for anyone struggling with this:

In rollup.config.js:

babel({
  extensions: ['.js', '.mjs', '.html', '.svelte'],
  include: ['src/**', 'node_modules/svelte/**'],
}),

In babel.config.js:

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
        corejs: 3,
      },
    ],
  ],
}

Note that I'm using babel.config.js, and NOT .babelrc. From what I understand, the files in the Svelte package need to be run through Babel as well, which is why the following not-obvious things need to be configured:

  • The .mjs extension needs to be included in Babel, as some files in the Svelte package use that extension. Note that while Babel does include .mjs files by default, we need to explicitly include it anyway since we need to provide our own custom list of extensions that includes .svelte.
  • node_modules/svelte/** needs to be specifically included in Babel along with your source files if you don't want Babel transpiling the rest of your node_modules. You can't just do a blanket ignore on node_modules like you normally would.
  • Babel MUST be configured through babel.config.js, rollup-plugin-babel, or babel-loader, as from what I understand, Babel does not support transpiling anything in node_modules if you configure it using .babelrc.

This doesn't work for me, would you mind sharing some more example code?

When I follow what you've done, the build still builds ES6 and doesn't work in IE11.

Just sharing my solution for getting Babel working for anyone struggling with this:
In rollup.config.js:

babel({
  extensions: ['.js', '.mjs', '.html', '.svelte'],
  include: ['src/**', 'node_modules/svelte/**'],
}),

In babel.config.js:

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
        corejs: 3,
      },
    ],
  ],
}

Note that I'm using babel.config.js, and NOT .babelrc. From what I understand, the files in the Svelte package need to be run through Babel as well, which is why the following not-obvious things need to be configured:

  • The .mjs extension needs to be included in Babel, as some files in the Svelte package use that extension. Note that while Babel does include .mjs files by default, we need to explicitly include it anyway since we need to provide our own custom list of extensions that includes .svelte.
  • node_modules/svelte/** needs to be specifically included in Babel along with your source files if you don't want Babel transpiling the rest of your node_modules. You can't just do a blanket ignore on node_modules like you normally would.
  • Babel MUST be configured through babel.config.js, rollup-plugin-babel, or babel-loader, as from what I understand, Babel does not support transpiling anything in node_modules if you configure it using .babelrc.

This doesn't work for me, would you mind sharing some more example code?

When I follow what you've done, the build still builds ES6 and doesn't work in IE11.

Sure. Below are my package.json, babel.config.js, and rollup.config.js. All of my other files are just the defaults from the sveltejs/template repo.

package.json

{
  "name": "svelte-app",
  "version": "1.0.0",
  "scripts": {
    "build": "rollup -c",
    "dev": "rollup -c -w",
    "start": "sirv public"
  },
  "devDependencies": {
    "@babel/core": "^7.7.4",
    "@babel/preset-env": "^7.7.4",
    "rollup": "^1.12.0",
    "rollup-plugin-babel": "^4.3.3",
    "rollup-plugin-commonjs": "^10.0.0",
    "rollup-plugin-livereload": "^1.0.0",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-svelte": "^5.0.3",
    "rollup-plugin-terser": "^5.1.2",
    "svelte": "^3.0.0"
  },
  "dependencies": {
    "core-js": "^3.4.5",
    "sirv-cli": "^0.4.4"
  }
}

babel.config.js

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage',
        corejs: 3,
      },
    ],
  ],
}

rollup.config.js

import svelte from 'rollup-plugin-svelte';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import babel from 'rollup-plugin-babel';

const production = !process.env.ROLLUP_WATCH;

export default {
    input: 'src/main.js',
    output: {
        sourcemap: true,
        format: 'iife',
        name: 'app',
        file: 'public/build/bundle.js'
    },
    plugins: [
        svelte({
            // enable run-time checks when not in production
            dev: !production,
            // we'll extract any component CSS out into
            // a separate file — better for performance
            css: css => {
                css.write('public/build/bundle.css');
            }
        }),

        // If you have external dependencies installed from
        // npm, you'll most likely need these plugins. In
        // some cases you'll need additional configuration —
        // consult the documentation for details:
        // https://github.com/rollup/rollup-plugin-commonjs
        resolve({
            browser: true,
            dedupe: importee => importee === 'svelte' || importee.startsWith('svelte/')
        }),
        commonjs(),

        babel({
            extensions: ['.js', '.mjs', '.html', '.svelte'],
            include: ['src/**', 'node_modules/svelte/**'],
        }),

        // In dev mode, call `npm run start` once
        // the bundle has been generated
        !production && serve(),

        // Watch the `public` directory and refresh the
        // browser on changes when not in production
        !production && livereload('public'),

        // If we're building for production (npm run build
        // instead of npm run dev), minify
        production && terser()
    ],
    watch: {
        clearScreen: false
    }
};

function serve() {
    let started = false;

    return {
        writeBundle() {
            if (!started) {
                started = true;

                require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
                    stdio: ['ignore', 'inherit', 'inherit'],
                    shell: true
                });
            }
        }
    };
}

@christopherbecker did you find a solution? i have the same issue, still getting es6 code using @rmtracey 's configuration...

got it working now using this guide: https://blog.az.sg/posts/svelte-and-ie11/

I am having issues with getting this config to accept the null coalescing operator in Svelte component code. Any ideas how I can use this syntax?

@ayZagen I've tested your setup and @babel/plugin-proposal-optional-chaining fails in Svelte components. Is it working for you?

@Klustre I have used optional chaining in ts files but not in svelte components. So as you asked, I tried and it fails on svelte components. To make it work you need to update svelte-loader's preprocessor like following:

preprocess: sveltePreprocess({
    babel: {
        presets: [
            ["@babel/preset-env", {
                loose: true,
                modules: false,
                targets: {
                    esmodules: true,
                }
            }],
            "@babel/typescript",
        ] ,
        plugins: [
            "@babel/proposal-class-properties",
            "@babel/proposal-object-rest-spread",
            "@babel/plugin-proposal-optional-chaining",
        ]
    }
})

Hope it helps you

Thanks a lot @ayZagen 👍

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Rich-Harris picture Rich-Harris  ·  3Comments

rob-balfre picture rob-balfre  ·  3Comments

Rich-Harris picture Rich-Harris  ·  3Comments

mmjmanders picture mmjmanders  ·  3Comments

davidcallanan picture davidcallanan  ·  3Comments