UNPKG

ui5-tooling-modules

Version:
331 lines (244 loc) 22.5 kB
# UI5 CLI Extensions for NPM Package Consumption > :wave: This is an **open‑source, community‑driven project**, developed and actively monitored by members of the UI5 community. You are welcome to use it, report issues, contribute enhancements, and support others in the community. The UI5 CLI extensions include a custom middleware and a custom task which allow to use NPM package names for AMD-like dependencies, e.g.: ```bash # Install a dev dependency to Chart.js (https://www.chartjs.org/) npm install chart.js ``` ```js // use Chart.js as a AMD-like dependency sap.ui.define([ "chart.js" ], function(chartjs) { [...] alert(chartjs.version); [...] }) ``` Once the UI5 application is finally built, the Chart.js dependency will be copied into the `dist/resources` folder with the proper namespace. In case of Chart.js above, there is no namespace and thus the location will be `dist/resources/chart.js.js`. ## Prerequisites - Requires at least [`@ui5/cli@3.0.0`](https://ui5.github.io/cli/v3/pages/CLI/) (to support [`specVersion: "3.0"`](https://ui5.github.io/cli/pages/Configuration/#specification-version-30)) > :warning: **UI5 CLI Compatibility** > All releases of this UI5 CLI extension using the major version `3` require UI5 CLI V3. Any previous releases below major version `3` (if available) also support older versions of the UI5 CLI. But the usage of the latest UI5 CLI is strongly recommended! ## Install ```bash npm install ui5-tooling-modules --save-dev ``` If you want the `ui5-tooling-modules` to add it's configuration to the UI5 YAML files, just add the command line argument `-rte` or `--register-tooling-extension`: ```bash # Register in default file (ui5.yaml if it exists) npm install ui5-tooling-modules --save-dev -rte # Register in specific YAML files npm install ui5-tooling-modules --save-dev -rte ui5.yaml,ui5-workspace.yaml # Register in custom YAML files npm install ui5-tooling-modules --save-dev -rte my-ui5.yaml,custom-workspace.yaml ``` When using `-rte` without specifying files, the extension will automatically register itself in `ui5.yaml` if it exists in the project directory. When specifying file names (comma-separated), the extension will only register in those specific files. ## Configuration options (in `$yourapp/ui5.yaml`) The following configuration options are relevant for the `task` and the `middleware`: - *debug*: `boolean|string` Enables debug logging (defaults to `false`), by setting value to `"verbose"` the extension will log even more detailed &nbsp; - *skipCache*: `boolean` For development scenarios, the module cache can be disabled by setting this option to true. Normally the cache is invalidated automatically whenever any of the following change: the `ui5-tooling-modules` version itself, any of its source files, the resolved configuration (e.g. `minify`, `inject`, `pluginOptions`), the project's `package.json`/lockfile, or any file in the bundle's transitive module graph. This option just forces the regeneration always (defaults to `false`) &nbsp; - *persistentCache*: `boolean` *experimental feature* With this option, the bundle information will be persistent and will be available again after the restart of the development server or for the next execution of your build task. The bundle information is stored in the working directory in the folder `.ui5-tooling-modules` folder. It's recommended to put this folder into `.gitignore`. On load, persisted entries are validated against the recorded transitive module graph (path + size + mtime) so a fresh checkout or CI run cannot silently reuse a stale bundle. (defaults to `false`) &nbsp; - *keepDynamicImports*: `boolean|String[]` *experimental feature* An arrays of NPM package names for which the dynamic imports with a generic target module (e.g. `import(anyUrl)`) will not be converted into a `require` based polyfill. If the value is a boolean, it activates/deactivates the feature for all modules (by default it is active!). This experimental feature is useful in scenarios loading ES modules dynamically from generic URLs. All dynamic imports resolving a concrete module (e.g. `import("jspdf")`) will be converted into chunks and loaded with a `require` call. &nbsp; - *skipTransform*: `boolean|String[]` *experimental feature* Array of glob patterns to verify whether the module transformation should be skipped for the modules which are matching the glob pattern. If the value is a `boolean` then the transformation can be skipped in general by setting the value to `true`. The configuration can be incorporated from the `customConfiguration` of a dependency using that middleware, e.g. a library can provide its `customConfiguration` for the application project like this (and can reuse the configuration internally via [YAML anchors](https://yaml.org/spec/1.2.2/#692-node-anchors)): ```yaml specVersion: "3.0" metadata: name: com.myorg.mylib type: library customConfiguration: ui5-tooling-modules: &cfgModules skipTransform: - "@luigi-project/container/dist/anypath/**" builder: customTasks: - name: ui5-tooling-modules-task afterTask: replaceVersion configuration: <<: *cfgModules ``` &nbsp; - *inject*: `Map<String, String|String[]>` *experimental feature* A map of Globals which are injected as imports to an NPM package to handle cases in which functions need to be poly- or ponyfilled. The configuration is aligned with definition of the Globals in the [@rollup/plugin-inject](https://www.npmjs.com/package/@rollup/plugin-inject): ```yaml configuration: inject: # import setImmediate from 'set-immediate-shim' setImmediate: set-immediate-shim # import { Promise } from 'es6-promise' Promise: - es6-promise - Promise # import { Promise as P } from 'es6-promise' P: - es6-promise - Promise # import $ from 'jquery' $: jquery # import * as fs from 'fs' fs: - 'fs' - '*' ``` &nbsp; - *legacyDependencyResolution*: `boolean` Re-enables the legacy dependency resolution of the UI5 CLI extension which allows to use entry points from `devDependencies` of the project. By default, only the `dependencies` maintained in the projects' `package.json` and the transitive dependencies are considered for the entry points and all other entry points are ignored. (available since new minor version `3.7.0` which introduces a new dependency resolution for `dependencies` only) &nbsp; - *additionalDependencies*: `string[]` List of additional dependency names to include into the bundling. By default all dependencies and their transitive dependencies are considered. In some cases it is useful to also include devDependencies, e.g. when re-packaging a thirdparty package. If you want to exclude this dependency from being a transitive dependency then you can put it into the devDependencies and list it in this configuration option (but keep in mind that this may exclude the dependency from some check tools!) &nbsp; - *entryPoints*: `string[]` *experimental feature* List of entry point modules to include into the bundling. By default the entry point modules are discovered from the sources (`*.ts`, `*.tsx`, `*.js`, `*.jsx`, `*.xml`) &nbsp; - *minify*: `boolean` *experimental feature* Flag to indicate that the generated code should be minified (in case of excluding thirdparty resources from minification in general, this option can be used to minify just the generated code) &nbsp; - *sourcemap*: `boolean` *experimental feature* Flag to indicate that the sourcemap generation should be enabled. By default the option is diabled. Possible values are `true`, `false`, `"inline"`, and `hidden`. &nbsp; The following configuration options are relevant for the `task` and the `middleware` which allow you to directly configure rollup plugins which is used as follows: ```yaml configuration: pluginOptions: # map of plugin options webcomponents: # the name of the rollup plugin skip: true # configuration ``` The available plugin configuration options are: - *pluginOptions.webcomponents.skip*: `boolean` Flag to skip the transformation of Web Components to UI5 Controls a.k.a. *Seamless Web Components* support. This allows to directly require the Web Components modules from NPM packages and use them as UI5 Controls. The NPM packages providing Web Components must include a [Custom Elements Manifest](https://custom-elements-manifest.open-wc.org/) declared in the `customElements` field in the `package.json`. (defaults to `false`) &nbsp; - *pluginOptions.webcomponents.force*: `boolean` Flag to force the transformation of Web Components to UI5 Controls a.k.a. *Seamless Web Components* support. In some cases, in which the framework version cannot be part of the `ui5.yaml`, you can force the transformation. (defaults to `true`) &nbsp; - *pluginOptions.webcomponents.scoping*: `boolean` Flag to disable the [Custom Elements Scoping](https://ui5.github.io/webcomponents/docs/advanced/scoping/) of UI5 Web Components. This allows to load multiple versions of UI5 Web Components into a single application without conflicts. This feature is enabled by default and can be disabled if needed. (defaults to `true`) &nbsp; - *pluginOptions.webcomponents.namespace*: `boolean|string` Defines the relative namespace for the Web Components modules (defaults to `"gen"`). If disabled, using the boolean value `false`, the Web Components modules are put into the namespace specified by `addToNamespace`. This is also relevant due to the JSDoc generation to allow a proper exclusion of the nested 3rd party namespace which could cause issues during the JSDoc build. The Web Components modules in the `gen` namespace are safe to be processed by the JSDoc generator to enable the generation of the API docu for the Web Components modules. &nbsp; - *pluginOptions.webcomponents.enrichBusyIndicator*: `boolean` *experimental flag* Flag to include the BusyIndicator support from into the generated Web Components package module. (defaults to `false`) &nbsp; - *pluginOptions.webcomponents.includeAssets*: `boolean` *experimental flag* Flag to include all assets into the thirdparty folder: such as all themes and languages. Although they will be lazily loaded the generation process will slow down and you will have many additional files. All dynamic imports will reside inside the `_dynamics` folder within the thirdparty folder. (defaults to `false`) &nbsp; - *pluginOptions.webcomponents.skipJSDoc*: `boolean` *experimental flag* Flag to enable/disable the generation of the JSDoc for the UI5 control wrappers. (defaults to `true` until the feature is fully implemented, in future `false`) &nbsp; - *pluginOptions.webcomponents.skipDtsGeneration*: `boolean` *experimental flag* Flag to enable/disable the generation of the *.d.ts files for the UI5 control wrappers, enabling basic TypeScript support. (defaults to `true` until the feature is fully implemented, in future `false`) &nbsp; The following configuration options are just relevant for the `task`: - *prependPathMappings*: `boolean` Prepends the path mappings for the UI5 loader to the `Component.js` which allows to run the Component using 3rd party modules in e.g. Fiori launchpad environments (defaults to `false`) &nbsp; - *addToNamespace*: `boolean|string` Puts 3rd party modules into the namespace of the Component to make them Component-local. All used 3rd party modules will be moved by default into the sub-namespace `thirdparty` of the Component namespace at build time. If you use a string as value of `addToNamespace` you can define a custom namespace, e.g. `my/namespace/for/libs` which will be nested in the Components' namespace. With that option you can run your application using 3rd party modules in a Fiori launchpad environment. In some cases (wrong dependency rewrite, wish to keep 3rd party modules in the `resources` path, ...) it might be necessary to disable this option. (defaults to `true`, disables *prependPathMappings* when set to `true`) &nbsp; > :warning: While this works great for any non-UI5 3rd party module, there are limitations for the consumption of UI5 modules from custom control 3rd party modules (NPM packages). The UI5 module names (used for `Object.extend(...)`, aggregation types, ...) are not rewritten and this may cause issues. UI5 assumes that the used module path (slash syntax) is matching the module name (dot syntax). This can lead to issues for `Object.isA(...)` checks, Renderer lookups (when not inlined or referenced by module), or for any other API which derives the module name from the module path or vice versa. If you encounter such issues you may consider to disable the option and instruct the loader via `sap.ui.loader.config({ paths: ... })` to load the resources properly. - *removeScopePrefix*: `boolean` Removes the scope prefix `@` from the namespace/path of the 3rd party module when adding it to the namespace with the *addToNamespace* option. &nbsp; - *sanitizeNpmPackageName*: `boolean` Sanitizes the NPM package name from the namespace/path of the 3rd party module when adding it to the namespace with the *addToNamespace* option. This option overrides and enables the `removeScopePrefix` option to remove the leading `@` and all `-` characters are converted to `_`. This is necessary to support the JSDoc generation for Web Components properly. &nbsp; - *providedDependencies*: `String[]` An array of NPM package names which will be available in the development server via the middleware but will be ignored for the build process via the task. Provided dependencies are considered as being provided by the application runtime rather than it must be shipped with the current project. Provided dependencies will ignore any of the configurations above. &nbsp; - *includeAssets*: `Map<String, String[]>` *experimental feature* A map of NPM package names to list of glob patterns which defines the assets to be included. The list of glob patterns can be omitted in case of copying all resources of the NPM package. &nbsp; The following configuration options are just relevant for the `middleware`: - *useRelativeModulePaths*: `boolean` *experimental flag* Usually, the middleware converts the module paths to absolute modules paths relative to the `application` project. In some special cases, in which the application doesn't register its namespace in the start page, it is better to use relative module paths for the local development (e.g. the UI5 testsuite application serving many test pages for different controls). For `library` projects this option is always turned on as for library projects they are not served in the root context of the server, they are also served via the `resources` + namespace URL. &nbsp; - *watch*: `boolean|String[]` *experimental flag* For development scenarios, the server is listening to changes of the source files of the project and its dependencies and triggers the generation of the bundle if the used NPM packages have been changed (defaults to `true`) - additionally, you can pass a list of folders or files to watch for changes for special cases &nbsp; ## Usage 1. Define the dependency in `$yourapp/package.json`: ```json "devDependencies": { // ... "ui5-tooling-modules": "*" // ... } ``` 2. Configure it in `$yourapp/ui5.yaml`: The configuration for the custom task: ```yaml builder: customTasks: - name: ui5-tooling-modules-task afterTask: replaceVersion ``` The configuration for the custom middleware: ```yaml server: customMiddleware: - name: ui5-tooling-modules-middleware afterMiddleware: compression ``` > :warning: In case your application is using a proxy such `fiori-tools-proxy`, the proxy must run after `ui5-tooling-modules-middleware` middleware. Otherwise proxy will try to serve the resources for your installed npm package instead of `ui5-tooling-modules-middleware`. You can achieve this by setting `afterMiddleware: ui5-tooling-modules-middleware` in `fiori-tools-proxy` middleware. --- :fire: **TIP** :fire: [YAML anchors](https://yaml.org/spec/1.2.2/#692-node-anchors) are a great feature to avoid redundancies in `.yaml` files. You can share the configuration of the `task` and the `middleware` in the following way: **`ui5.yaml`** ```yaml customConfiguration: ui5-tooling-modules: &cfgModules debug: true keepDynamicImports: - "@luigi-project/container" [...] server: customMiddleware: - name: ui5-tooling-modules-middleware afterMiddleware: compression configuration: <<: *cfgModules [...] builder: customTasks: - name: ui5-tooling-modules-task afterTask: replaceVersion configuration: <<: *cfgModules addToNamespace: true ``` With this approach you can ensure that you have a consistent configuration across your `task` and `middleware`. --- ## How it works The custom middleware is listening to incoming requests and checks those requests to match npm packages. E.g. a dependency to `chart.js` will cause a request to `resource/chart.js.js`. The middleware now derives the module name which is `"chart.js"` and uses `require.resolve("chart.js")` to lookup the npm package for it. If an npm package exists, the middleware is using `rollup` to create a custom AMD bundle for the npm package which uses `sap.ui.define` instead of `define`. The custom task is scanning all AMD dependencies of all modules and tries to resolve the module names. If a module has been found a custom bundle will be created in the proper namespace of the module, e.g. `@apollo/client/core` will create a custom bundle at `dist/resources/@apollo/client/core.js`. ## Web Components support Besides plain NPM packages, the `ui5-tooling-modules` also has dedicated support for [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) a.k.a. *Seamless Web Components* support. A package is recognized as a Web Components package when it ships a [Custom Elements Manifest](https://custom-elements-manifest.open-wc.org/) — either referenced via the `customElements` field in its `package.json`, or found at the conventional `dist/custom-elements.json` (or `dist/custom-elements-internal.json`) location. Based on that manifest, the tooling generates the glue code so the Web Components can be consumed like regular UI5 controls. There are three different cases which are handled automatically depending on the nature of the package and its components: ### 1. UI5 Web Components Packages whose components extend `UI5Element` from `@ui5/webcomponents-base` (detected because the package *is* `@ui5/webcomponents-base` or lists it in its `dependencies`), e.g. [`@ui5/webcomponents`](https://www.npmjs.com/package/@ui5/webcomponents). For these, the tooling runs the full pipeline: - a **package module** is generated (in the `gen` namespace by default, see `pluginOptions.webcomponents.namespace`) which registers the library, applies the [Custom Elements Scoping](https://ui5.github.io/webcomponents/docs/advanced/scoping/), and pulls in the base package - a **UI5 control wrapper** is generated per custom element so it can be used like a native UI5 control (incl. properties, aggregations, events, and — when enabled — JSDoc and `*.d.ts` files) - the base package (`@ui5/webcomponents-base`) is imported ahead of the component so scoping and shared features are initialized before any component is defined ### 2. Non-UI5 Web Components (with a package) Packages that ship a Custom Elements Manifest but whose components do **not** extend `UI5Element`, e.g. [`@luigi-project/container`](https://www.npmjs.com/package/@luigi-project/container). UI5 control wrappers are still generated for every custom element so they can be used as UI5 controls, but the UI5-specific package-chunk emission, scoping, and base-package import are skipped — those components neither need nor understand the `@ui5/webcomponents-base` runtime. Importing the package (e.g. `@luigi-project/container`) exposes the generated wrapper classes, and importing an individual component module (e.g. `@luigi-project/container/LuigiContainer`) resolves through its UI5 wrapper. ### 3. Native Web Components (load the component itself, no package) This is the case where a Web Component module is imported **directly** and there is no package glue to load — the module simply *is* the Web Component and it only needs to register itself. This covers custom elements that do not extend `UI5Element` and that are imported as standalone modules rather than through their package entry point. For such native Web Components the tooling neither prepends an `import` of the package nor of `@ui5/webcomponents-base` — the generated entry point loads **only the Web Component module itself**. Since the component does not rely on scoping or any other feature provided by a UI5 Web Components package, there is nothing else to bundle: the component defines its own custom element (`customElements.define(...)`) when its module is evaluated. A `sap.ui.define` re-export stub is still emitted for the imported module so that a direct import (e.g. an application importing a single component module from a Web Components package) resolves through the generated UI5 wrapper. > :bulb: All three cases are detected automatically from the package metadata and the component's superclass chain — no additional configuration is required. Use the `pluginOptions.webcomponents.*` options (see [above](#configuration-options-in-yourappui5yaml)) to fine-tune the behavior (e.g. `skip`, `scoping`, `namespace`, `includeAssets`), and `pluginOptions.webcomponents.force: true` to enable the transformation when the framework version cannot be derived from the `ui5.yaml`. ## How to obtain support Please use the GitHub bug tracking system to post questions, bug reports or to create pull requests. ## Contributing Any type of contribution (code contributions, pull requests, issues) to this showcase will be equally appreciated. ## License This work is licensed under [Apache 2.0](../../LICENSE). Built with care (and a lot of caffeine). If this helped you build, test, or ship, the next coffee — or drink — is on you when you bump into a contributor.