From 182994c282f971d825164533a8c1f933c51a3444 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 10 Aug 2016 19:35:52 -0700 Subject: [PATCH 1/7] First implementation of get lazy modules entries. --- addon/ng2/blueprints/ng2/files/package.json | 1 + addon/ng2/models/find-lazy-modules.ts | 68 ++++ addon/ng2/models/webpack-build-common.ts | 50 ++- .../webpack-plugin-systemjs-registry.ts | 293 ++++++++++++++++++ addon/ng2/utilities/ast-utils.ts | 39 ++- package.json | 2 + 6 files changed, 436 insertions(+), 17 deletions(-) create mode 100644 addon/ng2/models/find-lazy-modules.ts create mode 100644 addon/ng2/models/webpack-plugin-systemjs-registry.ts diff --git a/addon/ng2/blueprints/ng2/files/package.json b/addon/ng2/blueprints/ng2/files/package.json index e1fe192eb6e3..81643a5ec3a3 100644 --- a/addon/ng2/blueprints/ng2/files/package.json +++ b/addon/ng2/blueprints/ng2/files/package.json @@ -22,6 +22,7 @@ "@angular/router": "3.0.0-rc.1", "core-js": "^2.4.0", "rxjs": "5.0.0-beta.6", + "systemjs": "^0.19.36", "ts-helpers": "^1.1.1", "zone.js": "0.6.12" }, 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..ad9ae9061bcd 100644 --- a/addon/ng2/models/webpack-build-common.ts +++ b/addon/ng2/models/webpack-build-common.ts @@ -2,11 +2,13 @@ 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 * as atl from 'awesome-typescript-loader'; import { CliConfig } from './config'; -export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, outputDir: string) { +import {SystemJSRegisterPublicModules} from './webpack-plugin-systemjs-registry'; +import {findLazyModules} from './find-lazy-modules'; +export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { let outputPath: string = path.resolve(projectRoot, outputDir); return { @@ -16,10 +18,7 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, o root: path.resolve(projectRoot, `./${sourceDir}`) }, context: path.resolve(__dirname, './'), - entry: { - main: [path.resolve(projectRoot, `./${sourceDir}/main.ts`)], - polyfills: path.resolve(projectRoot, `./${sourceDir}/polyfills.ts`) - }, + entry: entries, output: { path: outputPath, filename: '[name].bundle.js' @@ -44,8 +43,7 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, o useForkChecker: true, tsconfig: path.resolve(projectRoot, `./${sourceDir}/tsconfig.json`) } - }, - { + }, { loader: 'angular2-template-loader' } ], @@ -61,13 +59,13 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, o ] }, plugins: [ - new ForkCheckerPlugin(), + new atl.ForkCheckerPlugin(), new HtmlWebpackPlugin({ template: path.resolve(projectRoot, `./${sourceDir}/index.html`), chunksSortMode: 'dependency' }), new webpack.optimize.CommonsChunkPlugin({ - name: ['polyfills'] + name: 'polyfills' }), new webpack.optimize.CommonsChunkPlugin({ minChunks: Infinity, @@ -79,7 +77,35 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, o context: path.resolve(projectRoot, './public'), from: '**/*', to: outputPath - }]) + }]), + // new SystemJSRegisterPublicModules({ + // // automatically configure SystemJS to load webpack chunks (defaults to true) + // bundlesConfigForChunks: true, + // + // // select which modules to expose as public modules + // registerModules: [ + // // "default" filters provided are "local" and "public" + // { filter: 'public' }, + // // + // // // keyname allows a custom naming system for public modules + // // { + // // filter: 'local', + // // keyname: 'app/[relPath]' + // // }, + // // + // // // keyname can be a function + // // { + // // filter: 'public', + // // keyname: (module) => 'publicModule-' + module.id + // // }, + // // + // // // filter can also be a function + // // { + // // filter: (m) => m.relPath.match(/src/), + // // keyname: 'random-naming-system-[id]' + // // } + // ] + // }) ], node: { fs: 'empty', @@ -90,4 +116,4 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string, o setImmediate: false } } -}; +} diff --git a/addon/ng2/models/webpack-plugin-systemjs-registry.ts b/addon/ng2/models/webpack-plugin-systemjs-registry.ts new file mode 100644 index 000000000000..f04f1fba98b1 --- /dev/null +++ b/addon/ng2/models/webpack-plugin-systemjs-registry.ts @@ -0,0 +1,293 @@ +var sep = require('path').sep; + + +export class SystemJSRegisterPublicModules { + private registerModules: any; + private bundlesConfigForChunks: any; + + constructor(options: any) { + options = options || {}; + + // default is public modules + this.registerModules = options.registerModules || [{filter: 'public'}]; + + this.bundlesConfigForChunks = + typeof options.bundlesConfigForChunks == 'boolean' ? options.bundlesConfigForChunks : true; + } + + + // given the entry chunk, determine which modules are public + // and create the manifest of public modules and chunks to public modules + // + // id to public name (if public, otherwise undefined) + // manifest.registerModules = ['b', 'a', 'main', 'lodash', undefined, undefined]; + // + // id to boolean, indicating which are ES module objects + // manifest.esModules = [0,0,1,0,0]; + // + // chunk id to list of public module ids in that chunk + // manifest.chunks = [[0, 3]]; + getModuleLoaderManifest(modules, entryChunk, outputOptions, hash) { + var bundlesConfigForChunks = this.bundlesConfigForChunks; + var includes = this.registerModules; + + var manifest = { + registerModules: [], + esModules: [], + chunks: [] + }; + + var existingKeys = []; + + var path = outputOptions.path; + + // convert module objects into structured module objects for our own use + var moduleObjs = modules.map(function (m) { + return { + id: m.id, + request: m.rawRequest || '', + path: m.resource || '', + + relPath: m.resource && m.resource.substr(0, path.length + 1) == path + sep ? m.resource.substr(path.length + 1) : m.resource || '', + + // NB TODO: + // isPackageMain: true / false + // packageName: from package.json / node_modules derivation + // packageVersion: from package.json + + meta: m.meta + }; + }); + + // default filters and naming functions + function publicFilter(module) { + // is this good enough? + return module.request.match(/^@[^\/\\]+\/\\[^\/\\]$|^[^\/\\]+$/); + } + + function localFilter(module) { + // modules outside of the project root are not considered local anymore + if (module.path.substr(0, path.length) != path) + return false; + return !module.path.substr(path.length).match(/(^|\/|\\)node_modules(\/|\\|$)/); + } + + function publicModuleName(module) { + return module.request; + } + + function localModuleName(module) { + return module.relPath; + } + + // determine includes + includes.forEach(function (include, index) { + var filter = include.filter; + var publicKeyFn = include.keyname; + + // public key template function + // we should really do this with better properties than the normal module entries + if (typeof publicKeyFn == 'string') { + var string = publicKeyFn; + publicKeyFn = function (module, existingKeys) { + var str = string; + // allow simple templating + for (var p in module) { + if (module.hasOwnProperty(p)) + str = str.replace('[' + p + ']', module[p]); + } + return str; + } + } + + // default filters + if (filter == 'all') { + filter = function (module) { + return true; + }; + publicKeyFn = publicKeyFn || function (module, existingKeys) { + if (publicFilter(module)) + return publicNames(module); + else + return localNames(module); + }; + } + else if (filter == 'public') { + filter = publicFilter; + publicKeyFn = publicKeyFn || publicModuleName; + } + else if (filter == 'local') { + filter = localFilter; + publicKeyFn = publicKeyFn || localModuleName; + } + + if (!publicKeyFn) + throw new TypeError('SystemJS register public modules plugin has no keyname function defined for filter ' + index); + + moduleObjs.filter(function (m) { + return filter(m, existingKeys); + }).forEach(function (m) { + var publicKey = publicKeyFn(m, existingKeys); + if (typeof publicKey != 'string') + throw new TypeError('SystemJS register public modules plugin did not return a valid key for ' + m.path); + if (existingKeys.indexOf(publicKey) != -1) { + if (manifest.registerModules[m.id] != publicKey) + throw new TypeError('SystemJS register public module ' + publicKey + ' is already defined to another module'); + existingKeys.push(publicKey); + } + manifest.registerModules[m.id] = publicKey; + + if (m.meta.harmonyModule) + manifest.esModules[m.id] = true; + }); + }); + + // build up list of public modules against chunkids + if (bundlesConfigForChunks) { + function visitChunks(chunk, visitor) { + visitor(chunk); + chunk.chunks.forEach(visitor); + } + + visitChunks(entryChunk, function (chunk) { + var publicChunkModuleIds = []; + chunk.modules.forEach(function (module) { + if (manifest.registerModules[module.id]) + publicChunkModuleIds.push(module.id); + }); + + // is it possible for the main entry point to contain multiple chunks? how would we know what these are? + // or is the main compilation always the first chunk? + if (publicChunkModuleIds.length && chunk.id != entryChunk.id) + manifest.chunks[chunk.id] = publicChunkModuleIds; + }); + } + + return manifest; + }; + + apply(compiler) { + var self = this; + + compiler.plugin('compilation', function (compilation) { + var mainTemplate = compilation.mainTemplate; + + mainTemplate.plugin('bootstrap', function (source, chunk, hash) { + var bundlesConfigForChunks = self.bundlesConfigForChunks; + + var publicModuleLoaderManifest = []; + var publicModuleChunks = []; + + var manifest = self.getModuleLoaderManifest(compilation.modules, chunk, this.outputOptions, hash); + + function stringifySparseArray(arr) { + return '[' + arr.map(function (value) { + if (value === undefined) + return ''; + else if (typeof value == 'boolean') + return value ? 1 : 0; + else + return JSON.stringify(value); + }).join(',').replace(/,+$/, '') + ']'; + } + + return this.asString([ + "var publicModuleLoaderManifest = " + stringifySparseArray(manifest.registerModules) + ";", + "var publicESModules = " + stringifySparseArray(manifest.esModules) + ";", + (bundlesConfigForChunks ? "var publicModuleChunks = " + stringifySparseArray(manifest.chunks) + ";" : ""), + source + ]); + }); + + mainTemplate.plugin('add-module', function (source) { + return this.asString([ + source, + "defineIfPublicSystemJSModule(moduleId);" + ]); + }); + + mainTemplate.plugin('require-extensions', function (source, chunk, hash) { + var bundlesConfigForChunks = self.bundlesConfigForChunks; + + var output = [source]; + + if (bundlesConfigForChunks) { + var chunkMaps = chunk.getChunkMaps(); + var chunkFilename = this.outputOptions.chunkFilename; +console.log(chunkMaps); + output.push("var systemJSBundlesConfig = {};"); + output.push("for (var chunkId in publicModuleChunks) {"); + output.push(this.indent([ + "var moduleIds = publicModuleChunks[chunkId];", + "var moduleNames = [];", + "for (var i = 0; i < moduleIds.length; i++)", + this.indent([ + "moduleNames.push(publicModuleLoaderManifest[moduleIds[i]]);", + ]), + + // this is copied from https://github.com/webpack/webpack/blob/master/lib/JsonpMainTemplatePlugin.js#L43 + "systemJSBundlesConfig[" + this.requireFn + ".p + " + + this.applyPluginsWaterfall("asset-path", JSON.stringify(chunkFilename), { + hash: "\" + " + this.renderCurrentHashCode(hash) + " + \"", + hashWithLength: function (length) { + return "\" + " + this.renderCurrentHashCode(hash, length) + " + \""; + }.bind(this), + chunk: { + id: "\" + chunkId + \"", + hash: "\" + " + JSON.stringify(chunkMaps.hash) + "[chunkId] + \"", + hashWithLength: function (length) { + var shortChunkHashMap = {}; + Object.keys(chunkMaps.hash).forEach(function (chunkId) { + if (typeof chunkMaps.hash[chunkId] === "string") + shortChunkHashMap[chunkId] = chunkMaps.hash[chunkId].substr(0, length); + }); + return "\" + " + JSON.stringify(shortChunkHashMap) + "[chunkId] + \""; + }, + name: "\" + (" + JSON.stringify(chunkMaps.name) + "[chunkId]||chunkId) + \"" + } + }) + "] = moduleNames;" + ])); + output.push("}"); + + // this check could potentially left out to assume SystemJS-only and throw otherwise, + // but it seems nice to make it optional + output.push("var hasSystemJS = typeof SystemJS != 'undefined';"); + + output.push("if (hasSystemJS)"); + output.push(this.indent(["SystemJS.config({ bundles: systemJSBundlesConfig });"])); + } + + output.push("function defineIfPublicSystemJSModule(moduleId) {"); + output.push("var publicKey = publicModuleLoaderManifest[moduleId];"); + output.push("if (publicKey && hasSystemJS)"); + output.push(this.indent([ + "if (publicESModules[moduleId])", + this.indent([ + "SystemJS.register(publicKey, [], function($__export) {", + // this could be moved into execution scope + this.indent([ + "$__export(__webpack_require__(moduleId));" + ]), + "});" + ]), + "else", + this.indent([ + "SystemJS.registerDynamic(publicKey, [], false, function() {", + this.indent([ + "return __webpack_require__(moduleId);" + ]), + "});" + ]) + ])); + output.push("}"); + output.push("for (var moduleId in modules)"); + output.push(this.indent([ + "if (Object.prototype.hasOwnProperty.call(modules, moduleId))", + this.indent(["defineIfPublicSystemJSModule(moduleId);"]) + ])); + + return this.asString(output); + }); + }); + } +} \ No newline at end of file diff --git a/addon/ng2/utilities/ast-utils.ts b/addon/ng2/utilities/ast-utils.ts index c3c52b4d4476..2fd85af428db 100644 --- a/addon/ng2/utilities/ast-utils.ts +++ b/addon/ng2/utilities/ast-utils.ts @@ -62,16 +62,29 @@ export function getSourceNodes(sourceFile: ts.SourceFile): Observable { * @param kind * @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[] = []; + + let arr: ts.Node[] = new Array(Math.min(max, 5)); if (node.kind === kind) { arr.push(node); + max--; + } + if (max > 0) { + for (const child of node.getChildren()) { + findNodes(child, kind).forEach(node => { + arr.push(node); + max--; + }); + + if (max <= 0) { + break; + } + } } - return node.getChildren().reduce((foundNodes, child) => - foundNodes.concat(findNodes(child, kind)), arr); + return arr; } @@ -111,9 +124,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", From 1f638344be636c1865b9e6700819c660fc7c60b4 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 10 Aug 2016 20:59:51 -0700 Subject: [PATCH 2/7] Actually allow the router to lazy load. --- addon/ng2/models/webpack-build-common.ts | 52 ++++++++++-------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/addon/ng2/models/webpack-build-common.ts b/addon/ng2/models/webpack-build-common.ts index ad9ae9061bcd..8979b519a896 100644 --- a/addon/ng2/models/webpack-build-common.ts +++ b/addon/ng2/models/webpack-build-common.ts @@ -5,11 +5,19 @@ import * as webpack from 'webpack'; import * as atl from 'awesome-typescript-loader'; import { CliConfig } from './config'; -import {SystemJSRegisterPublicModules} from './webpack-plugin-systemjs-registry'; import {findLazyModules} from './find-lazy-modules'; export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { + const sourceRoot = path.resolve(projectRoot, `./${sourceDir}`); + let outputPath: string = path.resolve(projectRoot, outputDir); + const lazyModules = findLazyModules(path.resolve(projectRoot, sourceDir)); + + const entries = Object.assign({ + main: [path.join(sourceRoot, 'main.ts')], + polyfills: path.join(sourceRoot, 'polyfills.ts') + }, lazyModules); + return { devtool: 'source-map', @@ -29,9 +37,19 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { test: /\.js$/, loader: 'source-map-loader', exclude: [ - /node_modules/ + path.resolve(projectRoot, 'node_modules/rxjs'), + path.resolve(projectRoot, 'node_modules/@angular'), ] - } + }, + { + test: /(systemjs_component_resolver|system_js_ng_module_factory_loader)\.js$/, + loader: 'string-replace-loader', + query: { + search: '(lang_1(.*[\\n\\r]+\\s*\\.|\\.))?(global(.*[\\n\\r]+\\s*\\.|\\.))?(System|SystemJS)(.*[\\n\\r]+\\s*\\.|\\.)import', + replace: 'System.import', + flags: 'g' + } + }, ], loaders: [ { @@ -78,34 +96,6 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { from: '**/*', to: outputPath }]), - // new SystemJSRegisterPublicModules({ - // // automatically configure SystemJS to load webpack chunks (defaults to true) - // bundlesConfigForChunks: true, - // - // // select which modules to expose as public modules - // registerModules: [ - // // "default" filters provided are "local" and "public" - // { filter: 'public' }, - // // - // // // keyname allows a custom naming system for public modules - // // { - // // filter: 'local', - // // keyname: 'app/[relPath]' - // // }, - // // - // // // keyname can be a function - // // { - // // filter: 'public', - // // keyname: (module) => 'publicModule-' + module.id - // // }, - // // - // // // filter can also be a function - // // { - // // filter: (m) => m.relPath.match(/src/), - // // keyname: 'random-naming-system-[id]' - // // } - // ] - // }) ], node: { fs: 'empty', From c3452ea402484e98ac1940b7d49407bb6e03b32d Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 10 Aug 2016 21:07:16 -0700 Subject: [PATCH 3/7] Fix findNodes WRT tests. --- addon/ng2/utilities/ast-utils.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/addon/ng2/utilities/ast-utils.ts b/addon/ng2/utilities/ast-utils.ts index 2fd85af428db..73c877a4f7a4 100644 --- a/addon/ng2/utilities/ast-utils.ts +++ b/addon/ng2/utilities/ast-utils.ts @@ -57,25 +57,28 @@ 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, max: number = Infinity): ts.Node[] { if (!node || max == 0) { return []; } - let arr: ts.Node[] = new Array(Math.min(max, 5)); + let arr: ts.Node[] = []; if (node.kind === kind) { arr.push(node); max--; } if (max > 0) { for (const child of node.getChildren()) { - findNodes(child, kind).forEach(node => { - arr.push(node); + findNodes(child, kind, max).forEach(node => { + if (max > 0) { + arr.push(node); + } max--; }); From 0680ecd32945baa43858b2f115d2d74ae684a361 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 10 Aug 2016 21:28:28 -0700 Subject: [PATCH 4/7] Fixed the import statement so webpack doesnt show an error --- addon/ng2/models/webpack-build-common.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addon/ng2/models/webpack-build-common.ts b/addon/ng2/models/webpack-build-common.ts index 8979b519a896..88089caddb41 100644 --- a/addon/ng2/models/webpack-build-common.ts +++ b/addon/ng2/models/webpack-build-common.ts @@ -45,8 +45,8 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { test: /(systemjs_component_resolver|system_js_ng_module_factory_loader)\.js$/, loader: 'string-replace-loader', query: { - search: '(lang_1(.*[\\n\\r]+\\s*\\.|\\.))?(global(.*[\\n\\r]+\\s*\\.|\\.))?(System|SystemJS)(.*[\\n\\r]+\\s*\\.|\\.)import', - replace: 'System.import', + search: '(lang_1(.*[\\n\\r]+\\s*\\.|\\.))?(global(.*[\\n\\r]+\\s*\\.|\\.))?(System|SystemJS)(.*[\\n\\r]+\\s*\\.|\\.)import\\(', + replace: 'System.import("" + ', flags: 'g' } }, From aa132b1cb4811e223dfcbd10adf4da870ba8f846 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 10 Aug 2016 21:31:09 -0700 Subject: [PATCH 5/7] cleanup --- addon/ng2/blueprints/ng2/files/package.json | 1 - .../webpack-plugin-systemjs-registry.ts | 293 ------------------ 2 files changed, 294 deletions(-) delete mode 100644 addon/ng2/models/webpack-plugin-systemjs-registry.ts diff --git a/addon/ng2/blueprints/ng2/files/package.json b/addon/ng2/blueprints/ng2/files/package.json index 81643a5ec3a3..e1fe192eb6e3 100644 --- a/addon/ng2/blueprints/ng2/files/package.json +++ b/addon/ng2/blueprints/ng2/files/package.json @@ -22,7 +22,6 @@ "@angular/router": "3.0.0-rc.1", "core-js": "^2.4.0", "rxjs": "5.0.0-beta.6", - "systemjs": "^0.19.36", "ts-helpers": "^1.1.1", "zone.js": "0.6.12" }, diff --git a/addon/ng2/models/webpack-plugin-systemjs-registry.ts b/addon/ng2/models/webpack-plugin-systemjs-registry.ts deleted file mode 100644 index f04f1fba98b1..000000000000 --- a/addon/ng2/models/webpack-plugin-systemjs-registry.ts +++ /dev/null @@ -1,293 +0,0 @@ -var sep = require('path').sep; - - -export class SystemJSRegisterPublicModules { - private registerModules: any; - private bundlesConfigForChunks: any; - - constructor(options: any) { - options = options || {}; - - // default is public modules - this.registerModules = options.registerModules || [{filter: 'public'}]; - - this.bundlesConfigForChunks = - typeof options.bundlesConfigForChunks == 'boolean' ? options.bundlesConfigForChunks : true; - } - - - // given the entry chunk, determine which modules are public - // and create the manifest of public modules and chunks to public modules - // - // id to public name (if public, otherwise undefined) - // manifest.registerModules = ['b', 'a', 'main', 'lodash', undefined, undefined]; - // - // id to boolean, indicating which are ES module objects - // manifest.esModules = [0,0,1,0,0]; - // - // chunk id to list of public module ids in that chunk - // manifest.chunks = [[0, 3]]; - getModuleLoaderManifest(modules, entryChunk, outputOptions, hash) { - var bundlesConfigForChunks = this.bundlesConfigForChunks; - var includes = this.registerModules; - - var manifest = { - registerModules: [], - esModules: [], - chunks: [] - }; - - var existingKeys = []; - - var path = outputOptions.path; - - // convert module objects into structured module objects for our own use - var moduleObjs = modules.map(function (m) { - return { - id: m.id, - request: m.rawRequest || '', - path: m.resource || '', - - relPath: m.resource && m.resource.substr(0, path.length + 1) == path + sep ? m.resource.substr(path.length + 1) : m.resource || '', - - // NB TODO: - // isPackageMain: true / false - // packageName: from package.json / node_modules derivation - // packageVersion: from package.json - - meta: m.meta - }; - }); - - // default filters and naming functions - function publicFilter(module) { - // is this good enough? - return module.request.match(/^@[^\/\\]+\/\\[^\/\\]$|^[^\/\\]+$/); - } - - function localFilter(module) { - // modules outside of the project root are not considered local anymore - if (module.path.substr(0, path.length) != path) - return false; - return !module.path.substr(path.length).match(/(^|\/|\\)node_modules(\/|\\|$)/); - } - - function publicModuleName(module) { - return module.request; - } - - function localModuleName(module) { - return module.relPath; - } - - // determine includes - includes.forEach(function (include, index) { - var filter = include.filter; - var publicKeyFn = include.keyname; - - // public key template function - // we should really do this with better properties than the normal module entries - if (typeof publicKeyFn == 'string') { - var string = publicKeyFn; - publicKeyFn = function (module, existingKeys) { - var str = string; - // allow simple templating - for (var p in module) { - if (module.hasOwnProperty(p)) - str = str.replace('[' + p + ']', module[p]); - } - return str; - } - } - - // default filters - if (filter == 'all') { - filter = function (module) { - return true; - }; - publicKeyFn = publicKeyFn || function (module, existingKeys) { - if (publicFilter(module)) - return publicNames(module); - else - return localNames(module); - }; - } - else if (filter == 'public') { - filter = publicFilter; - publicKeyFn = publicKeyFn || publicModuleName; - } - else if (filter == 'local') { - filter = localFilter; - publicKeyFn = publicKeyFn || localModuleName; - } - - if (!publicKeyFn) - throw new TypeError('SystemJS register public modules plugin has no keyname function defined for filter ' + index); - - moduleObjs.filter(function (m) { - return filter(m, existingKeys); - }).forEach(function (m) { - var publicKey = publicKeyFn(m, existingKeys); - if (typeof publicKey != 'string') - throw new TypeError('SystemJS register public modules plugin did not return a valid key for ' + m.path); - if (existingKeys.indexOf(publicKey) != -1) { - if (manifest.registerModules[m.id] != publicKey) - throw new TypeError('SystemJS register public module ' + publicKey + ' is already defined to another module'); - existingKeys.push(publicKey); - } - manifest.registerModules[m.id] = publicKey; - - if (m.meta.harmonyModule) - manifest.esModules[m.id] = true; - }); - }); - - // build up list of public modules against chunkids - if (bundlesConfigForChunks) { - function visitChunks(chunk, visitor) { - visitor(chunk); - chunk.chunks.forEach(visitor); - } - - visitChunks(entryChunk, function (chunk) { - var publicChunkModuleIds = []; - chunk.modules.forEach(function (module) { - if (manifest.registerModules[module.id]) - publicChunkModuleIds.push(module.id); - }); - - // is it possible for the main entry point to contain multiple chunks? how would we know what these are? - // or is the main compilation always the first chunk? - if (publicChunkModuleIds.length && chunk.id != entryChunk.id) - manifest.chunks[chunk.id] = publicChunkModuleIds; - }); - } - - return manifest; - }; - - apply(compiler) { - var self = this; - - compiler.plugin('compilation', function (compilation) { - var mainTemplate = compilation.mainTemplate; - - mainTemplate.plugin('bootstrap', function (source, chunk, hash) { - var bundlesConfigForChunks = self.bundlesConfigForChunks; - - var publicModuleLoaderManifest = []; - var publicModuleChunks = []; - - var manifest = self.getModuleLoaderManifest(compilation.modules, chunk, this.outputOptions, hash); - - function stringifySparseArray(arr) { - return '[' + arr.map(function (value) { - if (value === undefined) - return ''; - else if (typeof value == 'boolean') - return value ? 1 : 0; - else - return JSON.stringify(value); - }).join(',').replace(/,+$/, '') + ']'; - } - - return this.asString([ - "var publicModuleLoaderManifest = " + stringifySparseArray(manifest.registerModules) + ";", - "var publicESModules = " + stringifySparseArray(manifest.esModules) + ";", - (bundlesConfigForChunks ? "var publicModuleChunks = " + stringifySparseArray(manifest.chunks) + ";" : ""), - source - ]); - }); - - mainTemplate.plugin('add-module', function (source) { - return this.asString([ - source, - "defineIfPublicSystemJSModule(moduleId);" - ]); - }); - - mainTemplate.plugin('require-extensions', function (source, chunk, hash) { - var bundlesConfigForChunks = self.bundlesConfigForChunks; - - var output = [source]; - - if (bundlesConfigForChunks) { - var chunkMaps = chunk.getChunkMaps(); - var chunkFilename = this.outputOptions.chunkFilename; -console.log(chunkMaps); - output.push("var systemJSBundlesConfig = {};"); - output.push("for (var chunkId in publicModuleChunks) {"); - output.push(this.indent([ - "var moduleIds = publicModuleChunks[chunkId];", - "var moduleNames = [];", - "for (var i = 0; i < moduleIds.length; i++)", - this.indent([ - "moduleNames.push(publicModuleLoaderManifest[moduleIds[i]]);", - ]), - - // this is copied from https://github.com/webpack/webpack/blob/master/lib/JsonpMainTemplatePlugin.js#L43 - "systemJSBundlesConfig[" + this.requireFn + ".p + " + - this.applyPluginsWaterfall("asset-path", JSON.stringify(chunkFilename), { - hash: "\" + " + this.renderCurrentHashCode(hash) + " + \"", - hashWithLength: function (length) { - return "\" + " + this.renderCurrentHashCode(hash, length) + " + \""; - }.bind(this), - chunk: { - id: "\" + chunkId + \"", - hash: "\" + " + JSON.stringify(chunkMaps.hash) + "[chunkId] + \"", - hashWithLength: function (length) { - var shortChunkHashMap = {}; - Object.keys(chunkMaps.hash).forEach(function (chunkId) { - if (typeof chunkMaps.hash[chunkId] === "string") - shortChunkHashMap[chunkId] = chunkMaps.hash[chunkId].substr(0, length); - }); - return "\" + " + JSON.stringify(shortChunkHashMap) + "[chunkId] + \""; - }, - name: "\" + (" + JSON.stringify(chunkMaps.name) + "[chunkId]||chunkId) + \"" - } - }) + "] = moduleNames;" - ])); - output.push("}"); - - // this check could potentially left out to assume SystemJS-only and throw otherwise, - // but it seems nice to make it optional - output.push("var hasSystemJS = typeof SystemJS != 'undefined';"); - - output.push("if (hasSystemJS)"); - output.push(this.indent(["SystemJS.config({ bundles: systemJSBundlesConfig });"])); - } - - output.push("function defineIfPublicSystemJSModule(moduleId) {"); - output.push("var publicKey = publicModuleLoaderManifest[moduleId];"); - output.push("if (publicKey && hasSystemJS)"); - output.push(this.indent([ - "if (publicESModules[moduleId])", - this.indent([ - "SystemJS.register(publicKey, [], function($__export) {", - // this could be moved into execution scope - this.indent([ - "$__export(__webpack_require__(moduleId));" - ]), - "});" - ]), - "else", - this.indent([ - "SystemJS.registerDynamic(publicKey, [], false, function() {", - this.indent([ - "return __webpack_require__(moduleId);" - ]), - "});" - ]) - ])); - output.push("}"); - output.push("for (var moduleId in modules)"); - output.push(this.indent([ - "if (Object.prototype.hasOwnProperty.call(modules, moduleId))", - this.indent(["defineIfPublicSystemJSModule(moduleId);"]) - ])); - - return this.asString(output); - }); - }); - } -} \ No newline at end of file From a4ac1ef0e1122255a9151253d05f12f109cc9290 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 11 Aug 2016 09:46:51 -0700 Subject: [PATCH 6/7] Do not add the lazy routes to the entry points. --- addon/ng2/models/webpack-build-common.ts | 33 +++++------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/addon/ng2/models/webpack-build-common.ts b/addon/ng2/models/webpack-build-common.ts index 88089caddb41..e5a338428f66 100644 --- a/addon/ng2/models/webpack-build-common.ts +++ b/addon/ng2/models/webpack-build-common.ts @@ -13,12 +13,6 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { let outputPath: string = path.resolve(projectRoot, outputDir); const lazyModules = findLazyModules(path.resolve(projectRoot, sourceDir)); - const entries = Object.assign({ - main: [path.join(sourceRoot, 'main.ts')], - polyfills: path.join(sourceRoot, 'polyfills.ts') - }, lazyModules); - - return { devtool: 'source-map', resolve: { @@ -26,31 +20,15 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { root: path.resolve(projectRoot, `./${sourceDir}`) }, context: path.resolve(__dirname, './'), - entry: entries, + entry: { + main: [path.join(sourceRoot, 'main.ts')], + polyfills: path.join(sourceRoot, 'polyfills.ts') + }, output: { path: outputPath, filename: '[name].bundle.js' }, module: { - preLoaders: [ - { - test: /\.js$/, - loader: 'source-map-loader', - exclude: [ - path.resolve(projectRoot, 'node_modules/rxjs'), - path.resolve(projectRoot, 'node_modules/@angular'), - ] - }, - { - test: /(systemjs_component_resolver|system_js_ng_module_factory_loader)\.js$/, - loader: 'string-replace-loader', - query: { - search: '(lang_1(.*[\\n\\r]+\\s*\\.|\\.))?(global(.*[\\n\\r]+\\s*\\.|\\.))?(System|SystemJS)(.*[\\n\\r]+\\s*\\.|\\.)import\\(', - replace: 'System.import("" + ', - flags: 'g' - } - }, - ], loaders: [ { test: /\.ts$/, @@ -77,13 +55,14 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { ] }, plugins: [ + new webpack.ContextReplacementPlugin(/.*/, sourceRoot, lazyModules), new atl.ForkCheckerPlugin(), new HtmlWebpackPlugin({ template: path.resolve(projectRoot, `./${sourceDir}/index.html`), chunksSortMode: 'dependency' }), new webpack.optimize.CommonsChunkPlugin({ - name: 'polyfills' + name: ['polyfills'] }), new webpack.optimize.CommonsChunkPlugin({ minChunks: Infinity, From ae8ea97ce3e9bfa8398e7ee1476584d13991b562 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Fri, 19 Aug 2016 10:10:30 -0700 Subject: [PATCH 7/7] cleanup --- addon/ng2/models/webpack-build-common.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/addon/ng2/models/webpack-build-common.ts b/addon/ng2/models/webpack-build-common.ts index e5a338428f66..e71749a1a2eb 100644 --- a/addon/ng2/models/webpack-build-common.ts +++ b/addon/ng2/models/webpack-build-common.ts @@ -3,21 +3,20 @@ import * as CopyWebpackPlugin from 'copy-webpack-plugin'; import * as HtmlWebpackPlugin from 'html-webpack-plugin'; import * as webpack from 'webpack'; import * as atl from 'awesome-typescript-loader'; -import { CliConfig } from './config'; import {findLazyModules} from './find-lazy-modules'; -export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { - const sourceRoot = path.resolve(projectRoot, `./${sourceDir}`); - 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: { @@ -29,6 +28,15 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { filename: '[name].bundle.js' }, module: { + preLoaders: [ + { + test: /\.js$/, + loader: 'source-map-loader', + exclude: [ + /node_modules/ + ] + } + ], loaders: [ { test: /\.ts$/, @@ -37,7 +45,7 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { loader: 'awesome-typescript-loader', query: { useForkChecker: true, - tsconfig: path.resolve(projectRoot, `./${sourceDir}/tsconfig.json`) + tsconfig: path.resolve(sourceRoot, 'tsconfig.json') } }, { loader: 'angular2-template-loader' @@ -58,7 +66,7 @@ export function getWebpackCommonConfig(projectRoot: string, sourceDir: string) { 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({