X

Technical Articles relating to Oracle Development Tools and Frameworks

  • JET
    January 28, 2020

Advanced Components - JET Packs Part 2

Duncan Mills
Architect

Last Updated Jan 2020 for JET 8

Introduction 

This article is a follow on from Advanced Components - JET Packs Part 1. In that article I introduced JET Packs and importantly, why we have them.  In this second article, I'm going to look at what this all means from a practical perspective, how you create a pack and how things are laid out on disk.

For the purposes of illustration I'm going to stick to JavaScript, however, if you wish to use Typescript instead then that's exactly the same except that everything will be placed under scr/ts rather than src/js.

Getting it Done

Creating a Pack 

Assuming that you are using the ojet command line interface (CLI) to create and manage your projects (highly recommended!) then creating a JET Pack is trivial - as they say, "There's a command for that!"

ojet create pack my-pack

If you run this from the root of your project then the pack will be created as a new folder under /src/js/jet-composites/ and we'll end up with 

/src
  /js
    /jet-composites
      /my-pack
        component.json

Notice that the only file in that folder is a skeleton component.json. A reminder at this point is to think about the namespacing of your components, the name you choose for the pack will determine the prefix to any components within that pack, as we will see. 

At this point the JET Pack is really not of much use, so we need to create some components to live inside it

Creating a Component in a Pack

As before, there is an ojet CLI command to create a new component in the context of a pack:

ojet create component widget-1 --pack=my-pack

When issuing this command don't forget the component directive to the create command otherwise you'll end up accidently creating a new project inside of your original one!

Once this is run this will be the result in terms of my source code structure:

/src
  /js
    /jet-composites
      /my-pack
        component.json
        /widget-1
          /resources
           /nls
              widget-1-strings.js
          component.json
          loader.js
          README.md
          widget-1-viewModel.js
          widget-1-styles.css
          widget-1-view.html

So you can see that we have a totally normal component folder layout created under /widget-1 however, this folder itself is nested under the pack (my-pack) root folder.

Now take a moment to look at the loader.js that was created - notice that it registers the HTML tag for this new component as my-pack-widget-1. We refer to this as a fullName of the component, a concatenation of both the  pack name and the component name. When-ever we need to refer to this component in a dependency from another components metadata then it's this fullName that we will use.

Likewise check the component.json and notice that the name of the component is set to just widget-1 and the pack is set to my-pack, providing the complete definition of the component's identity.  Within a pack you must maintain a unique set of component names e.g. I can't create a second widget-1 component, however, I could create a stand-alone widget-1 component (as a singleton) or a widget-1 in a different pack, all because the resulting fullNames of those components would end up being unique.

Associating The Versions

Finally we need to ensure that the JET Pack metadata includes the widget-1 component we have just created, so the final task is to open the pack level component.json and update it so that the component is included in it's dependencies.  The dependency used must be for an absolute version in this JET Pack > Containee relationship. 

So here's what my pack metadata would look like: 


{
  "name": "my-pack",
  "version": "1.0.0",
  "jetVersion": "^8.0.0",
  "type": "pack",
  "displayName": "My Component Set",
  "description": "An example JET Pack",
  "dependencies": {
    "my-pack-widget-1":"1.0.0"
  }
}

Note how the fullName of the widget-1 component is used in the dependency here.

Managing Versions Over Time

When adding or updating components in the context of a pack, you of course need to maintain the versioning of the pack as well, e.g. if you update the version number of a component within the pack you will need to both update that reference in the dependencies section of the pack metadata and update the version of the pack. You can't update something inside of the pack and not update the pack version at the same time.  Both your components and the pack should use a semantic versioning scheme and the version change to the pack should reflect the most significant change within it.  As an example of this imagine I have a pack containing one component.  I make a patch change to that component and I add a new component to the pack at the same time.  In this scenario the addition of a new component is the most significant and would constitute a minor change in the semantic versioning schema (i.e. the second segment digit would change for the pack version). A patch change on it's own would only normally imply a third segment number change.

For more information about semantic versioning head over to semver.org.

Referencing a Component in a JET Pack

As I discussed in the first article in this mini series about JET Packs, there is a small twist to the way that your code consumes a component that is contained in a pack.  First of all, the HTML tag name is, as I've mentioned, the fullName of the component, a concatenation of <packname>-hyphen-<componentName> e.g. my-pack-widget-1. The loader script however is referenced using the pack name as the requireJS root. Therefore, your define block uses a path based on that root and folder name for the component e.g. my-pack/widget-1/loader.

What Happens at Runtime?

So now we have a JET Pack in play, what does that mean in terms of a running application?  Well when you issue an ojet build or ojet serve command you'll see that the /web folder of the application will end up with a layout like this:

/web
  /js
    /jet-composites
      /my-pack
        /1.0.0
          component.json
          /widget-1
            /resources
             /nls
                widget-1-strings.js
            component.json
            loader.js
            README.md
            widget-1-viewModel.js
            widget-1-styles.css
            widget-1-view.html

Notice here how a version folder has been interjected between the pack root and the contents of the pack. This are done this way because in a real application case, where a common set of components will be shared by several applications, the components are likely to be held externally to the application, e.g. on a CDN, and this folder structure allows that CDN to hold multiple versions of the pack with the correct contents in each. 

As well as the folder structure being created in this way, there is of course a corresponding requireJS path injected into the application main.js file which will map the /web/js/jet-composites/my-pack/1.0.0 folder to the my-pack path root that we need to use in out define() blocks.  The (important) implication of all this is that consumers can switch between versions of the pack by just changing that single requireJS path mapping and without having  to change any of the modules that need to load the component as the my-path root will now just point to the correct place. 

Q&A

Some additional notes on what I've covered in this article. 

Q. Can I Have a JET Pack inside a JET Pack? 

A. No, packs cannot be nested, under the pack root you essentially need a flat folder structure with one component per folder. So don't try and nest components either.

Q. Do I have to Stick to this Folder Structure? 

A. You can of course do what you like, however, you will find out that the layout that the ojet CLI uses is the optimal layout for both design time and run time.  If you choose your own layout scheme, the CLI will not be able to help you with any of the tasks associated with managing the components and in the log run you'll find yourself wishing that you'd gone with the one defined here (trust me on this!)

What's Next? 

In relation to JET Packs there are a few more topics that need to be covered, resource components, minification /  bundling and JET Packs in Oracle Visual Builder. Keep your eye on the blog for those.

JET Custom Component Series

If you've just arrived at Custom JET Components and would like to learn more, then you can access the whole series of articles on the topic from the Custom JET Component Architecture Learning Path

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.