Easy install of Oracle JET web components using npm
Tags: oraclejet, npm, javascript, webcomponents
Published: 2019-12-22 00:45:04

![Oracle JET logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bbzz0k3vxs2mw79i08d2.png)

If you have worked with Oracle JET before, you may have experienced how easy it is to create custom web components using the CLI.  However, sharing those web components is a little trickier when it comes to different ways of distributing them.

Of course, the CLI has built-in support for publishing to the Oracle Exchange (and far more interactions) but not everyone has an Oracle Cloud account. [Working with JET Web Components](https://www.oracle.com/pls/topic/lookup?ctx=jetlatest&id=GUID-A516B8F8-B6CC-4B89-9AD9-3667B1175E15)

Another way to package and distribute your web component is to publish it as an NPM package and allow others to simply do an *npm install* from the root of their project.

Thanks to the Oracle JET Community (and Hamed Roknizadeh [@hamedoracle](https://twitter.com/hamedoracle) specifically) there is a GitHub repository where you can host your web components to share with the community. [JET Community GitHub](https://github.com/Oracle-JET-Community/Web-Component-Repository)

Publishing an existing Git repository to npm is really simple. But once you have that all done, and you perform the install from the root of your project, you just have the web component code sitting in the /node_modules directory.  Now you have to go through the steps of either copying that code up to your project or doing some kind configuration to tell the project that your new web component exists.

## NPM postinstall script to the rescue

NPM provides a great feature as part of the scripts section of the package.json file.  You can do a post-* or pre-* on any script that you write, and there are post and pre versions for the npm default commands as well, like *install*.

Adding one line to the web components package.json like this:

`"scripts": {
    "postinstall": "node postinstall.js",
    "posti": "node postinstall.js"
  },`

will run the JavaScript file postinstall.js after the npm package has been installed. 
> Note that the _posti_ entry (post-i) is provided to support the use of the install shortcut of "npm i ..." instead of using the full "npm install ..." 

This JavaScript file is a simple Nodejs script and can do just about anything.  In the example below, I'm going to add an entry for the web component, into the JET project's path_mapping.json file.  This will make it really easy to add the component to the project, and all of the file copying, etc. will be handled at build time for you by the JET CLI.  Here is what the postinstall.js file looks like

```js
'use strict';

const fs = require('fs');
process.chdir("../../src/js");

let rawdata = fs.readFileSync('path_mapping.json');
let mappings = JSON.parse(rawdata);

const compDetails = {
  cwd: "node_modules/oraclejet-demo-card",
  debug: {
    src: ["**"],
    path: "components/oraclejet-demo-card"
  },
  release: {
    src: ["**"],
    path: "components/oraclejet-demo-card/min"
  }
}

mappings.libs['oraclejet-demo-card'] = compDetails;
fs.writeFileSync('path_mapping.json', JSON.stringify(mappings, null, 2));
console.log(
"The oraclejet-demo-card component has been added to your path_mapping.json file \n" +
"Add 'oraclejet-demo-card/loader' to your viewmodel dependency block to initialize this component. \n" +
"Add <demo-card> to your view to use the component.")
```

Let's break down the above file a little so that it's clearer what is going on.

```js
const fs = require('fs');
process.chdir("../../src/js");
```

We know that the postinstall.js script is going to be run from the */node_modules/<'package name'>* folder so performing a directory change up two levels will put us in the root of the JET project and from there we know that there is a /src/js folder structure.

>Yes, this does make an assumption that you are using the default JET project structure.  You can improve the script to be a little more flexible, but for demo purposes I'm going to live with some assumptions.

Once you are in the /js folder of your JET project, you can load the path_mapping.json file using the Node FileSystem object and parse it into a workable JSON object.

```js
let rawdata = fs.readFileSync('path_mapping.json');
let mappings = JSON.parse(rawdata);
```
Next, we create the path mapping entry that will be added for our web component. While this formatting isn't documented very well, looking at the existing entries gives us everything that we need to create the new entry.  We define the location of the files in the node_modules directory, what source files we want copied, and where we want them to be placed at runtime. If you had a minified, as well as debug version of your component, you could define them separately and they would be used appropriately when the application is built with either *ojet build* or *ojet build --release*. For this example, they are just pointing to the same files.

```js
const compDetails = {
  cwd: "node_modules/oraclejet-demo-card",
  debug: {
    src: ["**"],
    path: "components/oraclejet-demo-card/"
  },
  release: {
    src: ["**"],
    path: "components/oraclejet-demo-card/min"
  }
}
```

Now we can add the entry to the existing *libs* object in the mapping structure and write the result out to the path_mapping.json file.

```js
mappings.libs['oraclejet-demo-card'] = compDetails;
fs.writeFileSync('path_mapping.json', JSON.stringify(mappings, null, 2));
```

Finally, just to be nice, we add a console log telling the user what they can do next to actually use the component that was just installed.

## Seeing it all working

If you want to see this process in action, you can perform an npm install from the root of any JET v7.0.0 or newer based application.  Simply run:

`npm install oraclejet-demo-card`

In a viewModel (like incidents.js) add 'oraclejet-demo-card/loader' to the dependencies list of the define block. It will look similar to this:

```js
define(
  ['accUtils', 'oraclejet-demo-card/loader'],
  function (accUtils) {
    function IncidentsViewModel() {
      var self = this;
  ...
```

Add some data for the cards to be bound to.  This array will work fine as an example:

```js
      this.employees = [
        {
          name: 'Deb Raphaely',
          avatar: '../images/composites/debraphaely.png',
          title: 'Purchasing Director',
          work: 5171278899,
          email: 'deb.raphaely@oracle.com'
        },
        {
          name: 'Adam Fripp',
          avatar: null,
          title: 'IT Manager',
          work: 6501232234,
          email: 'adam.fripp@oracle.com'
        }
      ];
```
In the view (incidents.html) add a reference to the new component, and the bindings for the attributes of the component.  It should look something like this:

```html
  <oj-bind-for-each data="[[employees]]">
    <template>
      <demo-card class="oj-flex-item" 
                 name="[[$current.data.name]]" 
                 avatar="[[$current.data.avatar]]" 
                 work-title="[[$current.data.title]]" 
                 work-number="[[$current.data.work]]" 
                 email="[[$current.data.email]]">
      </demo-card>  
    </template>
  </oj-bind-for-each>
```
Save and serve the application and you should see a couple of cards loaded on the page that you can click on to see them flip and show more details.

![animated gif of flipping cards](https://thepracticaldev.s3.amazonaws.com/i/nnfwpz6m1pvsqcadfrf3.gif)

The GitHub repository for this component is currently located at: [Demo Card Sample](https://github.com/peppertech/oraclejet-demo-card)

To learning more about Oracle JET, visit the [Oracle JET website](https://oracle.com/jet), and/or follow us at
[Twitter @oraclejet](https://twitter.com/oraclejet)
[LinkedIn Group - Oracle JET](https://www.linkedin.com/groups/7000256/)
[JET Community Slack Channel](https://join.slack.com/t/oraclejetcommunity/shared_invite/zt-1nf9z1h46-8qQKF5X9bONSzPgv1ZcQjg)