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

feat(CSS): import of relative paths (#6023) · NativeScript/NativeScript@6ce1d22 · GitHub

Commit 6ce1d22

Browse files
Vasil Chimev
authored
feat(CSS): import of relative paths (#6023)
* feat(CSS): import of relative paths * refactor: address comments
1 parent bdf55d9 commit 6ce1d22

3 files changed

Lines changed: 124 additions & 47 deletions

File tree

‎tests/app/ui/styling/style-tests.ts‎

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1409,50 +1409,72 @@ export function test_CascadingClassNamesAppliesAfterPageLoad() {
14091409
}
14101410

14111411
export function test_resolveFileNameFromUrl_local_file_tilda() {
1412-
const localFileExistsMock = (fileName: string ) => true;
1413-
let url = "~/theme/core.css";
1414-
let appDirectory = "app";
1415-
let expected = `${appDirectory}/theme/core.css`;
1416-
let result = resolveFileNameFromUrl(url, appDirectory, localFileExistsMock);
1412+
const localFileExistsMock = (fileName: string) => true;
1413+
const url = "~/theme/core.css";
1414+
const appDirectory = "app";
1415+
const expected = `${appDirectory}/theme/core.css`;
1416+
const result = resolveFileNameFromUrl(url, appDirectory, localFileExistsMock);
14171417

14181418
TKUnit.assertEqual(result, expected, "Should resolve local file with leading tilda (~/)");
14191419
}
14201420

14211421
export function test_resolveFileNameFromUrl_local_file_no_tilda() {
1422-
const localFileExistsMock = (fileName: string ) => true;
1423-
let url = "theme/core.css";
1424-
let appDirectory = "app";
1425-
let expected = `${appDirectory}/theme/core.css`;
1426-
let result = resolveFileNameFromUrl(url, appDirectory, localFileExistsMock);
1422+
const localFileExistsMock = (fileName: string) => true;
1423+
const url = "theme/core.css";
1424+
const appDirectory = "app";
1425+
const expected = `${appDirectory}/theme/core.css`;
1426+
const result = resolveFileNameFromUrl(url, appDirectory, localFileExistsMock);
14271427

14281428
TKUnit.assertEqual(result, expected, "Should resolve local file without leading tilda (no ~/)");
14291429
}
14301430

14311431
export function test_resolveFileNameFromUrl_external_file_tilda() {
14321432
const externalFileExistsMock = (fileName: string) => (fileName.indexOf("tns_modules") !== -1);
1433-
let url = "~/theme/core.css";
1434-
let appDirectory = "app";
1435-
let expected = `${appDirectory}/tns_modules/theme/core.css`;
1436-
let result = resolveFileNameFromUrl(url, appDirectory, externalFileExistsMock);
1433+
const url = "~/theme/core.css";
1434+
const appDirectory = "app";
1435+
const expected = `${appDirectory}/tns_modules/theme/core.css`;
1436+
const result = resolveFileNameFromUrl(url, appDirectory, externalFileExistsMock);
14371437

14381438
TKUnit.assertEqual(result, expected, "Should resolve file from tns_modules with leading tilda (~/)");
14391439
}
14401440

14411441
export function test_resolveFileNameFromUrl_external_file_no_tilda() {
14421442
const externalFileExistsMock = (fileName: string) => (fileName.indexOf("tns_modules") !== -1);
1443-
let url = "theme/core.css";
1444-
let appDirectory = "app";
1445-
let expected = `${appDirectory}/tns_modules/theme/core.css`;
1446-
let result = resolveFileNameFromUrl(url, appDirectory, externalFileExistsMock);
1443+
const url = "theme/core.css";
1444+
const appDirectory = "app";
1445+
const expected = `${appDirectory}/tns_modules/theme/core.css`;
1446+
const result = resolveFileNameFromUrl(url, appDirectory, externalFileExistsMock);
14471447

14481448
TKUnit.assertEqual(result, expected, "Should resolve file from tns_modules without leading tilda (no ~/)");
14491449
}
14501450

1451+
export function test_resolveFileNameFromUrl_import_relative_file_curent_dir() {
1452+
const importedFileExistsMock = (fileName: string) => (fileName.indexOf("views") !== -1);
1453+
const importSource = "app/views/main-page.css";
1454+
const url = "./common.css";
1455+
const appDirectory = "app";
1456+
const expected = `${appDirectory}/views/common.css`;
1457+
const result = resolveFileNameFromUrl(url, appDirectory, importedFileExistsMock, importSource);
1458+
1459+
TKUnit.assertEqual(result, expected, "Should resolve relative file to current directory");
1460+
}
1461+
1462+
export function test_resolveFileNameFromUrl_import_relative_file_parent_dir() {
1463+
const importedFileExistsMock = (fileName: string) => (fileName.indexOf("views") !== -1);
1464+
const importSource = "app/views/shared/main-page.css";
1465+
const url = "../common.css";
1466+
const appDirectory = "app";
1467+
const expected = `${appDirectory}/views/common.css`;
1468+
const result = resolveFileNameFromUrl(url, appDirectory, importedFileExistsMock, importSource);
1469+
1470+
TKUnit.assertEqual(result, expected, "Should resolve relative file to parent directory");
1471+
}
1472+
14511473
export function test_resolveFileNameFromUrl_unexisting_file() {
14521474
const fileDoesNotExistMock = (fileName: string) => false;
1453-
let url = "~/theme/core.css";
1454-
let appDirectory = "app";
1455-
let result = resolveFileNameFromUrl(url, appDirectory, fileDoesNotExistMock);
1475+
const url = "~/theme/core.css";
1476+
const appDirectory = "app";
1477+
const result = resolveFileNameFromUrl(url, appDirectory, fileDoesNotExistMock);
14561478

14571479
TKUnit.assertNull(result, "Shouldn't resolve unexisting file");
14581480
}

‎tns-core-modules/ui/styling/style-scope.d.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ export class StyleScope {
3636
public getAnimations(ruleset: RuleSet): KeyframeAnimationInfo[];
3737
}
3838

39-
export function resolveFileNameFromUrl(url: string, appDirectory: string, fileExists: (string) => boolean): string;
39+
export function resolveFileNameFromUrl(url: string, appDirectory: string, fileExists: (string) => boolean, importSource?: string): string;
4040
export function applyInlineStyle(view: ViewBase, style: string): void;

‎tns-core-modules/ui/styling/style-scope.ts‎

Lines changed: 80 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,8 @@ class CSSSource {
7979
}
8080

8181
public static fromURI(uri: string, keyframes: KeyframesMap): CSSSource {
82-
// webpack modules require all file paths to be relative to /app folder.
83-
let appRelativeUri = uri;
84-
if (appRelativeUri.startsWith("/")) {
85-
var app = knownFolders.currentApp().path + "/";
86-
if (appRelativeUri.startsWith(app)) {
87-
appRelativeUri = "./" + appRelativeUri.substr(app.length);
88-
}
89-
}
82+
// webpack modules require all file paths to be relative to /app folder
83+
let appRelativeUri = CSSSource.pathRelativeToApp(uri);
9084

9185
try {
9286
const cssOrAst = global.loadModule(appRelativeUri);
@@ -109,6 +103,21 @@ class CSSSource {
109103
return CSSSource.fromFile(appRelativeUri, keyframes);
110104
}
111105

106+
private static pathRelativeToApp(uri: string): string {
107+
if (!uri.startsWith("/")) {
108+
return uri;
109+
}
110+
111+
const appPath = knownFolders.currentApp().path;
112+
if (!uri.startsWith(appPath)) {
113+
traceWrite(`${uri} does not start with ${appPath}`, traceCategories.Error, traceMessageType.error);
114+
return uri;
115+
}
116+
117+
const relativeUri = `.${uri.substr(appPath.length)}`;
118+
return relativeUri;
119+
}
120+
112121
public static fromFile(url: string, keyframes: KeyframesMap): CSSSource {
113122
// .scss, .sass, etc. css files in vanilla app are usually compiled to .css so we will try to load a compiled file first.
114123
let cssFileUrl = url.replace(/\..\w+$/, ".css");
@@ -123,10 +132,15 @@ class CSSSource {
123132
return new CSSSource(undefined, url, file, keyframes, undefined);
124133
}
125134

135+
public static fromFileImport(url: string, keyframes: KeyframesMap, importSource: string): CSSSource {
136+
const file = CSSSource.resolveCSSPathFromURL(url, importSource);
137+
return new CSSSource(undefined, url, file, keyframes, undefined);
138+
}
139+
126140
@profile
127-
public static resolveCSSPathFromURL(url: string): string {
141+
public static resolveCSSPathFromURL(url: string, importSource?: string): string {
128142
const app = knownFolders.currentApp().path;
129-
const file = resolveFileNameFromUrl(url, app, File.exists);
143+
const file = resolveFileNameFromUrl(url, app, File.exists, importSource);
130144
return file;
131145
}
132146

@@ -197,21 +211,33 @@ class CSSSource {
197211
}
198212

199213
private createSelectorsFromImports(): RuleSet[] {
200-
let selectors: RuleSet[] = [];
201214
const imports = this._ast["stylesheet"]["rules"].filter(r => r.type === "import");
202-
for (let i = 0; i < imports.length; i++) {
203-
const importItem = imports[i]["import"];
204215

205-
const match = importItem && (<string>importItem).match(pattern);
206-
const url = match && match[2];
216+
const urlFromImportObject = importObject => {
217+
const importItem = importObject["import"] as string;
218+
const urlMatch = importItem && importItem.match(pattern);
219+
return urlMatch && urlMatch[2];
220+
};
207221

208-
if (url !== null && url !== undefined) {
209-
const cssFile = CSSSource.fromURI(url, this._keyframes);
210-
selectors = selectors.concat(cssFile.selectors);
211-
}
212-
}
222+
const sourceFromImportObject = importObject =>
223+
importObject["position"] && importObject["position"]["source"];
224+
225+
const toUrlSourcePair = importObject => ({
226+
url: urlFromImportObject(importObject),
227+
source: sourceFromImportObject(importObject),
228+
});
213229

214-
return selectors;
230+
const getCssFile = ({ url, source }) => source ?
231+
CSSSource.fromFileImport(url, this._keyframes, source) :
232+
CSSSource.fromURI(url, this._keyframes);
233+
234+
const cssFiles = imports
235+
.map(toUrlSourcePair)
236+
.filter(({ url }) => !!url)
237+
.map(getCssFile);
238+
239+
const selectors = cssFiles.map(file => (file && file.selectors) || []);
240+
return selectors.reduce((acc, val) => acc.concat(val), []);
215241
}
216242

217243
private createSelectorsFromSyntaxTree(): RuleSet[] {
@@ -521,13 +547,13 @@ export class StyleScope {
521547

522548
let parsedCssSelectors = cssString ? CSSSource.fromSource(cssString, this._keyframes, cssFileName) : CSSSource.fromURI(cssFileName, this._keyframes);
523549
this._css = this._css + parsedCssSelectors.source;
524-
this._localCssSelectors.push.apply(this._localCssSelectors, parsedCssSelectors.selectors);
550+
this._localCssSelectors.push.apply(this._localCssSelectors, parsedCssSelectors.selectors);
525551
this._localCssSelectorVersion++;
526552
this.ensureSelectors();
527553
}
528554

529555
public getKeyframeAnimationWithName(animationName: string): kam.KeyframeAnimationInfo {
530-
const cssKeyframes = this._keyframes[animationName];
556+
const cssKeyframes = this._keyframes[animationName];
531557
if (!cssKeyframes) {
532558
return;
533559
}
@@ -611,9 +637,9 @@ export class StyleScope {
611637

612638
type KeyframesMap = Map<string, Keyframes>;
613639

614-
export function resolveFileNameFromUrl(url: string, appDirectory: string, fileExists: (name: string) => boolean): string {
615-
let fileName: string = typeof url === "string" ? url.trim() : "";
640+
export function resolveFileNameFromUrl(url: string, appDirectory: string, fileExists: (name: string) => boolean, importSource?: string): string {
616641

642+
let fileName: string = typeof url === "string" ? url.trim() : "";
617643
if (fileName.indexOf("~/") === 0) {
618644
fileName = fileName.replace("~/", "");
619645
}
@@ -628,6 +654,14 @@ export function resolveFileNameFromUrl(url: string, appDirectory: string, fileEx
628654
if (fileName[0] === "~" && fileName[1] !== "/" && fileName[1] !== "\"") {
629655
fileName = fileName.substr(1);
630656
}
657+
658+
if (importSource) {
659+
const importFile = resolveFilePathFromImport(importSource, fileName);
660+
if (fileExists(importFile)) {
661+
return importFile;
662+
}
663+
}
664+
631665
const external = path.join(appDirectory, "tns_modules", fileName);
632666
if (fileExists(external)) {
633667
return external;
@@ -637,6 +671,19 @@ export function resolveFileNameFromUrl(url: string, appDirectory: string, fileEx
637671
return null;
638672
}
639673

674+
function resolveFilePathFromImport(importSource: string, fileName: string): string {
675+
const importSourceParts = importSource.split(path.separator);
676+
const fileNameParts = fileName.split(path.separator)
677+
// exclude the dot-segment for current directory
678+
.filter(p => !isCurrentDirectory(p));
679+
680+
// remove current file name
681+
importSourceParts.pop();
682+
// remove element in case of dot-segment for parent directory or add file name
683+
fileNameParts.forEach(p => isParentDirectory(p) ? importSourceParts.pop() : importSourceParts.push(p));
684+
return importSourceParts.join(path.separator);
685+
}
686+
640687
export const applyInlineStyle = profile(function applyInlineStyle(view: ViewBase, styleStr: string) {
641688
let localStyle = `local { ${styleStr} }`;
642689
let inlineRuleSet = CSSSource.fromSource(localStyle, new Map()).selectors;
@@ -657,6 +704,14 @@ export const applyInlineStyle = profile(function applyInlineStyle(view: ViewBase
657704
});
658705
});
659706

707+
function isCurrentDirectory(uriPart: string): boolean {
708+
return uriPart === ".";
709+
}
710+
711+
function isParentDirectory(uriPart: string): boolean {
712+
return uriPart === "..";
713+
}
714+
660715
function isKeyframe(node: CssNode): node is KeyframesDefinition {
661716
return node.type === "keyframes";
662717
}

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL