mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat: ported xml-namespace-loader
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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