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:
au import <package>
When running this command the importer module will import an already installed package for use.Example usage:
npm install aurelia-dialog
(or yarn)au import aurelia-dialog
au install <package>
When runningau install <package>
the package will be installed (through npm/yarn) and will be imported afterwards.Example usage:
au install aurelia-dialog
Both commands support the --debug
flag.
3. Installing the fork
git clone https://github.com/monterey-framework/cli.git
cd cli
git fetch
git checkout feat/import
npm install
npm install esprima babel-polyfill babel-register
npm install git+https://[email protected]/gulpjs/gulp.git#4.0
npm link
cd ..
au new
and use defaultscd aurelia-app
npm link aurelia-cli
4. Flow
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
- 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 usesconsole.log
sinceconsole.info
etc are unavailable. - A couple of methods were added to
lib/file-system.js
: existsSync, copySync, resolve, join, statSync - The
package-managers
folder was added, containing both thenpm
and theyarn
implementation ofinstall(packages, options)
- The
aureliaJSONPath
is now set on the Project (lib/project.js
) - The
multiselect()
function was added toui.js
to allow a user to select multiple CSS resources at once. - 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:
- 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 - User selects the version number (current version is selected by default).
- Actions 1. and 2. together enable the
Add this pair
button - 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
See what services custom importers could need (and create custom importers for aurelia-i18n, aurelia-kendoui-bridge and aurelia-materialize-bridge)
Take a closer look at these issues
9. Questions
Should the single file dependency configuration only be used when there no
resources
either? The path is calculated incorrectly since thepath
is a reference to a file and not to a directory.Should we use
lib/ui.js
to log messages, or can we keep usingaurelia-logging
? The importer logs messages of typeinfo
anddebug
Does the bundler support the system module format? A plugin can configure its
dist
directory to point todist/system
, and the importer will configure the dependency using thedist/system
directory."jspm": { "directories": { "dist": "dist/system" } }