DEV Community

Joshua Nussbaum
Joshua Nussbaum

Posted on • Edited on

Setup Phoenix with Svelte+Rollup

By default, Phoenix uses Webpack for asset bundling. This step-by-step guide shows how to use rollup instead and configure rollup to use Svelte.

Why Rollup?

Rollup is especially great at tree-shaking, which results in the smallest bundle size. It originates from Rich Harris, who is also the creator of Svelte. This makes it an ideal choice for Svelte projects.

Personally, I find it easier to understand than Webpack (but that's just me).

Phoenix setup

Start by creating a new project without Webpack:

mix phx.new my_app --no-webpack
Enter fullscreen mode Exit fullscreen mode

Hop into the project and let's setup git:

cd my_app
git init
git add .
git commit --message "Initial commit 🐣"
Enter fullscreen mode Exit fullscreen mode

Assets folder setup

Since we told phx.new to not use webpack, we need to create the assets directories ourselves:

mkdir -p assets/js assets/css assets/static/images
Enter fullscreen mode Exit fullscreen mode

Create a place to put global css:

touch assets/css/global.css
Enter fullscreen mode Exit fullscreen mode

Add an assets/package.json to define all the frontend scripts and dependencies:

{
  "name": "assets",
  "version": "1.0.0",
  "scripts": {
    "deploy": "rollup --config",
    "watch": "rollup --config --watch"
  },
  "dependencies": {
    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html"
  },
  "devDependencies": {
    "@rollup/plugin-commonjs": "^11.0.0",
    "@rollup/plugin-node-resolve": "^7.0.0",
    "rollup": "^1.20.0",
    "rollup-plugin-postcss": "^2.0.5",
    "rollup-plugin-svelte": "^5.0.3",
    "rollup-plugin-terser": "^5.1.2",
    "svelte": "^3.0.0",
    "svelte-preprocess": "^3.3.1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Install those packages:

(cd assets && yarn)
Enter fullscreen mode Exit fullscreen mode

Make sure to exclude all node_modules from version control:

echo /assets/node_modules >> .gitignore
Enter fullscreen mode Exit fullscreen mode

Rollup config

Add a basic configuration in assets/rollup.config.js:

import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import autoPreprocess from 'svelte-preprocess';
import postcss from 'rollup-plugin-postcss';
import { terser } from 'rollup-plugin-terser';

// it's production mode if MIX_ENV is "prod"
const production = process.env.MIX_ENV == "prod";

export default {
  // main entry point
  input: 'js/main.js',

  // define output path & format and request sourcemaps
  output: {
    sourcemap: true,
    format: 'iife',
    name: 'app',
    file: '../priv/static/js/app.js'
  },

  // define all the plugins we'd like to use
  plugins: [
    // the postcss plugin is used to preprocess css
    // for more info, see: https://www.npmjs.com/package/rollup-plugin-postcss
    postcss(),

    // the svelte plugin converts .svelte files to .js equivalent
    svelte({
      // the preprocessor plugin allows you to use <style type="scss"> or <script lang="typescript"> inside .svelte files
      // for more info, see: https://www.npmjs.com/package/svelte-preprocess
      preprocess: autoPreprocess(),

      // enable run-time checks when not in production
      dev: !production,

      // take css output and write it to priv/static
      css: css => {
        css.write('../priv/static/css/app.css');
      }
    }),

    // the resolve plugin resolves modules located in node_modules folder
    resolve({
      // resolve modules that are designed to run in browser
      browser: true,

      // a dependency in node_modules may have svelte inside it's node_modules folder
      // dedupe option prevents bundling those duplicates
      dedupe: ['svelte']
    }),

    // use commonjs import convention
    commonjs(),

    // for production builds, use minification
    production && terser()
  ],

  // don't clear terminal screen after each re-compilation
  watch: {
    clearScreen: false
  }
}
Enter fullscreen mode Exit fullscreen mode

In dev mode, Phoenix can run yarn watch for us. Simply add a watcher in config/dev.exs:

--- watchers: []
+++ watchers: [yarn: ["watch", cd: Path.expand("../assets", __DIR__)]]
Enter fullscreen mode Exit fullscreen mode

Using Svelte

Let's create our first svelte file assets/js/App.svelte:

<script>
  export let name
</script>

<style>
  h1 { color: teal; }
</style>

<h1>Hello {name}!</h1>
Enter fullscreen mode Exit fullscreen mode

We need something that will mount this to the DOM, so create an entry point .js file assets/js/main.js:

// import phoenix html helpers (optional)
import 'phoenix_html'

// any css we import will be bundled in app.css
import '../css/global.css'

// import our component
import App from './App.svelte'

// instantiate the component
new App({
  // mount it to `document.body`
  target: document.body,

  // pass some props (optional)
  props: {
    name: 'world'
  }
})
Enter fullscreen mode Exit fullscreen mode

Start the server

mix phx.server
Enter fullscreen mode Exit fullscreen mode

Et Voila, you should see "Hello World" ✨

Example Repo

You can find a fully working repo here:

https://github.com/joshnuss/phoenix_svelte_rollup_example

Also, I'm working on a video course for svelte: https://svelte.video

Top comments (6)

Collapse
 
koder profile image
Ben Woodward

This was helpful thanks. I found that it's not necessary to create a 'watch' script in package.json. You can just run rollup directly:

  watchers: [
    rollup: [
      "--config",
      "--watch",
      cd: Path.expand("../assets", __DIR__)
    ]
Enter fullscreen mode Exit fullscreen mode

Just published an Svelte SSR plugin for Phoenix that might be helpful for any readers of this post: github.com/benwoodward/elixir_svel...

Collapse
 
joshnuss profile image
Joshua Nussbaum

Very cool package! thanks for sharing.

Collapse
 
akaibukai profile image
akaibukai

Hello,
Thanks for the blog post.
I was also trying to replace webpack with rollup.
In your project, did you managed to have the styles in the global.css file correctly bundled?
I follow those steps, and I didn't managed to got the styles from the global.css applied on the welcome page for example.
It seems that this replacement is lacking regular CSS bundling (the one that are by default loaded with css-loader in webpack)
I'm interesting to know what should be the correct configuration in rollup..
If you have any idea, I'll be happy to look into it..

Collapse
 
joshnuss profile image
Joshua Nussbaum

Hi @akaibukai ,

It should work for CSS too.
The global.css is imported inside main.js, inside rollup.config.js the css gets written to app.css.

Are you importing app.css in your layout?
Take a look at the example project: github.com/joshnuss/phoenix_svelte...
Let me know if it's still now working for you.

Collapse
 
akaibukai profile image
akaibukai

Hi OP and thank you for such a quick reply!
Indeed, I tried your repo from scratch and it worked then I redo from scratch on my side, and the problem was that I was keeping an @import "./phoenix.css statement at the top of my global.css file.

If I remove that statement and for example either import it from the JS side (like we already did on main.js like so import "../css/global.css" or simply by copy/pasting its content.

Do you know if it's possible with Rollup to @import CSS files within CSS files?
I'll probably need this anyway because of other library having those kind of @import?

Again thank you for taking the time to answer..

Thread Thread
 
joshnuss profile image
Joshua Nussbaum • Edited

Happy to help :)

Try importing phoenix.css inside main.js:

import '../css/phoenix.css'
import '../css/global.css'

Alternatively, postcss-import might do it, though I haven't tried it myself.