Antoine Lehurt

Build a Web app with component(1)

Component(1) is a front-end package manager created by TJ Holowaychuk. It embraces the philosophy of creating small and reusable modules. It is not restricted to JavaScript, indeed we are able to create components that also contain CSS, HTML, JSON, images, fonts, … Therefore we can create JavaScript libraries, UI component or reusable CSS utility classes. It allows us to organize applications around multiple components.

All in one place

Currently, the common architecture is to split files into different directories. For instance, this is how the files tree may look like for a menu:

├── assets
│   └── images
│       └── menu
│           └── background.jpg
├── scripts
│   └── views
│       └── menu.js
├── styles
│   └── modules
│       └── menu.css
└── templates
    └── menu.html

With component(1), we can regroup the logic and UI that define the menu component.

├── app
│   └── menu
│       ├── background.jpg
│       ├── demo.html
│       ├── index.js
│       ├── menu.css
│       ├── template.html
│       └── test
│           └── menu.js

We find all information about this component in one place. We should also add tests to be sure the component is bug free, before integrating it to the rest of the application. A readme file or demo page could help our colleagues, other developers or the future “me” to understand easily what the component does and how it works. Nothing really new, but in my opinion it simplifies the process a lot.

Registry system

There are a few things to know about component(1) registry system. It is based on GitHub style namespacing username/repository. It avoids name conflict, therefore you can choose the one you desire. To share a component with the community, you need to push it on GitHub, then add the repository link into the wiki page to register it. The component will be accessible on component.io or via the command component search.

However you don’t need to register a component to be able to install it.

Installation

To install component(1), Node.js is required.

$ npm install -g component

component(1) cli commands

Component(1) comes with multiple commands. You can printout the list with component --help.

Here is those I use often:

  • search [query]: search registered component.
  • wiki: open the components list page. Another way to find components.
  • create [dir]: create a component skeleton.
  • install [name ...]: install one or more components.
  • build: build the component. I will talk about it later.

Create a component

In this example we will create a progress bar component. First of all, we have to create a component.json file in which we add all the information about the component.

{
  "name": "progress-bar",
  "repo": "kewah/progress-bar",
  "description": "Visual indicator of progress",
  "version": "0.0.1",
  "keywords": ["progress", "bar"],
  "scripts": [
    "index.js"
  ],
  "styles": [
    "progress-bar.css"
  ],
  "templates": [
    "template.html"
  ]
}

We can also use the command component create progress-bar to create the component’s structure.

Install dependencies

We will need two dependencies:

$ component install component/domify nk-components/dom-transform

Build the component

Component(1) module definition is based on CommonJS spec, like Node.js. You can find more information about CommonJS module in this article.

We need to define the logic and the UI of the component. Let’s create index.js, progress-bar.css and template.html as we specified in component.json.

  • index.js:
// require installed dependencies.
var domify = require('domify');
var transform = require('dom-transform');

// require local template.
// component-build(1) converts HTML template to string.
var template = require('./template.html');

// ProgressBar function will be accessible to others components.
module.exports = ProgressBar;

function ProgressBar() {
  // Convert string to DOM element
  this.el = domify(template);
  this.percentBar = this.el.querySelector('.js-percentBar');

  transform(this.percentBar, {
    scaleX: 0,
  });
}

ProgressBar.prototype.percent = function (value) {
  transform(this.percentBar, {
    scaleX: value,
  });
};
  • progress-bar.css:
.progressBar {
  position: relative;
  width: 100%;
  height: 10px;
}

.progressBar-percent {
  width: 100%;
  height: 100%;
  background: red;
  -webkit-transform-origin: 0 0;
  transform-origin: 0 0;
}
  • template.html:
<div class="progressBar">
  <div class="progressBar-percent js-percentBar"></div>
</div>

To be able to test the component we need to build it:

$ component build --dev

--dev option adds source map which is really helpful for debugging. You can find more options with component build --help.

Component-build(1) converts HTML files to strings and JSON files to objects. You can require them directly inside a JavaScript module: require('./template.html') and require('./data.json').

While developing a small component like this, I use rewatch to execute the build command every time I save a file. There is no point in using Grunt or Gulp in that case.

$ rewatch *.js *.css *.html -c "component build --dev"

Finally, we add a demo page to see how it looks.

  • demo.html:
<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" type="text/css" href="build/build.css" />
  </head>
  <body>
    <script src="build/build.js"></script>
    <script>
      var ProgressBar = require('progress-bar');

      var bar = new ProgressBar();
      document.body.appendChild(bar.el);

      var percent = 0;
      var intervalId = setInterval(function () {
        bar.percent(percent);
        percent += 0.2;

        if (percent > 1) {
          clearInterval(intervalId);
        }
      }, 300);
    </script>
  </body>
</html>

Test your component

There are different possibilities to test a component: Mocha, Zuul, component-test, … And certainly others I don’t know yet. I currently use component-test. It is simple to use and doesn’t need configuration. You can run the test in the browser, PhantomJS or Sauce Labs. If you want more information, the readme is well documented.

Whatever solution you prefer to use, don’t forget to test your code.

We can now push the code to GitHub and add the link to Component’s wiki. Don’t forget to add a tag (git tag) for each version you release. Otherwise you won’t be able to specify a version number when you use it as a dependency. Which is really dangerous, if one day you decide to change the component’s API, it will break applications that use it. If you are lazy, you can still use mversion.

Structuring an application

Let’s pretend we want to develop an application which contains:

  • Two pages: home and about.
  • A script to load images.
├── component.json
├── app
│   ├── boot
│   │   └── component.json
│   ├── lib
│   │   └── image-loader
│   │       └── component.json
│   └── pages
│       ├── about
│       │   └── component.json
│       └── home
│           └── component.json

The file at the root of the project is the main component.json of the application. This is where we will run the command to build the application.

{
  "name": "appName",
  "paths": [
    "app"
  ],
  "local": [
    "boot"
  ]
}

The paths property is used by the builder to know where it has to look at to find local dependencies. Here we specify boot, the entry point of the application, located in app/ path.

Each components define their own dependencies, local or published, therefore we don’t need to add more information in this component.json.

Local components

Boot

The boot component will be the first script executed in the application. It manages routing (visionmedia/page.js) and transitions between each pages. It also defines the base of the interface, using normalize.css, global.css and fonts.css. We add the pages components, home and about, as local dependencies.

{
  "name": "boot",
  "dependencies": {
    "necolas/normalize.css": "*",
    "visionmedia/page.js": "*"
  },
  "paths": [
    "../pages"
  ],
  "local": [
    "home",
    "about"
  ],
  "scripts": [
    "index.js"
  ],
  "styles": [
    "fonts.css",
    "global.css"
  ],
  "fonts": [
    "fonts/open-sans.eot",
    "fonts/open-sans.svg",
    "fonts/open-sans.ttf",
    "fonts/open-sans.woff"
  ],
  "images": [
    "images/background.jpg"
  ]
}

To use a static assets, like the background image, you just need to use relative file path. component build symlinks those files to the build folder and replace the url() values (except if you specify an absolute path).

body {
  background-image: url('images/background.jpg');
}

Pages

The pages use the local component to load images and emitter to communicate with boot.

{
  "name": "home",
  "dependencies": {
    "component/emitter": "*"
  },
  "paths": [
    "../../lib"
  ],
  "local": [
    "image-loader"
  ],
  "scripts": [
    "index.js"
  ],
  "styles": [
    "home.css"
  ],
  "template": [
    "template.html"
  ]
}

I pushed an example on GitHub to show how an application could be organized. I also used Sass for the demo.

Sometimes you may wonder if you need to move a component to its own repository. Personally, when I’m working on a project, I only creates local components. When the project ends, I extract components that can be useful for a new project. At North Kingdom we started to do that after the launch of The Hobbit project. We share our components in nk-components.

Custom builds

When it comes to build larger applications, you may want to use CSS preprocessor or template engines. Component-build(1) has --use option, where you define the list of plugins it has to execute.

$ component build --use component-scss

If you don’t want to use component-build(1) but prefer tasks runner like Grunt or Gulp, I suggest you to use grunt-component-build or gulp-component. It is possible to create your own build script using builder.js.

Building the application

To build the application you need to be at the root of the project (where the main component.json is located) and run:

  • component install: installs the dependencies defined by local components.
  • component build: build the files that will be included into the index.html.

The final step is to create the index.html that will be served to the user. Don’t forget to require boot, otherwise the application will never start.

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" type="text/css" href="build/build.css" />
  </head>
  <body>
    <script src="build/build.js"></script>
    <script>
      require('boot');
    </script>
  </body>
</html>

My thoughts

I really like the way we organize applications with component(1), where each component is developed and lives separately in the project.

At North Kingdom, we don’t use a specific framework like Backbone or Angular. Each project has different constraints, some need to be connected to a database, some need to handle deep links… component(1) lets us to be flexible and just use what we really need. We find a lot of good resources published by the community and the number of shared components is still growing.

Component(1) has some drawback like the file size. Aliases, at the bottom of build.js, could take >10% of the file size. Which is a lot compared to other solution like browserify. Also, it may seem tedious to have to reference each of the dependencies and modules for each of the components. This is a design choice, but this could be simplified with a generator like Yeoman.

I see it as a preview of the future of Web development. Web Components will be available in modern browsers in a few years with even more powerful features. The team behind component(1) plans to support Web Components and this is a great news.

Further reading: