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',
|
'__jest__/package.json',
|
||||||
() => ({
|
() => ({
|
||||||
main: 'src/app.js',
|
main: 'src/app.js',
|
||||||
|
devDependencies: {
|
||||||
|
typescript: '*',
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{ virtual: true }
|
{ virtual: true }
|
||||||
);
|
);
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.12.3",
|
"@babel/core": "^7.12.3",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
|
||||||
|
"@types/sax": "^1.2.1",
|
||||||
"babel-loader": "^8.2.1",
|
"babel-loader": "^8.2.1",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"clean-webpack-plugin": "^3.0.0",
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
@ -32,6 +33,7 @@
|
|||||||
"react-refresh": "^0.9.0",
|
"react-refresh": "^0.9.0",
|
||||||
"sass": "^1.29.0",
|
"sass": "^1.29.0",
|
||||||
"sass-loader": "^10.1.0",
|
"sass-loader": "^10.1.0",
|
||||||
|
"sax": "^1.2.4",
|
||||||
"source-map": "^0.7.3",
|
"source-map": "^0.7.3",
|
||||||
"svelte-native-preprocessor": "^0.2.0",
|
"svelte-native-preprocessor": "^0.2.0",
|
||||||
"terser-webpack-plugin": "^5.0.3",
|
"terser-webpack-plugin": "^5.0.3",
|
||||||
@ -43,6 +45,7 @@
|
|||||||
"webpack-chain": "^6.5.1",
|
"webpack-chain": "^6.5.1",
|
||||||
"webpack-cli": "^4.2.0",
|
"webpack-cli": "^4.2.0",
|
||||||
"webpack-merge": "^5.4.0",
|
"webpack-merge": "^5.4.0",
|
||||||
|
"webpack-virtual-modules": "^0.4.1",
|
||||||
"worker-plugin": "^5.0.0"
|
"worker-plugin": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
getEntryPath,
|
getEntryPath,
|
||||||
getPlatform,
|
getPlatform,
|
||||||
} from '../helpers/project';
|
} from '../helpers/project';
|
||||||
|
import { hasDependency } from '../helpers/dependencies';
|
||||||
|
|
||||||
export default function (config: Config, env: IWebpackEnv): Config {
|
export default function (config: Config, env: IWebpackEnv): Config {
|
||||||
const entryPath = getEntryPath();
|
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
|
// 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
|
||||||
typescript: {
|
.plugin('ForkTsCheckerWebpackPlugin')
|
||||||
memoryLimit: 4096,
|
.use(ForkTsCheckerWebpackPlugin, [
|
||||||
},
|
{
|
||||||
},
|
typescript: {
|
||||||
]);
|
memoryLimit: 4096,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
// set up js
|
// set up js
|
||||||
// todo: do we need babel-loader? It's useful to support it
|
// 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 Config from 'webpack-chain';
|
||||||
|
|
||||||
|
import { getEntryPath } from '../helpers/project';
|
||||||
import { IWebpackEnv } from '../index';
|
import { IWebpackEnv } from '../index';
|
||||||
import base from './base';
|
import base from './base';
|
||||||
|
import dedent from 'ts-dedent';
|
||||||
|
|
||||||
// todo: add base configuration for core with javascript
|
// todo: add base configuration for core with javascript
|
||||||
export default function (config: Config, env: IWebpackEnv): Config {
|
export default function (config: Config, env: IWebpackEnv): Config {
|
||||||
base(config, env);
|
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
|
// set up xml
|
||||||
config.module
|
config.module
|
||||||
.rule('xml')
|
.rule('xml')
|
||||||
.test(/\.xml$/)
|
.test(/\.xml$/)
|
||||||
.use('xml-loader')
|
.use('xml-namespace-loader')
|
||||||
.loader('xml-loader');
|
.loader('xml-namespace-loader');
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { getPackageJson, getProjectRootPath } from './project';
|
import { getPackageJson, getProjectRootPath } from './project';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
|
// todo: memoize
|
||||||
export function getAllDependencies(): string[] {
|
export function getAllDependencies(): string[] {
|
||||||
const packageJSON = getPackageJson();
|
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 {
|
export function getDependencyPath(dependencyName: string): string | null {
|
||||||
try {
|
try {
|
||||||
const resolvedPath = require.resolve(`${dependencyName}/package.json`, {
|
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