Monterey infrastructure | Aurelia-cli import

Design

This pull request aims to add package importer functionality to the cli. For a quick visualization of the changes in this PR we have made a small video which can be viewed here.

1. Definition of terms

Install - The term install refers to the process of fetching a package from a remote registry (npm) and copying it to the local storage ( node_modules )

Import - Add configuration to the aurelia.json file so that an already installed package can be used with aurelia-cli

2. Commands

Two new commands will be added to aurelia-cli:

  1. au import <package> When running this command the importer module will import an already installed package for use.

    Example usage:

    1. npm install aurelia-dialog (or yarn)
    2. au import aurelia-dialog

  2. au install <package> When running au install <package> the package will be installed (through npm/yarn) and will be imported afterwards.

    Example usage:

    1. au install aurelia-dialog

Both commands support the --debug flag.

3. Installing the fork

  1. git clone https://github.com/monterey-framework/cli.git
  2. cd cli
  3. git fetch
  4. git checkout feat/import
  5. npm install
  6. npm install esprima babel-polyfill babel-register
  7. npm install git+https://git@github.com/gulpjs/gulp.git#4.0
  8. npm link
  9. cd ..
  10. au new and use defaults
  11. cd aurelia-app
  12. npm link aurelia-cli

4. Flow

Open in new tab img

The importer will first determine whether to install the package or just configure it (based on whether the au import command or the au install command is used). Then the importer will run through a list of (currently) 6 importer strategies. When a strategy is unable to "handle" the configuration of the plugin, it proceeds to the next strategy on the list.

After the best possible importer strategy has been found, the importer strategy does its thing. Then, the user is notified of which strategy has been used to configure the plugin.

After the plugin has been configured, the importer will look in the importer section of the package.json file. If there is a tutorial field, the contents of the field (which is an array of strings) will be printed.

5. Strategies

Currently 6 different importer strategies were implemented. Four of these strategies will be integrated in the importer and cannot be modified by plugin authors. Plugin authors can create instructions for the remaining two strategies. This is explained below.

Metadata in package.json

There are two ways for plugin authors to instruct the aurelia-cli importer.

First, in the aurelia section of the package.json file, plugin authors can add an import section. When the aurelia-cli importer detects this section, it will execute the instructions within.

Full-featured example of package.json:

{
   "name": "my-plugin",
   "aurelia": {
    "import": {
        "patches": [
            { "op": "replace", "path": "/build/loader/plugins/0/stub", "value": false}
        ],
        "dependencies": [],
        "bundles": [
        ...
        ],
        "tasks": [
          "prepare-materialize"
        ],
        "scripts": {
            "install": [
            "au prepare-materialize",
            "node node_modules/requirejs/bin/r.js -o tools/rbuild.js"
            ]
        },
        "tutorial": [
          "1. in main.js add .plugin('aurelia-materialize-bridge')"
        ]
      }
    }
   }


}

Sections:

[patches]

Apply patches to aurelia.json using RFC6902 standard.

[dependencies]

Append new dependency configurations to the default/specified bundle.dependencies array.

[bundles]

Add new bundles or even override an entire, existing bundle.

[tasks]

Copy custom CLI-tasks from package folder into aurelia_project/tasks folder.

[scripts]

You can execute any number or type of node scripts as needed.

[tutorial]

Allows plugin authors to explain to the user how the plugin can be used.

Custom importer strategy

Plugin authors may find that adding importer instructions to the package.json file is not sufficient. In this case the plugin author can create an importer-callbacks.js file.

This importer-callbacks.js file could look like this:

class ImporterCallbacks {

  static inject() { return ['ui']; };

  constructor(ui) { }

  /**
   * Determine whether this particular strategy can be used or not
   * @returns {boolean}
   */
  determine() {
    return true;
  }

  /**
   * Main entry point called by ImportEngine
   */
  execute() {
    return {
      dependencies: [{
        name: "my-plugin",
        path: "../node_modules/my-plugin",
        main: "index.js"
      }]
    };
  }

  /**
   * Short description display in CLI output
   * @returns {string}
   */
  get name() {
    return 'Custom Importer Strategy';
  }
}

Whenever the importer encounters an importer-callbacks.js file, it loads the file. If determine() returns true the importer will call the execute() method.

The return value of execute() is metadata that is equivalent to what can be defined in the "aurelia"."import" section of the plugin's package.json.

6. Changes to the CLI

  1. aurelia-logger was added to lib/cli.js, and the existence of the --debug flag determines whether the loglevel is info or debug. lib/logger.js is the custom logger for the cli, which always uses console.log since console.info etc are unavailable.
  2. A couple of methods were added to lib/file-system.js: existsSync, copySync, resolve, join, statSync
  3. The package-managers folder was added, containing both the npm and the yarn implementation of install(packages, options)
  4. The aureliaJSONPath is now set on the Project (lib/project.js)
  5. The multiselect() function was added to ui.js to allow a user to select multiple CSS resources at once.
  6. The following dependencies were added:
    • aurelia-logging
    • bluebird
    • node-glob (search for .css files in a package)
    • rfc6902 (to patch aurelia.json via rfc6902)
    • semver (to find the closest matching version of a package configuration in the cli's registry)

7. Additional CLI Feature suggestions:

7.1 Extending au new wizard steps with "Choose default package manager" option:

A new field has been added to aurelia.json called packageManager of which the default value is 'npm' (and 'yarn' is supported). Currently there is no way to change this value other than opening the aurelia.json file in a text editor and manually patching the value.

A new question could be added to the au new wizard asking the user which package manager should be used for the new project. The default should be 'npm' here, as not everyone has yarn installed.

7.2 Using npms (https://npms.io) services to search npm registry

While this functionality works best in Monterey GUI context, where:

  1. User starts typing into the autocomplete box, which finds the package in real time using npms API and populates the list of available version numbers
  2. User selects the version number (current version is selected by default).
  3. Actions 1. and 2. together enable the Add this pair button
  4. Click on Add this pair button adds a package to the list of packages to be imported.



we plan to make a similar, properly restricted service available to Aurelia CLI application as well. See Provide npms based package search for more information.

7.3 Assisting the user in the process of accessing the imported a package.

While testing the initial importer code, we made a random selection of packages and encountered many problems trying to invoke the code from the package. So, we came up with the idea to advice / intelligence / proactive support for the developer that has "installed" and "imported" a package and then sits stupefied pondering how to invoke the code from his app.

For most packages there is a two step process for importing package:

Loading CSS

The importer can detect css files in packages and add them to the resources property in a dependency configuration. A good example is flatpickr:

{
  "name": "flatpickr",
  "path": "../node_modules/flatpickr",
  "resources": [
    "dist/flatpickr.css"
  ],
  "main": "dist/flatpickr.js"
}

The user can load the css file with: <require from="flatpickr/dist/flatpickr.css"></require>. The importer provides this require statement so that the user only needs to copy and paste it into an html file.

Loading Javascript

There are many different kinds of import statements, leaving the developer to guess which one is the right one.

import 'flatpickr';
import flatpickr from 'flatpickr';
import * as flatpickr from 'flatpickr';

We would like to investigate whether it is possible to detect which import statement could be used to import the javascript portion of a package.

7.4 Backup and restore

Before modifying the aurelia.json file we can create a backup. Then, with au import --revert we can revert to the previous aurelia.json file.

8. Todo

  1. See what services custom importers could need (and create custom importers for aurelia-i18n, aurelia-kendoui-bridge and aurelia-materialize-bridge)

  2. Take a closer look at these issues

9. Questions

  1. Should the single file dependency configuration only be used when there no resources either? The path is calculated incorrectly since the path is a reference to a file and not to a directory.

  2. Should we use lib/ui.js to log messages, or can we keep using aurelia-logging? The importer logs messages of type info and debug

  3. Does the bundler support the system module format? A plugin can configure its dist directory to point to dist/system, and the importer will configure the dependency using the dist/system directory.

    "jspm": {
     "directories": {
       "dist": "dist/system"
     }
    }
    

results matching ""

    No results matching ""