Installing Bower and npm packages using Composer

In the Using Component in Symfony2 blog post we figured out how to use Component as a package manager. There are a few cons that don't allow us to use it all the time. Let's check another option - how to manage Bower and npm packages using Composer.

In a lot of cases CSS/JavaScript libraries use Bower as a package manager. If you use composer+robloach/component-installer, then you will need to register those libraries in your  composer.json as package. You will get enourmous list in the end.

Using the asset plugin

First of all, we need to install the plugin via $ composer require fxp/composer-asset-plugin

We plan to install Bower libraries to the web/components folder, so we need to configure Composer properly. Just add the configuration below to your composer.json file:

"extra": {
  "asset-installer-paths": {
    "bower-asset-library": "web/components"     
  } 
}

Now we can install any Bower library using Composer via a command similar to composer require bower-asset/jquery. Moreover, the plugin will remove all files that are listed under the ignore section in the bower.json file.

That's it! Now we can use our library in any way we want. I use RequireJS to structure my code. Let's figure out how we can install it. When we used the Component plugin, it created a configuration file by itself. In our case we need to do it manually.

Installing RequireJS

We can install this library through npm. First, we need to add an additional option to the composer.json file to store the library in the web/components folder:

"extra": {
  "asset-installer-paths": {
    "npm-asset-library": "web/components"
  }
}

Now we can install RequireJS via composer require npm-asset/requirejs

Configuring RequireJS

Let's create a configuration file at src/AppBundle/Resources/public/js/require.config.js. You can use your own path, the main requirement is that that folder must be available for a browser:

requirejs.config({
    "shim": {"materialize": {"deps": ["jquery", "jquery-hammerjs"]}},
    "paths": {
        "hammerjs": '/components/hammerjs/hammer.min',
        "jquery-hammerjs": ['/components/jquery-hammerjs/jquery.hammer'],
        "materialize": ['//cdnjs.cloudflare.com/ajax/libs/materialize/0.97.1/js/materialize.min', '/components/materialize/dist/js/materialize'],
        "code-prettify": ['//cdn.rawgit.com/google/code-prettify/master/loader/run_prettify', '/components/google-code-prettify/src/run_prettify'],
        "jquery": ['//code.jquery.com/jquery-2.1.4.min', '/components/jquery/dist/jquery.min']
    }
});

In our demo configuration we have a few libraries installed. The installation process is the same as we did with jQuery: one command composer require bower-asset/{package name} where {package name} is the name of a package from the Bower repository. In the paths section we set up aliases and paths to our libraries. If you use an array for a library, then the second file will be loaded only if the first one isn't accessible. The shim section is used to specify dependencies. In our example jquery and jquery-hammerjs will be loaded if materialize is used.

To use RequireJS you need to add a few lines to your HTML:

<script type="text/javascript" src=“/components/requirejs/require.js"></script>
<script type="text/javascript" src="{{ asset(bundles/app/js/require.config.js) }}"></script>

In case if you don't use Symfony, just specify a path to your require.config.js file.

Using RequireJS

Our JavaScript files will look something like this:

requirejs(['jquery'], function ($) {
    'use strict';
    $(".button-collapse").each(function () {
        // do something useful
    });
});

You need to create needed JavaScript files for every section of the site. Ensure that you included them. If you want to create a module, then you need to create a dedicated file. Let's use debug.js as an example.  You need to put it to the src/AppBundle/Resources/public/js/app/ folder. The contents may be as in the example below:

define(['jquery'], function ($) {
    'use strict';
    return {
        error: function (data) {
            console.error(data);
        }
    };
});

We return an object that can be used by other libraries or contexts.

Then we need to register a path for our own modules. Let's add it to the require.config.js file: 

{
  "paths": {
    "app": "/bundles/app/js/app"
  }
}

Here is how we can use our module:

requirejs(['app/debug'], function (debug) {
    'use strict';
    $(".button-collapse").each(function () {
        // do something useful
        debug.error('some error');
    });
});

Conclusions

This approach allows to install CSS/JavaScript libraries without configuring additional repositories in your composer.json. In our example we had to additionally install RequireJS, but we can even use libraries that weren't installed using a package manager. One more con of Component is that it is deprecated and won't be maintained in the future. This means that the plugin itself (robloach/component-installer) will work, but you won't be able to find new libraries there.