Skip to content

Instantly share code, notes, and snippets.

@gu-stav
Last active June 20, 2018 13:32
Show Gist options
  • Save gu-stav/7e21fb3b95758cc02a69 to your computer and use it in GitHub Desktop.
Save gu-stav/7e21fb3b95758cc02a69 to your computer and use it in GitHub Desktop.
Wien.info - Frontend Code Guidelines

Goals

An easy extendable & understandable Codebase, which performs well on all kind of devices. It should provide basic Helpers and In-Code Documentation to develop new features fast and comprehensible way. To achieve this, the Frontend uses the following Technology Stack:

  • Grunt, (Website) for building a release of all resources and to optimize Files
  • LESS (Website), to write less, more structured CSS-Code, which provides re-usable components
  • require.js, (Website) to work with asynchronous JS-Modules, which are loaded, when they are needed
  • Node/ NPM, (Website Nodejs, Website NPM) to install grunt and its dependencies. Run npm install.
  • Bower, (Website) to install Frontend Dependencies. Run bower install.

NOTE The commands npm install, bower install, grunt init and grunt release are included in buildout. As far as you not directly work with Icons, Less or Js the preferred way of updating everything is to run bin/buildout -N.

Problems

  • Lots, lots, lots of Templates with Tons of Variants of the same Thing
  • Many Sub-Projects (B2B, VEC, Events, ...)
  • Legacy-Code

Install Frontend-Dependencies

node.js & npm

Make sure you have at least node with version v0.10.35 installed (it should work with io.js aswell - see installation steps on their website). Npm should be minimum at 2.2.0. To achieve this (on) Ubuntu, run the following commands

# Add Nodejs ppa & run Installation
curl -sL https://deb.nodesource.com/setup | sudo bash -

# Install nodejs
sudo apt-get install -y nodejs

# Update NPM
npm install -g npm

# Check Versions
npm -v
~ 2.1.18

node -v
~ v0.10.35

Install Build-Dependencies

# Install global requirements
npm install -g grunt-cli bower

# Install local requirements
cd [project-root] && npm install

Build Frontend

# Builds Development Enviroment
grunt init

# To update package.json & bower.json
grunt update-packages

# Builds Production Environment
# NOTE: This will read the project version number of the package.json and do
# some replacement within the js-files. If there is a rollout, make sure you ran
# `grunt update-packages before` and committed the changes. For information on
# how this works, see https://trac.wienfluss.net/wieninfo/browser/wieninfo-site/Gruntfile.js#L394
grunt release

Changes

While working on .less and .js files, the files have to be compiled on-the-fly with Grunt. To listen for changes, use

grunt watch

# Only listen for changes in less
grunt watch:less

# Only listen for changes in js
grunt watch:js

Note: this only compiles the resources into the dev-enviroment. To create the production-buidl, also run grunt release.

Solutions

Compiled Js-Files

Grunt does some simple string-replacement within configured Js-Files.

/* Gruntfile.js */
{
  match: '${version}',
  
  /* development */
  replacement: ( new Date() ).getTime() + '',
  /* production */
  replacement: '<%= pkg.version %>',
},
{
  match: '${requirejs_base_url}',
  
  /* development */
  replacement: '/++resource++wir/js/dev',
  /* production */
  replacement: '/++resource++wir/js/dist',
}

This allows e.g. the following

/* player.js */
var urlBase = '${requirejs_base_url}/lib/jwplayer',
    defaults = {
      html5player: urlBase + '.html5.js?v=${version}',
    };

/* main.js */
require.config({
    baseUrl: '${requirejs_base_url}',
})

The files, within these replacements are happening are defined in the Gruntfile, to keep it fast.

BEM for an extensible CSS-Architecture

What is BEM?

BEM stands for Block-Element-Modifier and describes a way to structure CSS-Code in Class-Like Elements. The initial Idea was ivented by Yandex, but over the time it was modified by many Developers (e.g. single underscore instead of two). Beside semantic Advantages, it forces you to think about the HTML-Structure of a Widget first and avoids Tons of Hacks, needed because of !importants and other CSS-Specificity related Problems.

File Structure

  • Less-Files are located in [..]/publicskin/resources/less/ (trac). The initial main-File is main.less
  • Variables are defined in variables.less (Colors, Font-Sizes, Fonts, Grid-Size, Z-Indexes, Mediaqueries)
  • Each File represents a corresponding Block (see BEM-Principles)
  • form/ (trac) contains Input-Types (Input, Buttons, Textarea, ...)
  • hacks/ (trac) provides helpers, e.g. for older IEs and the file legacy-flexbox.less, which transforms the Flexbox-Layout into Floats.
  • rtl/ (trac) contains adjustments needed for RTL-Languages (Arabic in our case)

Special Cases

  • variables.less
  • main.less
  • utils.less Provides u-- several utilities such as u--is-accessible-hidden, u--is-hidden, u--clearfix ...
  • utilsprint.less Print-Utilities, to hide certain Elements ...
  • infoscreen.less CSS-Code for the Office-Infoscreen (Weather Information)
/* FILE: block1.less*/

/* BAD */
.block1 {}
.block1_module1 {}

/* GOOD */
.block1 {
  &_module1 {
    ..
  }
}

BEM

  • Syntax .block_module--modification (Block-Name followed by a single underscore, Module-Name, optional dash-dash Modification-Name)
  • Block-Names, Module-Names, Modification-Names should not be camelCased, but dashed
/* BAD */
.codeBlock_container {}
.code_block_container {}

/* GOOD */
.code-block_container {}

Less-Syntax (Indention: 2 spaces):

.block {
  &_module {
    /* Module-Names should be as semantic as possible */

    &--modification {
      /* A Module-Modification could be e.g. a state (.block_module--is-hidden), color changes (.block_module--highlight), ... */
    }
  }
}

Nesting with referencing to its parent (with & can be tricky in some cases)

/* BAD */
.block {
  &_module {
    &:hover,
    &:focus {
      .block_module {
        ...
      }
    }
  }
}

/* BETTER */
.block {
  &_module:hover &_module,
  &_module:focus &_module {
    ...
  }
}
  • Every Block should maintain it's own Mediaqueries (later they'll get merged by grunt-combine-media-queries Documentation)

Example

/* FILE: footer.less */

.footer {
  ...
}

/*
    MEDIAQUERIES
*/

@media @media-screen-lt-large {
  .footer {
    ...
  }
}
  • Mixins should always be used at the beginning of a Propery-Section, to make them overwriteable
/* BAD */
.module {
  ...
  .my-mixin();
  ...
}

/* GOOD */
.module {
  .my-mixin();
  ...
}
  • CSS-Properties should be ordered by Alphabet (when possible)
/* BAD */
.module {
  height: 1px;
  position: absolute;
  border: 1px solid red;
}

/* GOOD */
.module {
  border: 1px solid red;
  height: 1px;
  position: absolute;
}

Require.js for fast and modular JS, usable in todays Browsers

  • Entry point [..]/publicskin/resources/js/main.js
  • All modules, which are not written by us, but used (installed by Bower or Hand) should be referenced in the require.config() Call. This approach makes it easy to reference them by name later require( [ 'jq-cookie' ], function( jqCookie ) {} ).
...
paths: {
  'jq-cookie':          'bower/jquery.cookie/jquery.cookie',
  'jquery':             'bower/jquery/dist/jquery',
  'jquery-ui':          'bower/jquery-ui/ui'
  'jwplayer':           'lib/jwplayer',
  ...
},

As soon, as a function becomes larger, it can make sense, to move the code to a Module, which gets only loaded, if it is needed. By default all Top-Level requires are included in the build.js. To exclude certain Modules, see the configuration-section in the Gruntfile called requirejs.

JS, HTML, CSS and their play together

  • as far as possible, try to divide CSS + JS. In the case of CSS-Classes this means:
<!-- BAD -->

<div class="article" />

<script type="text/javascript">
  $( '.article' ).doSomething();
</script>

<!-- GOOD -->
<div class="article js--article" />

<script type="text/javascript">
  $( '.js--article' ).doSomething();
</script>

With this approach its harder to break the JS-Code, by changing Names/ Structure of the Elements. Additionally the JS-Code should respect this and not assume a specific HTML-Structure. There can be performance implications, so the Idea is not static, but can be interpreted.

/* BAD */

var $this = $( this ),
    $subThis = $this.children();

/* BETTER */

var $this = $( this ),
    $subThis = $this.find( '.this_target' );

Icons

  • All SVG-Icons are located in [..]/publicskin/resources/image/
  • grunt-grunticon (Documentation) compiles them into CSS-Sprites, grunt-svgmin (Documentation) removes the bloat added by Illustrator
  • CSS-Naming Convention for icons is <span class="icon icon--name" />, where the --name is the name of the SVG-File
  • Custom Selectors are defined in publicskin/resources/image/icons.js. This file is loaded by Grunt
  • icons.less holds a few adaptions (such as absolute positions, lists)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment