Using Svelve for Plone add-ons

joaomarcusc

João

Posted on June 3, 2019

Using Svelve for Plone add-ons

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 with a React-based frontend. 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, 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:

<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.

<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
  • A Plone installation that includes a develop.cfg configuration
  • Knowledge on how to develop add-ons using Mrbob

Create a sample add-on product

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

mrbob -O svelte.sample collective.mrbob:addon

Then, add it to the develop.cfg file:

...
[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:

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:

  ...
  <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:

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 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:

npx degit sveltejs/template component
cd component
npm install

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

<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

import App from './App.svelte';

export default {
  App
}

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

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 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.

💖 💪 🙅 🚩
joaomarcusc
João

Posted on June 3, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related