mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 18:12:09 +08:00
feat: ported xml-namespace-loader
This commit is contained in:
425
packages/webpack5/__tests__/loaders/xml-namespace-loader.spec.ts
Normal file
425
packages/webpack5/__tests__/loaders/xml-namespace-loader.spec.ts
Normal file
@ -0,0 +1,425 @@
|
||||
import xmlNsLoader from '../../src/loaders/xml-namespace-loader';
|
||||
import dedent from 'ts-dedent';
|
||||
|
||||
const CODE_FILE = dedent`
|
||||
<Page xmlns="http://www.nativescript.org/tns.xsd">
|
||||
<StackLayout>
|
||||
<GridLayout xmlns:chart="nativescript-ui-chart">
|
||||
<chart:RadCartesianChart></chart:RadCartesianChart>
|
||||
</GridLayout>
|
||||
<GridLayout xmlns:chart="nativescript-ui-chart">
|
||||
<chart:RadCartesianChart></chart:RadCartesianChart>
|
||||
</GridLayout>
|
||||
</StackLayout>
|
||||
</Page>
|
||||
`;
|
||||
|
||||
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`
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<!-- comment.xml -->
|
||||
<Page xmlns="http://www.nativescript.org/tns.xsd"></Page>
|
||||
`;
|
||||
|
||||
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 = `<Page xmlns="http://www.nativescript.org/tns.xsd"></PageOpsWrongTagHere>`;
|
||||
|
||||
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`
|
||||
<Page xmlns="http://www.nativescript.org/tns.xsd">
|
||||
<ios:GridLayout />
|
||||
<ios:GridLayout></ios:GridLayout>
|
||||
<android:GridLayout />
|
||||
<android:GridLayout></android:GridLayout>
|
||||
</Page>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<Page xmlns="http://www.nativescript.org/tns.xsd">
|
||||
<custom1:CustomComponent />
|
||||
<custom2:CustomComponent />
|
||||
</Page>`;
|
||||
|
||||
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 = `
|
||||
<Page xmlns="http://www.nativescript.org/tns.xsd">
|
||||
<StackLayout xmlns:chart="nativescript-ui-chart">
|
||||
<TextField text="{{ var1 && var2 || var1 >= var2 || var2 <= var1 }}" />
|
||||
<chart:RadCartesianChart></chart:RadCartesianChart>
|
||||
</StackLayout>
|
||||
</Page>`;
|
||||
|
||||
const loaderContext = getContext(done, {
|
||||
resolveMap,
|
||||
expectedDeps,
|
||||
expectedRegs,
|
||||
expectWarnings: 1,
|
||||
});
|
||||
|
||||
xmlNsLoader.call(loaderContext, testXml);
|
||||
});
|
||||
});
|
@ -17,6 +17,9 @@ jest.mock(
|
||||
'__jest__/package.json',
|
||||
() => ({
|
||||
main: 'src/app.js',
|
||||
devDependencies: {
|
||||
typescript: '*',
|
||||
},
|
||||
}),
|
||||
{ virtual: true }
|
||||
);
|
||||
|
@ -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": {
|
||||
|
@ -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, [
|
||||
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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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`, {
|
||||
|
228
packages/webpack5/src/loaders/xml-namespace-loader/index.ts
Normal file
228
packages/webpack5/src/loaders/xml-namespace-loader/index.ts
Normal file
@ -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<ParseResult> {
|
||||
// wrap this.resolve into a promise
|
||||
const resolveAsync = promisify(this.resolve);
|
||||
const promises: Promise<any>[] = [];
|
||||
const namespaces: NamespaceEntry[] = [];
|
||||
const distinctNamespaces = new Map<string, string>();
|
||||
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(<namespace>)
|
||||
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()
|
||||
// }
|
Reference in New Issue
Block a user