X

Technical Articles relating to Oracle Development Tools and Frameworks

  • March 3, 2020

Advanced Components - Resource Components

Duncan Mills
Architect

Last Updated March 2020 for JET 8

Introduction

In the last couple of articles in the Custom Web Component series I've been looking at JET packs - a way of managing supported groups of components as a single logical unit. Once you start to use JET Packs you can then take advantage of a new type of component, a resource. The idea behind resource components is pretty obvious, when you have several components developed, it's likely that you might start to see repetition in your code which you'd want to refactor into some common class, or perhaps you want to share a common resource bundle or CSS file amongst the components. This is exactly what a resource component is for, so let's take a look. 

What Is a Resource Component?

Fundamentally, a resource component is just a bucket, you can put whatever you like into it using whatever disk layout you like, so  can be used for js/ts files, common css, shared images and so forth, as required. The thing is, of course, that it's a no use on its own.  A user would never "install" a resource component directly, it will be installed as a downstream dependency of a standard UI (composite) component.  A resource component has no UI itself and would not (for example) appear in the component palette of Visual Builder. 

Defining a Resource Component

Resource components are very easy to define - essentially the only required file is a component.json file and that can be very simple, e.g. at a minimum:

{
  "name": "utils",
  "pack": "oj-sample",
  "type": "resource",
  "displayName": "Oracle Jet Sample Utilities",
  "description": "A set of reusable utility classes used by the Oracle JET sample component set and available for general use.",
  "version": "2.2.0"
}

Things to notice in this basic definition:

  1. Resource components should always be in a pack.  By definition they are managing a set of resources that a related set of components need.  In this example, the JET pack is oj-sample and the name of the component is utils and so therefore the fullName of the component, by which dependencies are registered will be oj-sample-utils
  2. The type attribute of the component is resource
  3. The component has a version of course
  4. There is no requirement for a jetVersion attribute, it is optional.  Of course, if some of the code carried by the resource component has a dependency on a particular JET version then you can add a jetVersion attribute to the metadata to make this clear.

As I mentioned, the actual layout within the component folder (and distribution .zip) is up to you, you just need to ensure the presence of the component.json in the root. As a result here is no direct help from the ojet command line tool (cli) to create one of these, but it's trivial to do manually given that it's just a matter of creating one folder and a single file. The cli will recognise and package / publish resource components for you, however.

Here's the layout of oj-sample-utils resource component in the context of the source for the owning JET Pack:

/oj-sample
  ...
  /utils
    component.json
    README.md
    license.txt
    /export
      dataCollectionExporter.js
      visualizationExporter.js
    /resources
      /nls
        oj-sample-strings.js
        /root
          oj-sample-strings.js
    /validators
      countryValidator.js
      countryValidatorFactory.js
      emailValidator.js
      emailValidatorFactory.js
      urlValidator.js
      urlValidatorFactory.js

Notice that I have still stuck to the conventions for string bundles by sticking them under /resources/nls.

Referencing a Resource Component

Within a pack, when another component needs to re-use something from the resource component it must of course declare that dependency. e.g. 

{
  "name": "export-data",
  "pack": "oj-sample",
  "type":"composite",
  "displayName": "Export Data",
  "license": "https://opensource.org/licenses/UPL",
  "version": "3.0.2",
  "jetVersion": ">=6.0.0 <9.0.0",
  "dependencies": {
    "oj-sample-utils": "^2.2.0"
  },
  ...

This will ensure that when the component gets installed, a compatible version of the resources will also be pulled in.

Once the dependency is set up, the consuming component can then reference the resources using the JET Pack requireJS path as a root. So in the example of the oj-sample-export-data component, it needs to get hold of the dataCollectionExporter.js from the oj-sample-utils resource component. To do this its define block in the viewModel looks like this:

define([
  'ojL10n!./resources/nls/export-data-strings',
  'oj-sample/utils/export/dataCollectionExporter',
  'ojs/ojknockout',
  'ojs/ojbutton',

], function (componentStrings, exporter) {

Notice how the path relative to oj-sample/utils reflects the layout within the resource component folder structure.

Visibility of Resource Component Contents

As we've seen, you can access any file within the resource component using the requireJS path defined by the owning pack.  However, by default we regard everything within the resource component as private to the JET Pack.  There are some cases where you want to communicate to consumers that actually it's OK to consume this class and you can do that in the component.json metadata using the publicModules attribute in the component.json.  Here's the oj-samples-utils resource component metadata with that added:


{
  "name": "utils",
  "pack": "oj-sample",
  "type": "resource",
  "displayName": "Oracle Jet Sample Utilities",
  "description": "A set of reusable utility classes used by the Oracle JET sample component set and available for general use.",
  "version": "2.2.0",
  "publicModules": [
    "validators/emailValidatorFactory",
    "validators/urlValidatorFactory",
    "validators/countryValidatorFactory",
    "export/dataCollectionExporter",
    "export/visualizationExporter"
  ]
}

So the publicModules is an array that defines the path to any classes that can be regarded as "public" e.g. available for re-use and with an API that will be stable with respect to the resource component versioning. Note that the js extension is not required as these entries reflect the requireJS path to reach the module at runtime. Not all of the classes within the component need to exported in this way, just the ones you want to classify as public.

This same information is used by components within the Pack when they are undergoing minifcation.  It will inform them not to include the exported classes into the minified code and to use the shared version instead.  

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

Join the discussion

Comments ( 2 )
  • Bibi Ali Wednesday, April 22, 2020
    This was a helpful guide Duncan. Thanks for putting it together.
    I found that I had to add a reference to the dependency inside the pack's component.json in addition to doing so in the component.json of the consuming component.
    "dependencies": {
    "oj-sample-utils": "^2.2.0",
    ...
    }
  • Duncan Mills Thursday, April 23, 2020
    Yes this is correct, the dependencies list at the pack level defines what is actually in the pack, so the resource component will always need to be referenced there as well as the recording of the usage by individual components within the pack. However, note that the dependency within the pack dependencies must be to an absolute version not a semver range - it is telling us what version of the resource component is part of this version stripe
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.