From 91fcad9c50595ff1ef87c625a6e9c52505384df0 Mon Sep 17 00:00:00 2001 From: imeepos <1037483576@qq.com> Date: Fri, 3 May 2019 10:20:51 +0800 Subject: [PATCH] native app --- .gitignore | 4 +- .../template/admin/task-edit/index.tsx | 3 +- .../template/admin/task-list/index.tsx | 7 +- nsconfig.json | 4 + package.json | 35 +- .../nger-core/lib/decorators/component.ts | 2 +- .../nger-platform-browser/lib/application.tsx | 24 +- packages/nger-platform-h5/lib/cache.ts | 12 +- packages/nger-platform-h5/lib/router.ts | 20 +- .../nger-platform-ios/lib/app-host-view.ts | 34 ++ packages/nger-platform-ios/lib/bootstrap.ts | 49 ++- packages/nger-platform-ios/lib/index.ts | 3 +- packages/nger-platform-native/lib/index.ts | 3 +- packages/nger-platform-swap/lib/cache.ts | 12 +- packages/nger-platform-swap/lib/logger.ts | 8 +- packages/nger-platform-tt/lib/cache.ts | 12 +- packages/nger-platform-weapp/lib/cache.ts | 12 +- src/template/admin/footer/index.tsx | 4 +- tsconfig.json | 20 +- tsconfig.tns.json | 7 + webpack.config.js | 373 ++++++++++++------ 21 files changed, 487 insertions(+), 161 deletions(-) create mode 100644 nsconfig.json create mode 100644 packages/nger-platform-ios/lib/app-host-view.ts create mode 100644 tsconfig.tns.json diff --git a/.gitignore b/.gitignore index 066c306..54f239b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ /**/.rts2_cache_cjs /**/.rts2_cache_es /**/.rts2_cache_umd -/**/node_modules \ No newline at end of file +/**/node_modules +/app +/hooks \ No newline at end of file diff --git a/addon/nger-todo/template/admin/task-edit/index.tsx b/addon/nger-todo/template/admin/task-edit/index.tsx index 9ae575c..f91796d 100644 --- a/addon/nger-todo/template/admin/task-edit/index.tsx +++ b/addon/nger-todo/template/admin/task-edit/index.tsx @@ -1,7 +1,8 @@ import { Page } from 'nger-core'; + @Page({ path: 'task/edit', - type: 'admin', + type: ['admin'], title: '编辑', styleUrls: ['./index.scss'] }) diff --git a/addon/nger-todo/template/admin/task-list/index.tsx b/addon/nger-todo/template/admin/task-list/index.tsx index d40d7d6..7b4af09 100644 --- a/addon/nger-todo/template/admin/task-list/index.tsx +++ b/addon/nger-todo/template/admin/task-list/index.tsx @@ -1,15 +1,12 @@ import { Page } from 'nger-core'; -import { Table } from 'nger-ui' import { NgerTodoAdminHome } from './index.controller' @Page({ path: 'task/list', - type: 'admin', + type: ['admin'], title: '任务', styleUrls: ['./index.scss'] }) export class NgerTodoTaskListPage { constructor(public controller: NgerTodoAdminHome) { } - render() { - return - } + render() {} } diff --git a/nsconfig.json b/nsconfig.json new file mode 100644 index 0000000..1098969 --- /dev/null +++ b/nsconfig.json @@ -0,0 +1,4 @@ +{ + "appResourcesPath": "app/App_Resources", + "appPath": "app" +} \ No newline at end of file diff --git a/package.json b/package.json index 0d0316d..8868376 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@babel/template": "^7.4.4", "@babel/traverse": "^7.4.4", "@babel/types": "^7.4.4", + "@progress-nativechat/nativescript-nativechat": "2.0.3", "@stencil/core": "^0.18.1", "@types/autoprefixer": "^9.4.0", "@types/babel__core": "^7.1.1", @@ -71,6 +72,7 @@ "imagemin-mozjpeg": "^8.0.0", "imagemin-pngquant": "^7.0.0", "imagemin-svgo": "^7.0.0", + "kinvey-nativescript-sdk": "3.12.4", "koa-compress": "^3.0.0", "koa-webpack": "^5.2.2", "less": "^3.9.0", @@ -79,11 +81,31 @@ "mocha": "^6.1.4", "multihashing": "^0.3.3", "multihashing-async": "^0.7.0", + "nativescript-accelerometer": "2.0.1", + "nativescript-background-http": "3.4.0", + "nativescript-camera": "4.4.0", + "nativescript-dev-typescript": "0.9.0", + "nativescript-dev-webpack": "0.21.0", + "nativescript-fresco": "5.2.0", + "nativescript-geolocation": "5.0.0", + "nativescript-imagepicker": "6.1.2", + "nativescript-intl": "3.0.0", + "nativescript-iqkeyboardmanager": "1.4.0", + "nativescript-social-share": "1.5.1", + "nativescript-theme-core": "1.0.4", + "nativescript-ui-autocomplete": "4.0.0", + "nativescript-ui-calendar": "4.0.0", + "nativescript-ui-chart": "4.0.0", + "nativescript-ui-dataform": "4.0.0", + "nativescript-ui-gauge": "4.0.0", + "nativescript-ui-listview": "6.1.0", + "nativescript-ui-sidedrawer": "6.0.0", "node-sass": "^4.11.0", "ora": "^3.4.0", "postcss": "^7.0.14", "postcss-mpvue-wxss": "^1.0.2", "postcss-wxss": "^1.0.6", + "preact": "^8.4.2", "readable-stream": "^3.3.0", "resolve-from": "^5.0.0", "rollup": "^1.10.1", @@ -95,6 +117,7 @@ "stylelint-webpack-plugin": "^0.10.5", "stylus": "^0.54.5", "tns-core-modules": "^5.3.1", + "tns-platform-declarations": "5.3.1", "ts-loader": "^5.4.4", "ts-node": "^8.1.0", "tsconfig-paths": "^3.8.0", @@ -133,6 +156,15 @@ "typeorm": "^0.2.16", "webpack-merge": "^4.2.1" }, + "nativescript": { + "id": "org.nativescript.blankts", + "tns-android": { + "version": "5.3.0" + }, + "tns-ios": { + "version": "5.3.0" + } + }, "scripts": { "test": "mocha -r ts-node/register -r tsconfig-paths/register ./**/__tests__/**/*.ts", "ts": "ts-node -r tsconfig-paths/register", @@ -143,8 +175,9 @@ "admin:build": "ts-node -r tsconfig-paths/register packages/nger-cli/lib/bin.ts build admin", "admin:dev": "ts-node -r tsconfig-paths/register packages/nger-cli/lib/bin.ts build admin --watch", "admin:stats": "webpack-bundle-analyzer template/admin/stats.json", + "preview:android": "tns preview", "platform:native": "ts-node -r tsconfig-paths/register packages/nger-cli/lib/bin.ts build lib -n nger-platform-native", "platform:ios": "ts-node -r tsconfig-paths/register packages/nger-cli/lib/bin.ts build lib -n nger-platform-ios", "platform:android": "ts-node -r tsconfig-paths/register packages/nger-cli/lib/bin.ts build lib -n nger-platform-android" } -} \ No newline at end of file +} diff --git a/packages/nger-core/lib/decorators/component.ts b/packages/nger-core/lib/decorators/component.ts index d96efe6..0dd5e46 100644 --- a/packages/nger-core/lib/decorators/component.ts +++ b/packages/nger-core/lib/decorators/component.ts @@ -17,7 +17,7 @@ export interface ComponentOptions extends DirectiveOptions { interpolation?: [string, string]; entryComponents?: Array | any[]>; preserveWhitespaces?: boolean; - type: ComponentType[]; + type?: ComponentType[]; } // P 是props // S 是state diff --git a/packages/nger-platform-browser/lib/application.tsx b/packages/nger-platform-browser/lib/application.tsx index 16364a1..c3164c3 100644 --- a/packages/nger-platform-browser/lib/application.tsx +++ b/packages/nger-platform-browser/lib/application.tsx @@ -23,18 +23,20 @@ export class BrowserApplicationRef extends ApplicationRef { const factory = ref.injector.get(ComponentFactory) const parent = ref.injector.get(ElementRef, null, InjectFlags.SkipSelf) || new ElementRef(this.root); //这里渲染preact - const tpl = factory.def.render; - const res = tpl(ref.instance) - render(res, parent.nativeElement) - // 更新试图,后面有可能会自己实现 - ref.$ngOnChange && ref.$ngOnChange.subscribe(() => { + if (ref.instance.render) { + const tpl = ref.instance.render(); const res = tpl(ref.instance) - render(res, parent.nativeElement, parent.nativeElement.lastElementChild) - }); - super.attachView(ref, injector); - const nowTime = new Date().getTime(); - const totalTime = nowTime - (window as any).nger.startTime - console.log(`总耗时:${totalTime}ms`); + render(res, parent.nativeElement) + // 更新试图,后面有可能会自己实现 + ref.$ngOnChange && ref.$ngOnChange.subscribe(() => { + const res = tpl(ref.instance) + render(res, parent.nativeElement, parent.nativeElement.lastElementChild) + }); + super.attachView(ref, injector); + const nowTime = new Date().getTime(); + const totalTime = nowTime - (window as any).nger.startTime + console.log(`总耗时:${totalTime}ms`); + } } catch (e) { console.log({ ref, injector, diff --git a/packages/nger-platform-h5/lib/cache.ts b/packages/nger-platform-h5/lib/cache.ts index 12e8200..9db4950 100644 --- a/packages/nger-platform-h5/lib/cache.ts +++ b/packages/nger-platform-h5/lib/cache.ts @@ -1,7 +1,13 @@ import { Cache } from 'nger-core' export class NgerH5Cache extends Cache { - get(key: string): Promise { } - put(key: string, value: T): Promise { } - remove(key: string): Promise { } + get(key: string): Promise { + return new Promise(() => { }) + } + put(key: string, value: T): Promise { + return new Promise(() => { }) + } + remove(key: string): Promise { + return new Promise(() => { }) + } clear(): void { } } \ No newline at end of file diff --git a/packages/nger-platform-h5/lib/router.ts b/packages/nger-platform-h5/lib/router.ts index 5d60ee8..e21c3ad 100644 --- a/packages/nger-platform-h5/lib/router.ts +++ b/packages/nger-platform-h5/lib/router.ts @@ -1,13 +1,23 @@ import { Router } from 'nger-core' export class NgerH5Router extends Router { // 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面 - switchTab(url: string): Promise { } + switchTab(url: string): Promise { + return new Promise(() => { }) + } // 关闭所有页面,打开到应用内的某个页面 - reLaunch(url: string): Promise { } + reLaunch(url: string): Promise { + return new Promise(() => { }) + } // 关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面。 - redirectTo(url: string): Promise { } + redirectTo(url: string): Promise { + return new Promise(() => { }) + } // 保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面 - navigateTo(url: string): Promise { } + async navigateTo(url: string): Promise { + return new Promise(() => { }) + } // 关闭当前页面,返回上一页面或多级页面 - navigateBack(delta: number): Promise { } + navigateBack(delta: number): Promise { + return new Promise(() => { }) + } } \ No newline at end of file diff --git a/packages/nger-platform-ios/lib/app-host-view.ts b/packages/nger-platform-ios/lib/app-host-view.ts new file mode 100644 index 0000000..8b7384b --- /dev/null +++ b/packages/nger-platform-ios/lib/app-host-view.ts @@ -0,0 +1,34 @@ +import { ContentView } from "tns-core-modules/ui/content-view"; +import { GridLayout } from "tns-core-modules/ui/layouts/grid-layout"; +import { ProxyViewContainer } from "tns-core-modules/ui/proxy-view-container"; +import { View } from "tns-core-modules/ui/core/view"; + +export class AppHostView extends ContentView { + private _ngAppRoot: View; + private _content: View; + get ngAppRoot(): View { + return this._ngAppRoot; + } + set ngAppRoot(value: View) { + this._ngAppRoot = value; + } + get content(): View { + return this._content; + } + set content(value: View) { + if (this._content) { + this._content.parentNode = undefined as any; + } + this._content = value; + if (value) { + this._content.parentNode = this; + } + this.ngAppRoot = value; + if (this._content instanceof ProxyViewContainer) { + const grid = new GridLayout(); + grid.addChild(this._content); + this.ngAppRoot = grid; + } + } + +} \ No newline at end of file diff --git a/packages/nger-platform-ios/lib/bootstrap.ts b/packages/nger-platform-ios/lib/bootstrap.ts index 29a1607..3e8c5fa 100644 --- a/packages/nger-platform-ios/lib/bootstrap.ts +++ b/packages/nger-platform-ios/lib/bootstrap.ts @@ -1,12 +1,55 @@ -import { NgModuleBootstrap, NgModuleRef } from 'nger-core' +import { NgModuleBootstrap, NgModuleRef, ApplicationRef, NgModuleMetadataKey, NgModuleClassAst } from 'nger-core' import { - run as applicationRun + run as applicationRun, + on, + launchEvent, + LaunchEventData, + exitEvent, + ApplicationEventData, } from "tns-core-modules/application"; -import { Injector } from 'nger-di' +import { profile, uptime } from "tns-core-modules/profiling"; +import { View } from "tns-core-modules/ui/core/view/view"; +import { Injector, InjectionToken } from 'nger-di' +import { AppHostView } from './app-host-view' +import { setRootPage } from 'nger-platform-native' +export const NATIVE_CONFIG = new InjectionToken(`NATIVE_CONFIG`) export class NgerPlatformIosBootstrap extends NgModuleBootstrap { injector: Injector; async run(ref: NgModuleRef) { this.injector = ref.injector; + on('launch', (args) => { + console.log(`launch`) + }) + on('exitEvent', (args) => { + console.log(`exitEvent`) + }); + const config = ref.injector.get(NATIVE_CONFIG) + // ref.componentFactoryResolver.resolveComponentFactory() + const ngModule = ref.context.getClass(NgModuleMetadataKey) as NgModuleClassAst; + const bootstrap = ngModule.ast.metadataDef.bootstrap; + const root = document.getElementById('app') as HTMLDivElement; + const application = ref.injector.get(ApplicationRef) + let rootContent: View; + let tempAppHostView: AppHostView; + tempAppHostView = new AppHostView(); + setRootPage(tempAppHostView); + if (bootstrap) { + bootstrap.map(boot => { + const factory = ref.componentFactoryResolver.resolveComponentFactory(boot) + const component = factory.create(ref.injector); + }) + } + const launchCallback = profile( + "nativescript-angular/platform-common.launchCallback", + (args: LaunchEventData) => { + console.log(`launchCallback`) + }) + const exitCallback = profile( + "nativescript-angular/platform-common.exitCallback", (args: ApplicationEventData) => { + console.log(`exitCallback`) + }) + on(launchEvent, launchCallback); + on(exitEvent, exitCallback); applicationRun(); } } diff --git a/packages/nger-platform-ios/lib/index.ts b/packages/nger-platform-ios/lib/index.ts index 9f10bfa..fc03b82 100644 --- a/packages/nger-platform-ios/lib/index.ts +++ b/packages/nger-platform-ios/lib/index.ts @@ -7,4 +7,5 @@ export default createPlatformFactory(ngerPlatformNative, 'native', [ useClass: NgerPlatformIosBootstrap, deps: [] } -]) \ No newline at end of file +]) +export { NATIVE_CONFIG } from './bootstrap' \ No newline at end of file diff --git a/packages/nger-platform-native/lib/index.ts b/packages/nger-platform-native/lib/index.ts index bcffe4f..975991e 100644 --- a/packages/nger-platform-native/lib/index.ts +++ b/packages/nger-platform-native/lib/index.ts @@ -2,4 +2,5 @@ import {platformCore,createPlatformFactory} from 'nger-core' import platformProviders from './platform-providers' export default createPlatformFactory(platformCore,'native',[ ...platformProviders -]) \ No newline at end of file +]) +export * from './platform-providers' \ No newline at end of file diff --git a/packages/nger-platform-swap/lib/cache.ts b/packages/nger-platform-swap/lib/cache.ts index 306dc0f..ee37cd9 100644 --- a/packages/nger-platform-swap/lib/cache.ts +++ b/packages/nger-platform-swap/lib/cache.ts @@ -1,7 +1,13 @@ import { Cache } from 'nger-core' export class NgerSwapCache extends Cache { - get(key: string): Promise { } - put(key: string, value: T): Promise { } - remove(key: string): Promise { } + get(key: string): Promise { + return new Promise(() => { }) + } + put(key: string, value: T): Promise { + return new Promise(() => { }) + } + remove(key: string): Promise { + return new Promise(() => { }) + } clear(): void { } } \ No newline at end of file diff --git a/packages/nger-platform-swap/lib/logger.ts b/packages/nger-platform-swap/lib/logger.ts index dc0416b..3c0c679 100644 --- a/packages/nger-platform-swap/lib/logger.ts +++ b/packages/nger-platform-swap/lib/logger.ts @@ -1 +1,7 @@ -export class NgerSwapLogger implements Logger { } \ No newline at end of file +import { Logger } from 'nger-core' +export class NgerSwapLogger implements Logger { + debug(...args: any[]) { } + info(...args: any[]) { } + warn(...args: any[]) { } + error(...args: any[]) { } +} \ No newline at end of file diff --git a/packages/nger-platform-tt/lib/cache.ts b/packages/nger-platform-tt/lib/cache.ts index 0fb067b..0b38235 100644 --- a/packages/nger-platform-tt/lib/cache.ts +++ b/packages/nger-platform-tt/lib/cache.ts @@ -1,7 +1,13 @@ import { Cache } from 'nger-core' export class NgerTtCache extends Cache { - get(key: string): Promise { } - put(key: string, value: T): Promise { } - remove(key: string): Promise { } + async get(key: string): Promise { + return new Promise(() => { }) + } + async put(key: string, value: T): Promise { + return new Promise(() => { }) + } + async remove(key: string): Promise { + return new Promise(() => { }) + } clear(): void { } } \ No newline at end of file diff --git a/packages/nger-platform-weapp/lib/cache.ts b/packages/nger-platform-weapp/lib/cache.ts index 6328d11..141bd0d 100644 --- a/packages/nger-platform-weapp/lib/cache.ts +++ b/packages/nger-platform-weapp/lib/cache.ts @@ -1,7 +1,13 @@ import { Cache } from 'nger-core' export class NgerWeappCache extends Cache { - get(key: string): Promise { } - put(key: string, value: T): Promise { } - remove(key: string): Promise { } + async get(key: string): Promise { + return new Promise(() => { }) + } + async put(key: string, value: T): Promise { + return new Promise(() => { }) + } + async remove(key: string): Promise { + return new Promise(() => { }) + } clear(): void { } } \ No newline at end of file diff --git a/src/template/admin/footer/index.tsx b/src/template/admin/footer/index.tsx index 86bb323..a8d36a5 100644 --- a/src/template/admin/footer/index.tsx +++ b/src/template/admin/footer/index.tsx @@ -3,7 +3,5 @@ import { Component } from 'nger-core' selector: 'nger-admin-footer' }) export class NgerAdminFooter { - render() { - return - } + render() { } } diff --git a/tsconfig.json b/tsconfig.json index 164be54..3dd01ec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,8 @@ "module": "commonjs", "lib": [ "dom", - "esnext" + "esnext", + "es6" ], "jsx": "react", "jsxFactory": "h", @@ -19,7 +20,7 @@ "allowUnusedLabels": true, "noImplicitReturns": false, "suppressImplicitAnyIndexErrors": false, - "baseUrl": "./", + "baseUrl": ".", "outDir": "./dist", "rootDir": "./", "strictPropertyInitialization": false, @@ -154,6 +155,13 @@ ], "@babel/traverse": [ "node_modules/@types/babel__traverse" + ], + "*": [ + "./node_modules/*", + "packages/*" + ], + "~/*": [ + "app/*" ] }, "typeRoots": [ @@ -179,9 +187,13 @@ "addon", "src", "typings/*.d.ts", - "src/global.d.ts" + "src/global.d.ts", + "app" ], "exclude": [ - "node_modules" + "node_modules", + "platforms", + "typings", + "**/*.aot.ts" ] } \ No newline at end of file diff --git a/tsconfig.tns.json b/tsconfig.tns.json new file mode 100644 index 0000000..95f2ece --- /dev/null +++ b/tsconfig.tns.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig", + "compilerOptions": { + "module": "es2015", + "moduleResolution": "node" + } +} diff --git a/webpack.config.js b/webpack.config.js index b9d0fdc..7d6822b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,135 +1,286 @@ -import { resolve } from 'path'; -import { - DefinePlugin, - EnvironmentPlugin, - IgnorePlugin, - optimize, -} from 'webpack'; -import WXAppWebpackPlugin, { Targets } from 'wxapp-webpack-plugin'; -import StylelintPlugin from 'stylelint-webpack-plugin'; -import MinifyPlugin from 'babel-minify-webpack-plugin'; -import CopyPlugin from 'copy-webpack-plugin'; -import pkg from './package.json'; - -const { NODE_ENV, LINT } = process.env; -const isDev = NODE_ENV !== 'production'; -const shouldLint = !!LINT && LINT !== 'false'; -const srcDir = resolve('src'); - -const copyPatterns = [] - .concat(pkg.copyWebpack || []) - .map( - (pattern) => - typeof pattern === 'string' ? { from: pattern, to: pattern } : pattern, - ); - -export default (env = {}) => { - const min = env.min; - const target = env.target || 'Wechat'; - const isWechat = env.target !== 'Alipay'; - const isAlipay = !isWechat; - - const relativeFileLoader = (ext = '[ext]') => { - const namePrefix = isWechat ? '' : '[path]'; - return { - loader: 'file-loader', - options: { - useRelativePath: isWechat, - name: `${namePrefix}[name].${ext}`, - context: srcDir, - }, - }; +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 CleanWebpackPlugin = require("clean-webpack-plugin"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer"); +const { NativeScriptWorkerPlugin } = require("nativescript-worker-loader/NativeScriptWorkerPlugin"); +const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); +const hashSalt = Date.now().toString(); + +module.exports = env => { + // Add your custom Activities, Services and other Android app components here. + const appComponents = [ + "tns-core-modules/ui/frame", + "tns-core-modules/ui/frame/activity", + ]; + + const platform = env && (env.android && "android" || env.ios && "ios"); + if (!platform) { + throw new Error("You need to provide a target platform!"); + } + + const platforms = ["ios", "android"]; + const projectRoot = __dirname; + + // Default destination inside platforms//... + const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot)); + const appResourcesPlatformDir = platform === "android" ? "Android" : "iOS"; + + const { + // The 'appPath' and 'appResourcesPath' values are fetched from + // the nsconfig.json configuration file + // when bundling with `tns run android|ios --bundle`. + appPath = "app", + appResourcesPath = "app/App_Resources", + + // You can provide the following flags when running 'tns run android|ios' + snapshot, // --env.snapshot + uglify, // --env.uglify + report, // --env.report + sourceMap, // --env.sourceMap + hmr, // --env.hmr, + unitTesting, // --env.unitTesting + } = env; + const externals = nsWebpack.getConvertedExternals(env.externals); + + const appFullPath = resolve(projectRoot, appPath); + const appResourcesFullPath = resolve(projectRoot, appResourcesPath); + + const entryModule = nsWebpack.getEntryModule(appFullPath); + const entryPath = `.${sep}${entryModule}.ts`; + const entries = { bundle: entryPath }; + if (platform === "ios") { + entries["tns_modules/tns-core-modules/inspector_modules"] = "inspector_modules.js"; }; - return { - entry: { - app: './src/app.js', + const config = { + mode: uglify ? "production" : "development", + context: appFullPath, + externals, + watchOptions: { + ignored: [ + appResourcesFullPath, + // Don't watch hidden files + "**/.*", + ] }, + target: nativescriptTarget, + entry: entries, output: { - filename: '[name].js', - publicPath: '/', - path: resolve('dist', isWechat ? 'wechat' : 'alipay'), + pathinfo: false, + path: dist, + libraryTarget: "commonjs2", + filename: "[name].js", + globalObject: "global", + hashSalt + }, + resolve: { + extensions: [".ts", ".js", ".scss", ".css"], + // Resolve {N} system modules from tns-core-modules + modules: [ + resolve(__dirname, "node_modules/tns-core-modules"), + resolve(__dirname, "node_modules"), + "node_modules/tns-core-modules", + "node_modules", + ], + alias: { + '~': appFullPath + }, + // resolve symlinks to symlinked modules + symlinks: true + }, + resolveLoader: { + // don't resolve symlinks to symlinked loaders + symlinks: false + }, + node: { + // Disable node shims that conflict with NativeScript + "http": false, + "timers": false, + "setImmediate": false, + "fs": "empty", + "__dirname": false, + }, + devtool: sourceMap ? "inline-source-map" : "none", + optimization: { + runtimeChunk: "single", + splitChunks: { + cacheGroups: { + vendor: { + name: "vendor", + chunks: "all", + test: (module, chunks) => { + const moduleName = module.nameForCondition ? module.nameForCondition() : ''; + return /[\\/]node_modules[\\/]/.test(moduleName) || + appComponents.some(comp => comp === moduleName); + + }, + enforce: true, + }, + } + }, + minimize: !!uglify, + minimizer: [ + new UglifyJsPlugin({ + parallel: true, + cache: true, + uglifyOptions: { + output: { + comments: false, + }, + compress: { + // The Android SBG has problems parsing the output + // when these options are enabled + 'collapse_vars': platform !== "android", + sequences: platform !== "android", + } + } + }) + ], }, - target: Targets[target], module: { rules: [ { - test: /\.js$/, - include: /src/, - exclude: /node_modules/, - use: ['babel-loader', shouldLint && 'eslint-loader'].filter(Boolean), - }, - { - test: /\.wxs$/, - include: /src/, - exclude: /node_modules/, - use: [ - relativeFileLoader(), - 'babel-loader', - shouldLint && 'eslint-loader', - ].filter(Boolean), - }, - { - test: /\.(scss|wxss|acss)$/, - include: /src/, + test: nsWebpack.getEntryPathRegExp(appFullPath, entryPath), use: [ - relativeFileLoader(isWechat ? 'wxss' : 'acss'), + // Require all Android app components + platform === "android" && { + loader: "nativescript-dev-webpack/android-app-components-loader", + options: { modules: appComponents } + }, + { - loader: 'sass-loader', + loader: "nativescript-dev-webpack/bundle-config-loader", options: { - includePaths: [resolve('src', 'styles'), srcDir], - }, + loadCss: !snapshot, // load the application css if in debug mode + unitTesting, + appFullPath, + projectRoot, + } }, - ], + ].filter(loader => !!loader) }, + { - test: /\.(json|png|jpg|gif)$/, - include: /src/, - use: relativeFileLoader(), + test: /-page\.ts$/, + use: "nativescript-dev-webpack/script-hot-loader" }, + + { + test: /\.(css|scss)$/, + use: "nativescript-dev-webpack/style-hot-loader" + }, + + { + test: /\.(html|xml)$/, + use: "nativescript-dev-webpack/markup-hot-loader" + }, + + { test: /\.(html|xml)$/, use: "nativescript-dev-webpack/xml-namespace-loader" }, + { - test: /\.(wxml|axml)$/, - include: /src/, + test: /\.css$/, + use: { loader: "css-loader", options: { minimize: false, url: false } } + }, + + { + test: /\.scss$/, use: [ - relativeFileLoader(isWechat ? 'wxml' : 'axml'), - { - loader: 'wxml-loader', - options: { - root: srcDir, - enforceRelativePath: true, - }, + { loader: "css-loader", options: { minimize: false, url: false } }, + "sass-loader" + ] + }, + + { + test: /\.ts$/, + use: { + loader: "ts-loader", + options: { + configFile: "tsconfig.tns.json", + allowTsInNodeModules: true, }, - ], + } }, - ], + ] }, plugins: [ - new EnvironmentPlugin({ - NODE_ENV: 'development', + // Define useful constants like TNS_WEBPACK + new webpack.DefinePlugin({ + "global.TNS_WEBPACK": "true", + "process": undefined, }), - new DefinePlugin({ - __DEV__: isDev, - __WECHAT__: isWechat, - __ALIPAY__: isAlipay, - wx: isWechat ? 'wx' : 'my', - my: isWechat ? 'wx' : 'my', + // Remove all files from the out dir. + new CleanWebpackPlugin([`${dist}/**/*`]), + // Copy assets to out dir. Add your own globs as needed. + new CopyWebpackPlugin([ + { from: { glob: "fonts/**" } }, + { from: { glob: "**/*.jpg" } }, + { from: { glob: "**/*.png" } }, + ], { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] }), + // Generate a bundle starter script and activate it in package.json + new nsWebpack.GenerateBundleStarterPlugin( + // Don't include `runtime.js` when creating a snapshot. The plugin + // configures the WebPack runtime to be generated inside the snapshot + // module and no `runtime.js` module exist. + (snapshot ? [] : ["./runtime"]) + .concat([ + "./vendor", + "./bundle", + ]) + ), + // For instructions on how to set up workers with webpack + // check out https://github.com/nativescript/worker-loader + new NativeScriptWorkerPlugin(), + new nsWebpack.PlatformFSPlugin({ + platform, + platforms, }), - new WXAppWebpackPlugin({ - clear: !isDev, - }), - new optimize.ModuleConcatenationPlugin(), - new IgnorePlugin(/vertx/), - shouldLint && new StylelintPlugin(), - min && new MinifyPlugin(), - new CopyPlugin(copyPatterns, { context: srcDir }), - ].filter(Boolean), - devtool: isDev ? 'source-map' : false, - resolve: { - modules: [resolve(__dirname, 'src'), 'node_modules'], - }, - watchOptions: { - ignored: /dist|manifest/, - aggregateTimeout: 300, - }, + // Does IPC communication with the {N} CLI to notify events when running in watch mode. + new nsWebpack.WatchStateLoggerPlugin(), + ], }; -}; \ No newline at end of file + + // Copy the native app resources to the out dir + // only if doing a full build (tns run/build) and not previewing (tns preview) + if (!externals || externals.length === 0) { + config.plugins.push(new CopyWebpackPlugin([ + { + from: `${appResourcesFullPath}/${appResourcesPlatformDir}`, + to: `${dist}/App_Resources/${appResourcesPlatformDir}`, + context: projectRoot + }, + ])); + } + + if (report) { + // Generate report files for bundles content + config.plugins.push(new BundleAnalyzerPlugin({ + analyzerMode: "static", + openAnalyzer: false, + generateStatsFile: true, + reportFilename: resolve(projectRoot, "report", `report.html`), + statsFilename: resolve(projectRoot, "report", `stats.json`), + })); + } + + if (snapshot) { + config.plugins.push(new nsWebpack.NativeScriptSnapshotPlugin({ + chunk: "vendor", + requireModules: [ + "tns-core-modules/bundle-entry-points", + ], + projectRoot, + webpackConfig: config, + })); + } + + if (hmr) { + config.plugins.push(new webpack.HotModuleReplacementPlugin()); + } + + + return config; +};