Skip to content

Commit 696e914

Browse files
author
Alexander Vakrilov
authored
feat(styling): Allow loading .css files as a fallback if no .scss file is found(NativeScript#954) (NativeScript#955)
* feat(styling): Allow loading .css files as a fallback if no .scss file is found(NativeScript#954) * Add tests * refactor: resource loader (NativeScript#968) * chore(lint): use 4 spaces instead of 2 in ns-file-system * refactor: resource loader * test(xhr-paths): assert resource loader throws when resolving non-existing files
1 parent fe672d2 commit 696e914

File tree

4 files changed

+132
-25
lines changed

4 files changed

+132
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import { Injectable } from "@angular/core";
2-
import { knownFolders, Folder } from "tns-core-modules/file-system";
2+
import { knownFolders, Folder, File } from "tns-core-modules/file-system";
33

44
// Allows greater flexibility with `file-system` and Angular
55
// Also provides a way for `file-system` to be mocked for testing
66

77
@Injectable()
88
export class NSFileSystem {
9-
public currentApp(): Folder {
10-
return knownFolders.currentApp();
11-
}
9+
public currentApp(): Folder {
10+
return knownFolders.currentApp();
11+
}
12+
13+
public fileFromPath(path: string): File {
14+
return File.fromPath(path);
15+
}
16+
17+
public fileExists(path: string): boolean {
18+
return File.exists(path);
19+
}
1220
}

nativescript-angular/platform.ts

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ if ((<any>global).___TS_UNUSED) {
3535
import "./dom-adapter";
3636

3737
import { NativeScriptElementSchemaRegistry } from "./schema-registry";
38+
import { NSFileSystem } from "./file-system/ns-file-system";
3839
import { FileSystemResourceLoader } from "./resource-loader";
3940

4041
export const NS_COMPILER_PROVIDERS = [
@@ -43,6 +44,7 @@ export const NS_COMPILER_PROVIDERS = [
4344
provide: COMPILER_OPTIONS,
4445
useValue: {
4546
providers: [
47+
NSFileSystem,
4648
{ provide: ResourceLoader, useClass: FileSystemResourceLoader },
4749
{ provide: ElementSchemaRegistry, useClass: NativeScriptElementSchemaRegistry },
4850
]
+60-13
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,72 @@
1-
import { path, knownFolders, File } from "tns-core-modules/file-system";
1+
import { Injectable } from "@angular/core";
22
import { ResourceLoader } from "@angular/compiler";
3+
import { path } from "tns-core-modules/file-system";
34

5+
import { NSFileSystem } from "./file-system/ns-file-system";
6+
7+
const extensionsFallbacks = [
8+
[".scss", ".css"],
9+
[".sass", ".css"],
10+
[".less", ".css"]
11+
];
12+
13+
@Injectable()
414
export class FileSystemResourceLoader extends ResourceLoader {
5-
resolve(url: string, baseUrl: string): string {
6-
// Angular assembles absolute URL's and prefixes them with //
15+
constructor(private fs: NSFileSystem) {
16+
super();
17+
}
18+
19+
get(url: string): Promise<string> {
20+
const resolvedPath = this.resolve(url);
21+
22+
const templateFile = this.fs.fileFromPath(resolvedPath);
23+
24+
return templateFile.readText();
25+
}
26+
27+
resolve(url: string): string {
28+
const normalizedUrl = this.resolveRelativeUrls(url);
29+
30+
if (this.fs.fileExists(normalizedUrl)) {
31+
return normalizedUrl;
32+
}
33+
34+
const { candidates: fallbackCandidates, resource: fallbackResource } =
35+
this.fallbackResolve(normalizedUrl);
36+
37+
if (fallbackResource) {
38+
return fallbackResource;
39+
}
40+
41+
throw new Error(`Could not resolve ${url}. Looked for: ${normalizedUrl}, ${fallbackCandidates}`);
42+
}
43+
44+
private resolveRelativeUrls(url: string): string {
45+
// Angular assembles absolute URLs and prefixes them with //
746
if (url.indexOf("/") !== 0) {
8-
// Resolve relative URL's based on the app root.
9-
return path.join(baseUrl, url);
47+
// Resolve relative URLs based on the app root.
48+
return path.join(this.fs.currentApp().path, url);
1049
} else {
1150
return url;
1251
}
1352
}
1453

15-
get(url: string): Promise<string> {
16-
const appDir = knownFolders.currentApp().path;
17-
const templatePath = this.resolve(url, appDir);
54+
private fallbackResolve(url: string):
55+
({ resource: string, candidates: string[] }) {
1856

19-
if (!File.exists(templatePath)) {
20-
throw new Error(`File ${templatePath} does not exist. Resolved from: ${url}.`);
21-
}
22-
let templateFile = File.fromPath(templatePath);
23-
return templateFile.readText();
57+
const candidates = extensionsFallbacks
58+
.filter(([extension]) => url.endsWith(extension))
59+
.map(([extension, fallback]) =>
60+
this.replaceExtension(url, extension, fallback));
61+
62+
const resource = candidates.find(candidate => this.fs.fileExists(candidate));
63+
64+
return { candidates, resource };
65+
}
66+
67+
private replaceExtension(fileName: string, oldExtension: string, newExtension: string): string {
68+
const baseName = fileName.substr(0, fileName.length - oldExtension.length);
69+
return baseName + newExtension;
2470
}
2571
}
72+

tests/app/tests/xhr-paths.ts

+58-8
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,72 @@
11
// make sure you import mocha-config before @angular/core
2-
import {assert} from "./test-config";
3-
import {FileSystemResourceLoader} from "nativescript-angular/resource-loader";
2+
import { assert } from "./test-config";
3+
import { FileSystemResourceLoader } from "nativescript-angular/resource-loader";
4+
5+
import { File } from "tns-core-modules/file-system";
6+
import { NSFileSystem } from "nativescript-angular/file-system/ns-file-system";
7+
8+
class NSFileSystemMock {
9+
public currentApp(): any {
10+
return { path: "/app/dir" };
11+
}
12+
13+
public fileFromPath(path: string): File {
14+
return null;
15+
}
16+
17+
public fileExists(path: string): boolean {
18+
// mycomponent.html always exists
19+
// mycomponent.css is the other file
20+
return path.indexOf("mycomponent.html") >= 0 || path === "/app/dir/mycomponent.css";
21+
}
22+
}
23+
const fsMock = new NSFileSystemMock();
424

525
describe("XHR name resolution", () => {
26+
let resourceLoader: FileSystemResourceLoader;
27+
before(() => {
28+
resourceLoader = new FileSystemResourceLoader(new NSFileSystemMock());
29+
});
30+
631
it("resolves relative paths from app root", () => {
7-
const xhr = new FileSystemResourceLoader();
8-
assert.strictEqual("/app/dir/mydir/mycomponent.html", xhr.resolve("mydir/mycomponent.html", "/app/dir"));
32+
assert.strictEqual("/app/dir/mydir/mycomponent.html", resourceLoader.resolve("mydir/mycomponent.html"));
933
});
1034

1135
it("resolves double-slashed absolute paths as is", () => {
12-
const xhr = new FileSystemResourceLoader();
13-
assert.strictEqual("//app/mydir/mycomponent.html", xhr.resolve("//app/mydir/mycomponent.html", "/app/dir"));
36+
assert.strictEqual("//app/mydir/mycomponent.html", resourceLoader.resolve("//app/mydir/mycomponent.html"));
1437
});
1538

1639
it("resolves single-slashed absolute paths as is", () => {
17-
const xhr = new FileSystemResourceLoader();
1840
assert.strictEqual(
1941
"/data/data/app/mydir/mycomponent.html",
20-
xhr.resolve("/data/data/app/mydir/mycomponent.html", "/app/dir"));
42+
resourceLoader.resolve("/data/data/app/mydir/mycomponent.html"));
43+
});
44+
45+
it("resolves existing CSS file", () => {
46+
assert.strictEqual(
47+
"/app/dir/mycomponent.css",
48+
resourceLoader.resolve("mycomponent.css"));
49+
});
50+
51+
it("resolves non-existing .scss file to existing .css file", () => {
52+
assert.strictEqual(
53+
"/app/dir/mycomponent.css",
54+
resourceLoader.resolve("mycomponent.scss"));
55+
});
56+
57+
it("resolves non-existing .sass file to existing .css file", () => {
58+
assert.strictEqual(
59+
"/app/dir/mycomponent.css",
60+
resourceLoader.resolve("mycomponent.sass"));
61+
});
62+
63+
it("resolves non-existing .less file to existing .css file", () => {
64+
assert.strictEqual(
65+
"/app/dir/mycomponent.css",
66+
resourceLoader.resolve("mycomponent.less"));
67+
});
68+
69+
it("throws for non-existing file that has no fallbacks", () => {
70+
assert.throws(() => resourceLoader.resolve("does-not-exist.css"));
2171
});
2272
});

0 commit comments

Comments
 (0)