From 8d8e2e8cce389a9de6da904d14c4f75d4ecad3c7 Mon Sep 17 00:00:00 2001 From: Colin Skow Date: Mon, 16 Jan 2017 01:50:39 -0800 Subject: [PATCH] (enhancement) TS compiles only dependencies instead of entire project --- .gitignore | 1 + README.md | 13 +++++++ config/specify-ts-files-plugin/index.js | 35 +++++++++++++++++++ config/webpack.common.js | 46 +++++++++++++++++++------ package.json | 2 +- src/app/lazy-loaded.ts | 7 ++++ src/main.browser.aot.ts | 1 + src/main.browser.ts | 1 + tsconfig.webpack.json | 6 ---- tslint.json | 1 + 10 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 config/specify-ts-files-plugin/index.js create mode 100644 src/app/lazy-loaded.ts diff --git a/.gitignore b/.gitignore index 5525b5026f..b0db1067d8 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ npm-debug.log /dist/** /.awcache .webpack.json +*.temp.json /compiled/ dll/ diff --git a/README.md b/README.md index d8340e40e0..636ea077a8 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ go to [http://0.0.0.0:3000](http://0.0.0.0:3000) or [http://localhost:3000](http * [Configuration](#configuration) * [AoT Don'ts](#aot-donts) * [External Stylesheets](#external-stylesheets) +* [Lazy Loading](#lazy-loading) * [Contributing](#contributing) * [TypeScript](#typescript) * [@Types](#types) @@ -241,11 +242,23 @@ The following are some things that will make AoT compile fail. Any stylesheets (Sass or CSS) placed in the `src/styles` directory and imported into your project will automatically be compiled into an external `.css` and embedded in your production builds. For example to use Bootstrap as an external stylesheet: + 1) Create a `styles.scss` file (name doesn't matter) in the `src/styles` directory. 2) `npm install` the version of Boostrap you want. 3) In `styles.scss` add `@import 'bootstrap/scss/bootstrap.scss';` 4) In `src/app/app.module.ts` add underneath the other import statements: `import '../styles/styles.scss';` +# Lazy Loading +When you lazy load a module in your router config, it will go into a separate chunk and the browser won't download the code until the user attempts to navigate to the route. + +You can make a module lazy load by using the `loadChildren` syntax in your route definitions: + +```js +{ path: 'detail', loadChildren: './+detail#DetailModule'} +``` + +To make sure TypeScript compiles your lazy-loaded modules, declare them in `./src/app/lazy-loaded.ts` with an import statement. Declaring the modules allows TypeScript to only compile the necessary files. Previously TS would compile every single `.ts` file in your project tree on every single build which was inefficient and lead to issues. + # Contributing You can include more examples as components but they must introduce a new concept such as `Home` component (separate folders), and Todo (services). I'll accept pretty much everything so feel free to open a Pull-Request diff --git a/config/specify-ts-files-plugin/index.js b/config/specify-ts-files-plugin/index.js new file mode 100644 index 0000000000..28ef9c544c --- /dev/null +++ b/config/specify-ts-files-plugin/index.js @@ -0,0 +1,35 @@ +/* + * Plugin: SpecifyTsFilesPlugin + * + * By default TypeScipt will compile every single .ts file in your project tree. + * This is inefficient and leads to problems. This plugin specifies your Webpack + * entries and any other files you pass in as "files" and writes them to a temporary + * tsconfig.json to use for your build. Only the entry files you pass in and their + * dependencies will get compiled. This makes builds faster and avoids issues. + */ +'use strict'; +const fs = require('fs'); +const path = require('path'); + +function SpecifyTsFilesPlugin(options) { + this.root = options.root || process.cwd(); + this.entry = options.entry; + this.otherFilesToCompile = options.otherFilesToCompile || []; + this.tsConfigBase = options.tsConfigBase || 'tsconfig.json'; + this.customTsConfigFileName = options.customTsConfigFileName || 'tsconfig.temp.json'; +} + +SpecifyTsFilesPlugin.prototype.apply = function(compiler) { + if(!this.entry || typeof this.entry !== 'object') { + throw new Error('SpecifyTsFilesPlugin Error: entry was not specified'); + } + const allFilesToCompile = Object.keys(this.entry) + .map(key => this.entry[key]) + .concat(this.otherFilesToCompile); + const baseConfigData = require(path.join(this.root, this.tsConfigBase)); + const tsConfigContent = Object.assign({}, baseConfigData, {files: allFilesToCompile}); + const customConfigFilePath = path.join(this.root, this.customTsConfigFileName); + fs.writeFileSync(customConfigFilePath, JSON.stringify(tsConfigContent)); +}; + +module.exports = SpecifyTsFilesPlugin; diff --git a/config/webpack.common.js b/config/webpack.common.js index f88b52adbd..24a0e0c9d4 100644 --- a/config/webpack.common.js +++ b/config/webpack.common.js @@ -8,7 +8,6 @@ const helpers = require('./helpers'); /* * Webpack Plugins */ -// problem with copy-webpack-plugin const AssetsPlugin = require('assets-webpack-plugin'); const NormalModuleReplacementPlugin = require('webpack/lib/NormalModuleReplacementPlugin'); const ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin'); @@ -20,6 +19,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const LoaderOptionsPlugin = require('webpack/lib/LoaderOptionsPlugin'); const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin'); const ngcWebpack = require('ngc-webpack'); +const SpecifyTsFilesPlugin = require('./specify-ts-files-plugin'); /* * Webpack Constants @@ -39,6 +39,25 @@ const METADATA = { */ module.exports = function (options) { isProd = options.env === 'production'; + + const entry = { + 'polyfills': './src/polyfills.browser.ts', + 'main': AOT ? './src/main.browser.aot.ts' : + './src/main.browser.ts' + }; + + const otherFilesToCompile = [ + './src/app/app.module.ts', + './src/app/lazy-loaded.ts' + ]; + + const tsConfigBase = 'tsconfig.webpack.json'; + const customTsConfigFileName = 'tsconfig.build.temp.json'; + + const atlConfig = { + configFileName: customTsConfigFileName + }; + return { /* @@ -56,13 +75,7 @@ module.exports = function (options) { * * See: http://webpack.github.io/docs/configuration.html#entry */ - entry: { - - 'polyfills': './src/polyfills.browser.ts', - 'main': AOT ? './src/main.browser.aot.ts' : - './src/main.browser.ts' - - }, + entry: entry, /* * Options affecting the resolving of modules. @@ -103,7 +116,7 @@ module.exports = function (options) { test: /\.ts$/, use: [ '@angularclass/hmr-loader?pretty=' + !isProd + '&prod=' + isProd, - 'awesome-typescript-loader?{configFileName: "tsconfig.webpack.json"}', + 'awesome-typescript-loader?' + JSON.stringify(atlConfig), 'angular2-template-loader', { loader: 'ng-router-loader', @@ -327,8 +340,21 @@ module.exports = function (options) { new ngcWebpack.NgcWebpackPlugin({ disabled: !AOT, - tsConfig: helpers.root('tsconfig.webpack.json'), + tsConfig: helpers.root(customTsConfigFileName), resourceOverride: helpers.root('config/resource-override.js') + }), + + /* + * Plugin: SpecifyTsFilesPlugin + * Description: Generates a temporary tsconfig.json which specifies all the entry files to compile. + * This prevents TypeScript from having to compile every single .ts file in the project tree. + */ + new SpecifyTsFilesPlugin({ + root: helpers.root('.'), + entry: entry, + otherFilesToCompile: otherFilesToCompile, + tsConfigBase: tsConfigBase, + customTsConfigFileName: customTsConfigFileName }) ], diff --git a/package.json b/package.json index 065d507d6f..203c444853 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "clean:aot": "npm run rimraf -- compiled", "clean:dist": "npm run rimraf -- dist", "clean:install": "npm set progress=false && npm install", - "clean": "npm cache clean && npm run rimraf -- node_modules doc coverage dist compiled dll", + "clean": "npm cache clean && npm run rimraf -- node_modules doc coverage dist compiled dll *.temp.json", "docker": "docker", "docs": "npm run typedoc -- --options typedoc.json --exclude '**/*.spec.ts' ./src/", "e2e:live": "npm-run-all -p -r server:prod:ci protractor:live", diff --git a/src/app/lazy-loaded.ts b/src/app/lazy-loaded.ts new file mode 100644 index 0000000000..807c88297b --- /dev/null +++ b/src/app/lazy-loaded.ts @@ -0,0 +1,7 @@ +/* + * Declare your lazy loaded modules here to ensure they are processed by the TypeScript compiler + */ +import './+barrel'; +import './+barrel/+child-barrel'; +import './+detail'; +import './+detail/+child-detail'; diff --git a/src/main.browser.aot.ts b/src/main.browser.aot.ts index 5496805a32..2e87bce8e2 100644 --- a/src/main.browser.aot.ts +++ b/src/main.browser.aot.ts @@ -1,6 +1,7 @@ /* * Angular bootstraping */ +/// import { platformBrowser } from '@angular/platform-browser'; import { decorateModuleRef } from './app/environment'; /* diff --git a/src/main.browser.ts b/src/main.browser.ts index e17eeb63f8..d9b2d47d20 100644 --- a/src/main.browser.ts +++ b/src/main.browser.ts @@ -1,6 +1,7 @@ /* * Angular bootstraping */ +/// import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { decorateModuleRef } from './app/environment'; import { bootloader } from '@angularclass/hmr'; diff --git a/tsconfig.webpack.json b/tsconfig.webpack.json index 994c56f84e..b8256f2cbc 100644 --- a/tsconfig.webpack.json +++ b/tsconfig.webpack.json @@ -22,12 +22,6 @@ "node" ] }, - "exclude": [ - "node_modules", - "dist", - "src/**/*.spec.ts", - "src/**/*.e2e.ts" - ], "awesomeTypescriptLoaderOptions": { "forkChecker": true, "useWebpackText": true diff --git a/tslint.json b/tslint.json index e1f9814cdd..a4c5ff1685 100644 --- a/tslint.json +++ b/tslint.json @@ -21,6 +21,7 @@ "no-forward-ref": true, "no-input-rename": true, "no-output-rename": true, + "no-reference": false, "pipe-naming": [true, "camelCase", "my"], "templates-use-public": true, "use-host-property-decorator": true,