forked from ProjectEvergreen/greenwood-demo-adapter-vercel
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
patching support import.meta.resolve during build
- Loading branch information
1 parent
8de4ab9
commit 0e65869
Showing
3 changed files
with
336 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,318 @@ | ||
diff --git a/src/lib/node-modules-utils.js b/src/lib/node-modules-utils.js | ||
index c1d6f1c1010020ce63c1736c528d2ecc461ca08d..2364cf74512d01f9b4b94679b678fd41309ce8c2 100644 | ||
--- a/src/lib/node-modules-utils.js | ||
+++ b/src/lib/node-modules-utils.js | ||
@@ -1,47 +1,23 @@ | ||
-import { createRequire } from 'module'; | ||
import { checkResourceExists } from './resource-utils.js'; | ||
+import { resolveBareSpecifier, derivePackageRoot } from './walker-package-ranger.js'; | ||
import fs from 'fs/promises'; | ||
|
||
-// TODO delete me and everything else in this file | ||
-// https://github.com/ProjectEvergreen/greenwood/issues/684 | ||
-async function getNodeModulesLocationForPackage(packageName) { | ||
- let nodeModulesUrl; | ||
- | ||
- // require.resolve may fail in the event a package has no main in its package.json | ||
- // so as a fallback, ask for node_modules paths and find its location manually | ||
- // https://github.com/ProjectEvergreen/greenwood/issues/557#issuecomment-923332104 | ||
- // // https://stackoverflow.com/a/62499498/417806 | ||
- const require = createRequire(import.meta.url); | ||
- const locations = require.resolve.paths(packageName); | ||
- | ||
- for (const location in locations) { | ||
- const nodeModulesPackageRoot = `${locations[location]}/${packageName}`; | ||
- const packageJsonLocation = `${nodeModulesPackageRoot}/package.json`; | ||
- | ||
- if (await checkResourceExists(new URL(`file://${packageJsonLocation}`))) { | ||
- nodeModulesUrl = nodeModulesPackageRoot; | ||
- } | ||
+// take a "shortcut" pathname, e.g. /node_modules/lit/lit-html.js | ||
+// and resolve it using import.meta.resolve | ||
+function getResolvedHrefFromPathnameShortcut(pathname) { | ||
+ const segments = pathname.replace('/node_modules/', '').split('/'); | ||
+ const hasScope = segments[0].startsWith('@'); | ||
+ const specifier = hasScope ? `${segments[0]}/${segments[1]}` : segments[0]; | ||
+ const resolved = resolveBareSpecifier(specifier); | ||
+ | ||
+ if (resolved) { | ||
+ const root = derivePackageRoot(resolved); | ||
+ | ||
+ return `${root}${segments.slice(hasScope ? 2 : 1).join('/')}`; | ||
+ } else { | ||
+ // for example, local theme pack development | ||
+ return `file://${pathname}`; | ||
} | ||
- | ||
- if (!nodeModulesUrl) { | ||
- console.debug(`Unable to look up ${packageName} using NodeJS require.resolve. Falling back to process.cwd()`); | ||
- nodeModulesUrl = new URL(`./node_modules/${packageName}`, `file://${process.cwd()}`).pathname; | ||
- } | ||
- | ||
- return nodeModulesUrl; | ||
-} | ||
- | ||
-// extract the package name from a URL like /node_modules/<some>/<package>/index.js | ||
-function getPackageNameFromUrl(url) { | ||
- const packagePathPieces = url.split('node_modules/')[1].split('/'); // double split to handle node_modules within nested paths | ||
- let packageName = packagePathPieces.shift(); | ||
- | ||
- // handle scoped packages | ||
- if (packageName.indexOf('@') === 0) { | ||
- packageName = `${packageName}/${packagePathPieces.shift()}`; | ||
- } | ||
- | ||
- return packageName; | ||
} | ||
|
||
async function getPackageJsonForProject({ userWorkspace, projectDirectory }) { | ||
@@ -59,6 +35,5 @@ async function getPackageJsonForProject({ userWorkspace, projectDirectory }) { | ||
|
||
export { | ||
getPackageJsonForProject, | ||
- getNodeModulesLocationForPackage, | ||
- getPackageNameFromUrl | ||
+ getResolvedHrefFromPathnameShortcut | ||
}; | ||
\ No newline at end of file | ||
diff --git a/src/lib/resource-utils.js b/src/lib/resource-utils.js | ||
index 6a78e464785b6dc11e909acf64f66db975e77909..0ab3458dc1542929a8b003509e6d63783b446967 100644 | ||
--- a/src/lib/resource-utils.js | ||
+++ b/src/lib/resource-utils.js | ||
@@ -1,5 +1,6 @@ | ||
import fs from 'fs/promises'; | ||
import { hashString } from './hashing-utils.js'; | ||
+import { getResolvedHrefFromPathnameShortcut } from './node-modules-utils.js'; | ||
import htmlparser from 'node-html-parser'; | ||
|
||
async function modelResource(context, type, src = undefined, contents = undefined, optimizationAttr = undefined, rawAttributes = undefined) { | ||
@@ -7,9 +8,10 @@ async function modelResource(context, type, src = undefined, contents = undefine | ||
const extension = type === 'script' ? 'js' : 'css'; | ||
let sourcePathURL; | ||
|
||
+ getResolvedHrefFromPathnameShortcut | ||
if (src) { | ||
sourcePathURL = src.startsWith('/node_modules') | ||
- ? new URL(`.${src}`, projectDirectory) | ||
+ ? new URL(getResolvedHrefFromPathnameShortcut(src)) | ||
: src.startsWith('/') | ||
? new URL(`.${src}`, userWorkspace) | ||
: new URL(`./${src.replace(/\.\.\//g, '').replace('./', '')}`, userWorkspace); | ||
diff --git a/src/lib/walker-package-ranger.js b/src/lib/walker-package-ranger.js | ||
index 3f5b0e870892bc59786ab466bcb7689fb5122539..0adb52cbb2f0f7aa8548855dbd0fdff7ac4e2255 100644 | ||
--- a/src/lib/walker-package-ranger.js | ||
+++ b/src/lib/walker-package-ranger.js | ||
@@ -3,11 +3,14 @@ import fs from 'fs'; | ||
/* eslint-disable max-depth,complexity */ | ||
// priority if from L -> R | ||
const SUPPORTED_EXPORT_CONDITIONS = ['import', 'module-sync', 'default']; | ||
+const IMPORT_MAP_RESOLVED_PREFIX = '/~'; | ||
const importMap = {}; | ||
const diagnostics = {}; | ||
|
||
-function updateImportMap(key, value) { | ||
- importMap[key.replace('./', '')] = value.replace('./', ''); | ||
+function updateImportMap(key, value, resolvedRoot) { | ||
+ if (!importMap[key.replace('./', '')]) { | ||
+ importMap[key.replace('./', '')] = `${IMPORT_MAP_RESOLVED_PREFIX}${resolvedRoot.replace('file://', '')}${value.replace('./', '')}`; | ||
+ } | ||
} | ||
|
||
// wrapper around import.meta.resolve to provide graceful error handling / logging | ||
@@ -35,11 +38,27 @@ function resolveBareSpecifier(specifier) { | ||
* root: 'file:///path/to/project/greenwood-lit-ssr/node_modules/.pnpm/lit-html@3.2.1/node_modules/lit-html/package.json' | ||
* } | ||
*/ | ||
-function derivePackageRoot(dependencyName, resolved) { | ||
- const root = resolved.slice(0, resolved.lastIndexOf(`/node_modules/${dependencyName}/`)); | ||
- const derived = `${root}/node_modules/${dependencyName}/`; | ||
+function derivePackageRoot(resolved) { | ||
+ // can't rely on the specifier, for example in monorepos | ||
+ // where @foo/bar may point to a non node_modules location | ||
+ // e.g. packages/some-namespace/package.json | ||
+ // so we walk backwards looking for nearest package.json | ||
+ const segments = resolved | ||
+ .replace('file://', '') | ||
+ .split('/') | ||
+ .filter(segment => segment !== '') | ||
+ .reverse(); | ||
+ let root = resolved.replace(segments[0], ''); | ||
+ | ||
+ for (const segment of segments.slice(1)) { | ||
+ if (fs.existsSync(new URL('./package.json', root).pathname)) { | ||
+ break; | ||
+ } | ||
|
||
- return derived; | ||
+ root = root.replace(`${segment}/`, ''); | ||
+ } | ||
+ | ||
+ return root; | ||
} | ||
|
||
// Helper function to convert export patterns to a regex (thanks ChatGPT :D) | ||
@@ -109,7 +128,7 @@ async function walkExportPatterns(dependency, sub, subValue, resolvedRoot) { | ||
// https://unpkg.com/browse/@uswds/uswds@3.10.0/package.json | ||
const rootSubRelativePath = relativePath.replace(rootSubValueOffset, ''); | ||
|
||
- updateImportMap(`${dependency}${rootSubOffset}${rootSubRelativePath}`, `/node_modules/${dependency}${relativePath}`); | ||
+ updateImportMap(`${dependency}${rootSubOffset}${rootSubRelativePath}`, relativePath, resolvedRoot); | ||
} | ||
}); | ||
} | ||
@@ -117,18 +136,18 @@ async function walkExportPatterns(dependency, sub, subValue, resolvedRoot) { | ||
walkDirectoryForExportPatterns(new URL(`.${rootSubValueOffset}/`, resolvedRoot)); | ||
} | ||
|
||
-function trackExportConditions(dependency, exports, sub, condition) { | ||
+function trackExportConditions(dependency, exports, sub, condition, resolvedRoot) { | ||
if (typeof exports[sub] === 'object') { | ||
// also check for nested conditions of conditions, default to default for now | ||
// https://unpkg.com/browse/@floating-ui/dom@1.6.12/package.json | ||
if (sub === '.') { | ||
- updateImportMap(dependency, `/node_modules/${dependency}/${exports[sub][condition].default ?? exports[sub][condition]}`); | ||
+ updateImportMap(dependency, `${exports[sub][condition].default ?? exports[sub][condition]}`, resolvedRoot); | ||
} else { | ||
- updateImportMap(`${dependency}/${sub}`, `/node_modules/${dependency}/${exports[sub][condition].default ?? exports[sub][condition]}`); | ||
+ updateImportMap(`${dependency}/${sub}`, `${exports[sub][condition].default ?? exports[sub][condition]}`, resolvedRoot); | ||
} | ||
} else { | ||
// https://unpkg.com/browse/redux@5.0.1/package.json | ||
- updateImportMap(dependency, `/node_modules/${dependency}/${exports[sub][condition]}`); | ||
+ updateImportMap(dependency, `${exports[sub][condition]}`); | ||
} | ||
} | ||
|
||
@@ -151,7 +170,7 @@ async function walkPackageForExports(dependency, packageJson, resolvedRoot) { | ||
for (const condition of SUPPORTED_EXPORT_CONDITIONS) { | ||
if (exports[sub][condition]) { | ||
matched = true; | ||
- trackExportConditions(dependency, exports, sub, condition); | ||
+ trackExportConditions(dependency, exports, sub, condition, resolvedRoot); | ||
break; | ||
} | ||
} | ||
@@ -163,16 +182,16 @@ async function walkPackageForExports(dependency, packageJson, resolvedRoot) { | ||
} else { | ||
// handle (unconditional) subpath exports | ||
if (sub === '.') { | ||
- updateImportMap(dependency, `/node_modules/${dependency}/${exports[sub]}`); | ||
+ updateImportMap(dependency, `${exports[sub]}`, resolvedRoot); | ||
} else if (sub.indexOf('*') >= 0) { | ||
await walkExportPatterns(dependency, sub, exports[sub], resolvedRoot); | ||
} else { | ||
- updateImportMap(`${dependency}/${sub}`, `/node_modules/${dependency}/${exports[sub]}`); | ||
+ updateImportMap(`${dependency}/${sub}`, `${exports[sub]}`, resolvedRoot); | ||
} | ||
} | ||
} | ||
} else if (module || main) { | ||
- updateImportMap(dependency, `/node_modules/${dependency}/${module ?? main}`); | ||
+ updateImportMap(dependency, `${module ?? main}`, resolvedRoot); | ||
} else { | ||
// ex: https://unpkg.com/browse/uuid@3.4.0/package.json | ||
diagnostics[dependency] = `WARNING: No supported entry point detected for => \`${dependency}\``; | ||
@@ -186,7 +205,7 @@ async function walkPackageJson(packageJson = {}) { | ||
const resolved = resolveBareSpecifier(dependency); | ||
|
||
if (resolved) { | ||
- const resolvedRoot = derivePackageRoot(dependency, resolved); | ||
+ const resolvedRoot = derivePackageRoot(resolved); | ||
const resolvedPackageJson = (await import(new URL('./package.json', resolvedRoot), { with: { type: 'json' } })).default; | ||
|
||
walkPackageForExports(dependency, resolvedPackageJson, resolvedRoot); | ||
@@ -196,7 +215,7 @@ async function walkPackageJson(packageJson = {}) { | ||
const resolved = resolveBareSpecifier(dependency); | ||
|
||
if (resolved) { | ||
- const resolvedRoot = derivePackageRoot(dependency, resolved); | ||
+ const resolvedRoot = derivePackageRoot(resolved); | ||
const resolvedPackageJson = (await import(new URL('./package.json', resolvedRoot), { with: { type: 'json' } })).default; | ||
|
||
walkPackageForExports(dependency, resolvedPackageJson, resolvedRoot); | ||
@@ -246,5 +265,8 @@ function mergeImportMap(html = '', map = {}, shouldShim = false) { | ||
|
||
export { | ||
walkPackageJson, | ||
- mergeImportMap | ||
+ mergeImportMap, | ||
+ resolveBareSpecifier, | ||
+ derivePackageRoot, | ||
+ IMPORT_MAP_RESOLVED_PREFIX | ||
}; | ||
\ No newline at end of file | ||
diff --git a/src/plugins/resource/plugin-node-modules.js b/src/plugins/resource/plugin-node-modules.js | ||
index a80b54d6e094762056595635a7c9d95eab139260..92125cdcdaac5584c7a31b92ad3814b9529412b6 100644 | ||
--- a/src/plugins/resource/plugin-node-modules.js | ||
+++ b/src/plugins/resource/plugin-node-modules.js | ||
@@ -6,11 +6,10 @@ | ||
import { checkResourceExists } from '../../lib/resource-utils.js'; | ||
import fs from 'fs/promises'; | ||
import { nodeResolve } from '@rollup/plugin-node-resolve'; | ||
-import { getNodeModulesLocationForPackage, getPackageJsonForProject, getPackageNameFromUrl } from '../../lib/node-modules-utils.js'; | ||
-import { resolveForRelativeUrl } from '../../lib/resource-utils.js'; | ||
+import { getPackageJsonForProject, getResolvedHrefFromPathnameShortcut } from '../../lib/node-modules-utils.js'; | ||
import { ResourceInterface } from '../../lib/resource-interface.js'; | ||
import { mergeImportMap } from '../../lib/walker-package-ranger.js'; | ||
-import { walkPackageJson } from '../../lib/walker-package-ranger.js'; | ||
+import { walkPackageJson, IMPORT_MAP_RESOLVED_PREFIX } from '../../lib/walker-package-ranger.js'; | ||
|
||
let generatedImportMap; | ||
|
||
@@ -22,42 +21,36 @@ class NodeModulesResource extends ResourceInterface { | ||
} | ||
|
||
async shouldResolve(url) { | ||
- return url.pathname.indexOf('/node_modules/') === 0; | ||
+ const { pathname } = url; | ||
+ | ||
+ return pathname.startsWith(IMPORT_MAP_RESOLVED_PREFIX) || pathname.startsWith('/node_modules/'); | ||
} | ||
|
||
async resolve(url) { | ||
const { projectDirectory } = this.compilation.context; | ||
const { pathname, searchParams } = url; | ||
- const packageName = getPackageNameFromUrl(pathname); | ||
- const absoluteNodeModulesLocation = await getNodeModulesLocationForPackage(packageName); | ||
- const packagePathPieces = pathname.split('node_modules/')[1].split('/'); // double split to handle node_modules within nested paths | ||
- // use node modules resolution logic first, else hope for the best from the root of the project | ||
- const absoluteNodeModulesPathname = absoluteNodeModulesLocation | ||
- ? `${absoluteNodeModulesLocation}${packagePathPieces.join('/').replace(packageName, '')}` | ||
- : (await resolveForRelativeUrl(url, projectDirectory)).pathname; | ||
+ const fromImportMap = pathname.startsWith(IMPORT_MAP_RESOLVED_PREFIX); | ||
+ const isNodeModulesPathnameShortcut = pathname.startsWith('/node_modules/'); | ||
+ const resolvedHref = fromImportMap | ||
+ ? pathname.replace(IMPORT_MAP_RESOLVED_PREFIX, 'file://') | ||
+ : isNodeModulesPathnameShortcut | ||
+ ? getResolvedHrefFromPathnameShortcut(pathname) | ||
+ : new URL(`.${pathname}`, projectDirectory).href; // worst case fall back, assume project root | ||
const params = searchParams.size > 0 | ||
? `?${searchParams.toString()}` | ||
: ''; | ||
|
||
- return new Request(`file://${absoluteNodeModulesPathname}${params}`); | ||
+ return new Request(`${resolvedHref}${params}`); | ||
} | ||
|
||
async shouldServe(url) { | ||
- const { href, pathname, protocol } = url; | ||
- const extension = pathname.split('.').pop(); | ||
- const existsAsJs = protocol === 'file:' && await checkResourceExists(new URL(`${href}.js`)); | ||
+ const { href, protocol } = url; | ||
|
||
- return extension === 'mjs' | ||
- || extension === '' && existsAsJs | ||
- || extension === 'js' && url.pathname.startsWith('/node_modules/'); | ||
+ return protocol === 'file:' && await checkResourceExists(new URL(href)); | ||
} | ||
|
||
async serve(url) { | ||
- const pathname = url.pathname; | ||
- const urlExtended = pathname.split('.').pop() === '' | ||
- ? new URL(`file://${pathname}.js`) | ||
- : url; | ||
- const body = await fs.readFile(urlExtended, 'utf-8'); | ||
+ const body = await fs.readFile(url, 'utf-8'); | ||
|
||
return new Response(body, { | ||
headers: new Headers({ |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.