The formatter threw an exception while trying to deserialize the message: Error in deserializing body of request message for operation 'Translate'. The maximum string content length quota (30720) has been exceeded while reading XML data. This quota may be increased by changing the MaxStringContentLength property on the XmlDictionaryReaderQuotas object used when creating the XML reader. Line 1, position 33438.
This article is a case study about the evolution of BEM, a methodology that enables team members to collaborate and communicate ideas using a unified language that consists of simple yet powerful terms: blocks, elements, modifiers. Learn about the challenges that a big company faces when gradually building an entire ecosystem of services with an ever-growing team of developers.
Once upon a time, in a distant country far far away, an IT company named Yandex started developing Web search and related services. Time went by and its services grew, and more and more front-end developers put tireless effort into improving the ecosystem of Yandex. Great things they did, and amazing tools they built, making their developers’ lives easier, and the time has now come to share that knowledge with the community, to unleash the magic power of open source for the benefit of all good people out there.
Front-end developers are well known for their insatiable curiosity, which often yields innovation, as well as their remarkable laziness, which drives them to devise sophisticated systems in order to save precious time and to unify and automate everything.
Let’s travel back in time to 2005 and sneak a peek over the shoulder of a very very busy Yandex front-end developer and thus see…
Back in 2005, the focus was still pretty much on the server side of things. From a front-ender’s perspective, a typical Yandex project was a set of static HTML pages used as a base reference to build advanced templates such as XSL style sheets. These pages were kept in a separate folder that looked like this after a checkout:
about.htmlindex.html…project.cssproject.jsi/ yandex.pngThere was a static HTML file for each page, with all of the CSS pushed into a single style sheet, project.css, and all JavaScript placed in a single project.js file, with both files shared between all project pages. Back in 2005, JavaScript was only sparsely applied, so all of the interaction magic could fit comfortably in a small file. Images resided in a separate folder, because they were numerous. With IE 5 roaming in the wild and no CSS3, images were used for all sorts of eye candy, even for rounded corners (none of you younger Web developers would probably believe me).
To retain the basic structure, style definitions for different page sections were separated using plain CSS comments:
/* Content container (begin) */ #body { font: 0.8em Arial, sans-serif; margin: 0.5em 1.95% 0.5em 2%; }/* Content container (end) *//* Graphical banner (begin) */ .banner { text-align: center; } .banner a { text-decoration: none; }/* Graphical banner (end) */Both IDs and class names were used in HTML markup.
Bits of HTML were manually pasted into production XSL style sheets, and all changes were synced two-way, manually. That was hard, and when it wasn’t hard, it was dull.
By the beginning of 2006, the first version of Yandex.Music had been under heavy development. Multiple pages, each unlike the others, didn’t fit well into familiar simplistic concepts. Dozens of CSS classes that one had to invent meaningful names for, a growing number of unintentional dependencies spreading across the project — all of this was calling for a better solution.
Here is a typical piece of CSS code from those days:
/* Albums (begin) */ .result .albums .info { padding-right: 8.5em; } .result .albums .title { float: left; padding-bottom: 0.3em; } .result .albums .album .listen { float: left; padding: 0.3em 1em 0 1em; } .result .albums .album .buy { float: left; padding: 0.4em 1em 0 1.6em; } .result .albums .info i { font-size: 85%; } /* Albums (end) */Long cascading rules were used throughout the code.
Have a look at another:
/* Background images (begin) */ .b-foot div { height: 71px; background: transparent url(../i/foot-1.png) 4% 50% no-repeat; } .b-foot div div { background-position: 21%; background-image: url(../i/foot-2.png); } .b-foot div div div { background-position: 38%; background-image: url(../i/foot-3.png); } .b-foot div div div div { background-position: 54%; background-image: url(../i/foot-4.png); } .b-foot div div div div div { background-position: 71%; background-image: url(../i/foot-5.png); } .b-foot div div div div div div { background-position: 87%; background-image: url(../i/foot-6.png); } /* Background images (end) */Notice that ID and tag name selectors were used in many rules.
At the same time, an even bigger project was being started wow.ya.ru: a blogging platform, a place for people to interact, to share, to read and to engage.
There were dozens of various pages to support. And with the old-fashioned approach, the code was losing control on so many levels.
We needed to specify a data domain to manage page interface objects. This was a methodology issue: we needed to clarify the way we worked with concepts such as classes, tags, visual components, etc.
For a typical Web page in a Yandex project, the HTML structure and its CSS styles were still the focus of our development efforts, with JavaScript being a supplementary technology. To be able to more easily maintain the HTML and CSS of many components, a new term was devised: “block.” A block was a part of a page design or layout whose specific and unique meaning was defined either semantically or visually.
In most cases, any distinct page element (either complex or simple) could be considered a block. Its HTML container got a unique CSS class, which also became a block name.
CSS classes for blocks got prefixes (b-, c-, g-) to provide a sort of namespace emulation in CSS. The naming convention itself was changed later, but here is the initial list, annotated:
b- (block)An independent block, placed on a page wherever you needed it.?- (control)
A control (i.e. an independent block), with a JavaScript object bound to it.g- (global)
A global definition, used sparingly and always defined for a specific, unique purpose. The number of these definitions were kept to a minimum.
Some suffixes were employed as well, such as:
-nojs (no JavaScript)A style rule to be applied with JavaScript turned off. An onload callback could remove these suffixes from all DOM nodes, semantically marking them up as “JavaScript-enabled.”
In an HTML container holding a block, some of the inner nodes had distinct CSS classes. This not only facilitated the creation of tag name-independent style rules, but also assigned semantically meaningful roles to each node. Such nodes were “block elements,” or simply “elements.”
The core distinction between a block and an element is an element’s inability to exist outside of its parent block’s context. If something couldn’t be detached from a block, it was an element; detachable elements (probably) should themselves be blocks.
At first, an element could exist only in a block container. Later, a technique was devised to place some elements outside and still keep the block consistent.
In style sheets, elements with a lot of CSS got extra indentation and were wrapped in comments:
/* Head (begin) */ .b-head { … } /* Logo (begin) */ .b-head .logo { … } .b-head .logo a { … } /* Logo (end) */ /* Right side (begin) */ .b-head .right { … } /* Info (begin) */ .b-head .info { … } .b-head .info .exit a { … } /* Info (end) */ /* Search (begin) */ .b-head .search { … } .b-head .search div div, .b-head .search div div i { … } /* Search (end) */ /* Right side (end) */ /* Head (end) */At Yandex, a front-end developer usually supports more than one project. Switching between different repositories and various branches is easier when all projects use the same (or a similar) file structure. Granularity is another requirement because it provides more flexibility for version-control systems and helps to avoid conflicts during concurrent development.
This led us to a more unified structure: CSS, JavaScript and image files would reside in separate folders. In CSS, there were dedicated files for IE-specific workarounds, to keep the main code clean and standards-compliant. In production, IE would get its well-earned CSS hackery via IE-only conditional comments.
JavaScript was being employed more and more; thus, the addition of optional components and libraries.
Here is a typical file structure:
index.htmlcss/ yaru.css yaru-ie.cssjs/ yaru.jsi/ yandex.pngIE-specific hacks could have gone into the main CSS file (yaru.css) if they complied with CSS standards:
/* Common definitions (begin) */ body { font-family: Arial, sans-serif; font-size: 0.8em; padding: 0 0 2em 0; background: #fff; } * html body { font-size: 80%; }Non-valid workarounds were put in a standalone yaru-ie.css file (loaded with IE-only conditional comments).
/* Common blocks (begin) */ /* Artist (begin) */ .b-artist .i i { top: expression(7 + (90 - this.parentNode.getElementsByTagName('img')[0].height)/2); filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../i/sticker-lt.png', sizingMethod='crop'); }Designing similar projects eventually meant recreating the same blocks over and over again. Yandex is a portal and offers more than a hundred services that share the same corporate style, so careless copying and pasting wouldn’t work on that scale. Just to have something to begin with, we made a small compilation of reusable components, known internally as the common blocks library, or simply the common.
The first page fragments to be unified were the header, footer and some CSS typographic elements. Corresponding files were hosted on an internal dedicated server (common.cloudkill.yandex.ru in the listing below). Those were the early days of our unified framework.
Styles could be imported directly from that server:
@import url(http://common.cloudkill.yandex.ru/css/global.css);@import url(http://common.cloudkill.yandex.ru/css/head/common.css);@import url(http://common.cloudkill.yandex.ru/css/static-text.css);@import url(http://common.cloudkill.yandex.ru/css/foot/common-absolute.css);@import url(http://common.cloudkill.yandex.ru/css/foot/common-absolute-4-columns.css);@import url(http://common.cloudkill.yandex.ru/css/list/hlist.css);@import url(http://common.cloudkill.yandex.ru/css/list/hlist-middot.css);@import url(http://common.cloudkill.yandex.ru/css/dropdown/dropdown.css);@import url(http://common.cloudkill.yandex.ru/css/dropdown/dropdown-arrow.css);@import url(slider.css);/* Header (begin) */ /* Service (begin) */ .b-head .service h1 { … } .b-head .service h1, .b-head .service h1 a, .b-head .service h1 b { … }Obviously, these were too many imports! So, we decided to precompile styles (and, later, JavaScript files) before deployment. The compilation would replace @import directives with the file’s actual contents (a process called “inlining”) and would perform optimizations. Our internal inlining tool evolved from a simple wrapper script into an open-source project, Borschik. Try it out!
By the fall of 2007, our everyday practice had gotten some theory behind it. The concept of independent blocks, the basic idea behind our understanding of HTML and CSS layouts, was featured at the ClientSide 2007 conference in Moscow, Russia.
In that presentation, the first attempt to define a block was made.
In our attempt to produce a formal (in fact, semi-formal) definition of a block, the following three principles were highlighted:
Only class names (not IDs) should be used for CSS.Each block’s class name should have a namespace (prefix).Every CSS rule must belong to a block.As soon as unique IDs were dropped, a block could be used on the same page more than once. This also allowed two or more classes to coexist in the same DOM node, which turned out to be quite useful later.
We defined “simple” blocks as ones not able to hold other blocks anywhere inside. “Compound” blocks, on the other hand, were allowed (even required) to have nested blocks.
This classification was naive. Even the simplest blocks were sometimes wrapped around other blocks and had to be “upgraded” and refactored to fit the new role. This misclassification backfired so many times, in fact, that we finally accepted the opposite principle: any block should allow for arbitrary content to be embedded, whenever possible.
CSS definitions weren’t bulletproof when we mixed a lot of styled content originating from different sources on a single page. In complex layouts, blocks could alter each other’s appearance because of conflicts in element names. Tag name-based CSS rules might match more nodes than intended. Therefore, a stricter version of an independent block (named “completely independent block,” or CIB) was defined, with the following rules added:
Never match CSS with tag names. Use class names for everything..b-user b ? .b-user .first-letterClass names for block elements must be prefixed with the parent’s block name.
.b-user .first-letter ? .b-user-first_letter
Such class names tend to be much longer, and the resulting HTML code was considerably bigger.
This was the main reason why CIB was considered to be a costly solution, used more as a remedy than as an everyday practice.
As you are certainly aware, naming variables is one of the most difficult development problems, ever. We approached it cautiously and came up with four prefixes that would be allowed in block names, each with its own semantics.
b-Common blocksh-
Holsters, used to glue several elements togetherl-
Layout gridsg-
Global styles
A “modifier” can be defined as a particular state of a block, a flag holding a specific property.
This is best explained with an example. A block representing a button could have three default sizes: small, normal and big. Instead of creating three different blocks, you would assign a modifier to the block. The modifier would require a name (for example, size) and a value (small, normal or big).
There are two reasons for a block to change its presentation state:
A block’s presentation could be altered because of its placement in the layout. This was called a “context-dependent” modification.An additional (postfixed) class name could change a block’s appearance by applying extra CSS rules. This was a “context-independent” modifier.class="b-block b-block-postfix"
At the beginning of 2008, Yandex was going through a major review of its internal design policies. We decided to create a branding book (for internal use) to enforce best practices in interface design, company-wide.
This task was assigned to the front-end team, and after some pondering of options, we decided to proceed with it using familiar technologies: HTML and CSS.
Interfaces evolve fast, so fast that any long-term attempt to describe interfaces with words and pictures would become obsolete even before completion. We needed a branding book that would represent our interfaces as they were: changing rapidly yet still unified between different Yandex services and products.
Therefore, we decided that our interface branding book should be built with the same blocks that we used to build our websites. Blocks could be shared between projects and would represent the latest in Yandex’s interface design.
We decided to build a portal-wide framework of blocks so that all could benefit from it and contribute back. The project was internally named “Lego.”
The top-most level corresponded to various available implementations:
css/html/js/xml/xsl/Each implementation had its own folder sub-structure.
CSS went into three different folders:
css/ block/ b-dropdown/ b-dropdown.css service/ auto/ block/ b-head-logo-auto.css head.css util/ b-hmenu/ b-hmenu.cssblockThese were blocks shared between services.util
There were general-purpose blocks ready to be open-sourced.service
These were CSS styles for specific Yandex services, used for branding, headers and footers, etc.
The HTML’s folder structure was identical to the CSS’:
html/ block/ b-dropdown.html service/ auto/ l-head.html util/ b-hmenu.htmlJavaScript was loosely structured and used inconsistently between services, though:
js/ check-is-frame.js check-session.js clean-on-focus.js dropdown.js event.add.js event.del.jsEach service had a corresponding XML file that semantically described its page header (and that provided necessary project-specific data). In conjunction with an XSL style sheet, the XML file was enough to generate the header HTML code.
xml/ block/ b-head-tabs-communication.xml common-services.ru.xml head-messages.ru.xml service/ auto/ head.xmlXSL templates for various blocks (one file per block) were contained in one folder:
xsl/ block/ b-dropdown.xsl b-head-line.xsl i-common.xsl i-locale.xsl l-foot.xsl l-head.xslWhat about integration?
Lego was linked to projects with the help of a version-control feature known as svn:externals.
When a package was built for production deployment, the code from the external library (Lego) was embedded in the package, similar to static library linking in compiled languages.
Lego provided an SVN branch for each of its major releases. Sticking to a branch in svn:externals allowed for hot fixes to be introduced to a project; for extreme stability, a project could be frozen at a specific Lego revision. In either case, major version switches could be prepared and done whenever necessary.
This simple technique proved quite flexible, and it is employed to this day for many Yandex services.
CSS files imported rule definitions for blocks used on a page from the Lego folder structure.
@import url(../../block/l-head/l-head.css);@import url(../../block/b-head-logo/b-head-logo.css);@import url(../../block/b-head-logo/b-head-logo_name.css);@import url(block/b-head-logo-auto.css);The consistency of importing directives was maintained manually.
By that point, we hadn’t yet come to a convention for unified file naming, and we tried several approaches.
Upon the release of Lego 1.2, the code had been refactored and the folder structure changed.
common/ css/ js/ xml/ xsl/example/ html/service/ auto/ css/ xml/Blocks previously separated and placed in util and block folders were combined. Common styles shared by most blocks were moved to common/css. We had been pondering the possibility of open-sourcing the code but postponed it until two years later.
common/ css/ b-dropdown/ arr/ b-dropdown.arr.css b-dropdown.arr.ie.css b-dropdown.css b-dropdown.ie.cssIE-specific styles were renamed from *-ie.css to *.ie.css.
All contents of optional CSS files (such as b-dropdown_arr.css) were moved into separate folders (arr/b-dropdown.arr.css).
For class name-based modification of a block, the underscore was assigned as a separator, replacing the single dash that was used previously.
This made a block name visually separate from a modifier name, and it proved quite useful for us while developing automated tools because it allowed for unambiguous search and pattern matching.
In March 2009, Lego 2.0 was released. That event marked the rise of the BEM methodology.
BEM stands for “block, element, modifier,” the three key entities we use to develop Web components.
What key update did version 2.0 deliver?
It established the primacy of the “block” concept over underlying implementation technologies.
Each block was contained in a separate folder, and each technology (CSS, JavaScript, XSL, etc.) represented by a separate file. Documentation got its own file type, such as .wiki.
What other principles did we follow at the time?
An “independent block” could be used on any Web page and placed anywhere in the layout. Because we used XML and XSL templating, a block was represented by a node in the lego namespace.
XML:
In HTML, a block container node got a class name corresponding exactly to the block’s name.
HTML:
কোন মন্তব্য নেই:
একটি মন্তব্য পোস্ট করুন