DEV Community

Cover image for Client-side HTML/CSS pre-processing
Gene
Gene

Posted on • Updated on • Originally published at glabs.it

Client-side HTML/CSS pre-processing


Though the topic covered on this post might still be actual, this content referrers to an older version of zuix.js library. See zuix.js website for up-to-date documentation.


Client-side pre-processing is intended as the task of transforming some content or style file before it is actually added to the DOM.

There are various scenarios where this "transformation" practice can be useful, just to mention some common uses:

  • converting MarkDown text to HTML
  • replacing Curly Braces (template variables) with matching variable value
  • compiling SCSS, LESS or Stylus enhanced styles to standard CSS

In this post I'll describe how to achieve this kind of processing on the client-side, even though this can also be done with a server-side script or with build tools.

I'll be using the zuix.js library as a support tool for this example, but the concept and the approach described here should be almost the same using any other utility library, framework or VanillaJS.

Implementation steps

  1. The first step is downloading the raw (unprocessed) resource.
  2. Then we can process and transform the raw data.
  3. Finally the result can be added to the DOM.

These steps can be implemented with VanillaJS by making an AJAX request, the old way, or by using the modern fetch method.

// 1) Download
fetch('//some.host/some/url', {
  method: 'get'
}).then(function(rawData) {
  // 2) pre-process
  let content = preProcess(rawData);
  // 3) attach to DOM 
  const el = document.createElement('div');
  el.innerHTML = processedContent;
  container.appendChild(el);
});
Enter fullscreen mode Exit fullscreen mode

See David Walsh blog for further info on the AJAX/fetch topic.

But in component-based development we will instead take advantage of Life-Cycle event handlers and specifically of Global Hooks.

Global hooks

In the following example the main index.html file includes some content using the data-ui-include attribute.

index.html (snippet)

<!-- Content View 1 -->
<div data-ui-include="content/lorem_ipsum_1"></div>
<!-- Content View 2 -->
<div data-ui-include="content/lorem_ipsum_2"></div>
<!-- Content View 3 -->
<div data-ui-include="content/lorem_ipsum_3"></div>
Enter fullscreen mode Exit fullscreen mode

The data-ui-include attribute instructs zuix.js to load the following files:

/* View 1 */
./content/lorem_ipsum_1.html
./content/lorem_ipsum_1.css
/* View 2 */
./content/lorem_ipsum_2.html
./content/lorem_ipsum_2.css
/* View 3 */
./content/lorem_ipsum_3.html
./content/lorem_ipsum_3.css
Enter fullscreen mode Exit fullscreen mode

These .html files contain MarkDown text and a few template variables, while the .css files are using LESS syntax.

After loading each .html file, zuix.js will trigger the html:parse global hook handler, while for each .css file it will trigger the css:parse handler.

Just for reference, this is the list of life-cycle steps that take place whenever a content (data-ui-include) or a component (data-ui-load) is being loaded:

GLOBAL HOOKS
Content/Component loading life-cycle
  // detached state
   HTML file loaded
     'html:parse'
   CSS file loaded
     'css:parse'
  // attached state
   Model to View
     'view:process'
   Controller setup
     'component:ready'
Enter fullscreen mode Exit fullscreen mode

So, briefly, pre-processing with zuix.js is just a matter of registering two hook handlers:

zuix.hook('html:parse', function(data) {

  // TODO: process and replace 'data.content'

}).hook('css:parse', function(data) {

  // TODO: process and replace 'data.content'

});
Enter fullscreen mode Exit fullscreen mode

and for the purpose the actual code is using

  • ShowDown - MarkDown to HTML converter
  • zuix.$.replaceBraces method for basic template variables
  • LESS - CSS, with just a little more

as shown in the index.js file below:

const fields = {
  'title': 'Quam magna gratus',
  'subtitle': 'Haberent obstat animi non sine vestigia tristis',
  'disclaimer': 'Random text generated with Lorem Markdownum.',
  'copyright': '&copy; Mickey Mouse and Associates'
};
zuix.hook('html:parse', function(data) {

  // Replace {{braces}} fields
  const parsed = zuix.$.replaceBraces(data.content, function(name) {
    // remove braces from '{{name}}'
    name = name.replace(/([{}])/g, '');
    // lookup value in `strings` object
    if (fields[name] != null) {
      return fields[name];
    }
  });
  if (parsed != null) data.content = parsed;

  // ShowDown - Markdown compiler
  data.content = new showdown.Converter().makeHtml(data.content);

}).hook('css:parse', function(data) {

  less.render(data.content, function(err, out) {
    data.content = out.css;
  });

});
Enter fullscreen mode Exit fullscreen mode

You can see the working example and browse its source code below:

In this example any included content will be always subject to pre-processing, but most of times it's preferable to explicitly set an option to trigger pre-processing.
In this case we can use the data-ui-option attribute and pass to it an object containing all desired flags.

index.html (snippet)

<!-- Only the first include will be processed -->
<div data-ui-include="content/lorem_ipsum_1"
     data-ui-options="options.process"></div>
<div data-ui-include="content/lorem_ipsum_2"></div>
<div data-ui-include="content/lorem_ipsum_3"></div>
Enter fullscreen mode Exit fullscreen mode

This is the modified version of index.js file

window.options = {
  process: {
    markdown: true,
    fields: {
      'title': 'Quam magna gratus',
      'subtitle': 'Haberent obstat animi non sine vestigia tristis',
      'disclaimer': 'Random text generated with Lorem Markdownum.',
      'copyright': '&copy; Mickey Mouse and Associates'
    },
    less: true
  }
};
zuix.hook('html:parse', function(data) {

  const fields = this.options().fields;
  if (fields != null) {
    // Replace {{braces}} fields
    const parsed = zuix.$.replaceBraces(data.content, function(name) {
      // remove braces from '{{name}}'
      name = name.replace(/([{}])/g, '');
      // lookup value in `fields` object
      if (fields[name] != null) {
        return fields[name];
      }
    });
    if (parsed != null) data.content = parsed;
  }

  if (this.options().markdown) {
    // ShowDown - Markdown compiler
    data.content = new showdown.Converter().makeHtml(data.content);
  }

}).hook('css:parse', function(data) {

  if (this.options().less) {
    less.render(data.content, function(err, out) {
      data.content = out.css;
    });
  }

});
Enter fullscreen mode Exit fullscreen mode

So, that's all for now. Time to go outside and get some fresh air =)

Read next:

Top comments (2)

Collapse
 
moopet profile image
Ben Sinclair

I think it's only sensible to consider (pre-)processing in the browser if there's already a hard dependency on javascript for your current site.

Collapse
 
genejams profile image
Gene

I agree, it's mainly the case of component-based websites. For a static site there's no reason of adding complexity.