February 17, 2014
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.
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.
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.
To install component(1), Node.js is required.
$ npm install -g component
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.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.
We will need two dependencies:
$ component install component/domify nk-components/dom-transform
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>
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.
Let’s pretend we want to develop an application which contains:
├── 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
.
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');
}
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.
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.
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>
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:
Feel free to send feedback or ask questions on Twitter.