diff --git a/.gitignore b/.gitignore index fcbafce7..829fe43b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,10 @@ plugins/NativeScriptAngularCompilerPlugin.d.ts plugins/NativeScriptAngularCompilerPlugin.js plugins/NativeScriptAngularCompilerPlugin.js.map +transformers/ns-replace-bootstrap.d.ts +transformers/ns-replace-bootstrap.js +transformers/ns-replace-bootstrap.js.map + plugins/PlatformFSPlugin.d.ts plugins/PlatformFSPlugin.js plugins/PlatformFSPlugin.js.map diff --git a/dependencyManager.js b/dependencyManager.js index 655e745c..3e5ebb05 100644 --- a/dependencyManager.js +++ b/dependencyManager.js @@ -90,7 +90,7 @@ function getRequiredDeps(packageJson) { }; if (!dependsOn(packageJson, "@angular-devkit/build-angular")) { - deps["@ngtools/webpack"] = "~6.1.0"; + deps["@ngtools/webpack"] = "~6.2.0-beta.3"; } return deps; diff --git a/index.js b/index.js index 363658eb..307236ae 100644 --- a/index.js +++ b/index.js @@ -8,8 +8,8 @@ const { isIos, } = require("./projectHelpers"); -Object.assign(exports, require('./plugins')); -Object.assign(exports, require('./host/resolver')); +Object.assign(exports, require("./plugins")); +Object.assign(exports, require("./host/resolver")); exports.getAotEntryModule = function (appDirectory) { verifyEntryModuleDirectory(appDirectory); diff --git a/package.json b/package.json index a1fe00bf..2b8731ac 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "devDependencies": { "@types/node": "^8.0.0", "conventional-changelog-cli": "^1.3.22", - "typescript": "~2.7.2" + "typescript": "~2.9.1", + "@ngtools/webpack": "~6.2.0-beta.3" } } diff --git a/templates/webpack.angular.js b/templates/webpack.angular.js index efd2b892..2073682d 100644 --- a/templates/webpack.angular.js +++ b/templates/webpack.angular.js @@ -3,6 +3,7 @@ const { join, relative, resolve, sep } = require("path"); const webpack = require("webpack"); const nsWebpack = require("nativescript-dev-webpack"); const nativescriptTarget = require("nativescript-dev-webpack/nativescript-target"); +const { nsReplaceBootstrap } = require("nativescript-dev-webpack/transformers/ns-replace-bootstrap"); const CleanWebpackPlugin = require("clean-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin"); const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer"); @@ -46,11 +47,18 @@ module.exports = env => { const appFullPath = resolve(projectRoot, appPath); const appResourcesFullPath = resolve(projectRoot, appResourcesPath); - const entryModule = aot ? - nsWebpack.getAotEntryModule(appFullPath) : - `${nsWebpack.getEntryModule(appFullPath)}.ts`; + const entryModule = `${nsWebpack.getEntryModule(appFullPath)}.ts`; const entryPath = `.${sep}${entryModule}`; + const ngCompilerPlugin = new AngularCompilerPlugin({ + hostReplacementPaths: nsWebpack.getResolver([platform, "tns"]), + platformTransformers: aot ? [nsReplaceBootstrap(() => ngCompilerPlugin)] : null, + mainPath: resolve(appPath, entryModule), + tsConfigPath: join(__dirname, "tsconfig.esm.json"), + skipCodeGeneration: !aot, + sourceMap: !!sourceMap, + }); + const config = { mode: uglify ? "production" : "development", context: appFullPath, @@ -107,7 +115,7 @@ module.exports = env => { test: (module, chunks) => { const moduleName = module.nameForCondition ? module.nameForCondition() : ''; return /[\\/]node_modules[\\/]/.test(moduleName) || - appComponents.some(comp => comp === moduleName); + appComponents.some(comp => comp === moduleName); }, enforce: true, }, @@ -195,10 +203,9 @@ module.exports = env => { // Define useful constants like TNS_WEBPACK new webpack.DefinePlugin({ "global.TNS_WEBPACK": "true", - "process": undefined, }), // Remove all files from the out dir. - new CleanWebpackPlugin([ `${dist}/**/*` ]), + new CleanWebpackPlugin([`${dist}/**/*`]), // Copy native app resources to out dir. new CopyWebpackPlugin([ { @@ -221,19 +228,13 @@ module.exports = env => { // For instructions on how to set up workers with webpack // check out https://github.com/nativescript/worker-loader new NativeScriptWorkerPlugin(), - - new AngularCompilerPlugin({ - hostReplacementPaths: nsWebpack.getResolver([platform, "tns"]), - mainPath: resolve(appPath, "main.ts"), - tsConfigPath: join(__dirname, "tsconfig.esm.json"), - skipCodeGeneration: !aot, - sourceMap: !!sourceMap, - }), + ngCompilerPlugin, // Does IPC communication with the {N} CLI to notify events when running in watch mode. new nsWebpack.WatchStateLoggerPlugin(), ], }; + if (report) { // Generate report files for bundles content config.plugins.push(new BundleAnalyzerPlugin({ diff --git a/transformers/ns-replace-bootstrap.d.ts b/transformers/ns-replace-bootstrap.d.ts new file mode 100644 index 00000000..59d35d45 --- /dev/null +++ b/transformers/ns-replace-bootstrap.d.ts @@ -0,0 +1,3 @@ +import * as ts from 'typescript'; +import { AngularCompilerPlugin } from '@ngtools/webpack'; +export declare function nsReplaceBootstrap(getNgCompiler: () => AngularCompilerPlugin): ts.TransformerFactory; diff --git a/transformers/ns-replace-bootstrap.js b/transformers/ns-replace-bootstrap.js new file mode 100644 index 00000000..354bc6c5 --- /dev/null +++ b/transformers/ns-replace-bootstrap.js @@ -0,0 +1,66 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const path_1 = require("path"); +const ts = require("typescript"); +const transformers_1 = require("@ngtools/webpack/src/transformers"); +const compiler_host_1 = require("@ngtools/webpack/src/compiler_host"); +function nsReplaceBootstrap(getNgCompiler) { + const shouldTransform = (fileName) => !fileName.endsWith('.ngfactory.ts') && !fileName.endsWith('.ngstyle.ts'); + const getTypeChecker = () => getNgCompiler().typeChecker; + const standardTransform = function (sourceFile) { + const ops = []; + const ngCompiler = getNgCompiler(); + const entryModule = ngCompiler.entryModule + ? { path: compiler_host_1.workaroundResolve(ngCompiler.entryModule.path), className: getNgCompiler().entryModule.className } + : ngCompiler.entryModule; + if (!shouldTransform(sourceFile.fileName) || !entryModule) { + return ops; + } + // Find all identifiers. + const entryModuleIdentifiers = transformers_1.collectDeepNodes(sourceFile, ts.SyntaxKind.Identifier) + .filter(identifier => identifier.text === entryModule.className); + if (entryModuleIdentifiers.length === 0) { + return []; + } + const relativeEntryModulePath = path_1.relative(path_1.dirname(sourceFile.fileName), entryModule.path); + const normalizedEntryModulePath = `./${relativeEntryModulePath}`.replace(/\\/g, '/'); + // Find the bootstrap calls. + entryModuleIdentifiers.forEach(entryModuleIdentifier => { + // Figure out if it's a `platformNativeScriptDynamic().bootstrapModule(AppModule)` call. + if (!(entryModuleIdentifier.parent + && entryModuleIdentifier.parent.kind === ts.SyntaxKind.CallExpression)) { + return; + } + const callExpr = entryModuleIdentifier.parent; + if (callExpr.expression.kind !== ts.SyntaxKind.PropertyAccessExpression) { + return; + } + const propAccessExpr = callExpr.expression; + if (propAccessExpr.name.text !== 'bootstrapModule' + || propAccessExpr.expression.kind !== ts.SyntaxKind.CallExpression) { + return; + } + const bootstrapModuleIdentifier = propAccessExpr.name; + const innerCallExpr = propAccessExpr.expression; + if (!(innerCallExpr.expression.kind === ts.SyntaxKind.Identifier + && innerCallExpr.expression.text === 'platformNativeScriptDynamic')) { + return; + } + const platformBrowserDynamicIdentifier = innerCallExpr.expression; + const idPlatformBrowser = ts.createUniqueName('__NgCli_bootstrap_'); + const idNgFactory = ts.createUniqueName('__NgCli_bootstrap_'); + // Add the transform operations. + const factoryClassName = entryModule.className + 'NgFactory'; + const factoryModulePath = normalizedEntryModulePath + '.ngfactory'; + ops.push( + // Replace the entry module import. + ...transformers_1.insertStarImport(sourceFile, idNgFactory, factoryModulePath), new transformers_1.ReplaceNodeOperation(sourceFile, entryModuleIdentifier, ts.createPropertyAccess(idNgFactory, ts.createIdentifier(factoryClassName))), + // Replace the platformBrowserDynamic import. + ...transformers_1.insertStarImport(sourceFile, idPlatformBrowser, 'nativescript-angular/platform-static'), new transformers_1.ReplaceNodeOperation(sourceFile, platformBrowserDynamicIdentifier, ts.createPropertyAccess(idPlatformBrowser, 'platformNativeScript')), new transformers_1.ReplaceNodeOperation(sourceFile, bootstrapModuleIdentifier, ts.createIdentifier('bootstrapModuleFactory'))); + }); + return ops; + }; + return transformers_1.makeTransform(standardTransform, getTypeChecker); +} +exports.nsReplaceBootstrap = nsReplaceBootstrap; +//# sourceMappingURL=ns-replace-bootstrap.js.map \ No newline at end of file diff --git a/transformers/ns-replace-bootstrap.js.map b/transformers/ns-replace-bootstrap.js.map new file mode 100644 index 00000000..1ed90a73 --- /dev/null +++ b/transformers/ns-replace-bootstrap.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ns-replace-bootstrap.js","sourceRoot":"","sources":["ns-replace-bootstrap.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/transformers/ns-replace-bootstrap.ts b/transformers/ns-replace-bootstrap.ts new file mode 100644 index 00000000..2d557ace --- /dev/null +++ b/transformers/ns-replace-bootstrap.ts @@ -0,0 +1,103 @@ +import { dirname, relative } from 'path'; +import * as ts from 'typescript'; +import { + StandardTransform, + TransformOperation, + collectDeepNodes, + insertStarImport, + ReplaceNodeOperation, + makeTransform +} from "@ngtools/webpack/src/transformers"; +import { workaroundResolve } from '@ngtools/webpack/src/compiler_host'; +import { AngularCompilerPlugin } from '@ngtools/webpack'; + +export function nsReplaceBootstrap(getNgCompiler: () => AngularCompilerPlugin): ts.TransformerFactory { + const shouldTransform = (fileName) => !fileName.endsWith('.ngfactory.ts') && !fileName.endsWith('.ngstyle.ts'); + const getTypeChecker = () => getNgCompiler().typeChecker; + + const standardTransform: StandardTransform = function (sourceFile: ts.SourceFile) { + const ops: TransformOperation[] = []; + const ngCompiler = getNgCompiler(); + + const entryModule = ngCompiler.entryModule + ? { path: workaroundResolve(ngCompiler.entryModule.path), className: getNgCompiler().entryModule.className } + : ngCompiler.entryModule; + + if (!shouldTransform(sourceFile.fileName) || !entryModule) { + return ops; + } + + // Find all identifiers. + const entryModuleIdentifiers = collectDeepNodes(sourceFile, + ts.SyntaxKind.Identifier) + .filter(identifier => identifier.text === entryModule.className); + + if (entryModuleIdentifiers.length === 0) { + return []; + } + + const relativeEntryModulePath = relative(dirname(sourceFile.fileName), entryModule.path); + const normalizedEntryModulePath = `./${relativeEntryModulePath}`.replace(/\\/g, '/'); + + // Find the bootstrap calls. + entryModuleIdentifiers.forEach(entryModuleIdentifier => { + // Figure out if it's a `platformNativeScriptDynamic().bootstrapModule(AppModule)` call. + if (!( + entryModuleIdentifier.parent + && entryModuleIdentifier.parent.kind === ts.SyntaxKind.CallExpression + )) { + return; + } + + const callExpr = entryModuleIdentifier.parent as ts.CallExpression; + + if (callExpr.expression.kind !== ts.SyntaxKind.PropertyAccessExpression) { + return; + } + + const propAccessExpr = callExpr.expression as ts.PropertyAccessExpression; + + if (propAccessExpr.name.text !== 'bootstrapModule' + || propAccessExpr.expression.kind !== ts.SyntaxKind.CallExpression) { + return; + } + + const bootstrapModuleIdentifier = propAccessExpr.name; + const innerCallExpr = propAccessExpr.expression as ts.CallExpression; + + if (!( + innerCallExpr.expression.kind === ts.SyntaxKind.Identifier + && (innerCallExpr.expression as ts.Identifier).text === 'platformNativeScriptDynamic' + )) { + return; + } + + const platformBrowserDynamicIdentifier = innerCallExpr.expression as ts.Identifier; + + const idPlatformBrowser = ts.createUniqueName('__NgCli_bootstrap_'); + const idNgFactory = ts.createUniqueName('__NgCli_bootstrap_'); + + // Add the transform operations. + const factoryClassName = entryModule.className + 'NgFactory'; + const factoryModulePath = normalizedEntryModulePath + '.ngfactory'; + ops.push( + // Replace the entry module import. + ...insertStarImport(sourceFile, idNgFactory, factoryModulePath), + new ReplaceNodeOperation(sourceFile, entryModuleIdentifier, + ts.createPropertyAccess(idNgFactory, ts.createIdentifier(factoryClassName))), + + // Replace the platformBrowserDynamic import. + ...insertStarImport(sourceFile, idPlatformBrowser, 'nativescript-angular/platform-static'), + new ReplaceNodeOperation(sourceFile, platformBrowserDynamicIdentifier, + ts.createPropertyAccess(idPlatformBrowser, 'platformNativeScript')), + + new ReplaceNodeOperation(sourceFile, bootstrapModuleIdentifier, + ts.createIdentifier('bootstrapModuleFactory')), + ); + }); + + return ops; + }; + + return makeTransform(standardTransform, getTypeChecker); +} diff --git a/tsconfig.json b/tsconfig.json index 1c2aac63..d743f100 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "files": [ "plugins/PlatformFSPlugin.ts", "plugins/WatchStateLoggerPlugin.ts", + "transformers/ns-replace-bootstrap.ts", "host/resolver.ts" ] }