Skip to content

Commit fa3192a

Browse files
committedAug 14, 2021
[resolvers/node] [fix] when "module" does not exist, fall back to "main"
Fixes #2186. This is actually exposing a bug with packages that are broken - that ship an invalid "module" field - but it‘s a more friendly and node-accurate behavior to still work when "main" works.
1 parent 513bb0b commit fa3192a

File tree

7 files changed

+58
-22
lines changed

7 files changed

+58
-22
lines changed
 

‎resolvers/node/index.js

+25-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ exports.resolve = function (source, file, config) {
1717
}
1818

1919
try {
20-
resolvedPath = resolve.sync(source, opts(file, config));
20+
const cachedFilter = function (pkg, dir) { return packageFilter(pkg, dir, config); };
21+
resolvedPath = resolve.sync(source, opts(file, config, cachedFilter));
2122
log('Resolved to:', resolvedPath);
2223
return { found: true, path: resolvedPath };
2324
} catch (err) {
@@ -26,7 +27,7 @@ exports.resolve = function (source, file, config) {
2627
}
2728
};
2829

29-
function opts(file, config) {
30+
function opts(file, config, packageFilter) {
3031
return Object.assign({
3132
// more closely matches Node (#333)
3233
// plus 'mjs' for native modules! (#939)
@@ -36,16 +37,32 @@ function opts(file, config) {
3637
{
3738
// path.resolve will handle paths relative to CWD
3839
basedir: path.dirname(path.resolve(file)),
39-
packageFilter: packageFilter,
40-
40+
packageFilter,
4141
});
4242
}
4343

44-
function packageFilter(pkg) {
44+
function identity(x) { return x; }
45+
46+
function packageFilter(pkg, dir, config) {
47+
let found = false;
48+
const file = path.join(dir, 'dummy.js');
4549
if (pkg.module) {
46-
pkg.main = pkg.module;
47-
} else if (pkg['jsnext:main']) {
48-
pkg.main = pkg['jsnext:main'];
50+
try {
51+
resolve.sync(String(pkg.module).replace(/^(?:\.\/)?/, './'), opts(file, config, identity));
52+
pkg.main = pkg.module;
53+
found = true;
54+
} catch (err) {
55+
log('resolve threw error trying to find pkg.module:', err);
56+
}
57+
}
58+
if (!found && pkg['jsnext:main']) {
59+
try {
60+
resolve.sync(String(pkg['jsnext:main']).replace(/^(?:\.\/)?/, './'), opts(file, config, identity));
61+
pkg.main = pkg['jsnext:main'];
62+
found = true;
63+
} catch (err) {
64+
log('resolve threw error trying to find pkg[\'jsnext:main\']:', err);
65+
}
4966
}
5067
return pkg;
5168
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = {};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"main": "./main.js",
3+
"module": "./doesNotExist.js"
4+
}

‎resolvers/node/test/packageMains.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,29 @@ const chai = require('chai');
44
const expect = chai.expect;
55
const path = require('path');
66

7-
const webpack = require('../');
7+
const resolver = require('../');
88

99
const file = path.join(__dirname, 'package-mains', 'dummy.js');
1010

1111

1212
describe('packageMains', function () {
1313
it('captures module', function () {
14-
expect(webpack.resolve('./module', file)).property('path')
14+
expect(resolver.resolve('./module', file)).property('path')
1515
.to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js'));
1616
});
1717

1818
it('captures jsnext', function () {
19-
expect(webpack.resolve('./jsnext', file)).property('path')
19+
expect(resolver.resolve('./jsnext', file)).property('path')
2020
.to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js'));
2121
});
2222

2323
it('captures module instead of jsnext', function () {
24-
expect(webpack.resolve('./module-and-jsnext', file)).property('path')
24+
expect(resolver.resolve('./module-and-jsnext', file)).property('path')
2525
.to.equal(path.join(__dirname, 'package-mains', 'module-and-jsnext', 'src', 'index.js'));
2626
});
27+
28+
it('falls back from a missing "module" to "main"', function () {
29+
expect(resolver.resolve('./module-broken', file)).property('path')
30+
.to.equal(path.join(__dirname, 'package-mains', 'module-broken', 'main.js'));
31+
});
2732
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = {};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"main": "./main.js",
3+
"module": "./doesNotExist.js"
4+
}

‎resolvers/webpack/test/packageMains.js

+14-10
Original file line numberDiff line numberDiff line change
@@ -4,51 +4,55 @@ const chai = require('chai');
44
const expect = chai.expect;
55
const path = require('path');
66

7-
const webpack = require('../');
7+
const resolver = require('../');
88

99
const file = path.join(__dirname, 'package-mains', 'dummy.js');
1010

1111

1212
describe('packageMains', function () {
1313

1414
it('captures module', function () {
15-
expect(webpack.resolve('./module', file)).property('path')
15+
expect(resolver.resolve('./module', file)).property('path')
1616
.to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js'));
1717
});
1818

1919
it('captures jsnext', function () {
20-
expect(webpack.resolve('./jsnext', file)).property('path')
20+
expect(resolver.resolve('./jsnext', file)).property('path')
2121
.to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js'));
2222
});
2323

2424
it('captures module instead of jsnext', function () {
25-
expect(webpack.resolve('./module-and-jsnext', file)).property('path')
25+
expect(resolver.resolve('./module-and-jsnext', file)).property('path')
2626
.to.equal(path.join(__dirname, 'package-mains', 'module-and-jsnext', 'src', 'index.js'));
2727
});
2828

29+
it('falls back from a missing "module" to "main"', function () {
30+
expect(resolver.resolve('./module-broken', file)).property('path')
31+
.to.equal(path.join(__dirname, 'package-mains', 'module-broken', 'main.js'));
32+
});
33+
2934
it('captures webpack', function () {
30-
expect(webpack.resolve('./webpack', file)).property('path')
35+
expect(resolver.resolve('./webpack', file)).property('path')
3136
.to.equal(path.join(__dirname, 'package-mains', 'webpack', 'webpack.js'));
3237
});
3338

3439
it('captures jam (array path)', function () {
35-
expect(webpack.resolve('./jam', file)).property('path')
40+
expect(resolver.resolve('./jam', file)).property('path')
3641
.to.equal(path.join(__dirname, 'package-mains', 'jam', 'jam.js'));
3742
});
3843

3944
it('uses configured packageMains, if provided', function () {
40-
expect(webpack.resolve('./webpack', file, { config: 'webpack.alt.config.js' })).property('path')
45+
expect(resolver.resolve('./webpack', file, { config: 'webpack.alt.config.js' })).property('path')
4146
.to.equal(path.join(__dirname, 'package-mains', 'webpack', 'index.js'));
4247
});
4348

4449
it('always defers to module, regardless of config', function () {
45-
expect(webpack.resolve('./module', file, { config: 'webpack.alt.config.js' })).property('path')
50+
expect(resolver.resolve('./module', file, { config: 'webpack.alt.config.js' })).property('path')
4651
.to.equal(path.join(__dirname, 'package-mains', 'module', 'src', 'index.js'));
4752
});
4853

4954
it('always defers to jsnext:main, regardless of config', function () {
50-
expect(webpack.resolve('./jsnext', file, { config: 'webpack.alt.config.js' })).property('path')
55+
expect(resolver.resolve('./jsnext', file, { config: 'webpack.alt.config.js' })).property('path')
5156
.to.equal(path.join(__dirname, 'package-mains', 'jsnext', 'src', 'index.js'));
5257
});
53-
5458
});

0 commit comments

Comments
 (0)
Please sign in to comment.