To code the new home page we are going to need:

  • Create the language content in both files en.msg and pt-BR.msg.
  • Create the navigation bar that will be a React component.
  • Test the navigation component using Mocha, Expect, and Karma.
  • Test the Yesod home controller using HSpec.

You can find the related changes on github.

Hamlet template and i18n

To use i18n inside hamlet templates we need to refer to a <prefixKey> using the following syntax:

-- will render the value of the Title key for a given language.
_{MsgTitle}

Each key used in the hamlet file needs to be inserted in both en.msg and pt-BR.msg files otherwise the application will not compile.

React component and i18n

The react components are outside of the Yesod rendering scope. This means that we cannot use _{MsgSomeKey} to get the internationalized text.

The solution is to render the text needed by the component as data-* atributes of the html tag that will be the component container and pass the dataset attribute of the container as a property of the React component.

+<div class="header" id="header"
+     data-title=_{MsgTitle}
+     data-home=_{MsgNavigationHome}
+     data-try-free="_{MsgNavigationTryFree}"
+     data-sign-up="_{MsgNavigationSignUp}"
+     data-sign-in="_{MsgNavigationSignIn}">
+  <!--- navigation will be rendered here -->

Webpack and Saas

Webpack only understands javascript and in order to make it compile Saas files we need to require the Saas file in a javascript file.

However, this will generate the css inside the javascript file. To extract the css from the javascript file into css files, we need to use a webpack plugin called extract-text-webpack-pluginextract-text-webpack-plugin.

--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,4 +1,5 @@
 var path = require('path');
+var ExtractTextPlugin = require('extract-text-webpack-plugin');

 module.exports = {
   //The entry point for the bundle.
@@ -9,4 +10,18 @@ module.exports = {
     //Locatian of the artifact - ./static/js
     path: path.join(__dirname, 'static', 'js')
   }
+  ,
+  module: {
+    loaders: [
+      { test: /\.scss$/, loader: ExtractTextPlugin.extract('css!sass') },
+      { test: /\.js?$/,
+        loader: 'babel-loader',
+        exclude: /node_modules|static/,
+        query: { presets: ['es2015', 'react'] }
+      }
+    ]
+  },
+  plugins: [
+    new ExtractTextPlugin('../css/style.css', {allChunks: true})
+  ]
 };

Testing with Karma

The configuration of the test suite is composed by two files - test.webpack.js and karma.conf.js - The later is to configure Karma and to integrate it with Webpack. The former is to tell webpack that every file ending in .test.js is part of a test suite.

The last thing to do is to add a script block in the package.json to tell npm to start karma when running the tests.

"scripts": {
  "test": "karma start"
}

To run the tests we run the command below:

-- runs the test suite.
npm test

Testing with HSpec

To run the hspec tests we run the following command:

-- runs the test suite.
stack test

Rendering the React component

To add the navigation component in the home page we need to modify the index.js file as follows:

--- a/js/index.js
+++ b/js/index.js
@@ -1,4 +1,14 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Navigation from './components/navigation.js';
+
 (function() {
   'use strict';
-  console.log("webpack is working.");
+  // makes Webpack to compile the scss
+  require('../scss/main.scss');
+  const navigationContainer = document.getElementById('header');
+  ReactDOM.render(
+    <Navigation language={navigationContainer.dataset}/>,
+    navigationContainer
+  );

Making Yesod aware of the css and javascript files

Webpack will create two files - static/css/style.css and static/js/bundle.js - and we need to modify the Foundation.hs file to configure Yesod to include those files in the final html.

--- a/Foundation.hs
+++ b/Foundation.hs
@@ -65,7 +65,21 @@ instance Yesod App where
         -- you to use normal widget features in default-layout.

         pc <- widgetToPageContent $ do
-
+            -- add CDN-hosted
+            addStylesheetRemote "https:////maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
+            addStylesheetRemote "https://fonts.googleapis.com/css?family=Roboto:400,300"
+
+            -- Takes a list of css files and generate one big css file.
+            $(combineStylesheets 'StaticR
+             [ css_style_css
+             ])
+
+            -- this is an array of static js files that yesod will
+            -- combine into one single js file and load it via a
+            -- <script> tag.
+            $(combineScripts 'StaticR
+             [ js_bundle_js
+             ])
             $(widgetFile "default-layout")
         withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")

Final result

This is what you should see after starting the application and accessing http://localhost:3000 on your browser.