diff --git a/tns-core-modules/application/application-common.ts b/tns-core-modules/application/application-common.ts index 32ab9dc04..2ce00a10a 100644 --- a/tns-core-modules/application/application-common.ts +++ b/tns-core-modules/application/application-common.ts @@ -70,11 +70,11 @@ export function setApplication(instance: iOSApplication | AndroidApplication): v app = instance; } -export function livesync() { +export function livesync(context?: HmrContext) { events.notify({ eventName: "livesync", object: app }); const liveSyncCore = global.__onLiveSyncCore; if (liveSyncCore) { - liveSyncCore(); + liveSyncCore(context); } } @@ -92,7 +92,7 @@ export function loadAppCss(): void { events.notify({ eventName: "loadAppCss", object: app, cssFile: getCssFileName() }); } catch (e) { throw new Error(`The file ${getCssFileName()} couldn't be loaded! ` + - `You may need to register it inside ./app/vendor.ts.`); + `You may need to register it inside ./app/vendor.ts.`); } } diff --git a/tns-core-modules/application/application.android.ts b/tns-core-modules/application/application.android.ts index 0b33bba80..6d5166675 100644 --- a/tns-core-modules/application/application.android.ts +++ b/tns-core-modules/application/application.android.ts @@ -212,12 +212,12 @@ export function getNativeApplication(): android.app.Application { return nativeApp; } -global.__onLiveSync = function () { +global.__onLiveSync = function __onLiveSync(context?: HmrContext) { if (androidApp && androidApp.paused) { return; } - livesync(); + livesync(context); }; function initLifecycleCallbacks() { diff --git a/tns-core-modules/application/application.ios.ts b/tns-core-modules/application/application.ios.ts index 867f2ef6f..85adbbccc 100644 --- a/tns-core-modules/application/application.ios.ts +++ b/tns-core-modules/application/application.ios.ts @@ -18,6 +18,7 @@ export * from "./application-common"; import { createViewFromEntry } from "../ui/builder"; import { ios as iosView, View } from "../ui/core/view"; import { Frame, NavigationEntry } from "../ui/frame"; +import { loadCss } from "../ui/styling/style-scope"; import * as utils from "../utils/utils"; import { profile, level as profilingLevel, Level } from "../profiling"; @@ -225,10 +226,21 @@ class IOSApplication implements IOSApplicationDefinition { } } - public _onLivesync(): void { - // If view can't handle livesync set window controller. - if (!this._rootView._onLivesync()) { - this.setWindowContent(); + public _onLivesync(context?: HmrContext): void { + let executeLivesync = true; + // HMR has context, livesync does not + if (context) { + if (context.module === getCssFileName()) { + loadCss(context.module); + this._rootView._onCssStateChange(); + executeLivesync = false; + } + } + if (executeLivesync) { + // If view can't handle livesync set window controller. + if (!this._rootView._onLivesync()) { + this.setWindowContent(); + } } } @@ -264,8 +276,8 @@ exports.ios = iosApp; setApplication(iosApp); // attach on global, so it can be overwritten in NativeScript Angular -(global).__onLiveSyncCore = function () { - iosApp._onLivesync(); +(global).__onLiveSyncCore = function __onLiveSyncCore(context?: HmrContext) { + iosApp._onLivesync(context); } let mainEntry: NavigationEntry; @@ -373,10 +385,10 @@ function setViewControllerView(view: View): void { } } -global.__onLiveSync = function () { +global.__onLiveSync = function __onLiveSync(context?: HmrContext) { if (!started) { return; } - livesync(); + livesync(context); } diff --git a/tns-core-modules/module.d.ts b/tns-core-modules/module.d.ts index 5fd5d161d..3d973d27f 100644 --- a/tns-core-modules/module.d.ts +++ b/tns-core-modules/module.d.ts @@ -51,8 +51,8 @@ declare namespace NodeJS { __native?: any; __inspector?: any; __extends: any; - __onLiveSync: () => void; - __onLiveSyncCore: () => void; + __onLiveSync: (context?: { type: string, module: string }) => void; + __onLiveSyncCore: (context?: { type: string, module: string }) => void; __onUncaughtError: (error: NativeScriptError) => void; TNS_WEBPACK?: boolean; __requireOverride?: (name: string, dir: string) => any; @@ -64,6 +64,27 @@ declare function clearTimeout(timeoutId: number): void; declare function setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): number; declare function clearInterval(intervalId: number): void; +declare enum HmrType { + markup = "markup", + script = "script", + style = "style" +} + +/** + * Define a context for Hot Module Replacement. + */ +interface HmrContext { + /** + * The type of module for replacement. + */ + type: HmrType; + + /** + * The module for replacement. + */ + module: string; +} + /** * An extended JavaScript Error which will have the nativeError property initialized in case the error is caused by executing platform-specific code. */ diff --git a/tns-core-modules/ui/frame/frame.android.ts b/tns-core-modules/ui/frame/frame.android.ts index 1f56a8eb2..077d01038 100644 --- a/tns-core-modules/ui/frame/frame.android.ts +++ b/tns-core-modules/ui/frame/frame.android.ts @@ -17,6 +17,7 @@ import { _updateTransitions, _reverseTransitions, _clearEntry, _clearFragment, AnimationType } from "./fragment.transitions"; +import { loadCss } from "../styling/style-scope"; import { profile } from "../../profiling"; // TODO: Remove this and get it from global to decouple builder for angular @@ -82,13 +83,24 @@ function getAttachListener(): android.view.View.OnAttachStateChangeListener { return attachStateChangeListener; } -export function reloadPage(): void { +export function reloadPage(context?: HmrContext): void { const activity = application.android.foregroundActivity; const callbacks: AndroidActivityCallbacks = activity[CALLBACKS]; const rootView: View = callbacks.getRootView(); - if (!rootView || !rootView._onLivesync()) { - callbacks.resetActivityContent(activity); + let executeLivesync = true; + // HMR has context, livesync does not + if (context) { + if (context.module === application.getCssFileName()) { + loadCss(context.module); + rootView._onCssStateChange(); + executeLivesync = false; + } + } + if (executeLivesync) { + if (!rootView || !rootView._onLivesync()) { + callbacks.resetActivityContent(activity); + } } } @@ -469,19 +481,19 @@ export class Frame extends FrameBase { switch (this.actionBarVisibility) { case "never": return false; - + case "always": return true; - + default: if (page.actionBarHidden !== undefined) { return !page.actionBarHidden; } - + if (this._android && this._android.showActionBar !== undefined) { return this._android.showActionBar; } - + return true; } } @@ -846,14 +858,14 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks { // parent while its supposed parent believes it properly removed its children; in order to "force" the child to // lose its parent we temporarily add it to the parent, and then remove it (addViewInLayout doesn't trigger layout pass) const nativeView = page.nativeViewProtected; - if (nativeView != null) { - const parentView = nativeView.getParent(); + if (nativeView != null) { + const parentView = nativeView.getParent(); if (parentView instanceof android.view.ViewGroup) { if (parentView.getChildCount() === 0) { parentView.addViewInLayout(nativeView, -1, new org.nativescript.widgets.CommonLayoutParams()); } - parentView.removeView(nativeView); + parentView.removeView(nativeView); } } diff --git a/tns-core-modules/ui/page/page-common.ts b/tns-core-modules/ui/page/page-common.ts index 0d85ab1ab..738c0bfa8 100644 --- a/tns-core-modules/ui/page/page-common.ts +++ b/tns-core-modules/ui/page/page-common.ts @@ -17,17 +17,17 @@ export class PageBase extends ContentView implements PageDefinition { public static navigatedToEvent = "navigatedTo"; public static navigatingFromEvent = "navigatingFrom"; public static navigatedFromEvent = "navigatedFrom"; - + private _navigationContext: any; private _actionBar: ActionBar; public _frame: Frame; - + public actionBarHidden: boolean; public enableSwipeBackNavigation: boolean; public backgroundSpanUnderStatusBar: boolean; public hasActionBar: boolean; - + get navigationContext(): any { return this._navigationContext; } @@ -89,7 +89,7 @@ export class PageBase extends ContentView implements PageDefinition { const frame = this.parent; return frame instanceof Frame ? frame : undefined; } - + private createNavigatedData(eventName: string, isBackNavigation: boolean): NavigatedData { return { eventName: eventName, @@ -103,6 +103,10 @@ export class PageBase extends ContentView implements PageDefinition { public onNavigatingTo(context: any, isBackNavigation: boolean, bindingContext?: any) { this._navigationContext = context; + if (!this._cssState.isSelectorsLatestVersionApplied()) { + this._onCssStateChange(); + } + //https://github.com/NativeScript/NativeScript/issues/731 if (!isBackNavigation && bindingContext !== undefined && bindingContext !== null) { this.bindingContext = bindingContext; diff --git a/tns-core-modules/ui/styling/style-scope.d.ts b/tns-core-modules/ui/styling/style-scope.d.ts index b404ce9a7..b5bf01684 100644 --- a/tns-core-modules/ui/styling/style-scope.d.ts +++ b/tns-core-modules/ui/styling/style-scope.d.ts @@ -19,6 +19,11 @@ export class CssState { * Gets the static selectors that match the view and the dynamic selectors that may potentially match the view. */ public changeMap: ChangeMap; + + /** + * Checks whether style scope and CSS state selectors are in sync. + */ + public isSelectorsLatestVersionApplied(): boolean } export class StyleScope { @@ -29,6 +34,9 @@ export class StyleScope { public static createSelectorsFromImports(tree: SyntaxTree, keyframes: Object): RuleSet[]; public ensureSelectors(): number; + public isApplicationCssSelectorsLatestVersionApplied(): boolean; + public isLocalCssSelectorsLatestVersionApplied(): boolean; + public applySelectors(view: ViewBase): void public query(options: Node): SelectorCore[]; diff --git a/tns-core-modules/ui/styling/style-scope.ts b/tns-core-modules/ui/styling/style-scope.ts index 8f3cf607c..e085c2870 100644 --- a/tns-core-modules/ui/styling/style-scope.ts +++ b/tns-core-modules/ui/styling/style-scope.ts @@ -271,7 +271,7 @@ export function removeTaggedAdditionalCSS(tag: String | Number): Boolean { changed = true; } } - if (changed) { mergeCssSelectors(); } + if (changed) { mergeCssSelectors(); } return changed; } @@ -307,7 +307,7 @@ function onLiveSync(args: applicationCommon.CssChangedEventData): void { loadCss(applicationCommon.getCssFileName()); } -const loadCss = profile(`"style-scope".loadCss`, (cssFile: string) => { +export const loadCss = profile(`"style-scope".loadCss`, (cssFile: string) => { if (!cssFile) { return undefined; } @@ -343,6 +343,7 @@ export class CssState { _appliedChangeMap: Readonly>; _appliedPropertyValues: Readonly<{}>; _appliedAnimations: ReadonlyArray; + _appliedSelectorsVersion: number; _match: SelectorsMatch; _matchInvalid: boolean; @@ -367,6 +368,15 @@ export class CssState { } } + public isSelectorsLatestVersionApplied(): boolean { + if (this._appliedSelectorsVersion && this.view._styleScope) { + this.view._styleScope.ensureSelectors(); + return this.view._styleScope._getSelectorsVersion() === this._appliedSelectorsVersion; + } else { + return true; + } + } + public onLoaded(): void { if (this._matchInvalid) { this.updateMatch(); @@ -381,6 +391,7 @@ export class CssState { @profile private updateMatch() { + this._appliedSelectorsVersion = this.view._styleScope._getSelectorsVersion(); this._match = this.view._styleScope ? this.view._styleScope.matchSelectors(this.view) : CssState.emptyMatch; this._matchInvalid = false; } @@ -597,8 +608,8 @@ export class StyleScope { } public ensureSelectors(): number { - if (this._applicationCssSelectorsAppliedVersion !== applicationCssSelectorVersion || - this._localCssSelectorVersion !== this._localCssSelectorsAppliedVersion || + if (!this.isApplicationCssSelectorsLatestVersionApplied() || + !this.isLocalCssSelectorsLatestVersionApplied() || !this._mergedCssSelectors) { this._createSelectors(); @@ -607,6 +618,14 @@ export class StyleScope { return this._getSelectorsVersion(); } + public isApplicationCssSelectorsLatestVersionApplied(): boolean { + return this._applicationCssSelectorsAppliedVersion === applicationCssSelectorVersion; + } + + public isLocalCssSelectorsLatestVersionApplied(): boolean { + return this._localCssSelectorsAppliedVersion === this._localCssSelectorVersion; + } + @profile private _createSelectors() { let toMerge: RuleSet[][] = []; diff --git a/tsconfig.shared.json b/tsconfig.shared.json index eea37a566..d8fb42fd4 100644 --- a/tsconfig.shared.json +++ b/tsconfig.shared.json @@ -26,4 +26,4 @@ "tns-core-modules/*": ["tns-core-modules/*"] } } -} +} \ No newline at end of file