diff --git a/packages/webpack5/__tests__/loaders/xml-namespace-loader.spec.ts b/packages/webpack5/__tests__/loaders/xml-namespace-loader.spec.ts
new file mode 100644
index 000000000..59136248e
--- /dev/null
+++ b/packages/webpack5/__tests__/loaders/xml-namespace-loader.spec.ts
@@ -0,0 +1,425 @@
+import xmlNsLoader from '../../src/loaders/xml-namespace-loader';
+import dedent from 'ts-dedent';
+
+const CODE_FILE = dedent`
+
+
+
+
+
+
+
+
+
+
+`;
+
+interface TestSetup {
+ resolveMap: { [path: string]: string };
+ expectedDeps: string[];
+ expectedRegs: { name: string; path: string }[];
+ ignore?: RegExp;
+ assureNoDeps?: boolean;
+ expectError?: boolean;
+ expectWarnings?: number;
+}
+
+function getContext(
+ done,
+ {
+ resolveMap,
+ expectedDeps,
+ expectedRegs,
+ assureNoDeps,
+ ignore,
+ expectError,
+ expectWarnings,
+ }: TestSetup
+) {
+ const actualDeps: string[] = [];
+ const actualWarnings: Error[] = [];
+ let callbackCalled = false;
+
+ return {
+ rootContext: 'app',
+ context: 'app/component',
+ async: () => (error, source: string) => {
+ if (callbackCalled) {
+ done.fail('Callback called more than once!');
+ }
+ callbackCalled = true;
+
+ expectedDeps.forEach((expectedDep) => {
+ expect(actualDeps).toContain(expectedDep);
+ });
+
+ expectedRegs.forEach(({ name, path }) => {
+ expect(source).toContain(dedent`
+ global.registerModule(
+ '${name}',
+ () => require("${path}")
+ )
+ `);
+ });
+
+ if (assureNoDeps) {
+ expect(actualDeps.length).toBe(0);
+ expect(source).not.toContain('global.registerModule');
+ }
+
+ if (expectWarnings) {
+ expect(actualWarnings.length).toEqual(expectWarnings);
+ }
+
+ if (error && !expectError) {
+ done.fail(error);
+ } else if (!error && expectError) {
+ done.fail('Error expected here');
+ } else {
+ done();
+ }
+ },
+ resolve: (
+ context: string,
+ request: string,
+ callback: (err: Error, result: string) => void
+ ) => {
+ request = request.replace(/\\/g, '/');
+ if (resolveMap[request]) {
+ callback(undefined, resolveMap[request]);
+ } else {
+ callback(new Error(`Module ${request} not found`), undefined);
+ }
+ },
+ addDependency: (dep: string) => {
+ actualDeps.push(dep);
+ },
+ emitWarning: (err: Error) => {
+ actualWarnings.push(err);
+ },
+ emitError: (err: Error) => {
+ //actualWarnings.push(err);
+ },
+ query: { ignore },
+ };
+}
+
+describe('xml-namespace-loader', () => {
+ it('with namespace pointing to files', (done) => {
+ const resolveMap = {
+ 'app/nativescript-ui-chart': 'app/nativescript-ui-chart.js',
+ 'app/nativescript-ui-chart.xml': 'app/nativescript-ui-chart.xml',
+ 'app/nativescript-ui-chart.css': 'app/nativescript-ui-chart.css',
+ };
+
+ const expectedDeps = [
+ 'app/nativescript-ui-chart.js',
+ 'app/nativescript-ui-chart.xml',
+ 'app/nativescript-ui-chart.css',
+ ];
+
+ const expectedRegs = [
+ { name: 'nativescript-ui-chart', path: 'app/nativescript-ui-chart.js' },
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart',
+ path: 'app/nativescript-ui-chart.js',
+ },
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart.xml',
+ path: 'app/nativescript-ui-chart.xml',
+ },
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart.css',
+ path: 'app/nativescript-ui-chart.css',
+ },
+ ];
+
+ const loaderContext = getContext(done, {
+ resolveMap,
+ expectedDeps,
+ expectedRegs,
+ });
+
+ xmlNsLoader.call(loaderContext, CODE_FILE);
+ });
+
+ it('with namespace/elementName pointing to files (with package.json)', (done) => {
+ const resolveMap = {
+ 'app/nativescript-ui-chart':
+ 'app/nativescript-ui-chart/RadCartesianChart.js', //simulate package.json
+ 'app/nativescript-ui-chart/RadCartesianChart':
+ 'app/nativescript-ui-chart/RadCartesianChart.js',
+ 'app/nativescript-ui-chart/RadCartesianChart.xml':
+ 'app/nativescript-ui-chart/RadCartesianChart.xml',
+ 'app/nativescript-ui-chart/RadCartesianChart.css':
+ 'app/nativescript-ui-chart/RadCartesianChart.css',
+ };
+
+ const expectedDeps = [
+ 'app/nativescript-ui-chart/RadCartesianChart.js',
+ 'app/nativescript-ui-chart/RadCartesianChart.xml',
+ 'app/nativescript-ui-chart/RadCartesianChart.css',
+ ];
+
+ const expectedRegs = [
+ {
+ name: 'nativescript-ui-chart',
+ path: 'app/nativescript-ui-chart/RadCartesianChart.js',
+ },
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart',
+ path: 'app/nativescript-ui-chart/RadCartesianChart.js',
+ },
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart.xml',
+ path: 'app/nativescript-ui-chart/RadCartesianChart.xml',
+ },
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart.css',
+ path: 'app/nativescript-ui-chart/RadCartesianChart.css',
+ },
+ ];
+
+ const loaderContext = getContext(done, {
+ resolveMap,
+ expectedDeps,
+ expectedRegs,
+ });
+ xmlNsLoader.call(loaderContext, CODE_FILE);
+ });
+
+ it('with namespace/elementName pointing to files', (done) => {
+ const resolveMap = {
+ 'app/nativescript-ui-chart/RadCartesianChart':
+ 'app/nativescript-ui-chart/RadCartesianChart.js',
+ 'app/nativescript-ui-chart/RadCartesianChart.xml':
+ 'app/nativescript-ui-chart/RadCartesianChart.xml',
+ 'app/nativescript-ui-chart/RadCartesianChart.css':
+ 'app/nativescript-ui-chart/RadCartesianChart.css',
+ };
+
+ const expectedDeps = [
+ 'app/nativescript-ui-chart/RadCartesianChart.js',
+ 'app/nativescript-ui-chart/RadCartesianChart.xml',
+ 'app/nativescript-ui-chart/RadCartesianChart.css',
+ ];
+
+ const expectedRegs = [
+ {
+ name: 'nativescript-ui-chart',
+ path: 'app/nativescript-ui-chart/RadCartesianChart.js',
+ },
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart',
+ path: 'app/nativescript-ui-chart/RadCartesianChart.js',
+ },
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart.xml',
+ path: 'app/nativescript-ui-chart/RadCartesianChart.xml',
+ },
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart.css',
+ path: 'app/nativescript-ui-chart/RadCartesianChart.css',
+ },
+ ];
+
+ const loaderContext = getContext(done, {
+ resolveMap,
+ expectedDeps,
+ expectedRegs,
+ });
+ xmlNsLoader.call(loaderContext, CODE_FILE);
+ });
+
+ it('with namespace/elementName pointing to files - only XML and CSS', (done) => {
+ const resolveMap = {
+ 'app/nativescript-ui-chart/RadCartesianChart.xml':
+ 'app/nativescript-ui-chart/RadCartesianChart.xml',
+ 'app/nativescript-ui-chart/RadCartesianChart.css':
+ 'app/nativescript-ui-chart/RadCartesianChart.css',
+ };
+
+ const expectedDeps = [
+ 'app/nativescript-ui-chart/RadCartesianChart.xml',
+ 'app/nativescript-ui-chart/RadCartesianChart.css',
+ ];
+
+ const expectedRegs = [
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart.xml',
+ path: 'app/nativescript-ui-chart/RadCartesianChart.xml',
+ },
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart.css',
+ path: 'app/nativescript-ui-chart/RadCartesianChart.css',
+ },
+ ];
+
+ const loaderContext = getContext(done, {
+ resolveMap,
+ expectedDeps,
+ expectedRegs,
+ });
+ xmlNsLoader.call(loaderContext, CODE_FILE);
+ });
+
+ it('with plugin path', (done) => {
+ const resolveMap = {
+ 'nativescript-ui-chart': 'node_modules/nativescript-ui-chart/ui-chart.js',
+ };
+
+ const expectedDeps = [];
+
+ const expectedRegs = [
+ { name: 'nativescript-ui-chart', path: 'nativescript-ui-chart' },
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart',
+ path: 'nativescript-ui-chart',
+ },
+ ];
+
+ const loaderContext = getContext(done, {
+ resolveMap,
+ expectedDeps,
+ expectedRegs,
+ });
+ xmlNsLoader.call(loaderContext, CODE_FILE);
+ });
+
+ it('with ignored namespace should not add deps or register calls', (done) => {
+ const resolveMap = {
+ 'app/nativescript-ui-chart': 'app/nativescript-ui-chart.js',
+ 'app/nativescript-ui-chart.xml': 'app/nativescript-ui-chart.xml',
+ 'app/nativescript-ui-chart.css': 'app/nativescript-ui-chart.css',
+ };
+ const expectedDeps = [];
+ const expectedRegs = [];
+
+ const loaderContext = getContext(done, {
+ resolveMap,
+ expectedDeps,
+ expectedRegs,
+ ignore: /nativescript-ui-chart/,
+ assureNoDeps: true,
+ });
+
+ xmlNsLoader.call(loaderContext, CODE_FILE);
+ });
+
+ it('with XML declaration and Doctype does not fail', (done) => {
+ const resolveMap = {};
+ const expectedDeps = [];
+ const expectedRegs = [];
+
+ const testXml = dedent`
+
+
+
+
+ `;
+
+ const loaderContext = getContext(done, {
+ resolveMap,
+ expectedDeps,
+ expectedRegs,
+ assureNoDeps: true,
+ });
+
+ xmlNsLoader.call(loaderContext, testXml);
+ });
+
+ it('with invalid XML fails', (done) => {
+ const resolveMap = {};
+ const expectedDeps = [];
+ const expectedRegs = [];
+
+ const testXml = ``;
+
+ const loaderContext = getContext(done, {
+ resolveMap,
+ expectedDeps,
+ expectedRegs,
+ expectError: true,
+ });
+
+ xmlNsLoader.call(loaderContext, testXml);
+ });
+
+ it("doesn't throw with ios and android platform namespaces", (done) => {
+ const resolveMap = {};
+ const expectedDeps = [];
+ const expectedRegs = [];
+
+ const testXml = dedent`
+
+
+
+
+
+
+ `;
+
+ const loaderContext = getContext(done, {
+ resolveMap,
+ expectedDeps,
+ expectedRegs,
+ assureNoDeps: true,
+ });
+
+ xmlNsLoader.call(loaderContext, testXml);
+ });
+
+ it('throws with unbound namespace namespaces', (done) => {
+ const resolveMap = {};
+ const expectedDeps = [];
+ const expectedRegs = [];
+
+ const testXml = `
+
+
+
+ `;
+
+ const loaderContext = getContext(done, {
+ resolveMap,
+ expectedDeps,
+ expectedRegs,
+ expectError: true,
+ });
+
+ xmlNsLoader.call(loaderContext, testXml);
+ });
+
+ it("with '&&', '||', '<=' and '>=' in binding expression, emits warnings, but does not fail", (done) => {
+ const resolveMap = {
+ 'nativescript-ui-chart': 'node_modules/nativescript-ui-chart/ui-chart.js',
+ };
+
+ const expectedDeps = [];
+
+ const expectedRegs = [
+ { name: 'nativescript-ui-chart', path: 'nativescript-ui-chart' },
+ {
+ name: 'nativescript-ui-chart/RadCartesianChart',
+ path: 'nativescript-ui-chart',
+ },
+ ];
+
+ const testXml = `
+
+
+
+
+
+ `;
+
+ const loaderContext = getContext(done, {
+ resolveMap,
+ expectedDeps,
+ expectedRegs,
+ expectWarnings: 1,
+ });
+
+ xmlNsLoader.call(loaderContext, testXml);
+ });
+});
diff --git a/packages/webpack5/jest.setup.ts b/packages/webpack5/jest.setup.ts
index 3221dede3..cb63213d2 100644
--- a/packages/webpack5/jest.setup.ts
+++ b/packages/webpack5/jest.setup.ts
@@ -17,6 +17,9 @@ jest.mock(
'__jest__/package.json',
() => ({
main: 'src/app.js',
+ devDependencies: {
+ typescript: '*',
+ },
}),
{ virtual: true }
);
diff --git a/packages/webpack5/package.json b/packages/webpack5/package.json
index 366cc7c2f..2e25ad6c3 100644
--- a/packages/webpack5/package.json
+++ b/packages/webpack5/package.json
@@ -18,6 +18,7 @@
"dependencies": {
"@babel/core": "^7.12.3",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
+ "@types/sax": "^1.2.1",
"babel-loader": "^8.2.1",
"chalk": "^4.1.0",
"clean-webpack-plugin": "^3.0.0",
@@ -32,6 +33,7 @@
"react-refresh": "^0.9.0",
"sass": "^1.29.0",
"sass-loader": "^10.1.0",
+ "sax": "^1.2.4",
"source-map": "^0.7.3",
"svelte-native-preprocessor": "^0.2.0",
"terser-webpack-plugin": "^5.0.3",
@@ -43,6 +45,7 @@
"webpack-chain": "^6.5.1",
"webpack-cli": "^4.2.0",
"webpack-merge": "^5.4.0",
+ "webpack-virtual-modules": "^0.4.1",
"worker-plugin": "^5.0.0"
},
"devDependencies": {
diff --git a/packages/webpack5/src/configuration/base.ts b/packages/webpack5/src/configuration/base.ts
index 937d39eb9..95380ec3a 100644
--- a/packages/webpack5/src/configuration/base.ts
+++ b/packages/webpack5/src/configuration/base.ts
@@ -16,6 +16,7 @@ import {
getEntryPath,
getPlatform,
} from '../helpers/project';
+import { hasDependency } from '../helpers/dependencies';
export default function (config: Config, env: IWebpackEnv): Config {
const entryPath = getEntryPath();
@@ -136,13 +137,17 @@ export default function (config: Config, env: IWebpackEnv): Config {
});
// Use Fork TS Checker to do type checking in a separate non-blocking process
- config.plugin('ForkTsCheckerWebpackPlugin').use(ForkTsCheckerWebpackPlugin, [
- {
- typescript: {
- memoryLimit: 4096,
- },
- },
- ]);
+ config.when(hasDependency('typescript'), (config) => {
+ config
+ .plugin('ForkTsCheckerWebpackPlugin')
+ .use(ForkTsCheckerWebpackPlugin, [
+ {
+ typescript: {
+ memoryLimit: 4096,
+ },
+ },
+ ]);
+ });
// set up js
// todo: do we need babel-loader? It's useful to support it
diff --git a/packages/webpack5/src/configuration/javascript.ts b/packages/webpack5/src/configuration/javascript.ts
index c64c19089..affb354bb 100644
--- a/packages/webpack5/src/configuration/javascript.ts
+++ b/packages/webpack5/src/configuration/javascript.ts
@@ -1,18 +1,37 @@
+import VirtualModulesPlugin from 'webpack-virtual-modules';
import Config from 'webpack-chain';
+import { getEntryPath } from '../helpers/project';
import { IWebpackEnv } from '../index';
import base from './base';
+import dedent from 'ts-dedent';
// todo: add base configuration for core with javascript
export default function (config: Config, env: IWebpackEnv): Config {
base(config, env);
+ const virtualEntryPath = getEntryPath() + '.virtual.js';
+
+ config.entry('bundle').add(virtualEntryPath);
+
+ // Add a virtual entry module
+ config.plugin('VirtualModulesPlugin').use(VirtualModulesPlugin, [
+ {
+ [virtualEntryPath]: dedent`
+ require('@nativescript/core/bundle-entry-points')
+
+ const context = require.context("./", /* deep: */ true);
+ global.registerWebpackModules(context);
+ `,
+ },
+ ]);
+
// set up xml
config.module
.rule('xml')
.test(/\.xml$/)
- .use('xml-loader')
- .loader('xml-loader');
+ .use('xml-namespace-loader')
+ .loader('xml-namespace-loader');
return config;
}
diff --git a/packages/webpack5/src/helpers/dependencies.ts b/packages/webpack5/src/helpers/dependencies.ts
index 4c9aca5d8..c3c7dd470 100644
--- a/packages/webpack5/src/helpers/dependencies.ts
+++ b/packages/webpack5/src/helpers/dependencies.ts
@@ -1,6 +1,7 @@
import { getPackageJson, getProjectRootPath } from './project';
import path from 'path';
+// todo: memoize
export function getAllDependencies(): string[] {
const packageJSON = getPackageJson();
@@ -10,6 +11,12 @@ export function getAllDependencies(): string[] {
];
}
+// todo: memoize
+export function hasDependency(dependencyName: string) {
+ return getAllDependencies().includes(dependencyName);
+}
+
+// todo: memoize
export function getDependencyPath(dependencyName: string): string | null {
try {
const resolvedPath = require.resolve(`${dependencyName}/package.json`, {
diff --git a/packages/webpack5/src/loaders/xml-namespace-loader/index.ts b/packages/webpack5/src/loaders/xml-namespace-loader/index.ts
new file mode 100644
index 000000000..2bec1bdd0
--- /dev/null
+++ b/packages/webpack5/src/loaders/xml-namespace-loader/index.ts
@@ -0,0 +1,228 @@
+import { parse, join } from 'path';
+import { promisify } from 'util';
+import dedent from 'ts-dedent';
+import { parser } from 'sax';
+
+const noop = () => {};
+
+const DEBUG = false;
+
+interface NamespaceEntry {
+ name: string;
+ path: string;
+}
+
+interface ParseResult {
+ code: string;
+}
+
+export default function loader(content: string, map: any) {
+ const callback = this.async();
+
+ // parse content and dependencies async
+ parseXML
+ .bind(this)(content)
+ .then((res) => {
+ DEBUG && console.log({ res });
+ callback(null, res.code, map);
+ })
+ .catch((err) => {
+ DEBUG && console.log({ err });
+ callback(err);
+ });
+}
+
+async function parseXML(content: string): Promise {
+ // wrap this.resolve into a promise
+ const resolveAsync = promisify(this.resolve);
+ const promises: Promise[] = [];
+ const namespaces: NamespaceEntry[] = [];
+ const distinctNamespaces = new Map();
+ const moduleRegisters: string[] = [];
+ const { ignore } = this.query;
+ const errors = [];
+
+ const saxParser = parser(true, { xmlns: true });
+
+ // // Register ios and android prefixes as namespaces to avoid "unbound xml namespace" errors
+ (saxParser as any).ns['ios'] = 'http://schemas.nativescript.org/tns.xsd';
+ (saxParser as any).ns['android'] = 'http://schemas.nativescript.org/tns.xsd';
+ (saxParser as any).ns['desktop'] = 'http://schemas.nativescript.org/tns.xsd';
+ (saxParser as any).ns['web'] = 'http://schemas.nativescript.org/tns.xsd';
+
+ const handleOpenTag = async (namespace: string, elementName: string) => {
+ if (!namespace) {
+ return;
+ }
+
+ if (namespace.startsWith('http')) {
+ return;
+ }
+
+ const moduleName = `${namespace}/${elementName}`;
+
+ if (namespaces.some((n) => n.name === moduleName)) {
+ return;
+ }
+
+ if (ignore && moduleName.match(ignore)) {
+ return;
+ }
+
+ const localNamespacePath = join(this.rootContext, namespace);
+ const localModulePath = join(localNamespacePath, elementName);
+
+ const resolvePaths = [
+ localNamespacePath,
+ localModulePath,
+ `${localModulePath}.xml`,
+ moduleName,
+ namespace,
+ ];
+ DEBUG && console.log({ resolvePaths });
+ let resolvedPath;
+
+ for (const p of resolvePaths) {
+ resolvedPath = await resolveAsync(this.context, p).catch(noop);
+
+ // break on first match
+ if (resolvedPath) {
+ break;
+ }
+ }
+
+ DEBUG && console.log({ resolvedPath });
+
+ // bail if we haven't resolved a path
+ if (!resolvedPath) {
+ return;
+ }
+
+ const { dir, name } = parse(resolvedPath);
+
+ // register resolved path + short name
+ namespaces.push({ name: namespace, path: resolvedPath });
+ namespaces.push({ name: moduleName, path: resolvedPath });
+ this.addDependency(resolvedPath);
+
+ const noExtFilename = join(dir, name);
+
+ DEBUG &&
+ console.log({
+ noExtFilename,
+ });
+
+ // finally try resolving an XML file
+ await resolveAsync(this.context, `${noExtFilename}.xml`)
+ .then((xml) => {
+ this.addDependency(xml);
+ namespaces.push({ name: `${moduleName}.xml`, path: xml });
+ })
+ .catch(() => {
+ // if there is no XML file, fall back to namespace as the path
+ // will become require()
+ namespaces.push({ name: namespace, path: namespace });
+ namespaces.push({ name: moduleName, path: namespace });
+ });
+
+ // look for css files with the same name
+ await resolveAsync(this.context, `${noExtFilename}.css`)
+ .then((css) => {
+ this.addDependency(css);
+ // namespaces.push({ name: `${moduleName}.css`, path: css });
+ })
+ .catch(noop);
+ };
+
+ saxParser.onopentag = (node) => {
+ if ('uri' in node) {
+ promises.push(handleOpenTag(node.uri, node.local));
+ }
+ };
+ saxParser.onerror = (error) => {
+ saxParser.error = null;
+
+ // Do only warning about invalid character "&"" for back-compatibility
+ // as it is common to use it in a binding expression
+ if (
+ error.message.includes('Invalid character') &&
+ error.message.includes('Char: &')
+ ) {
+ return this.emitWarning(error);
+ }
+ errors.push(error);
+ };
+
+ saxParser.write(content).close();
+
+ await Promise.all(promises);
+
+ DEBUG && console.log({ namespaces });
+
+ namespaces.forEach(({ name, path }) => {
+ distinctNamespaces.set(name, path.replace(/\\/g, '/'));
+ });
+
+ distinctNamespaces.forEach((path, name) => {
+ moduleRegisters.push(dedent`
+ global.registerModule(
+ '${name}',
+ () => require("${path}")
+ )
+ `);
+ });
+
+ // escape special whitespace characters
+ // see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Issue_with_plain_JSON.stringify_for_use_as_JavaScript
+ const xml = JSON.stringify(content)
+ .replace(/\u2028/g, '\\u2028')
+ .replace(/\u2029/g, '\\u2029');
+
+ const code = dedent`
+ ${moduleRegisters.join('\n')}
+ /* XML-NAMESPACE-LOADER */
+ const ___XML_NAMESPACE_LOADER_EXPORT___ = ${xml}
+ export default ___XML_NAMESPACE_LOADER_EXPORT___
+ `;
+
+ if (errors.length) {
+ errors.map(this.emitError);
+
+ // finally throw the first one
+ throw errors[0];
+ }
+
+ return {
+ code,
+ };
+}
+
+//
+//
+//
+// function parseXML(xml: string) {
+// const saxParser = parser(true, { xmlns: true });
+//
+// saxParser.onopentag = (node) => {
+// if('ns' in node) {
+// const uri = node.uri
+// const tag = node.local
+//
+// DEBUG && console.log({
+// uri,
+// tag
+// })
+// }
+// }
+//
+// saxParser.onerror = (err) => {
+// DEBUG && console.log(err)
+// }
+//
+// // Register ios and android prefixes as namespaces to avoid "unbound xml namespace" errors
+// // saxParser.ns['ios'] = 'http://schemas.nativescript.org/tns.xsd';
+// // saxParser.ns['android'] = 'http://schemas.nativescript.org/tns.xsd';
+// // saxParser.ns['desktop'] = 'http://schemas.nativescript.org/tns.xsd';
+// // saxParser.ns['web'] = 'http://schemas.nativescript.org/tns.xsd';
+// saxParser.write(xml).close()
+// }