Background:

Recently I was trying to use Semantic UI framework with Phoenix project and came across the issue in integrating those two. Semantic UI comes with Gulp build tool, and Phoenix project comes bundled with Brunch build tool. For the integration, I decided to bring both to a middle ground - Webpack 2.

Phoenix

Phoenix is a web application framework on Erlang based Elixir programming language which is very popular for high performance. It follows server side MVC pattern and brings features like Channels which make it very practical for building a real-time application. Many concepts in Phoenix are similar to Ruby on Rails. This helped me pick it up very fast as I was familiar with Rails framework.

Semantic UI

Frontend frameworks have gained a lot of importance in recent years. Bootstrap has been most popular of them all due to high flexibility and good performance. But there are other good frameworks out there too. I am going to discuss about integrating Semantic UI framework with Phoenix + Webpack 2. Semantic UI looks great and provides a lot of ready to use components which is perfect for creating a decent looking application very quickly.

Components of Semantic UI:

Webpack

Webpack is a tool to build JavaScript modules in your application. There are few other popular build-tools available. But Webpack has made it's place on the top due to the large community behind it and it's integration with powerful plugins. Webpack is also relatively newer and easy to get started.

Pre-requisites:

Erlang, Elixir and Phoenix needs to be installed. If they are not installed, one can follow installation docs:


Steps:

  • Phoenix:
    • Create a new Phoenix Application
    • Remove Brunch
  • Webpack
    • Installation
    • Configuration
    • loaders
    • plugins
  • Semantic UI
    • Create Sematic-fix.js file
    • Configuration
    • Installation
    • Import files

Create a New Phoenix Application

Create a new Phoenix application project in your project folder. Phoenix gives some advanced configuration options while creating a new project, but we will go with the default ones here.

mix phoenix.new semantic_webpack_integration

While generating the project, it will prompt Fetch and install dependencies? [Y/n]. Hit Y to install all the dependencies.

A new project folder semantic_webpack_integration must be created. Now go to the project folder

cd semantic_webpack_integration

Now we will change the database authentication credentials and run the server to verify whether everything is working successfully.

To add database credentials, open the config/dev.exs file. The last section of the file is the configuration for the database where you make the desired changes.

# -----------------------
## File: config/dev.exs
#------------------------

# Configure your database
config :semantic_webpack_integration, SemanticWebpackIntegration.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "postgres",
  password: "postgres", # Add your postgres database password here
  database: "semantic_webpack_integration_dev", # Add your database name here
  hostname: "localhost",
  pool_size: 10

Once the dev.exs file is saved, we will proceed to create the database with these settings. Run

mix ecto.create

The above command will create a database for you, The success message will look something like this:

mix ecto.create

The database for SemanticWebpackIntegration.Repo has been created

Now we run the phoenix server to see if everything is in place.

mix phoenix.server

The server should start at localhost:4000 by default and the page should look like this:

If everything works fine, the Phoenix application has been successfully created. As we discussed earlier Phoenix comes with Brunch build tool, we will now proceed to remove Brunch from our project. Remove brunch config from the project's root. We will be adding Webpack config file in it's place after installing webpack.

rm brunch-config.js

The dependencies for brunch which are already install also have to be removed from package.json file. After removing all the brunch related packages from it, the file should look like this. (Instead of removing the packages from the file, you can also copy paste the contents below to it)

// ----------------------------------------------------
// File:  package.json
// ----------------------------------------------------

{
  "repository": {},
  "license": "MIT",
  "scripts": {
    "deploy": "",
    "watch": ""
  },
  "dependencies": {
    "phoenix": "file:deps/phoenix",
    "phoenix_html": "file:deps/phoenix_html"
  },
  "devDependencies": {

  }
}

Webpack 2

Installation

Note: Matthew Lehner's Phoenix + Webpack Article was really useful while integrating Phoenix with Webpack 1. I have taken a few references from this article in order to integrate Webpack 2.

Lets proceed to install Webpack 2. Run this npm command in project's root directory.

npm i -save-dev webpack@2 webpack-dev-server@2

Webpack refers to it's own config file. So create webpack.config.js in peoject's root and paste these lines

vim webpack.config.js
// ----------------------------------------------------
// File: webpack.config.js
// ----------------------------------------------------

module.exports = {
  entry: "./web/static/js/app.js",
  output: {
    path: "./priv/static/js",
    filename: "app.js"
  }
};

We need to point the node to start webpack when starting the server. For this, we need to replace Brunch command with Webpack command under watch and deploy config in package.json file's script section in the project root as follows:

// ----------------------------------------------------
// File: package.json
// ----------------------------------------------------

{
  ...
  "scripts": {
    //"deploy": "brunch build --production",        // Brunch command
    "deploy": "webpack -p",      // New Webpack command
    // "watch": "brunch watch --stdin"     // Brunch command
    "watch": "webpack --watch-stdin --progress --color",    // New Webpack command
  }
  ...
}

Now we’ll tell Phoenix to run webpack as a watcher while running the development server. Replace Brunch watcher with webpack watcher code in config/dev.exs

# -----------------------
## File: config/dev.exs
## Existing Brunch configuration section
#------------------------

config :semantic_webpack_integration, SemanticWebpackIntegration.Endpoint,
  http: [port: 4000],
  debug_errors: true,
  code_reloader: true,
  check_origin: false,
  # watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
                    cd: Path.expand("../", __DIR__)]]

# -----------------------
## File: config/dev.exs
## New Webpack configuration section:
#------------------------

config :semantic_webpack_integration, SemanticWebpackIntegration.Endpoint,
  # leave other settings as it is and change the 'watchers' option.
  watchers: [npm: ["run", "watch"]]

For Windows Users: npm doesn’t play very well with Windows so rather than using npm scripts to run webpack, we run it via node.js directly.

# -----------------------
## File: config/dev.exs
## New Webpack configuration section:
#------------------------

config :semantic_webpack_integration, SemanticWebpackIntegration.Endpoint,
  # leave other settings as it is and change the `watchers` option.
  watchers: [ node: [ "node_modules/webpack/bin/webpack.js",
                      "--watch-stdin --progress --color",
                      cd: Path.expand("../", __DIR__) ] ]

Now the webpack should start working by running mix phoenix.server command in project root. If all configurations have been made perfectly, we will see the same Phoenix landing page as we saw before webpack integration.

Configuration

Webpack provides different module loaders to handle different types of file compilations. This could be JS files, LESS files, fonts, icons, etc. Hence we will install babel, babel-loader, less, less-loader, url-loader, file-loader, postcss-loader and jQuery. There is a huge list of loaders available for webpack. We will implement some necessary loaders in our project.

Run this command to install all loaders in project's root

npm install css-loader style-loader babel-loader babel-core babel-preset-es2015 less less-loader postcss-loader url-loader file-loader url-loader jquery --save-dev

After installing the loaders with the above command, we will proceed to add them to webpack.config.js file as follows:

// ----------------------------------------------------
// File: webpack.config.js
// ----------------------------------------------------

const webpack = require("webpack");
module.exports = {

  ...
  // entry and output options...
  ...

  module: {
    rules: [
        {
          test: /\.css$/,
          loader: ["style-loader", "css-loader", "postcss-loader"]
        },
        {
          test: /\.less$/,
          loader: ["style-loader", "css-loader", "postcss-loader", "less-loader"]
        },
        {
          test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
          loader: "file-loader",
          query: {
            name: "fonts/[hash].[ext]",
            mimetype: "application/font-woff"
          }
        },
        {
          test: /\.(eot|svg|ttf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
          loader: "file-loader",
          query: {
            name: "fonts/[hash].[ext]"
          }
        },
        {
          test: /\.(png)$/,
          loader: "file-loader",
          query: {
            name: "images/[hash].[ext]"
          }
        },
        {
          test: /\.js$/,
          exclude: /(node_modules|bower_components)/,
          loader: "babel-loader"
        }
    ]
  },
}

After loaders, the second most powerful part of webpack are plugins. Webpack provides lot of plugins which can be used as per the requirement of the application. We will add just the basic plugins here. The first one is to use autoprefixer after compilation of css files. The second one is to define jQuery gobally so that it is accessible everywhere in our application.

// ----------------------------------------------------
// File: webpack.config.js
// ----------------------------------------------------


module.exports = {
  ...
  // entry and output options...
  ...

  ...
  // Module rules and loaders...
  ...

  plugins: [
    new webpack.LoaderOptionsPlugin({
      minimize: true,
      debug: false,
      options: {
        postcss: [
          ()=>require("autoprefixer"),
        ]
      }
    }),
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery",
      "window.jQuery": "jquery"
    })
  ]
};

Semantic UI (LESS Package)

Installation

Artem Butusov has posted a very effective fix for Semantic UI which helps us integrate Semantic UI (LESS) with Webpack. This helps us customize which modules we require from the framework separately and we can comment the remaining ones.

Before we install Semantic UI, we need to put some configurations in place.

Create a new file

vim web/static/lib/semantic-fix.js

Paste the following contents in the semantic-fix.js file which we just created.

// ----------------------------------------------------
// File: web/static/lib/semantic-fix.js
// ----------------------------------------------------

var fs = require('fs');

// relocate default config
fs.writeFileSync(
  'node_modules/semantic-ui-less/theme.config',
  "@import '../../src/semantic/theme.config';\n",
  'utf8'
);

// fix well known bug with default distribution
fixFontPath('node_modules/semantic-ui-less/themes/default/globals/site.variables');
fixFontPath('node_modules/semantic-ui-less/themes/flat/globals/site.variables');
fixFontPath('node_modules/semantic-ui-less/themes/material/globals/site.variables');

function fixFontPath(filename) {
  var content = fs.readFileSync(filename, 'utf8');
  var newContent = content.replace(
    "@fontPath  : '../../themes/",
    "@fontPath  : '../../../themes/"
  );
  fs.writeFileSync(filename, newContent, 'utf8');
}

We are going to make a custom theme.config file for Semantic UI. Hence change the path location in semantic-fix.js file as follows:

Find this section in the file

// ----------------------------------------------------
// File: web/static/lib/semantic-fix.js
// ----------------------------------------------------

// relocate default config
fs.writeFileSync(
  'node_modules/semantic-ui-less/theme.config',
  "@import '../../src/semantic/theme.config';\n",
  'utf8'
);

Replace it with following code:

// ----------------------------------------------------
// File: web/static/lib/semantic-fix.js
// ----------------------------------------------------

// relocate default config
fs.writeFileSync(
  'node_modules/semantic-ui-less/theme.config',
  "@import '../../web/static/css/theme.config';\n",
  'utf8'
);

We will create the theme.config file in a few minutes.

Add semantic-fix.js needs to run on every postinstall callback while installing npm packages. Hence we will place it as follows under package.json scripts section.

// ----------------------------------------------------
// File: package.json
// ----------------------------------------------------

{
  ...
  "scripts": {
    ...
    "postinstall": "node semantic-fix.js",
    ...
  }
  ...
}

Now it's time to install Semantic UI LESS package. After the installation, the semantic-fix.js file will be called from the postinstall script. To install Semantic UI, run the command in project's root directory.

npm install --save semantic-ui-less && node semantic-fix.js

After Semantic UI finishes installation, we need to copy the node_modules/semantic-ui-less/theme.config.example to web/static/css/theme.config.

cp node_modules/semantic-ui-less/theme.config.example web/static/css/theme.config

There are paths specified at the end of the theme.config file. Those paths will refer to old locations. We need to update the paths as follows. Copy the Folders and Import Theme sections from below and replace them into the file.

/*-------------------------------------------
File: web/static/css/theme.config
--------------------------------------------*/

/*******************************
        Theme Selection
*******************************/
...

/*******************************
        Folders
*******************************/

/* Path to theme packages */
@themesFolder : 'themes';

/* Path to site override folder */
@siteFolder  : '../../web/static/css/site';


/*******************************
     Import Theme
*******************************/

// @import "theme.less";
@import "~semantic-ui-less/theme.less";

/* End Config */

Create a site folder in web/static/css which will store site variables for later use. Currently it will be empty

mkdir web/static/css/site

Copy semantic LESS initialising file node_modules/semantic-ui-less/semantic.less to web/static/css. This file imports different component styles. We can import only the components we need and comment the remaining compoenent styles to stay performance friendly. It is important to update all import paths of different components as they will still have relative references semantic component styles inside node_modules. Change all the paths as follows:

/*-------------------------------------------
File: web/static/css/semantic.less
--------------------------------------------*/


/*

███████╗███████╗███╗   ███╗ █████╗ ███╗   ██╗████████╗██╗ ██████╗    ██╗   ██╗██╗
██╔════╝██╔════╝████╗ ████║██╔══██╗████╗  ██║╚══██╔══╝██║██╔════╝    ██║   ██║██║
███████╗█████╗  ██╔████╔██║███████║██╔██╗ ██║   ██║   ██║██║         ██║   ██║██║
╚════██║██╔══╝  ██║╚██╔╝██║██╔══██║██║╚██╗██║   ██║   ██║██║         ██║   ██║██║
███████║███████╗██║ ╚═╝ ██║██║  ██║██║ ╚████║   ██║   ██║╚██████╗    ╚██████╔╝██║
╚══════╝╚══════╝╚═╝     ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝   ╚═╝   ╚═╝ ╚═════╝     ╚═════╝ ╚═╝

  Import this file into your LESS project to use Semantic UI without build tools
*/

/* Global */
& { @import "~semantic-ui-less/definitions/globals/reset"; }
& { @import "~semantic-ui-less/definitions/globals/site"; }

/* Elements */
& { @import "~semantic-ui-less/definitions/elements/button"; }
& { @import "~semantic-ui-less/definitions/elements/container"; }
& { @import "~semantic-ui-less/definitions/elements/divider"; }
& { @import "~semantic-ui-less/definitions/elements/flag"; }
& { @import "~semantic-ui-less/definitions/elements/header"; }
& { @import "~semantic-ui-less/definitions/elements/icon"; }
& { @import "~semantic-ui-less/definitions/elements/image"; }
& { @import "~semantic-ui-less/definitions/elements/input"; }
& { @import "~semantic-ui-less/definitions/elements/label"; }
& { @import "~semantic-ui-less/definitions/elements/list"; }
& { @import "~semantic-ui-less/definitions/elements/loader"; }
& { @import "~semantic-ui-less/definitions/elements/rail"; }
& { @import "~semantic-ui-less/definitions/elements/reveal"; }
& { @import "~semantic-ui-less/definitions/elements/segment"; }
& { @import "~semantic-ui-less/definitions/elements/step"; }

/* Collections */
& { @import "~semantic-ui-less/definitions/collections/breadcrumb"; }
& { @import "~semantic-ui-less/definitions/collections/form"; }
& { @import "~semantic-ui-less/definitions/collections/grid"; }
& { @import "~semantic-ui-less/definitions/collections/menu"; }
& { @import "~semantic-ui-less/definitions/collections/message"; }
& { @import "~semantic-ui-less/definitions/collections/table"; }

/* Views */
& { @import "~semantic-ui-less/definitions/views/ad"; }
& { @import "~semantic-ui-less/definitions/views/card"; }
& { @import "~semantic-ui-less/definitions/views/comment"; }
& { @import "~semantic-ui-less/definitions/views/feed"; }
& { @import "~semantic-ui-less/definitions/views/item"; }
& { @import "~semantic-ui-less/definitions/views/statistic"; }

/* Modules */
& { @import "~semantic-ui-less/definitions/modules/accordion"; }
& { @import "~semantic-ui-less/definitions/modules/checkbox"; }
& { @import "~semantic-ui-less/definitions/modules/dimmer"; }
& { @import "~semantic-ui-less/definitions/modules/dropdown"; }
& { @import "~semantic-ui-less/definitions/modules/embed"; }
& { @import "~semantic-ui-less/definitions/modules/modal"; }
& { @import "~semantic-ui-less/definitions/modules/nag"; }
& { @import "~semantic-ui-less/definitions/modules/popup"; }
& { @import "~semantic-ui-less/definitions/modules/progress"; }
& { @import "~semantic-ui-less/definitions/modules/rating"; }
& { @import "~semantic-ui-less/definitions/modules/search"; }
& { @import "~semantic-ui-less/definitions/modules/shape"; }
& { @import "~semantic-ui-less/definitions/modules/sidebar"; }
& { @import "~semantic-ui-less/definitions/modules/sticky"; }
& { @import "~semantic-ui-less/definitions/modules/tab"; }
& { @import "~semantic-ui-less/definitions/modules/transition"; }

Paste the contents specified above as it is.

Similarly we need to add semantic.js file to web/static/js as follows. Note. Here also some components can be commented as per application requirements as seen in the given file.

vim web/static/js/semantic.js

Paste the following lines in the file

//---------------------------------------------
// File: web/statis/js/semantic.js
//---------------------------------------------

import 'semantic-ui-less/definitions/globals/site';
import 'semantic-ui-less/definitions/behaviors/api';
import 'semantic-ui-less/definitions/behaviors/colorize';
import 'semantic-ui-less/definitions/behaviors/form';
import 'semantic-ui-less/definitions/behaviors/state';
import 'semantic-ui-less/definitions/behaviors/visibility';
import 'semantic-ui-less/definitions/behaviors/visit';

import 'semantic-ui-less/definitions/modules/accordion';
import 'semantic-ui-less/definitions/modules/checkbox';
import 'semantic-ui-less/definitions/modules/dimmer';
import 'semantic-ui-less/definitions/modules/dropdown';
import 'semantic-ui-less/definitions/modules/embed';
import 'semantic-ui-less/definitions/modules/modal';
import 'semantic-ui-less/definitions/modules/nag';
import 'semantic-ui-less/definitions/modules/popup';
import 'semantic-ui-less/definitions/modules/progress';
import 'semantic-ui-less/definitions/modules/rating';
import 'semantic-ui-less/definitions/modules/search';
import 'semantic-ui-less/definitions/modules/shape';
import 'semantic-ui-less/definitions/modules/sidebar';
import 'semantic-ui-less/definitions/modules/sticky';
import 'semantic-ui-less/definitions/modules/tab';
import 'semantic-ui-less/definitions/modules/transition';

web/static/js/app.js is the entry file in our webpack config. Hence we need to import all the files including Semantic UI files to app.js. Add the following lines at the end of app.js file

//---------------------------------------------
// File: web/statis/js/app.js
//---------------------------------------------

...
...

import "./semantic.js";
import '../css/semantic.less';

Semantic UI is now completely intergrated with webpack and it's time to give it a try. Run the Phoenix server mix phoenix.server from project root. It should trigger webpack to compile all the files including semantic-ui.

mix phoenix.server

The Phoenix home page should get displayed now. Notice the font changes as semantic UI styles get applied now.

Note: The compiled file priv/static/js will be quite big in size as it will contain all JavaScript and CSS code combined.

The size of priv/static/js/app.js can also get bigger in size as more libraries and components are added. Webpack provides UglifyPlugin which is very efficient to compress the files and decrease the compiled file size.

Add Uglify Plugin in webpack config under plugin section as follows:

//---------------------------------------------
// File: web/statis/js/semantic.js
//---------------------------------------------

module.exports = {

  ...

  plugins: [

    ...

    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      compress: {
        warnings: false,
        screw_ie8: true,
        conditionals: true,
        unused: true,
        comparisons: true,
        sequences: true,
        dead_code: true,
        evaluate: true,
        if_return: true,
        join_vars: true,
      },
      output: {
        comments: false
      },
    }),

    ...

  ]
}

Semantic UI not only offers many components but also offers lot of theme options. Each and every components offer a whole set of options using which everything can be customised as per user's requirement. The framework can seem very different than Bootstrap or others, but it offers extensive and precise documentation to pick it up in no time. Compared to Bootstrap, I found the compiled source size of Semantic UI to be larger, but I was able to bring it down by a great extent by importing only the modules and themes I require and using webpack Uglifier plugin.

This guide has been put up taking references from different other articles. If you feel there can be a better way for Semantic integration we would be happy to know at hello@icicletech.com. For any feedback or queries tweet us at @icicletech

Published in design, web-development, javascript | Tagged with front-end, responsive-web-design, web-development, elixir, phoenix, javascript

CATEGORIES
web-development
javascript
ruby-on-rails
ruby
tutorials
startups
events
products
design
react-native

TAGS
web-development
javascript
frameworks
ruby
open-source
ruby-on-rails
tutorials
reactjs
ruby-gems
programming

MORE

RSS

X

Talk to us, that's always a good idea!