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/domify: to turn the HTML template into DOM elements.
- nk-components/dom-transform: to update the scaleX property value of the bar.
$ 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: