Back

How to add svelte on Elixir Phoenix Framework

2020-07-17

Adding the Svelte and Svelte-Loader

To do that, run this on the root dir of your phoenix project: bash cd assets && npm install svelte svelte-loader --save

Configuring the webpack

Based on: https://github.com/sveltejs/template-webpack

First add this on asserts/webpack.config.js:

resolve: {
    alias: {
        svelte: path.resolve('node_modules', 'svelte')
    },
    extensions: ['.mjs', '.js', '.svelte'],
    mainFields: ['svelte', 'browser', 'module', 'main']
}

Then add:

{
    test: /\.svelte$/,
    use: {
        loader: 'svelte-loader',
        options: {
            emitCss: true,
            hotReload: true
        }
    }
}

For the config explanations see: https://github.com/sveltejs/svelte-loader

Adding the function on Phoenix

On your APP_web.ex in the function view_helpers add the function:

@doc """
Will add a component from the asserts/js/svelte, the component must have the same `file_name`
"""
@spec svelte(bitstring(), map()) :: Phoenix.HTML.safe()
def svelte(file_name, props \\ %{}) do
  {:ok, props} = Jason.encode(props)

  tag =
    Phoenix.HTML.Tag.tag(:div,
      data: [props: props],
      class: "__svelte__-#{file_name}"
    )
    |> Phoenix.HTML.safe_to_string()

  Phoenix.HTML.raw(tag <> "</div>")
end

Note: You can define this function in another file and import if you want a more clean solution

Mounting the svelte component

Finally we need to get the svelte file and mount on the element defined by the function above, for this we create a file svelte.js on assets/js/

// This code will look in the assets/js/svelte/ and in the sub-directorys for
// .svelte files, the result of this files will be mounted on the element
// "svelte-file_name" who have props from the phoenix render(name, props)

// directory, useSubdirectories, regex
const context = require.context("./svelte", true, /\.svelte$/);

window.onload = function () {
  context.keys().forEach(file_path => {
    // name that will be requeride by render(name, props)
    const file_name = file_path.replace(/\.\/|\.svelte$/g, "");

    // COMPONENT
    const component_name = `svelte-${file_name}`;
    const component_container = document.getElementById(component_name);
    if (!component_container) {
      return;
    }

    // PROPS
    const { props } = component_container.dataset;
    let my_props = {};
    if (props) {
      my_props = JSON.parse(props);
    }

    // MOUNT ON
    const App = require(`./svelte/${file_name}`);

    new App.default({
      target: component_container,
      props: my_props
    });
  });
};

Testing

Just create a component and add a elixir <\%= svelte "YOUR_COMPONENT_FILE_NAME", %{some_var: "some value"} %> on a template :)

Note 1: Yes, the YOUR_COMPONENT_FILE_NAME is the name of your file, please change it ;)

Note 2: The props will be used on your export variables on the svelte component


Back