|
| 1 | +# Installing third party libraries |
| 2 | + |
| 3 | +# Abstract |
| 4 | + |
| 5 | +The current `ng install` process is faulty; third parties have to add a `bundles/` directory |
| 6 | +with metadata that we expect. This document explores ways to improve on the process. |
| 7 | + |
| 8 | +# Requirements |
| 9 | + |
| 10 | +The following use cases need to be supported by `ng install`: |
| 11 | + |
| 12 | +1. Support for _any_ thirdparties, ultimately falling back to running `npm install` only and |
| 13 | +doing nothing else, if necessary. |
| 14 | +1. Not a new package manager. |
| 15 | +1. Metadata storage by thirdparties in `package.json`. This must be accessible by plugins for |
| 16 | +the build process or even when scaffolding. |
| 17 | +1. Proper configuration of SystemJs in the browser. |
| 18 | +1. SystemJS configuration managed by the user needs to be kept separated from the autogenerated |
| 19 | +part. |
| 20 | +1. The build process right now is faulty because the `tmp/` directory used by Broccoli is |
| 21 | +inside the project. TypeScript when using `moduleResolution: "node"` can resolve the |
| 22 | +`node_modules` directory (since it's in an ancestor folder), which means that TypeScript |
| 23 | +compiles properly, but the build does not copy files used by TypeScript to `dist/`. We need |
| 24 | +to proper map the imports and move them to `tmp` before compiling, then to `dist/` properly. |
| 25 | +1. Potentially hook into scaffolding. |
| 26 | +1. Potentially hook into the build. |
| 27 | +1. Safe uninstall path. |
| 28 | + |
| 29 | +# Usages |
| 30 | + |
| 31 | +Here's a few stories that should work right off: |
| 32 | + |
| 33 | +```sh |
| 34 | +$ ng new my-app && cd my-app/ |
| 35 | +$ ng install jquery |
| 36 | +``` |
| 37 | +^(this makes jQuery available in the index) |
| 38 | + |
| 39 | +```sh |
| 40 | +$ ng install angularfire2 |
| 41 | +> Please specify your Firebase database URL [my-app.firebaseio.com]: _ |
| 42 | +``` |
| 43 | +^(makes firebase available and provided to the App) |
| 44 | + |
| 45 | +```sh |
| 46 | +$ ng install angularfire2 --dbUrl=my-firebase-db.example.com |
| 47 | +``` |
| 48 | +^(skip prompts for values passed on the command line) |
| 49 | + |
| 50 | +```sh |
| 51 | +$ ng install angularfire2 --quiet |
| 52 | +``` |
| 53 | +^(using `--quiet` to skip all prompts and use default values) |
| 54 | + |
| 55 | +```sh |
| 56 | +$ ng install less |
| 57 | +``` |
| 58 | +^(now compiles CSS files with less for the appropriate extensions) |
| 59 | + |
| 60 | +# Proposed Solution |
| 61 | + |
| 62 | +The `install` task will perform the following subtasks: |
| 63 | + |
| 64 | +1. **Run `npm install ${libName}`.** On failure, fail the install process. |
| 65 | +1. **Run `preinstall` scripts.** If any script fails, run `npm uninstall ${libName}` and fail |
| 66 | + the install process. |
| 67 | +1. **Copy the `appData` to the `angular.json` file of the generated app, creating it if it |
| 68 | + doesn't exist, under the `"modules"` key.** This data will be sent to the App as is. See the |
| 69 | + [appData](#appData) section for more information. |
| 70 | +1. **Read the `package["angular-cli"].appPrompt` and prompt the user configuration values, |
| 71 | + if any.** See the [appData](#appData) section for more information. |
| 72 | +1. **Add the providers specified in `package["angular-cli"]["providers"]` to the angular.js |
| 73 | + `providers` list.** Rebuild the `providers.js` file. See the [Providers](#providers) |
| 74 | + section for more information. |
| 75 | +1. **Run `package["angular-cli"].scripts["install"]` if it exists.** If the script fails, |
| 76 | + run `npm uninstall ${libName}` and fail the install process. |
| 77 | +1. **Detect if a package named `angular/cli-wrapper-${libName}` exist in the angular |
| 78 | + organization.** If so, run the steps above as if ng install angular/angular-${libName}. If |
| 79 | + this install fails, ignore the failure. |
| 80 | + |
| 81 | + These packages can be used to wrap libraries that we want to support but can't update |
| 82 | + easily, like Jasmine or LESS. |
| 83 | +1. **Install typings.** See the [Typings](#typings) section. |
| 84 | +1. **Run `postinstall` scripts.** |
| 85 | + |
| 86 | +# Proof of Concept |
| 87 | +A proof of concept is being developed. |
| 88 | + |
| 89 | +# Hooks |
| 90 | +The third party library can implement hooks into the scaffolding, and the build system. The |
| 91 | +CLI's tasks will look for the proper hooks prior to running and execute them. |
| 92 | + |
| 93 | +The order of execution of these hooks is breadth first, going through all node packages and |
| 94 | +checking for the `package['angular-cli']['hooks']['${hookName}']`. The hooks are then |
| 95 | +`require()`'d as is, from within the app root folder. Within the same level of the dependency |
| 96 | +tree, there is no guarantee for the order of execution. |
| 97 | + |
| 98 | +## Install Hooks |
| 99 | +The only tricky part here is install hooks. The installed package should recursively call |
| 100 | +its `(pre|post|)install` hooks only on packages that are newly installed. It should call |
| 101 | +`(pre|post|)reinstall` on those who were already installed. A map of the currently installed |
| 102 | +packages should be kept before performing `npm install`. |
| 103 | + |
| 104 | +# <a name="appData">appData</a> |
| 105 | +The `angular-cli` key in the generated app should be used for Angular CLI specific data. |
| 106 | +This includes the CLI configuration itself, as well as third-parties library configuration. |
| 107 | + |
| 108 | +Third-parties can store data that will be passed to the app, and can use that data themselves. |
| 109 | +That data can be anything. Any keys starting with either `$` or `_` will be ignored and not |
| 110 | +passed to the client. It could be used for builds or scaffolding information. |
| 111 | + |
| 112 | +During installation, there's a step to prompt the user for data. The schema contains a prompt |
| 113 | +text and a default value. The default value type is used to convert the string entered by the |
| 114 | +user. The key used in `appPrompt` is the key saved in appData. |
| 115 | + |
| 116 | +Example: |
| 117 | + |
| 118 | +```typescript |
| 119 | +{ // ... |
| 120 | + "angular-cli": { |
| 121 | + "appPrompt": { |
| 122 | + "number": { |
| 123 | + "prompt": "Please enter a number:", |
| 124 | + "defaultValue": 0 |
| 125 | + }, |
| 126 | + "url": { |
| 127 | + "prompt": "URL of your website:", |
| 128 | + "defaultValue": "${homepage}" |
| 129 | + } |
| 130 | + } |
| 131 | + } |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +The default value is necessary as a `quiet` mode is enforced, using default values to fill |
| 136 | +in the data without user interaction. The special strings `${...}` can be used to replace |
| 137 | +with special values in the default string values. The values are taken from the `package.json`. |
| 138 | + |
| 139 | +We use a declarative style to enforce sensible defaults and make sure developers think about |
| 140 | +this. *In the case where developers want more complex interaction, they can use the |
| 141 | +install/uninstall hooks to prompt users.* But 3rd party libraries developers need to be aware |
| 142 | +that those hooks might not prompt users (no STDIN) and throw an error. |
| 143 | + |
| 144 | +# <a name="providers">Providers</a> |
| 145 | +Adding Angular providers to the app should be seamless. The install process will create a |
| 146 | +`providers.js` from all the providers contained in all the dependencies. The User can blacklist |
| 147 | +providers it doesn't want. |
| 148 | + |
| 149 | +The `providers.js` file will always be overwritten by the `install` / `uninstall` process. It |
| 150 | +needs to exist for toolings to be able to understand dependencies. These providers are global |
| 151 | +to the application. |
| 152 | + |
| 153 | +In order to blacklist providers from being global, the user can use the `--no-global-providers` |
| 154 | +flag during installation, or can change the dependencies by using `ng providers`. As an example: |
| 155 | + |
| 156 | +```bash |
| 157 | +ng new my-todo-app |
| 158 | +ng generate component database |
| 159 | +ng install --no-global-providers angularfirebase2 |
| 160 | +ng providers database angularfirebase2 |
| 161 | +``` |
| 162 | + |
| 163 | +Or, alternatively, the user can add its own providers and dependencies to its components. |
| 164 | + |
| 165 | +# Dependencies |
| 166 | +Because dependencies are handled by `npm`, we don't have to handle it. |
| 167 | + |
| 168 | +# <a name="typings">Typings</a> |
| 169 | +Typings should be added, but failure to find typings should not be a failure of installation. The |
| 170 | +user might still want to install custom typings himself in the worst case. |
| 171 | + |
| 172 | +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. |
| 173 | + |
| 174 | +# Index.html |
| 175 | +We do not touch the `index.html` file during the installation task. The default page should |
| 176 | +link to a SystemJS configuration file that is auto generated by the CLI and not user |
| 177 | +configurable (see SystemJS below). If the user install a third party library, like jQuery, and |
| 178 | +wants to use it, they have to import it using `import * as $ from 'vendor/jquery'`. |
| 179 | + |
| 180 | +The `index.html` also includes a section to configure SystemJS that can be managed by the user. |
| 181 | +This is separate from the generated code. |
| 182 | + |
| 183 | +# SystemJS |
| 184 | +It is important that SystemJS works without any modifications by the user. It is also important |
| 185 | +to leave the liberty to the user to change the SystemJS configuration so that it fits their needs. |
| 186 | + |
| 187 | +We will not use SystemJS bundles in development. This is for better debugging, future proofing |
| 188 | +(when moving the build system) and better CDN support, as many of the loaded files will end up |
| 189 | +being pulled from a CDN in production. During the `ng build` process for production, the |
| 190 | +SystemJS configuration script will be rebuilt to fetch from the CDN. |
| 191 | + |
| 192 | +# Upgrade Strategy |
| 193 | +The upgrade process simply uses NPM. If new appData is added, it should be added manually using |
| 194 | +a migration hook for `postinstall`. |
| 195 | + |
| 196 | +# Remaining Problems |
| 197 | + |
| 198 | +1. Installing dependencies of packages need to be further sketched out. |
| 199 | +2. Need to add a fully fledged example with Firebase. |
0 commit comments