Webpack, Uikit, multi-language static website

If you want to build a custom website theme the best framework for this is Uikit, I used it the past for some of my projects and is great. In order to setup your environment and start customizing your theme you need a build system, and for that Webpack is a good choice.

TL;DR

Check out this github.com/gabihodoroaga/webpack-uikit-translate repository if you want to get started to create a new Uikit website or theme.

Prerequisites

The project setup

Let’s start from the beginning and initialize our repository,

npm init

and complete all the required fields.

Next let’s add our dependencies

npm install webpack webpack-cli webpack-dev-server \
    url-loader typescript ts-loader style-loader \
    sass-loader sass mini-css-extract-plugin \
    html-webpack-plugin file-loader html-loader \
    css-loader copy-webpack-plugin clean-webpack-plugin \
    @types/lodash --save-dev

add configure out build system; add a file named webpack.config.js with the following content

const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyPlugin = require('copy-webpack-plugin');
const path = require("path");
const devMode = process.env.NODE_ENV !== 'production';

const plugins = [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
        template: path.resolve(__dirname, "src", "index.html")
    })
];

if (!devMode) {
    plugins.push(new MiniCssExtractPlugin({ filename: "styles.[fullhash].css" }));
}

const webpackConfig = {
    entry: './src/index.ts',
    output: {
        filename: 'bundle.[fullhash].js',
        path: path.resolve(__dirname, 'dist'),
    },
    plugins: plugins,
    module: {
        rules: [
            {
                test: /\.ts$/,
                use: 'ts-loader',
                exclude: "/node_modules/"
            },
            {
                test: /\.scss$/,
                use: [
                    devMode ? 'style-loader' : {
                        loader: MiniCssExtractPlugin.loader, 
                        options: {
                            publicPath: ''
                        }
                    },
                    "css-loader",
                    "sass-loader"
                ]
            },
            {
                test: /\.(woff|woff2|ttf|eot)$/,
                loader: 'file-loader',
                options: {
                    name: 'assets/fonts/[name].[ext]'
                }
            },
            {
                test: /\.(jpe?g|png|gif|svg)$/,
                loader: 'url-loader',
            }
        ]
    },
    resolve: {
        extensions: [".ts", ".tsx", ".js"]
    }
};

module.exports = function(env)
{
    return webpackConfig;
};

and the tsconfig.json file

{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "allowJs": false
  },
  "exclude": [
      "node_modules"
    ]
}

Webpack entry point is src/index.ts so let’s create this this file also

import "./style.scss";

and the src/style.scss


body {
    background-color: lightgray;
}

and the src/index.html of course because what is a website without the index.html

<!doctype html>
<html lang="en" class="uk-notouch" dir="ltr">

<head>
</head>

<body>
</body>

</html>

Now we need to add some scripts to the package.json

  ...
  "scripts": {
    "dev": "webpack --mode development",
    "start": "webpack serve",
    "build": "export NODE_ENV=production && webpack --mode production"
  },
  ...

You can test your setup by running

npm start

Uikit

Uikit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.

Let’s add Uikit to out dependencies list, we will to the dev dependencies because we will bundle out UiKit together with our website

npm install uikit --save-dev

We need to update the src/index.ts file to include the uikit files

// @ts-ignore
import UIkit from 'uikit';

// @ts-ignore
import Icons from 'uikit/dist/js/uikit-icons';

// loads the Icon plugin
UIkit.use(Icons);

and the the src/style.scss file

// 1. Your custom variables and variable overwrites.
$base-body-background:          lightgray;
// 2. Import default variables and available mixins.
@import "uikit/src/scss/variables-theme.scss";
@import "uikit/src/scss/mixins-theme.scss";

// 3. Your custom mixin overwrites.

// 4. Import UIkit.
@import "uikit/src/scss/uikit-theme.scss";

and more meaningful elements to the /src/index.html file

<body>
    <nav class="uk-navbar-container" uk-navbar>
        <div class="uk-navbar-left">

            <ul class="uk-navbar-nav">
                <li class="uk-active"><a href="#">Active</a></li>
                <li>
                    <a href="#">Parent</a>
                    <div class="uk-navbar-dropdown">
                        <ul class="uk-nav uk-navbar-dropdown-nav">
                            <li class="uk-active"><a href="#">Active</a></li>
                            <li><a href="#">Item</a></li>
                            <li><a href="#">Item</a></li>
                        </ul>
                    </div>
                </li>
                <li><a href="#">Item</a></li>
            </ul>

        </div>
    </nav>
</body>

Translations

In order to be able to create localizable versions of our website we need 2 more packages

This gulp plugin that extracts localizable content from HTML templates into a JSON file that can be sent to translators. Once translated, the content can then be injected back into the templates as part of a localized build process, or just served to the client.

Webpack loader that localizes HTML templates and JSON files by injecting translated content, replacing the original content that was previously exported for translation.


npm install gulp-translate translation-loader globs --save-dev

and we need to add 2 more files, one file to define our translation configurations translate-config.js

module.exports =
{
    normalizeContent: true,
    prefixIdsInContentFiles: true,
    baseFilePath: "./src",
    allowDirectAnnotation: true,
    exportFilePath: "./src/translation/export/translate.json",
    importFilePath: "./src/translation/import/{locale}.json",
    includedFilePaths: [
        "./src/**/*.html",
     ],
    excludedFilePaths:[
    ]
};

and a helper script to export our translations translate-export.js

const fs = require("fs");
const globs = require("globs");
const translatePlugin = require("gulp-translate/lib/plugin/plugin");
const translateConfig = require("./translate-config");

// Get the source file paths.
const filePaths = globs.sync(translateConfig.includedFilePaths,
{
    ignore: translateConfig.excludedFilePaths
});

// Create the export task.
const plugin = new translatePlugin.Plugin(translateConfig);
const task = plugin.export(translateConfig);

// Process the source files.
for (let filePath of filePaths)
{
    const fileContents = fs.readFileSync(filePath);
    const file = { contents: fileContents, path: filePath };

    task.process(file);
}
// Finalize the export task.
task.finalize();

Now we need to update our webpack.config.js to handle translations

...
const translateConfig = require("./translate-config");

...
        rules: [
            {
                test: /\.html$/,
                use:
                [
                    { loader: "html-loader" },
                    { loader: "translation-loader", options: translateConfig }
                ]
            },
...

module.exports = function(env)
{
    if (env && env.locale)
    {
        translateConfig.importFilePath =
            translateConfig.importFilePath.replace("{locale}", env.locale);
        webpackConfig.output.path  += `/${env.locale}`
    }
    else
    {
        translateConfig.excludedFilePaths = ["**"];
    }
    return webpackConfig;
};

and add more scripts to the package.json file

  ...
  "scripts": {
    ...
    "tr-export": "node translate-export",
    "dev-es": "webpack --mode development --env locale=es",
    "start-es": "webpack serve --env locale=es",
    "build-es": "export NODE_ENV=production && webpack --mode production --env locale=es",
    "build-all": "npm run build && npm run build-es",
  },

In order to tell the gulp plugin what is translatable content and how extract it we need to add custom attributes to src/index.html file:

...
            <ul class="uk-navbar-nav">
                <li class="uk-active" translate><a href="#">Active</a></li>
                <li>
                    <a href="#" translate>Parent</a>
                    <div class="uk-navbar-dropdown">
                        <ul class="uk-nav uk-navbar-dropdown-nav">
                            <li class="uk-active"><a href="#" translate>Active</a></li>
                            <li><a href="#" translate>Item</a></li>
                            <li><a href="#" translate>Item</a></li>
                        </ul>
                    </div>
                </li>
                <li><a href="#" translate>Item</a></li>
            </ul>
...

Ok. We are ready to create our Spanish version of out website. First export your translations

npm run tr-export

and then create a new file src/translation/import/es.json with the this content

{
  "53e6e2910": "<a href=\"#\">Activo</a>",
  "bcf5c8ea0": "Principal",
  "43b6016f2": "Activo",
  "ab12e73f5": "Elemento"
}

If you run

npm run start-es

you should be able to see the Spanish version of your website.

Troubleshooting

If you see this error:

1 ERROR in child compilations.
webpack 5.6.0 compiled with 1 error in 7070 ms

and you have no idea what the error is, then you can try this:

  • disable/comment the HtmlWebpackPlugin from webpack.config.json
  • add import “./index.html” to “src/index.ts”
  • run “npm run start-es”

Conclusion

You can download this startup project from github.com/gabihodoroaga/webpack-uikit-translate and start working on you custom website design.