126 |
127 |
128 |
129 |
130 |
131 |
132 | File
133 | |
134 |
135 | Purpose
136 | |
137 |
138 |
139 |
140 | demo/app/app.component.ts
141 | |
142 |
143 | A demo component that renders the library component and a value from the library service.
144 | |
145 |
146 |
147 |
148 | demo/app/app.module.ts
149 | |
150 |
151 | A demo NgModule that imports the Library LibModule .
152 | |
153 |
154 |
155 |
156 | lib/src/component/app.component.ts
157 | |
158 |
159 | A sample library component that renders an h2 tag.
160 | |
161 |
162 |
163 |
164 | lib/src/service/lib.service.ts
165 | |
166 |
167 | A sample library service that exports a value.
168 | |
169 |
170 |
171 |
172 | lib/src/module.ts
173 | |
174 |
175 | The library's main NgModule , LibModule .
176 | |
177 |
178 |
179 |
180 | lib/index.ts
181 | |
182 |
183 | The public API of your library, where you choose what to export to consumers.
184 | |
185 |
186 |
187 |
188 |
189 | ## The build step
190 |
191 | You can build the library by running `npm run build`.
192 | This will generate a `dist/` directory with all the entry points described above.
193 |
194 | All the logic for creating the build can be found in `./build.js`. It consists of roughly 5 steps:
195 |
196 | - Compile with the AOT Compiler (AOT compiler or `ngc`) for ES5 and ES2015.
197 | - Inline html and css inside the generated JavaScript files.
198 | - Copy typings, metatada, html and css.
199 | - Create each bundle using Rollup.
200 | - Copy `LICENSE`, `package.json` and `README.md` files
201 |
202 |
203 | ## Testing libraries
204 |
205 | While testing is always important, it's **especially** important in libraries because consumer
206 | applications might break due to bugs in libraries.
207 |
208 | But the fact that a library is consumed by another application is also what makes it hard to test.
209 |
210 | To properly test a library, you need to have an integration tests.
211 | An integration test is to libraries what an end-to-end test is to applications.
212 | It tests how an app would install and use your library.
213 |
214 | The **QuickStart Library seed** includes a directory called `integration` containing a standalone
215 | app that consumes your built library in both AOT and JIT modes, with end-to-end tests to verify
216 | it works.
217 |
218 | To run the integration tests, do `npm run integration` which does the following:
219 | - Build your library.
220 | - Enter the integration app's directory.
221 | - Install dependencies.
222 | - Build the app in AOT mode.
223 | - Test the app in AOT mode.
224 | - Test the app in JIT mode.
225 |
226 | Running integration tests gives you greater confidence that your library is properly built.
227 |
228 | In addition to integration tests, you can also run unit tests in watch mode via `npm run test`,
229 | or single-run via `npm run test:once`.
230 |
231 | You can also test your library by installing it in another local repository you have.
232 | To do so, first build your lib via `npm run build`.
233 | Then install it from your other repo using a relative path to the dist folder:
234 | `npm install relative/path/to/library/dist`.
235 |
236 |
237 | ## Publishing your library
238 |
239 | Every package on NPM has a unique name, and so should yours.
240 | If you haven't already, now is the time to change the name of your library.
241 |
242 | Use your editor to search the project for all instances of `angular-quickstart-lib` and change it
243 | to your intended name (also in `dash-case` format).
244 | The library name is mentioned on at least these files:
245 |
246 | - `integration/src/app/app.component.ts`
247 | - `integration/src/app/app.module.ts`
248 | - `integration/src/systemjs.config.js`
249 | - `integrations/package.json`
250 | - `src/demo/app/app.component.ts`
251 | - `src/demo/app/app.module.ts`
252 | - `src/demo/systemjs.config.js`
253 | - `src/demo/tsconfig.json`
254 | - `src/lib/tsconfig.json`
255 | - `src/lib/tsconfig.es5.json`
256 | - `bs-config.json`
257 | - `package.json`
258 | - `README.md`
259 |
260 | You'll also need to rename the folder your project is in.
261 |
262 | After you have changed the package name, you can publish it to NPM (read
263 | [this link](https://docs.npmjs.com/getting-started/publishing-npm-packages) for details).
264 |
265 | Instead of following the `Updating the package` on that previous doc, here we use
266 | [standard-version](https://github.com/conventional-changelog/standard-version).
267 | Read their docs to see how to use it.
268 |
269 | First you'll need to create a NPM account and login on your local machine.
270 | Then you can publish your package by running `npm publish dist/`.
271 | Since your package is built on the `dist/` folder this is the one you should publish.
272 |
273 |
274 |
275 |
276 | ### Be a good library maintainer
277 |
278 | Now that you've published a library, you need to maintain it as well.
279 | Below are some of the most important points:
280 |
281 | - Document your library.
282 | - Keep an eye on the issue tracker.
283 | - [Manage your dependencies properly](#appendix-dependency-management)
284 | - Follow [Semantic Versioning](http://semver.org/)
285 | - Setup a Continuous Integration solution to test your library (included is a `.travis.yml`
286 | file for [Travis CI](https://docs.travis-ci.com/user/getting-started/))!
287 | - Choose an [appropriate license](https://choosealicense.com/).
288 |
289 |
290 |
291 |
292 | ## Appendix: Supporting AOT
293 |
294 | AOT plays an important role in optimizing Angular applications.
295 | It's therefore important that third party libraries be published in a format compatible with AOT
296 | compilation.
297 | Otherwise it will not be possible to include the library in an AOT compiled application.
298 |
299 | Only code written in TypeScript can be AOT compiled.
300 |
301 | Before publishing the library must first be compiled using the AOT compiler (`ngc`).
302 | `ngc` extends the `tsc` compiler by adding extensions to support AOT compilation in addition to
303 | regular TypeScript compilation.
304 |
305 | AOT compilation outputs three files that must be included in order to be compatible with AOT.
306 |
307 | *Transpiled JavaScript*
308 |
309 | As usual the original TypeScript is transpiled to regular JavaScript.
310 |
311 | *Typings files*
312 |
313 | JavaScript has no way of representing typings.
314 | In order to preserve the original typings, `ngc` will generate `.d.ts` typings files.
315 |
316 | *Meta Data JSON files*
317 |
318 | `ngc` outputs a metadata.json file for every `Component` and `NgModule`.
319 | These meta data files represent the information in the original `NgModule` and `Component`
320 | decorators.
321 |
322 | The meta data may reference external templates or css files.
323 | These external files must be included with the library.
324 |
325 | ### NgFactories
326 |
327 | `ngc` generates a series of files with an `.ngfactory` suffix as well.
328 | These files represent the AOT compiled source, but should not be included with the published library.
329 |
330 | Instead the `ngc` compiler in the consuming application will generate `.ngfactory` files based
331 | on the JavaScript, Typings and meta data shipped with the library.
332 |
333 | ### Why not publish TypeScript?
334 |
335 | Why not ship TypeScript source instead?
336 | After all the library will be part of another TypeScript compilation step when the library is
337 | imported by the consuming application.
338 |
339 | Generally it's discouraged to ship TypeScript with third party libraries.
340 | It would require the consumer to replicate the complete build environment of the library.
341 | Not only typings, but potentially a specific version of `ngc` as well.
342 |
343 | Publishing plain JavaScript with typings and meta data allows the consuming application to
344 | remain agnostic of the library's build environment.
345 |
346 |
347 | ## Appendix: Supporting JIT
348 |
349 | AOT compiled code is the preferred format for production builds, but due to the long compilation
350 | time it may not be practical to use AOT during development.
351 |
352 | To create a more flexible developer experience a JIT compatible build of the library should be
353 | published as well.
354 | The format of the JIT bundle is `umd`, which stands for Universal Module Definition.
355 | Shipping the bundle as `umd` ensures compatibility with most common module loading formats.
356 |
357 | The `umd` bundle will ship as a single file containing ES5 JavaScript and inlined versions of
358 | any external templates or css.
359 |
360 |
361 | ## Appendix: Dependency Management
362 |
363 | As a library maintainer, it's important to properly manage your dependencies in `package.json`.
364 |
365 | There are [three kinds of dependencies](https://docs.npmjs.com/files/package.json#dependencies):
366 | `dependencies`, `devDependencies` and `peerDependencies`.
367 |
368 | - `dependencies`: here go all the other libraries yours depends on when being used.
369 | A good way to figure out these is to go through your library source code (in `src/lib` **only**)
370 | and list all the libraries there.
371 | - `devDependencies`: libraries that you need while developing, testing and building your library
372 | go here.
373 | When a user installs your library, these won't be installed.
374 | Users don't need to develop, build or test your library, they just need to run it.
375 | - `peerDependencies`: these are similar to `dependencies` since your library expects them to be
376 | there at runtime.
377 | The difference is that you don't want to install a new version of these, but instead use
378 | the one already available.
379 |
380 | A good example of a peer dependency is `@angular/core` and all other main Angular libraries.
381 | If you listed these in `dependencies`, a new one - with a different version! - could be installed
382 | for your library to use.
383 | This isn't what you wanted though. You want your library to use *the exact same* `@angular/core`
384 | that the app is using.
385 |
386 | You'll usually used `@angular/*` libraries listed in both `devDependencies` and
387 | `peerDependencies`.
388 | This is normal and expected, because when you're developing your library also need a copy of
389 | them installed.
390 |
391 | Another thing to remember is to keep your dependencies from changing too much unexpectedly.
392 | Different versions of libraries can have different features, and if you inadvertently are too
393 | lenient with allowed versions your library might stop working because a dependency changed.
394 |
395 | You can choose what versions you allow by using [ranges](https://docs.npmjs.com/misc/semver).
396 |
397 | A good rule of thumb is to have all `dependencies` specified with a tilde `~`(`~1.2.3`),
398 | while your `peerDependencies` have a range (`"@angular/core": ">=4.0.0 <5.0.0 || >=4.0.0-beta <5.0.0"`).
399 |
400 | Any extra dependency or peer dependency that you add to `package.json` should also be added
401 | to the `globals` and `external` array in the `rollupBaseConfig` variable in `./build.js`.
402 |
403 | This ensures your library doesn't package extra libraries inside of it and instead uses the ones
404 | available in the consuming app.
405 |
--------------------------------------------------------------------------------
/template/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const glob = require('glob');
6 | const camelCase = require('camelcase');
7 | const ngc = require('@angular/compiler-cli/src/main').main;
8 | const rollup = require('rollup');
9 | const uglify = require('rollup-plugin-uglify');
10 | const sourcemaps = require('rollup-plugin-sourcemaps');
11 |
12 | const inlineResources = require('./inline-resources');
13 |
14 |
15 | const libName = require('./package.json').name;
16 | const rootFolder = path.join(__dirname);
17 | const compilationFolder = path.join(rootFolder, 'out-tsc');
18 | const srcFolder = path.join(rootFolder, 'src/lib');
19 | const distFolder = path.join(rootFolder, 'dist');
20 | const tempLibFolder = path.join(compilationFolder, 'lib');
21 | const es5OutputFolder = path.join(compilationFolder, 'lib-es5');
22 | const es2015OutputFolder = path.join(compilationFolder, 'lib-es2015');
23 |
24 | return Promise.resolve()
25 | // Copy library to temporary folder and inline html/css.
26 | .then(() => _relativeCopy(`**/*`, srcFolder, tempLibFolder)
27 | .then(() => inlineResources(tempLibFolder))
28 | .then(() => console.log('Inlining succeeded.'))
29 | )
30 | // Compile to ES2015.
31 | .then(() => ngc({ project: `${tempLibFolder}/tsconfig.lib.json` })
32 | .then(exitCode => exitCode === 0 ? Promise.resolve() : Promise.reject())
33 | .then(() => console.log('ES2015 compilation succeeded.'))
34 | )
35 | // Compile to ES5.
36 | .then(() => ngc({ project: `${tempLibFolder}/tsconfig.es5.json` })
37 | .then(exitCode => exitCode === 0 ? Promise.resolve() : Promise.reject())
38 | .then(() => console.log('ES5 compilation succeeded.'))
39 | )
40 | // Copy typings and metadata to `dist/` folder.
41 | .then(() => Promise.resolve()
42 | .then(() => _relativeCopy('**/*.d.ts', es2015OutputFolder, distFolder))
43 | .then(() => _relativeCopy('**/*.metadata.json', es2015OutputFolder, distFolder))
44 | .then(() => console.log('Typings and metadata copy succeeded.'))
45 | )
46 | // Bundle lib.
47 | .then(() => {
48 | // Base configuration.
49 | const es5Entry = path.join(es5OutputFolder, `${libName}.js`);
50 | const es2015Entry = path.join(es2015OutputFolder, `${libName}.js`);
51 | const rollupBaseConfig = {
52 | moduleName: camelCase(libName),
53 | sourceMap: true,
54 | // ATTENTION:
55 | // Add any dependency or peer dependency your library to `globals` and `external`.
56 | // This is required for UMD bundle users.
57 | globals: {
58 | // The key here is library name, and the value is the the name of the global variable name
59 | // the window object.
60 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#globals for more.
61 | '@angular/core': 'ng.core'
62 | },
63 | external: [
64 | // List of dependencies
65 | // See https://github.com/rollup/rollup/wiki/JavaScript-API#external for more.
66 | '@angular/core'
67 | ],
68 | plugins: [
69 | sourcemaps()
70 | ]
71 | };
72 |
73 | // UMD bundle.
74 | const umdConfig = Object.assign({}, rollupBaseConfig, {
75 | entry: es5Entry,
76 | dest: path.join(distFolder, `bundles`, `${libName}.umd.js`),
77 | format: 'umd',
78 | });
79 |
80 | // Minified UMD bundle.
81 | const minifiedUmdConfig = Object.assign({}, rollupBaseConfig, {
82 | entry: es5Entry,
83 | dest: path.join(distFolder, `bundles`, `${libName}.umd.min.js`),
84 | format: 'umd',
85 | plugins: rollupBaseConfig.plugins.concat([uglify({})])
86 | });
87 |
88 | // ESM+ES5 flat module bundle.
89 | const fesm5config = Object.assign({}, rollupBaseConfig, {
90 | entry: es5Entry,
91 | dest: path.join(distFolder, `${libName}.es5.js`),
92 | format: 'es'
93 | });
94 |
95 | // ESM+ES2015 flat module bundle.
96 | const fesm2015config = Object.assign({}, rollupBaseConfig, {
97 | entry: es2015Entry,
98 | dest: path.join(distFolder, `${libName}.js`),
99 | format: 'es'
100 | });
101 |
102 | const allBundles = [
103 | umdConfig,
104 | minifiedUmdConfig,
105 | fesm5config,
106 | fesm2015config
107 | ].map(cfg => rollup.rollup(cfg).then(bundle => bundle.write(cfg)));
108 |
109 | return Promise.all(allBundles)
110 | .then(() => console.log('All bundles generated successfully.'))
111 | })
112 | // Copy package files
113 | .then(() => Promise.resolve()
114 | .then(() => _relativeCopy('LICENSE', rootFolder, distFolder))
115 | .then(() => _relativeCopy('package.json', rootFolder, distFolder))
116 | .then(() => _relativeCopy('README.md', rootFolder, distFolder))
117 | .then(() => console.log('Package files copy succeeded.'))
118 | )
119 | .catch(e => {
120 | console.error('\Build failed. See below for errors.\n');
121 | console.error(e);
122 | process.exit(1);
123 | });
124 |
125 |
126 | // Copy files maintaining relative paths.
127 | function _relativeCopy(fileGlob, from, to) {
128 | return new Promise((resolve, reject) => {
129 | glob(fileGlob, { cwd: from, nodir: true }, (err, files) => {
130 | if (err) reject(err);
131 | files.forEach(file => {
132 | const origin = path.join(from, file);
133 | const dest = path.join(to, file);
134 | const data = fs.readFileSync(origin, 'utf-8');
135 | _recursiveMkDir(path.dirname(dest));
136 | fs.writeFileSync(dest, data);
137 | resolve();
138 | })
139 | })
140 | });
141 | }
142 |
143 | // Recursively create a dir.
144 | function _recursiveMkDir(dir) {
145 | if (!fs.existsSync(dir)) {
146 | _recursiveMkDir(path.dirname(dir));
147 | fs.mkdirSync(dir);
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/template/inline-resources.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const glob = require('glob');
6 |
7 |
8 | /**
9 | * Simple Promiseify function that takes a Node API and return a version that supports promises.
10 | * We use promises instead of synchronized functions to make the process less I/O bound and
11 | * faster. It also simplifies the code.
12 | */
13 | function promiseify(fn) {
14 | return function () {
15 | const args = [].slice.call(arguments, 0);
16 | return new Promise((resolve, reject) => {
17 | fn.apply(this, args.concat([function (err, value) {
18 | if (err) {
19 | reject(err);
20 | } else {
21 | resolve(value);
22 | }
23 | }]));
24 | });
25 | };
26 | }
27 |
28 | const readFile = promiseify(fs.readFile);
29 | const writeFile = promiseify(fs.writeFile);
30 |
31 | /**
32 | * Inline resources in a tsc/ngc compilation.
33 | * @param projectPath {string} Path to the project.
34 | */
35 | function inlineResources(projectPath) {
36 |
37 | // Match only TypeScript files in projectPath.
38 | const files = glob.sync('**/*.ts', {cwd: projectPath});
39 |
40 | // For each file, inline the templates and styles under it and write the new file.
41 | return Promise.all(files.map(filePath => {
42 | const fullFilePath = path.join(projectPath, filePath);
43 | return readFile(fullFilePath, 'utf-8')
44 | .then(content => inlineResourcesFromString(content, url => {
45 | // Resolve the template url.
46 | return path.join(path.dirname(fullFilePath), url);
47 | }))
48 | .then(content => writeFile(fullFilePath, content))
49 | .catch(err => {
50 | console.error('An error occured: ', err);
51 | });
52 | }));
53 | }
54 |
55 | /**
56 | * Inline resources from a string content.
57 | * @param content {string} The source file's content.
58 | * @param urlResolver {Function} A resolver that takes a URL and return a path.
59 | * @returns {string} The content with resources inlined.
60 | */
61 | function inlineResourcesFromString(content, urlResolver) {
62 | // Curry through the inlining functions.
63 | return [
64 | inlineTemplate,
65 | inlineStyle
66 | ].reduce((content, fn) => fn(content, urlResolver), content);
67 | }
68 |
69 | /**
70 | * Inline the templates for a source file. Simply search for instances of `templateUrl: ...` and
71 | * replace with `template: ...` (with the content of the file included).
72 | * @param content {string} The source file's content.
73 | * @param urlResolver {Function} A resolver that takes a URL and return a path.
74 | * @return {string} The content with all templates inlined.
75 | */
76 | function inlineTemplate(content, urlResolver) {
77 | return content.replace(/templateUrl:\s*'([^']+?\.html)'/g, function (m, templateUrl) {
78 | const templateFile = urlResolver(templateUrl);
79 | const templateContent = fs.readFileSync(templateFile, 'utf-8');
80 | const shortenedTemplate = templateContent
81 | .replace(/([\n\r]\s*)+/gm, ' ')
82 | .replace(/"/g, '\\"');
83 | return `template: "${shortenedTemplate}"`;
84 | });
85 | }
86 |
87 |
88 | /**
89 | * Inline the styles for a source file. Simply search for instances of `styleUrls: [...]` and
90 | * replace with `styles: [...]` (with the content of the file included).
91 | * @param urlResolver {Function} A resolver that takes a URL and return a path.
92 | * @param content {string} The source file's content.
93 | * @return {string} The content with all styles inlined.
94 | */
95 | function inlineStyle(content, urlResolver) {
96 | return content.replace(/styleUrls:\s*(\[[\s\S]*?\])/gm, function (m, styleUrls) {
97 | const urls = eval(styleUrls);
98 | return 'styles: ['
99 | + urls.map(styleUrl => {
100 | const styleFile = urlResolver(styleUrl);
101 | const styleContent = fs.readFileSync(styleFile, 'utf-8');
102 | const shortenedStyle = styleContent
103 | .replace(/([\n\r]\s*)+/gm, ' ')
104 | .replace(/"/g, '\\"');
105 | return `"${shortenedStyle}"`;
106 | })
107 | .join(',\n')
108 | + ']';
109 | });
110 | }
111 |
112 | module.exports = inlineResources;
113 | module.exports.inlineResourcesFromString = inlineResourcesFromString;
114 |
115 | // Run inlineResources if module is being called directly from the CLI with arguments.
116 | if (require.main === module && process.argv.length > 2) {
117 | console.log('Inlining resources from project:', process.argv[2]);
118 | return inlineResources(process.argv[2]);
119 | }
120 |
--------------------------------------------------------------------------------
/template/integration/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | src/**/*.js
4 | !src/systemjs.config.js
5 | !src/systemjs-angular-loader.js
6 | *.js.map
7 | e2e/**/*.js
8 | e2e/**/*.js.map
9 | out-tsc/*
10 | dist/*
11 |
--------------------------------------------------------------------------------
/template/integration/README.md:
--------------------------------------------------------------------------------
1 | # Integration App
2 |
3 | This is a simplified version of https://github.com/angular/quickstart used to test the built lib.
4 |
5 | ## npm scripts
6 |
7 | We've captured many of the most useful commands in npm scripts defined in the `package.json`:
8 |
9 | * `npm start` - runs the compiler and a server at the same time, both in "watch mode".
10 | * `npm run e2e` - compiles the app and run e2e tests.
11 | * `npm run e2e:aot` - compiles and the app with AOT and run e2e tests.
12 |
13 |
14 | If you need to manually test a library build, follow these steps:
15 | ```
16 | # starting at the project root, build the library
17 | npm run build
18 | # clean the integration app
19 | npm run preintegration
20 | cd integration
21 | npm install
22 | ```
23 |
24 | Now the library is installed in your integration app.
25 |
26 | You can use `npm start` to start a live reload server running the app in JIT mode, or `npm run build && npm run serve:aot` to run a static server in AOT mode.
27 |
--------------------------------------------------------------------------------
/template/integration/bs-config.aot.json:
--------------------------------------------------------------------------------
1 | {
2 | "server": {
3 | "baseDir": "dist"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/template/integration/bs-config.e2e-aot.json:
--------------------------------------------------------------------------------
1 | {
2 | "open": false,
3 | "logLevel": "silent",
4 | "port": 8080,
5 | "server": {
6 | "baseDir": "dist",
7 | "middleware": {
8 | "0": null
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/template/integration/bs-config.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "open": false,
3 | "logLevel": "silent",
4 | "port": 8080,
5 | "server": {
6 | "baseDir": "src",
7 | "routes": {
8 | "/node_modules": "node_modules"
9 | },
10 | "middleware": {
11 | "0": null
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/template/integration/bs-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "server": {
3 | "baseDir": "src",
4 | "routes": {
5 | "/node_modules": "node_modules"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/template/integration/build.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const glob = require('glob');
4 | const rollup = require('rollup');
5 | const uglify = require('rollup-plugin-uglify');
6 | const commonjs = require('rollup-plugin-commonjs');
7 | const nodeResolve = require('rollup-plugin-node-resolve');
8 | const ngc = require('@angular/compiler-cli/src/main').main;
9 |
10 |
11 | const srcDir = path.join(__dirname, 'src/');
12 | const distDir = path.join(__dirname, 'dist/');
13 | const aotDir = path.join(__dirname, 'aot/');
14 | const rollupConfig = {
15 | entry: `${srcDir}/main-aot.js`,
16 | sourceMap: false,
17 | format: 'iife',
18 | onwarn: function (warning) {
19 | // Skip certain warnings
20 | if (warning.code === 'THIS_IS_UNDEFINED') { return; }
21 | // console.warn everything else
22 | console.warn(warning.message);
23 | },
24 | plugins: [
25 | nodeResolve({ jsnext: true, module: true }),
26 | commonjs({
27 | include: ['node_modules/rxjs/**']
28 | }),
29 | uglify()
30 | ]
31 | };
32 |
33 | return Promise.resolve()
34 | // Compile using ngc.
35 | .then(() => ngc({ project: `./tsconfig.aot.json` }))
36 | // Create dist dir.
37 | .then(() => _recursiveMkDir(distDir))
38 | // Copy files.
39 | .then(() => {
40 | // Copy and rename index-aot.html.
41 | fs.createReadStream(path.join(srcDir, 'index-aot.html'))
42 | .pipe(fs.createWriteStream(path.join(distDir, 'index.html')));
43 |
44 | // Copy global stylesheets, images, etc.
45 | const assets = [
46 | 'favicon.ico',
47 | 'styles.css'
48 | ];
49 |
50 | return Promise.all(assets.map(asset => _relativeCopy(asset, srcDir, distDir)));
51 | })
52 | // Bundle app.
53 | .then(() => rollup.rollup(rollupConfig))
54 | // Concatenate app and scripts.
55 | .then(bundle => {
56 | const appBundle = bundle.generate(rollupConfig);
57 |
58 | const scripts = [
59 | 'node_modules/core-js/client/shim.min.js',
60 | 'node_modules/zone.js/dist/zone.min.js'
61 | ];
62 |
63 | let concatenatedScripts = scripts.map((script) => {
64 | return fs.readFileSync(path.join(__dirname, script)).toString();
65 | }).join('\n;');
66 |
67 | concatenatedScripts = concatenatedScripts.concat('\n;', appBundle.code);
68 |
69 | fs.writeFileSync(path.join(distDir, 'bundle.js'), concatenatedScripts);
70 | });
71 |
72 |
73 |
74 | // Copy files maintaining relative paths.
75 | function _relativeCopy(fileGlob, from, to) {
76 | return glob(fileGlob, { cwd: from, nodir: true }, (err, files) => {
77 | if (err) throw err;
78 | files.forEach(file => {
79 | const origin = path.join(from, file);
80 | const dest = path.join(to, file);
81 | _recursiveMkDir(path.dirname(dest));
82 | fs.createReadStream(origin).pipe(fs.createWriteStream(dest));
83 | })
84 | })
85 | }
86 |
87 | // Recursively create a dir.
88 | function _recursiveMkDir(dir) {
89 | if (!fs.existsSync(dir)) {
90 | _recursiveMkDir(path.dirname(dir));
91 | fs.mkdirSync(dir);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/template/integration/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 |
3 | describe('QuickStart Lib E2E Tests', function () {
4 |
5 | beforeEach(() => browser.get(''));
6 |
7 | afterEach(() => {
8 | browser.manage().logs().get('browser').then((browserLog: any[]) => {
9 | expect(browserLog).toEqual([]);
10 | });
11 | });
12 |
13 | it('should display lib', () => {
14 | expect(element(by.css('h2')).getText()).toEqual('Hello Angular Library');
15 | });
16 |
17 | it('should display meaning', () => {
18 | expect(element(by.css('h3')).getText()).toEqual('Meaning is: 42');
19 | });
20 |
21 | });
22 |
--------------------------------------------------------------------------------
/template/integration/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "lib": [ "es2015", "dom" ],
10 | "noImplicitAny": true,
11 | "suppressImplicitAnyIndexErrors": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/template/integration/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "integration-test",
3 | "version": "1.0.0",
4 | "description": "App for integration tests",
5 | "scripts": {
6 | "clean": "rimraf aot/ dist/ node_modules/quickstart-lib/",
7 | "build": "tsc -p src/",
8 | "build:watch": "tsc -p src/ -w",
9 | "build:e2e": "tsc -p e2e/",
10 | "build:aot": "node build.js",
11 | "serve": "lite-server -c=bs-config.json",
12 | "serve:aot": "lite-server -c bs-config.aot.json",
13 | "serve:e2e": "lite-server -c=bs-config.e2e.json",
14 | "serve:e2e-aot": "lite-server -c bs-config.e2e-aot.json",
15 | "prestart": "npm run build",
16 | "start": "concurrently \"npm run build:watch\" \"npm run serve\"",
17 | "pree2e": "npm run build:e2e && npm run build",
18 | "e2e": "concurrently \"npm run serve:e2e\" \"npm run protractor\" --kill-others --success first",
19 | "pree2e:aot": "npm run build:e2e && npm run build:aot",
20 | "e2e:aot": "concurrently \"npm run serve:e2e-aot\" \"npm run protractor\" --kill-others --success first",
21 | "preprotractor": "webdriver-manager update",
22 | "protractor": "protractor protractor.config.js"
23 | },
24 | "keywords": [],
25 | "author": "",
26 | "license": "MIT",
27 | "dependencies": {
28 | "@angular/common": "~4.2.6",
29 | "@angular/compiler": "~4.2.6",
30 | "@angular/compiler-cli": "~4.2.6",
31 | "@angular/core": "~4.2.6",
32 | "@angular/platform-browser": "~4.2.6",
33 | "@angular/platform-browser-dynamic": "~4.2.6",
34 | "quickstart-lib": "../dist/",
35 | "core-js": "^2.4.1",
36 | "rxjs": "5.0.1",
37 | "systemjs": "0.19.40",
38 | "zone.js": "^0.8.4"
39 | },
40 | "devDependencies": {
41 | "@types/jasmine": "2.5.36",
42 | "concurrently": "^3.4.0",
43 | "jasmine-core": "~2.4.1",
44 | "glob": "^7.1.1",
45 | "lite-server": "^2.2.2",
46 | "protractor": "~5.1.0",
47 | "rimraf": "^2.5.4",
48 | "rollup": "^0.42.0",
49 | "rollup-plugin-commonjs": "^8.0.2",
50 | "rollup-plugin-node-resolve": "3.0.0",
51 | "rollup-plugin-uglify": "^2.0.1",
52 | "typescript": "~2.3.0"
53 | },
54 | "repository": {}
55 | }
56 |
--------------------------------------------------------------------------------
/template/integration/protractor.config.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | allScriptsTimeout: 11000,
3 | specs: [
4 | './e2e/**/*.e2e-spec.js'
5 | ],
6 | capabilities: {
7 | 'browserName': 'chrome'
8 | },
9 | directConnect: true,
10 | baseUrl: 'http://localhost:8080/',
11 | framework: 'jasmine'
12 | };
13 |
--------------------------------------------------------------------------------
/template/integration/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |