Skip to content

Commit

Permalink
Initial support for conditional imports
Browse files Browse the repository at this point in the history
  • Loading branch information
kasperisager committed Nov 16, 2023
1 parent 9c3d49b commit 96060af
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 22 deletions.
46 changes: 25 additions & 21 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,22 +257,8 @@ const Module = module.exports = exports = class Module {
const module = this._cache[specifier] = new this(specifier)

module._defaultType = defaultType

let dirname = path.dirname(module._filename)
do {
const pkg = path.join(dirname, 'package.json')

if (protocol.exists(pkg)) {
try {
module._info = this.load(pkg, { protocol })._exports
} catch {}
break
}

dirname = path.dirname(dirname)
} while (dirname !== path.sep && dirname !== '.')

module._main = main || module
module._info = this._loadPackageManifest(path.dirname(module._filename), protocol)

let extension = this._extensionFor(type) || path.extname(specifier)

Expand All @@ -290,6 +276,22 @@ const Module = module.exports = exports = class Module {
return this._transform(module, referrer, dynamic)
}

static _loadPackageManifest (dirname, protocol) {
do {
const pkg = path.join(dirname, 'package.json')

if (protocol.exists(pkg)) {
try {
return this.load(pkg, { protocol })._exports
} catch {}
}

dirname = path.dirname(dirname)
} while (dirname !== path.sep && dirname !== '.')

return {}
}

static resolve (specifier, dirname = os.cwd(), opts = {}) {
if (typeof dirname !== 'string') {
opts = dirname
Expand Down Expand Up @@ -334,7 +336,9 @@ const Module = module.exports = exports = class Module {
}

static * _resolve (specifier, dirname, protocol, imports) {
specifier = this._mapConditionalSpecifier(specifier, specifier, imports, protocol.imports)
const info = this._loadPackageManifest(dirname, protocol)

specifier = this._mapConditionalSpecifier(specifier, specifier, imports, protocol.imports, info.imports)

protocol = this._protocolFor(specifier, protocol)

Expand Down Expand Up @@ -443,13 +447,13 @@ const Module = module.exports = exports = class Module {
}
}

static _mapConditionalSpecifier (specifier, fallback, ...exportMaps) {
const exports = this._flattenSpecifierMaps(exportMaps)
static _mapConditionalSpecifier (specifier, fallback, ...specifierMaps) {
const specifiers = this._flattenSpecifierMaps(specifierMaps)

if (specifier in exports) {
specifier = search(exports[specifier])
if (specifier in specifiers) {
specifier = search(specifiers[specifier])
} else {
specifier = search(exports)
specifier = search(specifiers)
}

return specifier || fallback
Expand Down
152 changes: 151 additions & 1 deletion test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1114,7 +1114,7 @@ test('exports in package.json', (t) => {

read (filename) {
if (filename === '/package.json') {
return '{ "exports": "./foo.js" }'
return '{ "exports": "./foo" }'
}

t.fail()
Expand Down Expand Up @@ -1187,6 +1187,156 @@ test('exports in node_modules', (t) => {
t.is(Module.resolve('foo', '/', { protocol }), '/node_modules/foo/foo.js')
})

test('imports in package.json', (t) => {
t.teardown(onteardown)

const protocol = new Module.Protocol({
preresolve (specifier, dirname) {
t.is(specifier, './baz')
t.is(dirname, '/')

return path.resolve(dirname, specifier)
},

exists (filename) {
return (
filename === '/package.json' ||
filename === '/baz.js'
)
},

read (filename) {
if (filename === '/package.json') {
return '{ "imports": { "bar": "./baz" } }'
}

if (filename === '/foo.js') {
return 'const bar = require(\'bar\')'
}

if (filename === '/baz.js') {
return 'module.exports = 42'
}

t.fail()
}
})

Module.load('/foo.js', { protocol })
})

test('conditional imports in package.json, .cjs before .mjs', (t) => {
t.teardown(onteardown)

const protocol = new Module.Protocol({
preresolve (specifier, dirname) {
t.is(specifier, './baz.cjs')
t.is(dirname, '/')

return path.resolve(dirname, specifier)
},

exists (filename) {
return (
filename === '/package.json' ||
filename === '/baz.cjs' ||
filename === '/baz.mjs'
)
},

read (filename) {
if (filename === '/package.json') {
return '{ "imports": { "bar": { "require": "./baz.cjs", "import": "./baz.mjs" } } }'
}

if (filename === '/foo.cjs') {
return 'const bar = require(\'bar\')'
}

if (filename === '/baz.cjs') {
return 'module.exports = 42'
}

t.fail()
}
})

Module.load('/foo.cjs', { protocol })
})

test('conditional imports in package.json, .mjs before .cjs', (t) => {
t.teardown(onteardown)

const protocol = new Module.Protocol({
preresolve (specifier, dirname) {
t.is(specifier, './baz.mjs')
t.is(dirname, '/')

return path.resolve(dirname, specifier)
},

exists (filename) {
return filename === '/package.json' || filename === '/baz.cjs' || filename === '/baz.mjs'
},

read (filename) {
if (filename === '/package.json') {
return '{ "imports": { "bar": { "import": "./baz.mjs", "require": "./baz.cjs" } } }'
}

if (filename === '/foo.cjs') {
return 'const bar = require(\'bar\')'
}

if (filename === '/baz.mjs') {
return 'export default 42'
}

t.fail()
}
})

Module.load('/foo.cjs', { protocol })
})

test('imports in node_modules', (t) => {
t.teardown(onteardown)

const protocol = new Module.Protocol({
preresolve (specifier, dirname) {
t.is(specifier, './baz')
t.is(dirname, '/node_modules/foo')

return path.resolve(dirname, specifier)
},

exists (filename) {
return (
filename === '/node_modules/foo/package.json' ||
filename === '/node_modules/foo/baz.js'
)
},

read (filename) {
if (filename === '/node_modules/foo/package.json') {
return '{ "imports": { "bar": "./baz" } }'
}

if (filename === '/node_modules/foo/foo.js') {
return 'const bar = require(\'bar\')'
}

if (filename === '/node_modules/foo/baz.js') {
return 'module.exports = 42'
}

t.fail()
}
})

Module.load('/node_modules/foo/foo.js', { protocol })
})

test('load file that cannot be read', async (t) => {
t.teardown(onteardown)

Expand Down

0 comments on commit 96060af

Please sign in to comment.