FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat(HMR): style views at runtime (#7012) · NativeScript/NativeScript@3c2c1d9 · GitHub

Commit 3c2c1d9

Browse files
authored
feat(HMR): style views at runtime (#7012)
* feat(HMR): style view at runtime test: module root view component test: update livesync tests refactor: _onLivesync function * style: remove a comment * refactor: rename the property
1 parent 451026f commit 3c2c1d9

10 files changed

Lines changed: 142 additions & 50 deletions

File tree

‎tests/app/app/main-page.css‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Button {
2+
color: green;
3+
}

‎tests/app/livesync/livesync-tests.ts‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const mainPageCssFileName = "./app/main-page.css";
1515
const mainPageHtmlFileName = "./app/main-page.html";
1616
const mainPageXmlFileName = "./app/main-page.xml";
1717

18+
const black = new Color("black");
1819
const green = new Color("green");
1920

2021
const mainPageTemplate = `
@@ -56,7 +57,7 @@ export function test_onLiveSync_ModuleContext_Script_AppTs() {
5657
}
5758

5859
export function test_onLiveSync_ModuleContext_Style_MainPageCss() {
59-
_test_onLiveSync_ModuleContext({ type: "style", path: mainPageCssFileName });
60+
_test_onLiveSync_ModuleContext_TypeStyle({ type: "style", path: mainPageCssFileName });
6061
}
6162

6263
export function test_onLiveSync_ModuleContext_Markup_MainPageHtml() {
@@ -110,4 +111,29 @@ function _test_onLiveSync_ModuleContext(context: { type, path }) {
110111
const topmostFrame = frame.topmost();
111112
TKUnit.waitUntilReady(() => topmostFrame.currentPage && topmostFrame.currentPage.isLoaded && !topmostFrame.canGoBack());
112113
TKUnit.assertTrue(topmostFrame.currentPage.getViewById("label").isLoaded);
114+
}
115+
116+
function _test_onLiveSync_ModuleContext_TypeStyle(context: { type, path }) {
117+
const pageBeforeNavigation = helper.getCurrentPage();
118+
119+
const page = <Page>parse(pageTemplate);
120+
helper.navigateWithHistory(() => page);
121+
122+
const pageBeforeLiveSync = helper.getCurrentPage();
123+
pageBeforeLiveSync._moduleName = "main-page";
124+
global.__onLiveSync({ type: context.type, path: context.path });
125+
126+
const pageAfterLiveSync = helper.getCurrentPage();
127+
TKUnit.waitUntilReady(() => pageAfterLiveSync.getViewById("button").style.color.toString() === green.toString());
128+
129+
TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), "Local styles NOT applied - livesync navigation executed!");
130+
TKUnit.assertEqual(pageAfterLiveSync, pageBeforeLiveSync, "Pages are different - livesync navigation executed!");
131+
TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version NOT applied!");
132+
133+
helper.goBack();
134+
135+
const pageAfterNavigationBack = helper.getCurrentPage();
136+
TKUnit.assertEqual(pageAfterNavigationBack.getViewById("label").style.color, black, "App styles applied on back navigation!");
137+
TKUnit.assertEqual(pageBeforeNavigation, pageAfterNavigationBack, "Pages are different - livesync navigation executed!");
138+
TKUnit.assertTrue(pageAfterNavigationBack._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version is NOT applied!");
113139
}

‎tests/app/testRunner.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,15 @@ allTests["VISUAL-STATE"] = visualStateTests;
168168
import * as valueSourceTests from "./ui/styling/value-source-tests";
169169
allTests["VALUE-SOURCE"] = valueSourceTests;
170170

171-
import * as buttonTests from "./ui/button/button-tests";
172-
allTests["BUTTON"] = buttonTests;
173-
174171
import * as borderTests from "./ui/border/border-tests";
175172
allTests["BORDER"] = borderTests;
176173

174+
import * as builderTests from "./ui/builder/builder-tests";
175+
allTests["BUILDER"] = builderTests;
176+
177+
import * as buttonTests from "./ui/button/button-tests";
178+
allTests["BUTTON"] = buttonTests;
179+
177180
import * as labelTests from "./ui/label/label-tests";
178181
allTests["LABEL"] = labelTests;
179182

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { path } from "tns-core-modules/file-system";
2+
import { loadPage } from "tns-core-modules/ui/builder";
3+
import { assertEqual, assertNull } from "../../TKUnit";
4+
5+
const COMPONENT_MODULE = "component-module";
6+
const LABEL = "label";
7+
8+
function getViewComponent() {
9+
const moduleNamePath = path.join(__dirname, COMPONENT_MODULE);
10+
const fileName = path.join(__dirname, `${COMPONENT_MODULE}.xml`);
11+
const view = loadPage(moduleNamePath, fileName);
12+
return view;
13+
}
14+
15+
export function test_view_is_module_root_component() {
16+
const view = getViewComponent();
17+
const actualModule = view._moduleName;
18+
assertEqual(actualModule, COMPONENT_MODULE, `View<${view}> is NOT root component of module <${COMPONENT_MODULE}>.`);
19+
}
20+
21+
export function test_view_is_NOT_module_root_component() {
22+
const view = getViewComponent();
23+
const nestedView = view.getViewById(`${LABEL}`);
24+
const undefinedModule = nestedView._moduleName;
25+
assertNull(undefinedModule, `View<${nestedView}> should NOT be a root component of a module.`);
26+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<StackLayout>
2+
<Label id="label"></Label>
3+
</StackLayout>

‎tns-core-modules/ui/builder/builder.ts‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ export function load(pathOrOptions: string | LoadOptions, context?: any): View {
5959

6060
export function loadPage(moduleNamePath: string, fileName: string, context?: any): View {
6161
const componentModule = loadInternal(fileName, context, moduleNamePath);
62-
return componentModule && componentModule.component;
62+
const componentView = componentModule && componentModule.component;
63+
markAsModuleRoot(componentView, moduleNamePath);
64+
return componentView;
6365
}
6466

6567
const loadModule = profile("loadModule", (moduleNamePath: string, entry: ViewEntry): ModuleExports => {
@@ -96,7 +98,7 @@ export const createViewFromEntry = profile("createViewFromEntry", (entry: ViewEn
9698
} else if (entry.moduleName) {
9799
// Current app full path.
98100
const currentAppPath = knownFolders.currentApp().path;
99-
101+
100102
// Full path of the module = current app full path + module name.
101103
const moduleNamePath = path.join(currentAppPath, entry.moduleName);
102104
const moduleExports = loadModule(moduleNamePath, entry);
@@ -108,7 +110,7 @@ export const createViewFromEntry = profile("createViewFromEntry", (entry: ViewEn
108110
return viewFromBuilder(moduleNamePath, moduleExports);
109111
}
110112
}
111-
113+
112114
throw new Error("Failed to load page XML file for module: " + entry.moduleName);
113115
});
114116

@@ -128,14 +130,20 @@ interface ModuleExports {
128130
const moduleCreateView = profile("module.createView", (moduleNamePath: string, moduleExports: ModuleExports): View => {
129131
const view = moduleExports.createPage();
130132
const cssFileName = resolveFileName(moduleNamePath, "css");
131-
133+
132134
// If there is no cssFile only appCss will be applied at loaded.
133135
if (cssFileName) {
134136
view.addCssFile(cssFileName);
135137
}
136138
return view;
137139
});
138140

141+
function markAsModuleRoot(componentView: View, moduleNamePath: string): void {
142+
const lastIndexOfSeparator = moduleNamePath.lastIndexOf(path.separator);
143+
const moduleName = moduleNamePath.substring(lastIndexOfSeparator + 1);
144+
componentView._moduleName = moduleName;
145+
}
146+
139147
function loadInternal(fileName: string, context?: any, moduleNamePath?: string): ComponentModule {
140148
let componentModule: ComponentModule;
141149

‎tns-core-modules/ui/core/view-base/view-base.d.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ export abstract class ViewBase extends Observable {
108108
flexWrapBefore: FlexWrapBefore;
109109
alignSelf: AlignSelf;
110110

111+
/**
112+
* @private
113+
* Module name when the view is a module root. Otherwise, it is undefined.
114+
*/
115+
_moduleName?: string;
116+
111117
//@private
112118
/**
113119
* @private

‎tns-core-modules/ui/core/view-base/view-base.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
244244
public _defaultPaddingLeft: number;
245245
public _isPaddingRelative: boolean;
246246

247+
public _moduleName: string;
248+
247249
constructor() {
248250
super();
249251
this._domId = viewIdCounter++;
@@ -651,7 +653,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
651653
}
652654

653655
public resetNativeView(): void {
654-
//
656+
//
655657
}
656658

657659
private resetNativeViewInternal(): void {
@@ -688,7 +690,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
688690

689691
this._context = context;
690692

691-
// This will account for nativeView that is created in createNativeView, recycled
693+
// This will account for nativeView that is created in createNativeView, recycled
692694
// or for backward compatability - set before _setupUI in iOS contructor.
693695
let nativeView = this.nativeViewProtected;
694696

‎tns-core-modules/ui/core/view/view-common.ts‎

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from ".";
66

77
import {
8-
ViewBase, Property, booleanConverter, EventData, layout,
8+
ViewBase, Property, booleanConverter, eachDescendant, EventData, layout,
99
getEventOrGestureName, traceEnabled, traceWrite, traceCategories,
1010
InheritedProperty,
1111
ShowModalOptions
@@ -136,6 +136,37 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
136136
}
137137
}
138138

139+
public _onLivesync(context?: ModuleContext): boolean {
140+
_rootModalViews.forEach(v => v.closeModal());
141+
_rootModalViews.length = 0;
142+
143+
// Currently, we pass `context` only for style modules
144+
if (context && context.path) {
145+
return this.changeLocalStyles(context.path);
146+
}
147+
148+
return false;
149+
}
150+
151+
private changeLocalStyles(contextPath: string): boolean {
152+
if (!this.changeStyles(this, contextPath)) {
153+
eachDescendant(this, (child: ViewBase) => {
154+
this.changeStyles(child, contextPath);
155+
return true;
156+
});
157+
}
158+
// Do not execute frame navigation for a change in styles
159+
return true;
160+
}
161+
162+
private changeStyles(view: ViewBase, contextPath: string): boolean {
163+
if (view._moduleName && contextPath.includes(view._moduleName)) {
164+
(<this>view).changeCssFile(contextPath);
165+
return true;
166+
}
167+
return false;
168+
}
169+
139170
_setupAsRootView(context: any): void {
140171
super._setupAsRootView(context);
141172
if (!this._styleScope) {
@@ -210,12 +241,6 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
210241
}
211242
}
212243

213-
_onLivesync(): boolean {
214-
_rootModalViews.forEach(v => v.closeModal());
215-
_rootModalViews.length = 0;
216-
return false;
217-
}
218-
219244
public onBackPressed(): boolean {
220245
return false;
221246
}

‎tns-core-modules/ui/frame/frame-common.ts‎

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -564,44 +564,34 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
564564
}
565565

566566
public _onLivesync(context?: ModuleContext): boolean {
567-
super._onLivesync();
568-
569-
if (!this._currentEntry || !this._currentEntry.entry) {
570-
return false;
571-
}
572-
573-
const currentEntry = this._currentEntry.entry;
574-
if (context && context.path) {
575-
// Use topmost instead of this to cover nested frames scenario
576-
const topmostFrame = topmost();
577-
const moduleName = topmostFrame.currentEntry.moduleName;
578-
const reapplyStyles = context.path.includes(moduleName);
579-
if (reapplyStyles && moduleName) {
580-
topmostFrame.currentPage.changeCssFile(context.path);
581-
return true;
567+
// Execute a navigation if not handled on `View` level
568+
if (!super._onLivesync(context)) {
569+
if (!this._currentEntry || !this._currentEntry.entry) {
570+
return false;
582571
}
583-
}
584572

585-
const newEntry: NavigationEntry = {
586-
animated: false,
587-
clearHistory: true,
588-
context: currentEntry.context,
589-
create: currentEntry.create,
590-
moduleName: currentEntry.moduleName,
591-
backstackVisible: currentEntry.backstackVisible
592-
}
573+
const currentEntry = this._currentEntry.entry;
574+
const newEntry: NavigationEntry = {
575+
animated: false,
576+
clearHistory: true,
577+
context: currentEntry.context,
578+
create: currentEntry.create,
579+
moduleName: currentEntry.moduleName,
580+
backstackVisible: currentEntry.backstackVisible
581+
}
593582

594-
// If create returns the same page instance we can't recreate it.
595-
// Instead of navigation set activity content.
596-
// This could happen if current page was set in XML as a Page instance.
597-
if (newEntry.create) {
598-
const page = newEntry.create();
599-
if (page === this.currentPage) {
600-
return false;
583+
// If create returns the same page instance we can't recreate it.
584+
// Instead of navigation set activity content.
585+
// This could happen if current page was set in XML as a Page instance.
586+
if (newEntry.create) {
587+
const page = newEntry.create();
588+
if (page === this.currentPage) {
589+
return false;
590+
}
601591
}
602-
}
603592

604-
this.navigate(newEntry);
593+
this.navigate(newEntry);
594+
}
605595
return true;
606596
}
607597
}

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL