mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-21 14:02:43 +08:00
216 lines
8.6 KiB
Markdown
216 lines
8.6 KiB
Markdown
# Installing third party libraries
|
|
|
|
# Abstract
|
|
|
|
The current `ng install` process is faulty; third parties have to add a `bundles/` directory
|
|
with metadata that we expect. This document explores ways to improve on the process.
|
|
|
|
# Requirements
|
|
|
|
The following use cases need to be supported by `ng install`:
|
|
|
|
1. Support for _any_ thirdparties, ultimately falling back to running `npm install` only and
|
|
doing nothing else, if necessary.
|
|
1. Not a new package manager.
|
|
1. Metadata storage by thirdparties in `package.json`. This must be accessible by plugins for
|
|
the build process or even when scaffolding.
|
|
1. Proper configuration of SystemJs in the browser.
|
|
1. SystemJS configuration managed by the user needs to be kept separated from the autogenerated
|
|
part.
|
|
1. The build process right now is faulty because the `tmp/` directory used by Broccoli is
|
|
inside the project. TypeScript when using `moduleResolution: "node"` can resolve the
|
|
`node_modules` directory (since it's in an ancestor folder), which means that TypeScript
|
|
compiles properly, but the build does not copy files used by TypeScript to `dist/`. We need
|
|
to proper map the imports and move them to `tmp` before compiling, then to `dist/` properly.
|
|
1. Potentially hook into scaffolding.
|
|
1. Potentially hook into the build.
|
|
1. Safe uninstall path.
|
|
|
|
# Usages
|
|
|
|
Here's a few stories that should work right off:
|
|
|
|
```sh
|
|
$ ng new my-app && cd my-app/
|
|
$ ng install jquery
|
|
```
|
|
|
|
^(this makes jQuery available in the index)
|
|
|
|
```sh
|
|
$ ng install angularfire2
|
|
> Please specify your Firebase database URL [my-app.firebaseio.com]: _
|
|
```
|
|
|
|
^(makes firebase available and provided to the App)
|
|
|
|
```sh
|
|
$ ng install angularfire2 --dbUrl=my-firebase-db.example.com
|
|
```
|
|
|
|
^(skip prompts for values passed on the command line)
|
|
|
|
```sh
|
|
$ ng install angularfire2 --quiet
|
|
```
|
|
|
|
^(using `--quiet` to skip all prompts and use default values)
|
|
|
|
```sh
|
|
$ ng install less
|
|
```
|
|
|
|
^(now compiles CSS files with less for the appropriate extensions)
|
|
|
|
# Proposed Solution
|
|
|
|
The `install` task will perform the following subtasks:
|
|
|
|
1. **Run `npm install ${libName}`.** On failure, fail the install process.
|
|
1. **Run `preinstall` scripts.** If any script fails, run `npm uninstall ${libName}` and fail
|
|
the install process.
|
|
1. **Copy the `appData` to the `angular.json` file of the generated app, creating it if it
|
|
doesn't exist, under the `"modules"` key.** This data will be sent to the App as is. See the
|
|
[appData](#appData) section for more information.
|
|
1. **Read the `package["angular-cli"].appPrompt` and prompt the user configuration values,
|
|
if any.** See the [appData](#appData) section for more information.
|
|
1. **Add the providers specified in `package["angular-cli"]["providers"]` to the angular.js
|
|
`providers` list.** Rebuild the `providers.js` file. See the [Providers](#providers)
|
|
section for more information.
|
|
1. **Run `package["angular-cli"].scripts["install"]` if it exists.** If the script fails,
|
|
run `npm uninstall ${libName}` and fail the install process.
|
|
1. **Detect if a package named `angular/cli-wrapper-${libName}` exist in the angular
|
|
organization.** If so, run the steps above as if ng install angular/angular-${libName}. If
|
|
this install fails, ignore the failure.
|
|
|
|
These packages can be used to wrap libraries that we want to support but can't update
|
|
easily, like Jasmine or LESS.
|
|
|
|
1. **Install typings.** See the [Typings](#typings) section.
|
|
1. **Run `postinstall` scripts.**
|
|
|
|
# Proof of Concept
|
|
|
|
A proof of concept is being developed.
|
|
|
|
# Hooks
|
|
|
|
The third party library can implement hooks into the scaffolding, and the build system. The
|
|
CLI's tasks will look for the proper hooks prior to running and execute them.
|
|
|
|
The order of execution of these hooks is breadth first, going through all node packages and
|
|
checking for the `package['angular-cli']['hooks']['${hookName}']`. The hooks are then
|
|
`require()`'d as is, from within the app root folder. Within the same level of the dependency
|
|
tree, there is no guarantee for the order of execution.
|
|
|
|
## Install Hooks
|
|
|
|
The only tricky part here is install hooks. The installed package should recursively call
|
|
its `(pre|post|)install` hooks only on packages that are newly installed. It should call
|
|
`(pre|post|)reinstall` on those who were already installed. A map of the currently installed
|
|
packages should be kept before performing `npm install`.
|
|
|
|
# <a name="appData">appData</a>
|
|
|
|
The `angular-cli` key in the generated app should be used for Angular CLI specific data.
|
|
This includes the CLI configuration itself, as well as third-parties library configuration.
|
|
|
|
Third-parties can store data that will be passed to the app, and can use that data themselves.
|
|
That data can be anything. Any keys starting with either `$` or `_` will be ignored and not
|
|
passed to the client. It could be used for builds or scaffolding information.
|
|
|
|
During installation, there's a step to prompt the user for data. The schema contains a prompt
|
|
text and a default value. The default value type is used to convert the string entered by the
|
|
user. The key used in `appPrompt` is the key saved in appData.
|
|
|
|
Example:
|
|
|
|
```typescript
|
|
{ // ...
|
|
"angular-cli": {
|
|
"appPrompt": {
|
|
"number": {
|
|
"prompt": "Please enter a number:",
|
|
"defaultValue": 0
|
|
},
|
|
"url": {
|
|
"prompt": "URL of your website:",
|
|
"defaultValue": "${homepage}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
The default value is necessary as a `quiet` mode is enforced, using default values to fill
|
|
in the data without user interaction. The special strings `${...}` can be used to replace
|
|
with special values in the default string values. The values are taken from the `package.json`.
|
|
|
|
We use a declarative style to enforce sensible defaults and make sure developers think about
|
|
this. _In the case where developers want more complex interaction, they can use the
|
|
install/uninstall hooks to prompt users._ But 3rd party libraries developers need to be aware
|
|
that those hooks might not prompt users (no STDIN) and throw an error.
|
|
|
|
# <a name="providers">Providers</a>
|
|
|
|
Adding Angular providers to the app should be seamless. The install process will create a
|
|
`providers.js` from all the providers contained in all the dependencies. The User can disclude
|
|
providers it doesn't want.
|
|
|
|
The `providers.js` file will always be overwritten by the `install` / `uninstall` process. It
|
|
needs to exist for toolings to be able to understand dependencies. These providers are global
|
|
to the application.
|
|
|
|
In order to prevent providers from being global, the user can use the `--no-global-providers`
|
|
flag during installation, or can change the dependencies by using `ng providers`. As an example:
|
|
|
|
```bash
|
|
ng new my-todo-app
|
|
ng generate component database
|
|
ng install --no-global-providers angularfirebase2
|
|
ng providers database angularfirebase2
|
|
```
|
|
|
|
Or, alternatively, the user can add its own providers and dependencies to its components.
|
|
|
|
# Dependencies
|
|
|
|
Because dependencies are handled by `npm`, we don't have to handle it.
|
|
|
|
# <a name="typings">Typings</a>
|
|
|
|
Typings should be added, but failure to find typings should not be a failure of installation. The
|
|
user might still want to install custom typings himself in the worst case.
|
|
|
|
The `typings` package can be used to install/verify that typings exist. If the typings do not exist natively, we should tell the user to install the ambient version if he wants to.
|
|
|
|
# Index.html
|
|
|
|
We do not touch the `index.html` file during the installation task. The default page should
|
|
link to a SystemJS configuration file that is auto generated by the CLI and not user
|
|
configurable (see SystemJS below). If the user install a third party library, like jQuery, and
|
|
wants to use it, they have to import it using `import * as $ from 'vendor/jquery'`.
|
|
|
|
The `index.html` also includes a section to configure SystemJS that can be managed by the user.
|
|
This is separate from the generated code.
|
|
|
|
# SystemJS
|
|
|
|
It is important that SystemJS works without any modifications by the user. It is also important
|
|
to leave the liberty to the user to change the SystemJS configuration so that it fits their needs.
|
|
|
|
We will not use SystemJS bundles in development. This is for better debugging, future proofing
|
|
(when moving the build system) and better CDN support, as many of the loaded files will end up
|
|
being pulled from a CDN in production. During the `ng build` process for production, the
|
|
SystemJS configuration script will be rebuilt to fetch from the CDN.
|
|
|
|
# Upgrade Strategy
|
|
|
|
The upgrade process simply uses NPM. If new appData is added, it should be added manually using
|
|
a migration hook for `postinstall`.
|
|
|
|
# Remaining Problems
|
|
|
|
1. Installing dependencies of packages need to be further sketched out.
|
|
2. Need to add a fully fledged example with Firebase.
|