Getting tailwind css to work with Roots Sage 9 theme

I’m really enjoying how easy makes Sage WordPress Theme development. It’s very different in the beginning, but it soon feels a lot more like working in Django instead of WordPress.

At the same time, I’ve also been trying to use tailwind for this project. To make it work in production, you need to configure a few more settings for purgecss that official instructions don’t cover. The trick is that you need to define a TailwindExtractor that doesn’t strip out md:underline, hover:underline and similar color prefixed CSS classes.

Notice that I also exclude a few of external packages, so that purgecss doesn’t strip their CSS rules.

// webpack.config.optimize.js

'use strict'; // eslint-disable-line

const { default: ImageminPlugin } = require('imagemin-webpack-plugin');
const imageminMozjpeg = require('imagemin-mozjpeg');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const glob = require('glob-all');
const PurgecssPlugin = require('purgecss-webpack-plugin');

const config = require('./config');

class TailwindExtractor {
  static extract(content) {
    return content.match(/[A-Za-z0-9-_:\/]+/g) || [];

module.exports = {
  plugins: [
    new ImageminPlugin({
      optipng: { optimizationLevel: 7 },
      gifsicle: { optimizationLevel: 3 },
      pngquant: { quality: '65-90', speed: 4 },
      svgo: {
        plugins: [
          { removeUnknownsAndDefaults: false },
          { cleanupIDs: false },
          { removeViewBox: false },
      plugins: [imageminMozjpeg({ quality: 75 })],
      disable: (config.enabled.watcher),
    new UglifyJsPlugin({
      uglifyOptions: {
        ecma: 5,
        compress: {
          warnings: true,
          drop_console: true,
    new PurgecssPlugin({
      paths: glob.sync([
      extractors: [
          extractor: TailwindExtractor,
          extensions: ["html", "js", "php"],
      whitelist: [

Using PurgeCSS with Ember.js

After watching talks about Functional CSS at Ember Map, I started looking into starting to usetailwind for my future projects. The way tailwind works is that it generates a lot of CSS classes that you then use purgecss to remove. So I decided to try it on some of my existing Ember.js projects.

I ran it on Open Education Week and Val 202 web site. Both are built on top of Zurb Foundation. Here are results:

Open Education Week:
Before: 84.3 KB (14.91 KB gzipped)
After: 31.05 KB (7.04 KB gzipped)
A 52% reduction in gzipped size!

Val 202:
Before: 156.48 KB (24.5 KB gzipped)
After: 107.68 KB (18.45 KB gzipped)
A 24% reduction in gzipped size!

Not a bad improvement, since we get it almost for free, just by including it in the build pipeline. The only downsize is probably a few seconds longer production build time.

Using it in your Ember.js project

First install dependencies:

ember install ember-cli-postcss
yarn add --dev @fullhuman/postcss-purgecss

Then add it to your ember-cli-build.js

const EmberApp = require('ember-cli/lib/broccoli/ember-app');
const purgecss = require('@fullhuman/postcss-purgecss');

module.exports = function (defaults) {
  const app = new EmberApp(defaults, {
    postcssOptions: {
      filter: {
        enabled: true,
        plugins: [
            module: purgecss,
            options: {
              content: ['./app/**/*.hbs', './app/**/.js'],
  return app.toTree();

Finally, open your styles/app.scss or styles/app.css and modify it so purgecss doesn’t remove any of your custom CSS. 

// import framework like Foundation or Bootstrap here

/*! purgecss start ignore */

// your css and ember-addon @imports go here

/*! purgecss end ignore */

That’s all. If this isn’t enough, you can also set additional whitelistPatterns and whitelistPatternsChildren to keep additional CSS rules in your final build.

Thanks goes to @samselikoff for pointing me in the right direction to make this work.

Logging DRF Serializer errors into Sentry

For one of my Ember.js apps, I have a bit of a too complex Form flow. While I’m working on simplifying frontend, I wanted a way to easily log validation errors that users received. In addition to debugging, this helps me improve the labels and instructions on the form itself.

Backend in this case if Django Request Framework driven with JSON API. The idea is to log all validation errors and redirect them to Sentry in Debug mode:

First we declare a custom DRF Exception handler the uses JSON API exception handler and copies the data to sentry:

from rest_framework_json_api.exceptions import exception_handler
from raven.contrib.django.raven_compat.models import client

from app.serializers import SubmissionSerializer

def custom_drf_exception_handler(exc, context):
    response = exception_handler(exc, context)

    if isinstance(context.get('view').get_serializer_class(), SubmissionSerializer):
        client.captureMessage('Form Submission Validation Error', level='debug', extra=exc.get_full_details())

    return response

And we also have to configure DRF to re-route errors through it:

	'EXCEPTION_HANDLER': 'app.exceptions.custom_drf_exception_handler',

And that’s it. The end result is that when serialization error is triggered, we now get a nice error log in Sentry:

Screenshot of validation errors from Sentry

A tip on connecting djangorestframework{-jsonapi, -jwt} with ember-simple-auth

The main issue that I’ve encountered was the fact that djangorestframework-jsonapi wanted to get requests in different format that ember-simple-auth sent them. This was great for the rest of my Ember app, but authentication didn’t work. The solution is to include both rest_framework.parsers.JSONParser and rest_framework_json_api.parsers.JSONParser in the mix. The final result in that worked for me was:

    'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata'

WebCamp Ljubljana 2017: Lessons learned

This years WebCamp was massive. 500+ attendees, 38+ speakers, 36 sessions, 4 tracks, Internet of Things corner, VR room, sponsors booths, alternate reality games and kids corner. Core team of 10 and additional 30 people volunteered on the day of the event. Event itself was in planning since June 2016 with most of the work done in the last 6 months.

What follows are some of my general notes about running an event of such size and complexity. First “BarCamp” event we did in Slovenia was in 2009 and Eventbrite tells me that this was 8th WebCamp event.

On Size

Last year, we stopped at 300 attendees. This means that this year, we almost doubled in size. The amazing thing with having a faculty as a hosting institution is that it’s designed for such amount of people. The venue itself can probably take another 200 attendees.

Having said that, the feeling of event was different for me. Maybe we broke through the magical number of attendees, where event stops being an extended family and turns into a bigger conference.

From organisational perspective, there is not much difference between organising event for 300 people, vs. 500. It gets a bit riskier with a bigger budget and there’s a more communication required inside each sub-team. With current setup, it should be possible to scale this type of festival to 1000+ attendees.

But since we’re not an extended family anymore, communication with attendees matters a lot more. One of the things that we learned is that people don’t read newsletters/emails. They also ignore almost all of social media. So the things that used to be given, now need to be spelled very clearly on the page of event itself (we’ll probably learn in 2018 edition, that people also don’t read websites).

On having a Modern Event

I’m very spoiled by tech events I attend. The organisers always make sure that different voices from community are represented, are giving consideration to gender identity and make sure that everyone has a chance to fully participate in a healthy way. I always fail to appreciate what a massive undertaking that is.

It’s not hard to do something on a large scale. Lets say, order two vans of pizza boxes and sugar pastries. It becomes a problem when you decided that pizza is not enough and that you also want healthy options. So now instead of having to talk to two vendors, you’re talking to four. This has ripples through out the system – planning overhead, time for person doing logistics, two more invoices to pay, etc. This means that anything that we added to the system, compared to last year, increased complexity of whole operation.

But it turns out, that it matters to people. One example was kids corner that we added relatively late. We got 30 parents that indicated interest of bringing their kids and about 10 actually showed up with them in the morning. Kids had a blast and by the feedback from parents, grownups also had a good time.

Besides increasing complexity, it also has consequence for timelines. When I still had hopes that people will show up and do a “BarCamp”, we basically called people a week before the event and let them know that they should come and that they’ll be speaking. It worked mostly ok.

But above example with kids has effect on timelines.  Since we need to know how many kids to expect and what are their ages. There are different considerations, if there are 5 or 15. To have this number, we need attendees to have tickets, and then we need to survey them. It would be great to have 2 weeks to do this. So that pushes tickets 2 weeks back, and with it everything else (speaker selection, event promotion, etc.). The more we try to make it a friendly event, the bigger operation becomes and farther out we have to schedule things.

On Budget and Tickets

People actually laugh at me when I tell them we were doing WebCamps on about 2,500 EUR of complete budget. This year with all the improvements and bigger size it will be about 6,000 EUR of total cost. WebCamp itself is an volunteer operation.

But the change from recent years is that we actually raised a bit more money that we spent. This means that for the first time in recent WebCamps, I didn’t have to do creative accounting to budget with hidden funds coming from my personal money. A first step to making this potentially a sustainable separate organisation and that I won’t have to front all the money for 2018 edition.

I still have no idea what to do about tickets. I’m humbled that 50 people and organisations actually supported event with 60 EUR per ticket. It also introduced a lot of additional paper work and accounting. What we didn’t figure out yet – what’s the story behind supporter tickets. Do we need to give them anything extra in return, or is warm and fuzzy feeling enough?

We also have a problem that there is more demand than we can realistically fulfill. Web is growing and it would be great to have at least one more Web conference in Slovenia.

On Future

WebCamp has been bigger than myself for a few years now. There’s still too much of it that it’s stuck just in my head and in processes that are internal to my company that I lend so that we have an entity to do the event.

Given the fact that current team already excitedly talks about 2018 event, there will most probably be some kind of event. Of what size and focus, I don’t know. It’s not (just) up to me anymore and that’s a good thing.