Skip to content

Commit

Permalink
Merge pull request #389 from preactjs/default-over-node
Browse files Browse the repository at this point in the history
(wmr) - resolve default over node
  • Loading branch information
marvinhagemeister authored Mar 6, 2021
2 parents 1b52512 + d0f5fe8 commit fe51b7e
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 73 deletions.
5 changes: 5 additions & 0 deletions .changeset/strange-beers-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'wmr': minor
---

Use resolve.exports for export map resolving in the npm-plugin
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ wmr.cjs
package-lock.json
!packages/wmr/test/fixtures/commonjs/node_modules
!packages/wmr/test/fixtures/package-exports/node_modules
!packages/wmr/test/fixtures/exports/node_modules
.DS_Store
1 change: 1 addition & 0 deletions packages/wmr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
"premove": "^3.0.1",
"prettier": "^2.0.5",
"puppeteer": "^3.3.0",
"resolve.exports": "^1.0.2",
"rollup": "^2.39.0",
"rollup-plugin-preserve-shebang": "^1.0.1",
"sade": "^1.7.3",
Expand Down
96 changes: 25 additions & 71 deletions packages/wmr/src/plugins/npm-plugin/resolve.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
import { resolve as _resolveExports, legacy as _resolveLegacyEntry } from 'resolve.exports';

function resolveExports(pkg, key) {
return _resolveExports(pkg, key, {
browser: true,
conditions: [process.env.NODE_ENV === 'production' ? 'production' : 'development', 'esmodules', 'module']
});
}

function resolveLegacyEntry(pkg, path) {
const entry =
_resolveLegacyEntry(pkg, {
browser: true,
fields: ['esmodules', 'modern', 'module', 'jsnext:main', 'browser', 'main']
}) || 'index.js';
return '/' + entry.replace(/^\.?\//, '');
}

/**
* @param {string} path
* @param {object} context
Expand All @@ -21,23 +39,16 @@ export async function resolveModule(path, { readFile, hasFile, module, internal

// Package Export Maps
if (!internal && pkg.exports) {
const entry = path ? `./${path}` : '.';

const mapped = resolveExportMap(pkg.exports, entry, ENV_KEYS);

if (!mapped) {
throw new Error(`Unknown package export ${entry} in ${module}.\n\n${JSON.stringify(pkg.exports, null, 2)}`);
}

// `mapped:true` means directory access was allowed for this entry, but it was not resolved.
if (mapped !== true && !internal) {
return mapped.replace(/^\./, '');
}
// will normalize entry & will throw error if no match
const mapped = resolveExports(pkg, path || '.');
// An entry ending in `/` remaps to a directory, but is not considered resolved.
if (mapped.endsWith('/')) path = mapped;
else return mapped.replace(/^\./, '');
}

// path is a bare import of a package, use its legacy exports (module/main):
if (!path) {
path = getLegacyEntry(pkg);
path = resolveLegacyEntry(pkg, path || '.');
}

// fallback: implement basic commonjs-style resolution
Expand All @@ -50,7 +61,7 @@ export async function resolveModule(path, { readFile, hasFile, module, internal
if (!isExportMappedSpecifier) {
try {
const subPkg = JSON.parse(await readFile(path + '/package.json'));
path += getLegacyEntry(subPkg);
path += resolveLegacyEntry(subPkg, '.');
} catch (e) {}
}

Expand All @@ -66,60 +77,3 @@ export async function resolveModule(path, { readFile, hasFile, module, internal

return path;
}

/**
* Get the best possible entry from a package.json that doesn't have an Export Map
* @TODO this does not currently support {"browser":{"./foo.js":"./browser-foo.js"}}
*/
function getLegacyEntry(pkg) {
const mainFields = [pkg.esmodules, pkg.modern, pkg.module, pkg['jsnext:main'], pkg.browser, pkg.main, 'index.js'];
const entry = mainFields.find(p => p && typeof p === 'string');
return '/' + entry.replace(/^\.?\//, '');
}

const ENV_KEYS = ['esmodules', 'import', 'module', 'require', 'browser', 'node', 'default'];

/** Get the best resolution for an entry from an Export Map
* @param {Object} exp `package.exports`
* @param {string} entry `./foo` or `.`
* @param {string[]} envKeys package environment keys
* @returns {string | boolean} a resolved path, or a boolean indicating if the given entry is exposed
*/
function resolveExportMap(exp, entry, envKeys) {
if (typeof exp === 'string') {
// {"exports":"./foo.js"}
// {"exports":{"./foo":"./foo.js"}}
return exp;
}
let isFileListing;
let isDirectoryExposed = false;

let fallbacks = [];
for (let i in exp) {
if (isFileListing === undefined) isFileListing = i[0] === '.';
if (isFileListing) {
// {"exports":{".":"./index.js"}}
if (i === entry) {
return resolveExportMap(exp[i], entry, envKeys);
}
if (!isDirectoryExposed && i.endsWith('/') && entry.startsWith(i)) {
isDirectoryExposed = true;
}
} else if (envKeys.includes(i)) {
// intentionally de-prioritize "require" and "default" keys
if (i === 'require' || i === 'default') {
fallbacks.push(i);
} else {
// {"exports":{"import":"./foo.js"}}
return resolveExportMap(exp[i], entry, envKeys);
}
}
}

// None of the in-order keys matched - fall back to require/default in the order specified
for (let i of fallbacks) {
return resolveExportMap(exp[i], entry, envKeys);
}

return isDirectoryExposed;
}
18 changes: 16 additions & 2 deletions packages/wmr/test/fixtures.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,20 @@ describe('fixtures', () => {
});
});

describe('export-map', () => {
beforeEach(async () => {
await loadFixture('exports', env);
instance = await runWmrFast(env.tmp.path);
await env.page.goto(await instance.address);
});

it('should not pick node for a browser', async () => {
const test = await env.page.$('.test');
let text = test ? await test.evaluate(el => el.textContent) : null;
expect(text).toEqual('Browser implementation');
});
});

describe('package-exports', () => {
beforeEach(async () => {
await loadFixture('package-exports', env);
Expand Down Expand Up @@ -521,12 +535,12 @@ describe('fixtures', () => {
default: 'import'
});
expect(await env.page.evaluate(`import('/@npm/exports-fallbacks-defaultfirst')`)).toEqual({
default: 'import'
default: 'default'
});

// When import/module/browser isn't present (but a random other one is!), we fall back to require/default:
expect(await env.page.evaluate(`import('/@npm/exports-fallbacks-requirefallback')`)).toEqual({
default: 'require'
default: 'default'
});
expect(await env.page.evaluate(`import('/@npm/exports-fallbacks-defaultfallback')`)).toEqual({
default: 'default'
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions packages/wmr/test/fixtures/exports/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf8" />
<title>Exports</title>
</head>
<body>
<div id="root"></div>
<script src="./index.js" type="module"></script>
</body>
</html>
6 changes: 6 additions & 0 deletions packages/wmr/test/fixtures/exports/public/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import value from 'test';

const div = document.createElement('div');
div.setAttribute('class', 'test');
div.innerText = value;
document.body.appendChild(div);
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6791,6 +6791,11 @@ resolve-url@^0.2.1:
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=

resolve.exports@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.0.2.tgz#98c82ba8a4d3f9fcc32cbfa6f950b803d77b6c21"
integrity sha512-1+PDdTR3xrGWB/NzXLkzS1+PQlJ+BOR2baBGJSVat4HasiY1mnkyAQws3FUTmBDB79oK54QFaDM8Ig9nUtJwvQ==

resolve@^1.10.0, resolve@^1.11.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18.1:
version "1.20.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
Expand Down

0 comments on commit fe51b7e

Please sign in to comment.