How I built

synth.spacet.me

Using Polymer, Gulp, Jade, Sass and ES6

I competed in this year’s Static Showdown. This year I created a web-based synthesizer.

This year’s grand sponsor is the Polymer project, with a special prize for Best Use of Web Components, so this event is the best time to experiment with Polymer.

Web Components

With Polymer and Web Components approaches web development with a radically different approach.

Usually, we’d write our HTML markup, use CSS to style it, and write JavaScript to make it interactive. Chances are these three things will live at completely separate places. Your web app may look like this:

  • index.html (the markup)
  • javascripts/ (containing all your JavaScripts)
  • stylesheets/ (containing all your app’s stylesheets)

Using a third-party component on your web app would require adding a <link> tag for stylesheets, and a <script> tag for your JavaScript.


With Polymer, your web application will be totally component based. Instead of putting parts of your components in different places, you now put all of them in a single “polymer-element” tag.

Web Components has some magic to prevent CSS or IDs from interfering with each other, so you’re free to use any ID inside your component.

Your component becomes a new tag. To use it, import the component using a <link> tag and simply use the new tag.


Compiled Languages

But nowadays I don’t write plain HTML, JavaScript and CSS anymore. Most of the code I write are preprocessed, compiled, and postprocessed.

For JavaScript, you’d probably have heard of CoffeeScript, or other languages that compile to JavaScript, or an ES6 transpiler. For CSS, it’s usually Sass, LESS, or Stylus. For HTML, there are Haml, Slim, and Jade.

For my synthesizer app, I used:

  • Jade for HTML
  • Sass for CSS, and
  • 6to5 to transpile ES6 to ES5 JavaScript.

Post-processing steps may include vendor-prefixing your CSS, inlining, and minification.

For traditional web applications, this is not a problem since your markup, script, and stylesheet are all in different files.

With Polymer, however, markups, stylesheets, and scripts are combined into a single tag, so this is not as straightforward.


Jade Filters are Awesome

Jade comes with pluggable filters. It lets you use other languages in Jade markup. From its example:

6to5 Jade filter already exists. For Sass, implementing a filter is trivial. I also post-processed the resulting CSS using pleeease:

Now I can write my component like this:

…and it will be compiled into this:

Jade also allows including other files, and you can combine that with filters. That means you can put stylesheets and scripts in a different file:


Building using Gulp

The next problem is how to build these .jade files into HTML files. Gulp is a simple stream-based build system, and a Jade plugin for Gulp is available.

Here’s my gulpfile.js which takes all .jade file, pipe it through gulp-jade, and pipe it into dist/ directory. Simple, quick and dirty:

In the last three lines I also defined a “watch” task that watches jade, js, and css files and run the “compile” tasks whenever a file changes.

To compile and recompile when file changes, I used this command:

while true; do gulp compile watch; done

I had to put it in a while loop, because a compilation error crashes the Gulp process, so I had to re-spawn it to keep it watching my files.


Vulcanization

To make the web application load even faster, I also used Vulcanize to combine all components into a single HTML file.

I only want to do this when I deploy to the Divshot, not on development environment. So I set up Codeship to vulcanize the app before deploying:


That’s it. I always loved it when different tools and libraries just work together in a beautiful manner, because many times, tools don’t just work together.


Postmortem

Because my timeframe was limited, I made a lot of design decisions without thinking.

I ended up with a badly-designed mess of code. The UI and audio processing code for a single unit ended up in different places, totally breaking Polymer’s concept of “components,” making it virtually non-extensible.

There were also a lot of naming inconsistencies and uncontrolled access to global state. The two-way data bindings in the code were very messy. A lot of logic goes directly into the main ‘synth-app’ element.

This is a maintenance nightmare.

When using Polymer, personally I think you have to be very careful on how to do data binding, because it’s so easy to create such mess. But for a project that needs to be finished fast, Polymer does’t get in your way.

In fact, Polymer has a lot of off-the-shelf components that makes your life easier. For instance, the application’s global state is stored inside a single Object:

I wanted the synthesizer’s state to be persisted so that the synth’s configuration won’t be lost when you refresh the page. I hooked the synth’s state to the localStorage using a single tag:

core-localstorage#stateStorage(name='synthState' value='{{model}}')

And now they are synchronized. Very convenient!

But there’s one more problem: because of uncontrolled access, the UI mutated properties deep inside the model directly! The app could not detect a change such deeply-nested object and won’t save the synth’s state.

To fix that in a quickest possible way, I ended up putting this wherever the synth’s state is altered.

this.fire('modelchanged')

There are 10 occurrences of the above code, and if I forget to call it somewhere, then the synth will not adjust the processing chain and will not save its state.

This is very error-prone.

React promotes the Flux architecture that encourages one-way data flow. But it makes your code more verbose with all these Dispatcher, Store and change listener stuff, on the other hand, it makes code less error-prone. However, I might not finish the project on time if I strictly use the React+Flux architecture, as sometimes architecture can often get in my way.

But with Polymer, you are your own boss. Just be reminded about the technical debts you may incur.

Show your support

Clapping shows how much you appreciated Thai Pangsakulyanont’s story.