Using Svelve for Plone add-ons
Published: 2019-06-03 19:46:30

> Zope/Plone bashing follows. If you like Zope/Plone, that's okay, just
> skip the first paragraph and I promise you'll read no bashing!

I don't like Plone. Okay, actually, I don't like Zope/ZODB. It's overly complex, *VERY* inneficient, difficult to maintain, quite fragile. Plone itself is a miracle. The people behind it should be canonized as saints and revered as geniuses. They managed to do something actually usable with the mess that is Zope. Why I'm bashing Zope if I'm going to talk about Plone? Well, I have a job. My job is not to only work with technologies that I like. My job is to work with technologies that I have to. 

> End of Zope/Plone bashing, carry on!

# The problem

I needed to develop an add-on which included a view that calls an internal server to search for specific results and render them differently depending on the context. There's not much to it, except the UI, which is not too complex, but complex enough for me to not like using vanilla JS, jQuery or simple ZPT templates. I first tried React, because it Plone 6 [will ship](https://plone.org/roadmap/2019-plone-roadmap) with a [React-based frontend](https://github.com/plone/volto). I managed to make it work with Plone 5, for then I had a problem: I still needed to support Plone 4, and then I found my solutions too bug-prone. 

# A solution: Svelte

I thought to myself: "What I need is something that doesn't require any external dependency at all". Then I remembered reading something about [Svelte](https://svelte.dev/), because, well, it promised to be a "disappearing" library that outputs just plain Javascript that I would just need to include in my add-on. Does it work? Turns out it does! Here is a sample:

```html
<script>
	export let name;
</script>

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

<h1>Hello {name}!</h1>
```

When you build it, the output is a Javascript file that does *not* need Svelte itself: it's a self-contained file that can easily be used inside a page with a simple JS entry. The CSS is split into a separate file as well.

```html
<link rel="stylesheet" href="my-built-app.css" />
<script type="text/javascript" src="my-built-app.js"></script>
<div id="appContainer"></div>
<script>
    // target is the element where the component will be rendered
    // props are the properties that the components need
    const app = new App({
        target: document.getElementById("appContainer"),
        props: {
            name: "Elvis Presley"
        }
    });
</script>

```

# Hands-on: Using Svelte inside an add-on 


For this hands-on, you will need:

* The latest Node.js version. I highly recommend using [NVM](https://github.com/nvm-sh/nvm)
* A Plone installation that includes a `develop.cfg` configuration
* Knowledge on how to [develop add-ons using Mrbob](https://docs.plone.org/develop/addons/bobtemplates.plone/README.html)

## Create a sample add-on product

First, create a sample add-on product using mrbob inside the `src` directory of the plone instance:

```shell
mrbob -O svelte.sample collective.mrbob:addon
```

Then, add it to the `develop.cfg` file:

```buildout
...
[sources]
svelte.sample = fs svelte.sample
...

eggs +=
    ...
    svelte.sample   

```


## Add the initial structure

For now, let's just add a file inside `src/svelte/sample/browser/static/sample.js` that will output something to the console so we can tell everything's ok for now. We will replace it with the component later:

```javascript
console.log("svelte.sample Javascript included");
```

## Add a View

The Svelte sample will be run inside a simple view, so, let's first add it to `configure.zcml`:

```xml
  ...
  <browser:page
      name="svelte-sample"
      for="*"
      permission="zope2.View"
      class=".views.SampleView"
      />
  ...
```

The view is inside the views module, so, let's create it inside `src/svelte/sample/views.py`:


```python
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile

class SampleView(BrowserView):
    index = ViewPageTemplateFile('templates/sample.pt')

    def render(self):
        return self.index()

    def __call__(self):
        return self.render()
```

We now need the template, so, create the file `src/svelte/sample/templates/sample.pt`:

```html
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
     metal:use-macro="context/main_template/macros/master">

<metal:block fill-slot="javascript_head_slot">
  <script type="text/javascript" src="++plone++svelte.sample/sample.js" />
</metal:block>

<metal:block fill-slot="content-core">
  Nothing here yet
</metal:block>

</html>
```

Now we have a working add-on. If you run the Plone instance, install it and open the `@@svelte-sample` view, it will show a page containing "Nothing here yet", and the console will show 'selvte.sample Javascript included'. The add-on itself won't depend on anything else, but we obviously need to create the Svelte components. Thus, we need to create a Svelte project.

## Create the Svelte project

Creating the project itself is easy. We will be creating the JS project inside our add-on source for this hands-on. Inside the project's root directory, run the following commands:

```shell
npx degit sveltejs/template component
cd component
npm install
```

Now, modify the file `component/src/App.svelte`:

```html
<script>
	export let name;

	$: uppercaseName = (name || "").toUpperCase();
</script>

<h1>Hello {uppercaseName}!</h1>

```

Before we build the project, we need to tell the builder to just export your components we built, by modifying `component/src/main.js`

```javascript
import App from './App.svelte';

export default {
  App
}
```

Then, let's build the project and copy the resulting bundle.js file:

```shell
npm run build
cp public/bundle.js ../src/svelte/sample/browser/static/sample.js
```

## Add the Svelte component to the view

That's it, now the App component is going to be available when we include our sample.js file inside the Plone's view page. Change the `src/svelte/component/templates/sample.pt` file to:

```html
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
     metal:use-macro="context/main_template/macros/master">

<metal:block fill-slot="javascript_head_slot">
  <script type="text/javascript" src="++plone++svelte.sample/sample.js" />
</metal:block>

<metal:block fill-slot="content-core">
  <div id="sampleContainer"></div>
  <script type="text/javascript">
    const sample = new app.App({
      target: document.getElementById("sampleContainer"),
      props: {
        name: "Person"
      }
    });
  </script>
</metal:block>

</html>
```

Now, if you open the `@@svelte-sample` view, it should show a big `Hello PERSON!`. No external includes needed.

# Bottom Line

I'm not saying you should just use Svelte for every single add-on. If you can target Plone5+, you could probably use React. If you, however, plan to support Plone 4 and/or don't want to rely too much on specific dependencies, it may be a good idea do pick Svelte. Just remember that what Svelte does is *not* to embed other dependencies inside the resulting JS build. So, my advice is to pick Svelte if you can live without other dependencies and pick React for anything else.