From e8b457f2b33a3e294d162aa674acba9307b66b3a Mon Sep 17 00:00:00 2001 From: Stanimira Vlaeva Date: Wed, 8 Feb 2017 14:58:25 +0200 Subject: [PATCH] feat: Add AoT compilation with webpack (#215) Added AoT/Webpack support and updates it to NativeScript 2.5 Fixes #213 --- app/app.css | 2 +- app/app.module.ts | 5 +- app/groceries/groceries.component.ts | 5 +- app/groceries/groceries.module.ts | 7 +- .../grocery-list/grocery-list.component.ts | 5 +- app/login/login.component.ts | 5 +- app/login/login.module.ts | 7 +- app/main.aot.ts | 6 + app/vendor-platform.android.ts | 25 +++ app/vendor-platform.ios.ts | 0 app/vendor.ts | 13 ++ package.json | 63 ++++--- tsconfig.aot.json | 49 ++++++ tsconfig.json | 3 +- webpack.android.js | 2 + webpack.common.js | 156 ++++++++++++++++++ webpack.config.js | 5 - webpack.ios.js | 2 + 18 files changed, 320 insertions(+), 40 deletions(-) create mode 100644 app/main.aot.ts create mode 100644 app/vendor-platform.android.ts create mode 100644 app/vendor-platform.ios.ts create mode 100644 app/vendor.ts create mode 100644 tsconfig.aot.json create mode 100644 webpack.android.js create mode 100644 webpack.common.js delete mode 100644 webpack.config.js create mode 100644 webpack.ios.js diff --git a/app/app.css b/app/app.css index 290c4de8..15c70f1f 100644 --- a/app/app.css +++ b/app/app.css @@ -1,4 +1,4 @@ -@import url("~/platform.css"); +@import url("./platform.css"); Page { font-size: 15; diff --git a/app/app.module.ts b/app/app.module.ts index 1b8b61fd..8dd4f246 100644 --- a/app/app.module.ts +++ b/app/app.module.ts @@ -1,5 +1,5 @@ import { NativeScriptModule } from "nativescript-angular/nativescript.module"; -import { NgModule } from "@angular/core"; +import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; import { NativeScriptHttpModule } from "nativescript-angular/http"; import { NativeScriptRouterModule } from "nativescript-angular/router"; @@ -29,6 +29,7 @@ setStatusBarColors(); declarations: [ AppComponent, ], - bootstrap: [AppComponent] + bootstrap: [AppComponent], + schemas: [NO_ERRORS_SCHEMA] }) export class AppModule { } diff --git a/app/groceries/groceries.component.ts b/app/groceries/groceries.component.ts index 97101822..d3ca1ba9 100644 --- a/app/groceries/groceries.component.ts +++ b/app/groceries/groceries.component.ts @@ -12,8 +12,9 @@ import { LoginService, alert } from "../shared"; @Component({ selector: "gr-groceries", - templateUrl: "groceries/groceries.component.html", - styleUrls: ["groceries/groceries-common.css", "groceries/groceries.component.css"], + moduleId: module.id, + templateUrl: "./groceries.component.html", + styleUrls: ["./groceries-common.css", "./groceries.component.css"], providers: [GroceryService] }) export class GroceriesComponent implements OnInit { diff --git a/app/groceries/groceries.module.ts b/app/groceries/groceries.module.ts index 74038b36..58183462 100644 --- a/app/groceries/groceries.module.ts +++ b/app/groceries/groceries.module.ts @@ -1,6 +1,6 @@ -import { NativeScriptModule } from "nativescript-angular/platform"; +import { NativeScriptModule } from "nativescript-angular/nativescript.module"; import { NativeScriptFormsModule } from "nativescript-angular/forms"; -import { NgModule } from "@angular/core"; +import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; import { groceriesRouting } from "./groceries.routing"; import { GroceriesComponent } from "./groceries.component"; import { GroceryListComponent } from "./grocery-list/grocery-list.component"; @@ -16,6 +16,7 @@ import { ItemStatusPipe } from "./grocery-list/item-status.pipe"; GroceriesComponent, GroceryListComponent, ItemStatusPipe - ] + ], + schemas: [NO_ERRORS_SCHEMA] }) export class GroceriesModule {} diff --git a/app/groceries/grocery-list/grocery-list.component.ts b/app/groceries/grocery-list/grocery-list.component.ts index 46a6617f..18e5877f 100644 --- a/app/groceries/grocery-list/grocery-list.component.ts +++ b/app/groceries/grocery-list/grocery-list.component.ts @@ -8,8 +8,9 @@ declare var UIColor: any; @Component({ selector: "gr-grocery-list", - templateUrl: "groceries/grocery-list/grocery-list.component.html", - styleUrls: ["groceries/grocery-list/grocery-list.component.css"], + moduleId: module.id, + templateUrl: "./grocery-list.component.html", + styleUrls: ["./grocery-list.component.css"], changeDetection: ChangeDetectionStrategy.OnPush }) export class GroceryListComponent { diff --git a/app/login/login.component.ts b/app/login/login.component.ts index c560ae84..18b41576 100644 --- a/app/login/login.component.ts +++ b/app/login/login.component.ts @@ -12,8 +12,9 @@ import { alert, LoginService, User } from "../shared"; @Component({ selector: "gr-login", - templateUrl: "login/login.component.html", - styleUrls: ["login/login-common.css", "login/login.component.css"], + moduleId: module.id, + templateUrl: "./login.component.html", + styleUrls: ["./login-common.css", "./login.component.css"], }) export class LoginComponent implements OnInit { user: User; diff --git a/app/login/login.module.ts b/app/login/login.module.ts index 950ef892..2370b16c 100644 --- a/app/login/login.module.ts +++ b/app/login/login.module.ts @@ -1,6 +1,6 @@ -import { NativeScriptModule } from "nativescript-angular/platform"; +import { NativeScriptModule } from "nativescript-angular/nativescript.module"; import { NativeScriptFormsModule } from "nativescript-angular/forms"; -import { NgModule } from "@angular/core"; +import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; import { loginRouting } from "./login.routing"; import { LoginComponent } from "./login.component"; @@ -13,6 +13,7 @@ import { LoginComponent } from "./login.component"; ], declarations: [ LoginComponent - ] + ], + schemas: [NO_ERRORS_SCHEMA] }) export class LoginModule { } diff --git a/app/main.aot.ts b/app/main.aot.ts new file mode 100644 index 00000000..8f033e9c --- /dev/null +++ b/app/main.aot.ts @@ -0,0 +1,6 @@ +// this import should be first in order to load some required settings (like globals and reflect-metadata) +import { platformNativeScript } from "nativescript-angular/platform-static"; + +import { AppModuleNgFactory } from "./app.module.ngfactory"; + +platformNativeScript().bootstrapModuleFactory(AppModuleNgFactory); diff --git a/app/vendor-platform.android.ts b/app/vendor-platform.android.ts new file mode 100644 index 00000000..ba9742fd --- /dev/null +++ b/app/vendor-platform.android.ts @@ -0,0 +1,25 @@ +// Resolve JavaScript classes that extend a Java class, and need to resolve +// their JavaScript module from a bundled script. For example: +// NativeScriptApplication, NativeScriptActivity, etc. +// +// This module gets bundled together with the rest of the app code and the +// `require` calls get resolved to the correct bundling import call. +// +// At runtime the module gets loaded *before* the rest of the app code, so code +// placed here needs to be careful about its dependencies. + +require("application"); +require("ui/frame"); +require("ui/frame/activity"); + +if (global.TNS_WEBPACK) { + global.__requireOverride = function (name, dir) { + if (name === "./tns_modules/application/application.js") { + return require("application"); + } else if (name === "./tns_modules/ui/frame/frame.js") { + return require("ui/frame"); + } else if (name === "./tns_modules/ui/frame/activity.js") { + return require("ui/frame/activity"); + } + }; +} diff --git a/app/vendor-platform.ios.ts b/app/vendor-platform.ios.ts new file mode 100644 index 00000000..e69de29b diff --git a/app/vendor.ts b/app/vendor.ts new file mode 100644 index 00000000..0d969654 --- /dev/null +++ b/app/vendor.ts @@ -0,0 +1,13 @@ +require("./vendor-platform"); + +require("reflect-metadata"); +require("@angular/platform-browser"); +require("@angular/core"); +require("@angular/common"); +require("@angular/forms"); +require("@angular/http"); +require("@angular/router"); + +require("nativescript-angular/platform-static"); +require("nativescript-angular/forms"); +require("nativescript-angular/router"); diff --git a/package.json b/package.json index 4ea2c84a..531bf0b0 100644 --- a/package.json +++ b/package.json @@ -18,39 +18,55 @@ "nativescript": { "id": "org.nativescript.groceries", "tns-android": { - "version": "2.4.1" + "version": "2.5.0" }, "tns-ios": { - "version": "2.4.0" + "version": "2.5.0" } }, "scripts": { - "tslint": "tslint \"app/**/*.ts\"" + "tslint": "tslint \"app/**/*.ts\"", + "clean-android": "tns clean-app android", + "clean-ios": "tns clean-app ios", + "prewebpack-android": "npm run clean-android", + "prewebpack-ios": "npm run clean-ios", + "webpack-android": "webpack --config=webpack.android.js --progress", + "webpack-ios": "webpack --config=webpack.ios.js --progress", + "prestart-android-bundle": "npm run webpack-android", + "prestart-ios-bundle": "npm run webpack-ios", + "start-android-bundle": "tns run android --bundle --disable-npm-install", + "start-ios-bundle": "tns run ios --bundle --disable-npm-install", + "prebuild-android-bundle": "npm run webpack-android", + "prebuild-ios-bundle": "npm run webpack-ios", + "build-android-bundle": "tns build android --bundle --disable-npm-install", + "build-ios-bundle": "tns build ios --bundle --disable-npm-install" }, "dependencies": { - "@angular/common": "~2.3.1", - "@angular/compiler": "~2.3.1", - "@angular/core": "~2.3.1", - "@angular/forms": "~2.3.1", - "@angular/http": "~2.3.1", - "@angular/platform-browser": "~2.3.1", - "@angular/platform-browser-dynamic": "~2.3.1", - "@angular/router": "~3.3.1", + "@angular/common": "2.4.5", + "@angular/compiler": "2.4.5", + "@angular/core": "2.4.5", + "@angular/forms": "2.4.5", + "@angular/http": "2.4.5", + "@angular/platform-browser": "2.4.5", + "@angular/platform-browser-dynamic": "2.4.5", + "@angular/router": "3.4.5", "email-validator": "1.0.4", - "nativescript-angular": "1.3.0", + "nativescript-angular": "1.4.0", "nativescript-iqkeyboardmanager": "1.0.1", - "nativescript-social-share": "1.3.1", + "nativescript-social-share": "~1.3.2", "nativescript-unit-test-runner": "^0.3.3", "reflect-metadata": "^0.1.8", - "rxjs": "5.0.0-rc.4", - "tns-core-modules": "^2.4.2" + "rxjs": "~5.0.1", + "tns-core-modules": "2.5.0" }, "devDependencies": { - "zone.js": "~0.7.2", + "@angular/compiler-cli": "2.4.5", "babel-traverse": "6.8.0", "babel-types": "6.8.1", "babylon": "6.8.0", - "codelyzer": "0.0.28", + "codelyzer": "2.0.0-beta.4", + "copy-webpack-plugin": "~3.0.1", + "extract-text-webpack-plugin": "~2.0.0-beta.4", "filewalker": "0.1.2", "jasmine-core": "^2.4.1", "karma": "^1.2.0", @@ -58,7 +74,16 @@ "karma-nativescript-launcher": "^0.4.0", "lazy": "1.0.11", "nativescript-dev-typescript": "^0.3.2", - "tslint": "^3.14.0", - "typescript": "~2.0.10" + "nativescript-dev-webpack": "^0.3.1", + "raw-loader": "~0.5.1", + "resolve-url-loader": "~1.6.0", + "tslint": "^4.0.0", + "typescript": "^2.1.0", + "webpack-sources": "~0.1.3", + "zone.js": "~0.7.2", + "nativescript-css-loader": "~0.26.0", + "htmlparser2": "^3.9.2", + "webpack": "2.2.0", + "@ngtools/webpack": "1.2.4" } } diff --git a/tsconfig.aot.json b/tsconfig.aot.json new file mode 100644 index 00000000..af686844 --- /dev/null +++ b/tsconfig.aot.json @@ -0,0 +1,49 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "es2015", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "removeComments": false, + "noImplicitAny": false, + "suppressImplicitAnyIndexErrors": true, + "types": [], + "baseUrl": ".", + "paths": { + "ui/*": ["node_modules/tns-core-modules/ui/*"], + "platform": ["node_modules/tns-core-modules/platform"], + "image-source": ["node_modules/tns-core-modules/image-source"], + "xml": ["node_modules/tns-core-modules/xml"], + "xhr": ["node_modules/tns-core-modules/xhr"], + "text": ["node_modules/tns-core-modules/text"], + "data": ["node_modules/tns-core-modules/data"], + "fetch": ["node_modules/tns-core-modules/fetch"], + "trace": ["node_modules/tns-core-modules/trace"], + "fps-meter": ["node_modules/tns-core-modules/fps-meter"], + "color": ["node_modules/tns-core-modules/color"], + "application-settings": ["node_modules/tns-core-modules/application-settings"], + "http": ["node_modules/tns-core-modules/http"], + "camera": ["node_modules/tns-core-modules/camera"], + "console": ["node_modules/tns-core-modules/console"], + "timer": ["node_modules/tns-core-modules/timer"], + "utils": ["node_modules/tns-core-modules/utils"], + "location": ["node_modules/tns-core-modules/location"], + "file-system": ["node_modules/tns-core-modules/file-system"], + "application": ["node_modules/tns-core-modules/application"], + "image-asset": ["node_modules/tns-core-modules/image-asset"], + "connectivity": ["node_modules/tns-core-modules/connectivity"], + "globals": ["node_modules/tns-core-modules/globals"] + + } + }, + "exclude": [ + "node_modules", + "platforms" + ], + "angularCompilerOptions": { + "skipMetadataEmit": true, + "genDir": "./" + } +} diff --git a/tsconfig.json b/tsconfig.json index e650192e..3bd4ae06 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ }, "exclude": [ "node_modules", - "platforms" + "platforms", + "**/*.aot.ts" ] } \ No newline at end of file diff --git a/webpack.android.js b/webpack.android.js new file mode 100644 index 00000000..968e93b8 --- /dev/null +++ b/webpack.android.js @@ -0,0 +1,2 @@ +var makeConfig = require("./webpack.common"); +module.exports = makeConfig("android"); diff --git a/webpack.common.js b/webpack.common.js new file mode 100644 index 00000000..550163c3 --- /dev/null +++ b/webpack.common.js @@ -0,0 +1,156 @@ +var webpack = require("webpack"); +var nsWebpack = require("nativescript-dev-webpack"); +var nativescriptTarget = require("nativescript-dev-webpack/nativescript-target"); +var path = require("path"); +var CopyWebpackPlugin = require("copy-webpack-plugin"); +var ExtractTextPlugin = require("extract-text-webpack-plugin"); +var AotPlugin = require("@ngtools/webpack").AotPlugin; + +module.exports = function (platform, destinationApp) { + if (!destinationApp) { + //Default destination inside platforms//... + destinationApp = nsWebpack.getAppPath(platform); + } + var entry = {}; + //Discover entry module from package.json + entry.bundle = "./" + nsWebpack.getEntryModule(); + //Vendor entry with third party libraries. + entry.vendor = "./vendor"; + //app.css bundle + entry["app.css"] = "./app.css"; + + var plugins = [ + new ExtractTextPlugin("app.css"), + //Vendor libs go to the vendor.js chunk + new webpack.optimize.CommonsChunkPlugin({ + name: ["vendor"] + }), + //Define useful constants like TNS_WEBPACK + new webpack.DefinePlugin({ + "global.TNS_WEBPACK": "true", + }), + //Copy assets to out dir. Add your own globs as needed. + new CopyWebpackPlugin([ + { from: "app.css" }, + { from: "css/**" }, + { from: "fonts/**" }, + { from: "**/*.jpg" }, + { from: "**/*.png" }, + { from: "**/*.xml" }, + ], { ignore: ["App_Resources/**"] }), + //Generate a bundle starter script and activate it in package.json + new nsWebpack.GenerateBundleStarterPlugin([ + "./vendor", + "./bundle", + ]), + + // Exclude explicitly required but never declared in XML elements. + // Loader nativescript-dev-webpack/tns-xml-loader should be added for *.xml/html and *.ts files. + new nsWebpack.ExcludeUnusedElementsPlugin(), + + //Angular AOT compiler + new AotPlugin({ + tsConfigPath: "tsconfig.aot.json", + entryModule: path.resolve(__dirname, "app/app.module#AppModule"), + typeChecking: false + }), + new nsWebpack.StyleUrlResolvePlugin({platform}), + ]; + + if (process.env.npm_config_uglify) { + plugins.push(new webpack.LoaderOptionsPlugin({ + minimize: true + })); + + //Work around an Android issue by setting compress = false + var compress = platform !== "android"; + plugins.push(new webpack.optimize.UglifyJsPlugin({ + mangle: { + except: nsWebpack.uglifyMangleExcludes, + }, + compress: compress, + })); + } + + return { + context: path.resolve("./app"), + target: nativescriptTarget, + entry: entry, + output: { + pathinfo: true, + path: path.resolve(destinationApp), + libraryTarget: "commonjs2", + filename: "[name].js", + }, + resolve: { + //Resolve platform-specific modules like module.android.js + extensions: [ + ".aot.ts", + ".ts", + ".js", + ".css", + "." + platform + ".ts", + "." + platform + ".js", + "." + platform + ".css", + ], + //Resolve {N} system modules from tns-core-modules + modules: [ + "node_modules/tns-core-modules", + "node_modules" + ] + }, + node: { + //Disable node shims that conflict with NativeScript + "http": false, + "timers": false, + "setImmediate": false, + }, + module: { + loaders: [ + { + test: /\.html$|\.xml$/, + loaders: [ + "raw-loader", + "nativescript-dev-webpack/tns-xml-loader", + ] + }, + // Root app.css file gets extracted with bundled dependencies + { + test: /app\.css$/, + loader: ExtractTextPlugin.extract([ + "resolve-url-loader", + "nativescript-css-loader", + "nativescript-dev-webpack/platform-css-loader", + ]), + }, + // Other CSS files get bundled using the raw loader + { + test: /\.css$/, + exclude: /app\.css$/, + loaders: [ + "raw-loader", + ] + }, + // Compile TypeScript files with ahead-of-time compiler. + { + test: /\.ts$/, + loaders: [ + "nativescript-dev-webpack/tns-xml-loader", + "nativescript-dev-webpack/tns-aot-loader", + "@ngtools/webpack", + ] + }, + // SASS support + { + test: /\.scss$/, + loaders: [ + "raw-loader", + "resolve-url-loader", + "sass-loader" + ] + }, + ] + }, + plugins: plugins, + }; +}; diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index fc192736..00000000 --- a/webpack.config.js +++ /dev/null @@ -1,5 +0,0 @@ -var bundler = require("nativescript-dev-webpack"); - -module.exports = bundler.getConfig({ - // TODO: add project-specific webpack settings here... -}); diff --git a/webpack.ios.js b/webpack.ios.js new file mode 100644 index 00000000..806f3ef2 --- /dev/null +++ b/webpack.ios.js @@ -0,0 +1,2 @@ +var makeConfig = require("./webpack.common"); +module.exports = makeConfig("ios");