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

Commit 3362cd6

Browse files
fix: some packages are treated as externals when they shouldn't (#771)
* fix: some packages are treated as externals when they shouldn't In case `externals` are passed to webpack.config.js, they are converted to regular expression. The idea is to detect all imports for this module, for example if it is `nativescript-vue`, we would like to make externals imports like `nativescript-vue/somesubdir/file1`. However, the regular expression we construct also matches packages like `nativescript-vue-template`. Fix the regular expressions in all template webpack.config.js files and add unit tests for them. Add devDependency to `proxyquire` as the webpack configs that are tested, require some packages that are not available in node_modules of `nativescript-dev-webpack` (including the `nativescript-dev-webpack` itself). This is expected for the `webpack.config.js` files, as they should be placed in the NativeScript applications, so mocking the requires shouldn't be a problem in this case. * chore: move externals generation to a common place Move the conversion of regular expressions from `string[]` to `RegExp[]` to index.js file, so it can be reused in all webpack.config.js files. Move most of the tests to the `index.spec.js` due to this change. * chore: fix grammar errors Co-Authored-By: rosen-vladimirov <[email protected]>
1 parent c7bcbaf commit 3362cd6

8 files changed

+194
-15
lines changed

Diff for: index.js

+19
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,25 @@ exports.getAppPath = (platform, projectDir) => {
5353
}
5454
};
5555

56+
/**
57+
* Converts an array of strings externals to an array of regular expressions.
58+
* Input is an array of string, which we need to convert to regular expressions, so all imports for this module will be treated as externals.
59+
60+
* For example, in case we want nativescript-vue to be external, we will pass `["nativescript-vue"]`.
61+
* If we pass it to webpack in this way, it will treat all `require("nativescript-vue")` as externals.
62+
* However, you may import some file/subdir of the module, for example `require("nativescript-vue/somedir/file1")`.
63+
* To treat this as external, we convert the strings to regular expressions, which makes webpack exclude all imports of the module.
64+
* @param {string[]} externals Array of strings.
65+
* @returns {RegExp[]} Array of regular expressions constructed from the input strings. In case the input is nullable, an empty array is returned.
66+
*/
67+
exports.getConvertedExternals = (externals) => {
68+
const modifiedExternals = (externals || []).map((e) => {
69+
return new RegExp(`^${e}((/.*)|$)`);
70+
});
71+
72+
return modifiedExternals;
73+
};
74+
5675
const sanitize = name => name
5776
.split("")
5877
.filter(char => /[a-zA-Z0-9]/.test(char))

Diff for: index.spec.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { getConvertedExternals } from './index';
2+
3+
describe('index', () => {
4+
describe('getConvertedExternals', () => {
5+
it('returns empty array when nullable is passed', () => {
6+
const actualResult = getConvertedExternals(null);
7+
expect(actualResult).toEqual([]);
8+
});
9+
10+
const testCases = [
11+
{
12+
input: ['nativescript-vue'],
13+
expectedOutput: [/^nativescript-vue((\/.*)|$)/]
14+
},
15+
{
16+
input: ['nativescript-vue', 'nativescript-angular'],
17+
expectedOutput: [/^nativescript-vue((\/.*)|$)/, /^nativescript-angular((\/.*)|$)/]
18+
}
19+
];
20+
21+
testCases.forEach(testCase => {
22+
it('converts passed strings to regular expressions', () => {
23+
const actualResult = getConvertedExternals(testCase.input);
24+
expect(actualResult).toEqual(testCase.expectedOutput);
25+
});
26+
27+
it(`returns regular expressions which match expected modules and their subdirs, for input ${testCase.input}`, () => {
28+
[
29+
'nativescript-vue',
30+
'nativescript-vue/subdir',
31+
'nativescript-vue/subdir/subdir-2'
32+
].forEach(testString => {
33+
const regExpsExternals = getConvertedExternals(testCase.input);
34+
const result = regExpsExternals.some((regExp: RegExp) => !!regExp.exec(testString));
35+
expect(result).toBe(true, `String ${testString} does not match any of the regular expressions: ${regExpsExternals.join(', ')}`);
36+
});
37+
});
38+
39+
it(`returns regular expressions which does not match expected modules and their subdirs, for input ${testCase.input}`, () => {
40+
[
41+
'nativescript-facebook',
42+
'nativescript-facebook/nativescript-vue',
43+
'main-plugin/subdir/nativescript-vue',
44+
'nativescript-vue-template-compiler',
45+
'nativescript-vue-template-compiler/subdir'
46+
].forEach(testString => {
47+
const regExpsExternals = getConvertedExternals(testCase.input);
48+
const result = regExpsExternals.some((regExp: RegExp) => !!regExp.exec(testString));
49+
expect(result).toBe(false, `String ${testString} matches some of the regular expressions: ${regExpsExternals.join(', ')}`);
50+
});
51+
});
52+
});
53+
});
54+
});

Diff for: package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,13 @@
100100
},
101101
"devDependencies": {
102102
"@ngtools/webpack": "~7.1.0",
103-
"@types/jasmine": "^3.3.1",
103+
"@types/jasmine": "^3.3.7",
104104
"@types/node": "^10.12.12",
105+
"@types/proxyquire": "1.3.28",
105106
"conventional-changelog-cli": "^1.3.22",
106107
"jasmine": "^3.2.0",
107108
"jasmine-spec-reporter": "^4.2.1",
109+
"proxyquire": "2.1.0",
108110
"typescript": "~3.1.1"
109111
}
110112
}

Diff for: templates/webpack.angular.js

+2-5
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,8 @@ module.exports = env => {
4646
sourceMap, // --env.sourceMap
4747
hmr, // --env.hmr,
4848
} = env;
49-
env.externals = env.externals || [];
50-
const externals = (env.externals).map((e) => { // --env.externals
51-
return new RegExp(e + ".*");
52-
});
5349

50+
const externals = nsWebpack.getConvertedExternals(env.externals);
5451
const appFullPath = resolve(projectRoot, appPath);
5552
const appResourcesFullPath = resolve(projectRoot, appResourcesPath);
5653

@@ -65,7 +62,7 @@ module.exports = env => {
6562
// when "@angular/core" is external, it's not included in the bundles. In this way, it will be used
6663
// directly from node_modules and the Angular modules loader won't be able to resolve the lazy routes
6764
// fixes https://github.com/NativeScript/nativescript-cli/issues/4024
68-
if (env.externals.indexOf("@angular/core") > -1) {
65+
if (externals.indexOf("@angular/core") > -1) {
6966
const appModuleRelativePath = getMainModulePath(resolve(appFullPath, entryModule));
7067
if (appModuleRelativePath) {
7168
const appModuleFolderPath = dirname(resolve(appFullPath, appModuleRelativePath));

Diff for: templates/webpack.config.spec.ts

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import * as proxyquire from 'proxyquire';
2+
import * as nsWebpackIndex from '../index';
3+
// With noCallThru enabled, `proxyquire` will not fall back to requiring the real module to populate properties that are not mocked.
4+
// This allows us to mock packages that are not available in node_modules.
5+
// In case you want to enable fallback for a specific object, just add `'@noCallThru': false`.
6+
proxyquire.noCallThru();
7+
8+
class EmptyClass { };
9+
10+
const nativeScriptDevWebpack = {
11+
GenerateBundleStarterPlugin: EmptyClass,
12+
WatchStateLoggerPlugin: EmptyClass,
13+
PlatformFSPlugin: EmptyClass,
14+
getAppPath: () => 'app',
15+
getEntryModule: () => 'EntryModule',
16+
getResolver: () => null,
17+
getConvertedExternals: nsWebpackIndex.getConvertedExternals
18+
};
19+
20+
const emptyObject = {};
21+
22+
const webpackConfigAngular = proxyquire('./webpack.angular', {
23+
'nativescript-dev-webpack': nativeScriptDevWebpack,
24+
'nativescript-dev-webpack/nativescript-target': emptyObject,
25+
'nativescript-dev-webpack/transformers/ns-replace-bootstrap': emptyObject,
26+
'nativescript-dev-webpack/transformers/ns-replace-lazy-loader': emptyObject,
27+
'nativescript-dev-webpack/utils/ast-utils': emptyObject,
28+
'@ngtools/webpack': {
29+
AngularCompilerPlugin: EmptyClass
30+
}
31+
});
32+
33+
const webpackConfigTypeScript = proxyquire('./webpack.typescript', {
34+
'nativescript-dev-webpack': nativeScriptDevWebpack,
35+
'nativescript-dev-webpack/nativescript-target': emptyObject,
36+
});
37+
38+
const webpackConfigJavaScript = proxyquire('./webpack.javascript', {
39+
'nativescript-dev-webpack': nativeScriptDevWebpack,
40+
'nativescript-dev-webpack/nativescript-target': emptyObject,
41+
});
42+
43+
const webpackConfigVue = proxyquire('./webpack.vue', {
44+
'nativescript-dev-webpack': nativeScriptDevWebpack,
45+
'nativescript-dev-webpack/nativescript-target': emptyObject,
46+
'vue-loader/lib/plugin': EmptyClass,
47+
'nativescript-vue-template-compiler': emptyObject
48+
});
49+
50+
describe('webpack.config.js', () => {
51+
[
52+
{ type: 'javascript', webpackConfig: webpackConfigJavaScript },
53+
{ type: 'typescript', webpackConfig: webpackConfigTypeScript },
54+
{ type: 'angular', webpackConfig: webpackConfigAngular },
55+
{ type: 'vue', webpackConfig: webpackConfigVue }
56+
].forEach(element => {
57+
const { type, webpackConfig } = element;
58+
59+
describe(`verify externals for webpack.${type}.js`, () => {
60+
const getInput = (platform: string, externals: string[]) => {
61+
const input: any = { externals };
62+
input[platform] = true;
63+
return input;
64+
};
65+
66+
[
67+
'android',
68+
'ios'
69+
].forEach(platform => {
70+
describe(`for ${platform}`, () => {
71+
afterEach(() => {
72+
nativeScriptDevWebpack.getConvertedExternals = nsWebpackIndex.getConvertedExternals;
73+
});
74+
75+
it('returns empty array when externals are not passed', () => {
76+
const config = webpackConfig(getInput(platform, null));
77+
expect(config.externals).toEqual([]);
78+
});
79+
80+
it('calls getConvertedExternals to convert externals', () => {
81+
let isCalled = false;
82+
nativeScriptDevWebpack.getConvertedExternals = () => {
83+
isCalled = true;
84+
return [];
85+
};
86+
87+
const input = getInput(platform, ['nativescript-vue']);
88+
webpackConfig(input);
89+
expect(isCalled).toBe(true, 'Webpack.config.js must use the getConvertedExternals method');
90+
});
91+
92+
[
93+
{
94+
input: ['nativescript-vue'],
95+
expectedOutput: [/^nativescript-vue((\/.*)|$)/]
96+
},
97+
{
98+
input: ['nativescript-vue', 'nativescript-angular'],
99+
expectedOutput: [/^nativescript-vue((\/.*)|$)/, /^nativescript-angular((\/.*)|$)/]
100+
},
101+
].forEach(testCase => {
102+
const input = getInput(platform, testCase.input);
103+
104+
it(`are correct regular expressions, for input ${testCase.input}`, () => {
105+
const config = webpackConfig(input);
106+
expect(config.externals).toEqual(testCase.expectedOutput);
107+
});
108+
});
109+
});
110+
});
111+
});
112+
});
113+
});

Diff for: templates/webpack.javascript.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ module.exports = env => {
4242
sourceMap, // --env.sourceMap
4343
hmr, // --env.hmr,
4444
} = env;
45-
const externals = (env.externals || []).map((e) => { // --env.externals
46-
return new RegExp(e + ".*");
47-
});
45+
const externals = nsWebpack.getConvertedExternals(env.externals);
4846

4947
const appFullPath = resolve(projectRoot, appPath);
5048
const appResourcesFullPath = resolve(projectRoot, appResourcesPath);

Diff for: templates/webpack.typescript.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ module.exports = env => {
4242
sourceMap, // --env.sourceMap
4343
hmr, // --env.hmr,
4444
} = env;
45-
const externals = (env.externals || []).map((e) => { // --env.externals
46-
return new RegExp(e + ".*");
47-
});
45+
const externals = nsWebpack.getConvertedExternals(env.externals);
4846

4947
const appFullPath = resolve(projectRoot, appPath);
5048
const appResourcesFullPath = resolve(projectRoot, appResourcesPath);

Diff for: templates/webpack.vue.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ module.exports = env => {
4646
hmr, // --env.hmr
4747
} = env;
4848

49-
const externals = (env.externals || []).map((e) => { // --env.externals
50-
return new RegExp(e + ".*");
51-
});
49+
const externals = nsWebpack.getConvertedExternals(env.externals);
5250

5351
const mode = production ? "production" : "development"
5452

0 commit comments

Comments
 (0)