Skip to content

Commit 1c80e44

Browse files
committed
design(): 3rd Party Library Design Doc.
1 parent b128906 commit 1c80e44

File tree

1 file changed

+199
-0
lines changed

1 file changed

+199
-0
lines changed

docs/design/third-party-libraries.md

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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 full fledged example with Firebase.

0 commit comments

Comments
 (0)