Skip to content
This repository was archived by the owner on Aug 7, 2021. It is now read-only.

Commit 45e2411

Browse files
author
Alexander Vakrilov
authored
refactor: remove tns-core-modules/xml dependency (#1047)
* test: add xml-namespace-loader tests * chore: convert xml-ns-loader to TS * refactor: remove duplicate registers * refactor: use sax xml parser instead of tns * chore: clean useless value set (causes strict err) * text: fix tests for Windows
1 parent c9bd11d commit 45e2411

File tree

4 files changed

+270
-29
lines changed

4 files changed

+270
-29
lines changed

Diff for: .gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ jasmine-config/reporter.js
3131
bundle-config-loader.d.ts
3232
bundle-config-loader.js
3333

34+
xml-namespace-loader.d.ts
35+
xml-namespace-loader.js
36+
3437
**/*.spec.js*
3538
**/*.spec.d.ts*
3639

Diff for: package.json

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"request": "2.88.0",
6464
"resolve-url-loader": "~3.0.0",
6565
"sass-loader": "~7.1.0",
66+
"sax": "^1.2.4",
6667
"schema-utils": "0.4.5",
6768
"semver": "^6.0.0",
6869
"shelljs": "0.6.0",
@@ -84,6 +85,7 @@
8485
"@types/loader-utils": "^1.1.3",
8586
"@types/node": "^10.12.12",
8687
"@types/proxyquire": "1.3.28",
88+
"@types/sax": "^1.2.0",
8789
"@types/semver": "^6.0.0",
8890
"@types/webpack": "^4.4.34",
8991
"conventional-changelog-cli": "^1.3.22",

Diff for: xml-namespace-loader.spec.ts

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import xmlNsLoader from "./xml-namespace-loader";
2+
import { convertSlashesInPath } from "./projectHelpers";
3+
4+
const CODE_FILE = `
5+
<Page xmlns="http://www.nativescript.org/tns.xsd">
6+
<StackLayout>
7+
<GridLayout xmlns:chart="nativescript-ui-chart">
8+
<chart:RadCartesianChart></chart:RadCartesianChart>
9+
</GridLayout>
10+
<GridLayout xmlns:chart="nativescript-ui-chart">
11+
<chart:RadCartesianChart></chart:RadCartesianChart>
12+
</GridLayout>
13+
</StackLayout>
14+
</Page>
15+
`;
16+
17+
interface TestSetup {
18+
resolveMap: { [path: string]: string },
19+
expectedDeps: string[],
20+
expectedRegs: { name: string, path: string }[],
21+
ignore?: RegExp,
22+
assureNoDeps?: boolean,
23+
expectError?: boolean
24+
}
25+
26+
function getContext(
27+
done: DoneFn,
28+
{ resolveMap, expectedDeps, expectedRegs, assureNoDeps, ignore, expectError }: TestSetup) {
29+
const actualDeps: string[] = [];
30+
31+
const loaderContext = {
32+
rootContext: "app",
33+
context: "app/component",
34+
async: () => (error, source: string) => {
35+
expectedDeps.forEach(expectedDep => expect(actualDeps).toContain(expectedDep));
36+
37+
expectedRegs.forEach(({ name, path }) => {
38+
const regCode = `global.registerModule("${name}", function() { return require("${path}"); });`;
39+
expect(source).toContain(regCode);
40+
})
41+
42+
if (assureNoDeps) {
43+
expect(actualDeps.length).toBe(0);
44+
expect(source).not.toContain("global.registerModule");
45+
}
46+
47+
if (error && !expectError) {
48+
done.fail(error)
49+
} else if (!error && expectError) {
50+
done.fail("Error expected here")
51+
} else {
52+
done();
53+
}
54+
},
55+
resolve: (context: string, request: string, callback: (err: Error, result: string) => void) => {
56+
request = convertSlashesInPath(request);
57+
if (resolveMap[request]) {
58+
callback(undefined, resolveMap[request]);
59+
} else {
60+
callback(new Error(`Module ${request} not found`), undefined);
61+
}
62+
},
63+
addDependency: (dep: string) => {
64+
actualDeps.push(dep);
65+
},
66+
query: { ignore }
67+
}
68+
69+
return loaderContext;
70+
}
71+
72+
describe("XmlNamespaceLoader", () => {
73+
it("with namespace pointing to files", (done) => {
74+
const resolveMap = {
75+
"app/nativescript-ui-chart": "app/nativescript-ui-chart.js",
76+
"app/nativescript-ui-chart.xml": "app/nativescript-ui-chart.xml",
77+
"app/nativescript-ui-chart.css": "app/nativescript-ui-chart.css",
78+
};
79+
80+
const expectedDeps = [
81+
"app/nativescript-ui-chart.js",
82+
"app/nativescript-ui-chart.xml",
83+
"app/nativescript-ui-chart.css",
84+
];
85+
86+
const expectedRegs = [
87+
{ name: "nativescript-ui-chart", path: "app/nativescript-ui-chart.js" },
88+
{ name: "nativescript-ui-chart/RadCartesianChart", path: "app/nativescript-ui-chart.js" },
89+
{ name: "nativescript-ui-chart/RadCartesianChart.xml", path: "app/nativescript-ui-chart.xml" },
90+
{ name: "nativescript-ui-chart/RadCartesianChart.css", path: "app/nativescript-ui-chart.css" },
91+
];
92+
93+
const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs });
94+
95+
xmlNsLoader.call(loaderContext, CODE_FILE);
96+
})
97+
98+
it("with namespace/elementName pointing to files (with package.json)", (done) => {
99+
const resolveMap = {
100+
"app/nativescript-ui-chart": "app/nativescript-ui-chart/RadCartesianChart.js", //simulate package.json
101+
"app/nativescript-ui-chart/RadCartesianChart": "app/nativescript-ui-chart/RadCartesianChart.js",
102+
"app/nativescript-ui-chart/RadCartesianChart.xml": "app/nativescript-ui-chart/RadCartesianChart.xml",
103+
"app/nativescript-ui-chart/RadCartesianChart.css": "app/nativescript-ui-chart/RadCartesianChart.css",
104+
}
105+
106+
const expectedDeps = [
107+
"app/nativescript-ui-chart/RadCartesianChart.js",
108+
"app/nativescript-ui-chart/RadCartesianChart.xml",
109+
"app/nativescript-ui-chart/RadCartesianChart.css",
110+
];
111+
112+
const expectedRegs = [
113+
{ name: "nativescript-ui-chart", path: "app/nativescript-ui-chart/RadCartesianChart.js" },
114+
{ name: "nativescript-ui-chart/RadCartesianChart", path: "app/nativescript-ui-chart/RadCartesianChart.js" },
115+
{ name: "nativescript-ui-chart/RadCartesianChart.xml", path: "app/nativescript-ui-chart/RadCartesianChart.xml" },
116+
{ name: "nativescript-ui-chart/RadCartesianChart.css", path: "app/nativescript-ui-chart/RadCartesianChart.css" },
117+
];
118+
119+
const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs });
120+
xmlNsLoader.call(loaderContext, CODE_FILE);
121+
})
122+
123+
it("with namespace/elementName pointing to files", (done) => {
124+
const resolveMap = {
125+
"app/nativescript-ui-chart/RadCartesianChart": "app/nativescript-ui-chart/RadCartesianChart.js",
126+
"app/nativescript-ui-chart/RadCartesianChart.xml": "app/nativescript-ui-chart/RadCartesianChart.xml",
127+
"app/nativescript-ui-chart/RadCartesianChart.css": "app/nativescript-ui-chart/RadCartesianChart.css",
128+
}
129+
130+
const expectedDeps = [
131+
"app/nativescript-ui-chart/RadCartesianChart.js",
132+
"app/nativescript-ui-chart/RadCartesianChart.xml",
133+
"app/nativescript-ui-chart/RadCartesianChart.css",
134+
];
135+
136+
const expectedRegs = [
137+
{ name: "nativescript-ui-chart", path: "app/nativescript-ui-chart/RadCartesianChart.js" },
138+
{ name: "nativescript-ui-chart/RadCartesianChart", path: "app/nativescript-ui-chart/RadCartesianChart.js" },
139+
{ name: "nativescript-ui-chart/RadCartesianChart.xml", path: "app/nativescript-ui-chart/RadCartesianChart.xml" },
140+
{ name: "nativescript-ui-chart/RadCartesianChart.css", path: "app/nativescript-ui-chart/RadCartesianChart.css" },
141+
];
142+
143+
const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs });
144+
xmlNsLoader.call(loaderContext, CODE_FILE);
145+
})
146+
147+
it("with namespace/elementName pointing to files - only XML and CSS", (done) => {
148+
const resolveMap = {
149+
"app/nativescript-ui-chart/RadCartesianChart.xml": "app/nativescript-ui-chart/RadCartesianChart.xml",
150+
"app/nativescript-ui-chart/RadCartesianChart.css": "app/nativescript-ui-chart/RadCartesianChart.css",
151+
}
152+
153+
const expectedDeps = [
154+
"app/nativescript-ui-chart/RadCartesianChart.xml",
155+
"app/nativescript-ui-chart/RadCartesianChart.css",
156+
];
157+
158+
const expectedRegs = [
159+
{ name: "nativescript-ui-chart/RadCartesianChart.xml", path: "app/nativescript-ui-chart/RadCartesianChart.xml" },
160+
{ name: "nativescript-ui-chart/RadCartesianChart.css", path: "app/nativescript-ui-chart/RadCartesianChart.css" },
161+
];
162+
163+
const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs });
164+
xmlNsLoader.call(loaderContext, CODE_FILE);
165+
})
166+
167+
it("with plugin path", (done) => {
168+
const resolveMap = {
169+
"nativescript-ui-chart": "node_module/nativescript-ui-chart/ui-chart.js",
170+
}
171+
172+
const expectedDeps = [
173+
];
174+
175+
const expectedRegs = [
176+
{ name: "nativescript-ui-chart", path: "nativescript-ui-chart" },
177+
{ name: "nativescript-ui-chart/RadCartesianChart", path: "nativescript-ui-chart" },
178+
];
179+
180+
const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs });
181+
xmlNsLoader.call(loaderContext, CODE_FILE);
182+
})
183+
184+
it("with ignored namespace should not add deps or register calls", (done) => {
185+
const resolveMap = {
186+
"app/nativescript-ui-chart": "app/nativescript-ui-chart.js",
187+
"app/nativescript-ui-chart.xml": "app/nativescript-ui-chart.xml",
188+
"app/nativescript-ui-chart.css": "app/nativescript-ui-chart.css",
189+
};
190+
const expectedDeps = [];
191+
const expectedRegs = [];
192+
193+
const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs, ignore: /nativescript\-ui\-chart/, assureNoDeps: true });
194+
195+
xmlNsLoader.call(loaderContext, CODE_FILE);
196+
})
197+
198+
it("with XML declaration and Doctype does not fail", (done) => {
199+
const resolveMap = {};
200+
const expectedDeps = [];
201+
const expectedRegs = [];
202+
203+
const testXml = `
204+
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
205+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
206+
<!-- comment.xml -->
207+
<Page xmlns="http://www.nativescript.org/tns.xsd"></Page>`;
208+
209+
const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs, assureNoDeps: true });
210+
211+
xmlNsLoader.call(loaderContext, testXml);
212+
})
213+
it("with invalid XML fails", (done) => {
214+
const resolveMap = {};
215+
const expectedDeps = [];
216+
const expectedRegs = [];
217+
218+
const testXml = `<Page xmlns="http://www.nativescript.org/tns.xsd"></PageOpsWrongTagHere>`;
219+
220+
const loaderContext = getContext(done, { resolveMap, expectedDeps, expectedRegs, expectError: true });
221+
222+
xmlNsLoader.call(loaderContext, testXml);
223+
})
224+
});

Diff for: xml-namespace-loader.js renamed to xml-namespace-loader.ts

+41-29
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
1-
const { parse, relative, join, basename, extname } = require("path");
2-
const { promisify } = require('util');
3-
const { convertSlashesInPath } = require("./projectHelpers");
1+
import { parse, join } from "path";
2+
import { promisify } from "util";
3+
import { loader } from "webpack";
4+
import { parser, QualifiedTag } from "sax";
45

5-
module.exports = function (source, map) {
6-
this.value = source;
6+
import { convertSlashesInPath } from "./projectHelpers";
7+
8+
interface NamespaceEntry {
9+
name: string;
10+
path: string
11+
}
12+
13+
const loader: loader.Loader = function (source: string, map) {
714
const { ignore } = this.query;
815
const callback = this.async();
916

10-
const { XmlParser } = require("tns-core-modules/xml");
11-
1217
const resolvePromise = promisify(this.resolve);
13-
const promises = [];
18+
const promises: Promise<any>[] = [];
19+
const namespaces: NamespaceEntry[] = [];
20+
let parsingError = false;
1421

15-
const namespaces = [];
16-
const parser = new XmlParser((event) => {
17-
const { namespace, elementName } = event;
22+
const handleOpenTag = (namespace: string, elementName: string) => {
1823
const moduleName = `${namespace}/${elementName}`;
19-
2024
if (
2125
namespace &&
2226
!namespace.startsWith("http") &&
@@ -55,7 +59,7 @@ module.exports = function (source, map) {
5559
promises.push(resolvePromise(this.context, localNamespacePath)
5660
.then(path => pathResolved(path))
5761
.catch(() => {
58-
return promise = resolvePromise(this.context, localModulePath)
62+
return resolvePromise(this.context, localModulePath)
5963
.then(path => pathResolved(path))
6064
.catch(() => {
6165
return Promise.all([
@@ -81,34 +85,42 @@ module.exports = function (source, map) {
8185
})
8286
);
8387
}
84-
}, undefined, true);
88+
}
8589

86-
parser.parse(source);
90+
const saxParser = parser(true, { xmlns: true });
91+
saxParser.onopentag = (node: QualifiedTag) => { handleOpenTag(node.uri, node.local); };
92+
saxParser.onerror = (err) => {
93+
saxParser.error = null;
94+
parsingError = true;
95+
callback(err);
96+
};
97+
saxParser.write(source).close();
8798

8899
Promise.all(promises).then(() => {
89-
const moduleRegisters = namespaces
90-
.map(convertPath)
91-
.map(n =>
92-
`global.registerModule("${n.name}", function() { return require("${n.path}"); });`
93-
)
94-
.join("");
100+
const distinctNamespaces = new Map<string, string>();
101+
namespaces.forEach(({ name, path }) => distinctNamespaces.set(name, convertSlashesInPath(path)));
102+
103+
const moduleRegisters: string[] = [];
104+
distinctNamespaces.forEach((path, name) => {
105+
moduleRegisters.push(`global.registerModule("${name}", function() { return require("${path}"); });\n`);
106+
});
95107

96108
// escape special whitespace characters
97109
// see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Issue_with_plain_JSON.stringify_for_use_as_JavaScript
98110
const json = JSON.stringify(source)
99111
.replace(/\u2028/g, '\\u2028')
100112
.replace(/\u2029/g, '\\u2029');
101113

102-
const wrapped = `${moduleRegisters}\nmodule.exports = ${json}`;
114+
const wrapped = `${moduleRegisters.join("")}\nmodule.exports = ${json}`;
103115

104-
callback(null, wrapped, map);
116+
if (!parsingError) {
117+
callback(null, wrapped, map);
118+
}
105119
}).catch((err) => {
106-
callback(err);
120+
if (!parsingError) {
121+
callback(err);
122+
}
107123
})
108-
109124
}
110125

111-
function convertPath(obj) {
112-
obj.path = convertSlashesInPath(obj.path);
113-
return obj;
114-
}
126+
export default loader;

0 commit comments

Comments
 (0)