diff --git a/addon/ng2/models/find-lazy-modules.ts b/addon/ng2/models/find-lazy-modules.ts new file mode 100644 index 000000000000..cc6f13c1dc8e --- /dev/null +++ b/addon/ng2/models/find-lazy-modules.ts @@ -0,0 +1,68 @@ +import * as fs from 'fs'; +import * as glob from 'glob'; +import * as path from 'path'; +import * as ts from 'typescript'; + +import {Observable} from 'rxjs/Observable'; +import {getSource, findNodes, getContentOfKeyLiteral} from '../utilities/ast-utils'; + + +const loadChildrenRegex = /(\{[^{}]+?(loadChildren|['"]loadChildren['"])\s*:\s*)('[^']+'|"[^"]+")/gm; + + +interface Array { + flatMap: (mapFn: (item: T) => Array) => Array; +} +Array.prototype.flatMap = function(mapFn: (item: T) => Array): Array { + if (!mapFn) { + return []; + } + + return this.reduce((arr, current) => { + const result = mapFn.call(null, current); + return result !== undefined ? arr.concat(result) : arr; + }, []); +}; + + +export function findLoadChildren(tsFilePath: string): string[] { + const source = getSource(tsFilePath); + const unique = {}; + + return findNodes(source, ts.SyntaxKind.ObjectLiteralExpression) + .flatMap(node => findNodes(node, ts.SyntaxKind.PropertyAssignment)) + .filter((node: ts.PropertyAssignment) => { + const key = getContentOfKeyLiteral(source, node.name); + if (!key) { + // key is an expression, can't do anything. + return false; + } + return key == 'loadChildren' + }) + // Remove initializers that are not files. + .filter((node: ts.PropertyAssignment) => { + return node.initializer.kind === ts.SyntaxKind.StringLiteral; + }) + // Get the full text of the initiliazer. + .map((node: ts.PropertyAssignment) => { + return eval(node.initializer.getText(source)); + }) + .flatMap((value: string) => unique[value] ? undefined : unique[value] = value) + .map((moduleName: string) => moduleName.split('#')[0]); +} + + +export function findLazyModules(projectRoot: any): string[] { + const allTs = glob.sync(path.join(projectRoot, '/**/*.ts')); + const result = {}; + allTs + .flatMap(tsPath => { + findLoadChildren(tsPath).forEach(moduleName => { + const fileName = path.resolve(path.dirname(tsPath), moduleName) + '.ts'; + if (fs.existsSync(fileName)) { + result[moduleName] = fileName; + } + }); + }); + return result; +} diff --git a/addon/ng2/models/webpack-build-common.ts b/addon/ng2/models/webpack-build-common.ts index 98abd8ebfe5b..e71749a1a2eb 100644 --- a/addon/ng2/models/webpack-build-common.ts +++ b/addon/ng2/models/webpack-build-common.ts @@ -2,23 +2,26 @@ import * as path from 'path'; import * as CopyWebpackPlugin from 'copy-webpack-plugin'; import * as HtmlWebpackPlugin from 'html-webpack-plugin'; import * as webpack from 'webpack'; -import { ForkCheckerPlugin } from 'awesome-typescript-loader'; -import { CliConfig } from './config'; +import * as atl from 'awesome-typescript-loader'; + +import {findLazyModules} from './find-lazy-modules'; -export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, outputDir: string) { - let outputPath: string = path.resolve(projectRoot, outputDir); +export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, outputDir: string) { + const sourceRoot = path.resolve(projectRoot, sourceDir); + const outputPath = path.resolve(projectRoot, outputDir); + const lazyModules = findLazyModules(path.resolve(projectRoot, sourceDir)); return { devtool: 'source-map', resolve: { extensions: ['', '.ts', '.js'], - root: path.resolve(projectRoot, `./${sourceDir}`) + root: sourceRoot }, context: path.resolve(__dirname, './'), entry: { - main: [path.resolve(projectRoot, `./${sourceDir}/main.ts`)], - polyfills: path.resolve(projectRoot, `./${sourceDir}/polyfills.ts`) + main: [path.join(sourceRoot, 'main.ts')], + polyfills: path.join(sourceRoot, 'polyfills.ts') }, output: { path: outputPath, @@ -42,10 +45,9 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, o loader: 'awesome-typescript-loader', query: { useForkChecker: true, - tsconfig: path.resolve(projectRoot, `./${sourceDir}/tsconfig.json`) + tsconfig: path.resolve(sourceRoot, 'tsconfig.json') } - }, - { + }, { loader: 'angular2-template-loader' } ], @@ -61,9 +63,10 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, o ] }, plugins: [ - new ForkCheckerPlugin(), + new webpack.ContextReplacementPlugin(/.*/, sourceRoot, lazyModules), + new atl.ForkCheckerPlugin(), new HtmlWebpackPlugin({ - template: path.resolve(projectRoot, `./${sourceDir}/index.html`), + template: path.resolve(sourceRoot, 'index.html'), chunksSortMode: 'dependency' }), new webpack.optimize.CommonsChunkPlugin({ @@ -79,7 +82,7 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, o context: path.resolve(projectRoot, './public'), from: '**/*', to: outputPath - }]) + }]), ], node: { fs: 'empty', @@ -90,4 +93,4 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, o setImmediate: false } } -}; +} diff --git a/addon/ng2/utilities/ast-utils.ts b/addon/ng2/utilities/ast-utils.ts index c3c52b4d4476..73c877a4f7a4 100644 --- a/addon/ng2/utilities/ast-utils.ts +++ b/addon/ng2/utilities/ast-utils.ts @@ -57,21 +57,37 @@ export function getSourceNodes(sourceFile: ts.SourceFile): Observable { /** -* Find all nodes from the AST in the subtree of node of SyntaxKind kind. -* @param node -* @param kind -* @return all nodes of kind, or [] if none is found + * Find all nodes from the AST in the subtree of node of SyntaxKind kind. + * @param node + * @param kind + * @param max The maximum number of items to return. + * @return all nodes of kind, or [] if none is found */ -export function findNodes(node: ts.Node, kind: ts.SyntaxKind): ts.Node[] { - if (!node) { +export function findNodes(node: ts.Node, kind: ts.SyntaxKind, max: number = Infinity): ts.Node[] { + if (!node || max == 0) { return []; } + let arr: ts.Node[] = []; if (node.kind === kind) { arr.push(node); + max--; + } + if (max > 0) { + for (const child of node.getChildren()) { + findNodes(child, kind, max).forEach(node => { + if (max > 0) { + arr.push(node); + } + max--; + }); + + if (max <= 0) { + break; + } + } } - return node.getChildren().reduce((foundNodes, child) => - foundNodes.concat(findNodes(child, kind)), arr); + return arr; } @@ -111,9 +127,25 @@ export function insertAfterLastOccurrence(nodes: ts.Node[], toInsert: string, } +export function getContentOfKeyLiteral(source: ts.SourceFile, node: ts.Node): string { + if (node.kind == ts.SyntaxKind.Identifier) { + return (node).text; + } else if (node.kind == ts.SyntaxKind.StringLiteral) { + try { + return JSON.parse(node.getFullText(source)) + } catch (e) { + return null; + } + } else { + return null; + } +} + + export function getDecoratorMetadata(source: ts.SourceFile, identifier: string, module: string): Observable { const symbols = new Symbols(source); + return getSourceNodes(source) .filter(node => { return node.kind == ts.SyntaxKind.Decorator diff --git a/package.json b/package.json index cd1167db903f..7368d6560011 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ }, "homepage": "https://github.com/angular/angular-cli", "dependencies": { + "@angular/core": "^2.0.0-rc.5", "@angular/tsc-wrapped": "^0.2.2", "@types/lodash": "^4.0.25-alpha", "@types/rimraf": "0.0.25-alpha", @@ -81,6 +82,7 @@ "silent-error": "^1.0.0", "source-map-loader": "^0.1.5", "sourcemap-istanbul-instrumenter-loader": "^0.2.0", + "string-replace-loader": "^1.0.3", "style-loader": "^0.13.1", "stylus": "^0.54.5", "stylus-loader": "^2.1.0",