astro-ghostcms/.pnpm-store/v3/files/ab/175a149e1c157732d5b773d96a6...

273 lines
9.4 KiB
Plaintext
Raw Normal View History

2024-02-14 14:10:47 +00:00
import fs from 'node:fs/promises'
import fsSync from 'node:fs'
import path from 'node:path'
import {
isDepIncluded,
isDepExcluded,
isDepNoExternaled,
isDepExternaled
} from './sync.cjs'
/** @type {import('pnpapi')} */
let pnp
if (process.versions.pnp) {
try {
const { createRequire } = (await import('module')).default
pnp = createRequire(import.meta.url)('pnpapi')
} catch {}
}
export { isDepIncluded, isDepExcluded, isDepNoExternaled, isDepExternaled }
/** @type {import('..').crawlFrameworkPkgs} */
export async function crawlFrameworkPkgs(options) {
const pkgJsonPath = await findClosestPkgJsonPath(options.root)
if (!pkgJsonPath) {
// @ts-expect-error don't throw in deno as package.json is not required
if (typeof Deno !== 'undefined') {
return {
optimizeDeps: { include: [], exclude: [] },
ssr: { noExternal: [], external: [] }
}
} else {
throw new Error(`Cannot find package.json from ${options.root}`)
}
}
const pkgJson = await readJson(pkgJsonPath).catch((e) => {
throw new Error(`Unable to read ${pkgJsonPath}`, { cause: e })
})
/** @type {string[]} */
let optimizeDepsInclude = []
/** @type {string[]} */
let optimizeDepsExclude = []
/** @type {string[]} */
let ssrNoExternal = []
/** @type {string[]} */
let ssrExternal = []
await crawl(pkgJsonPath, pkgJson)
// respect vite user config
if (options.viteUserConfig) {
// remove includes that are explicitly excluded in optimizeDeps
const _optimizeDepsExclude = options.viteUserConfig?.optimizeDeps?.exclude
if (_optimizeDepsExclude) {
optimizeDepsInclude = optimizeDepsInclude.filter(
(dep) => !isDepExcluded(dep, _optimizeDepsExclude)
)
}
// remove excludes that are explicitly included in optimizeDeps
const _optimizeDepsInclude = options.viteUserConfig?.optimizeDeps?.include
if (_optimizeDepsInclude) {
optimizeDepsExclude = optimizeDepsExclude.filter(
(dep) => !isDepIncluded(dep, _optimizeDepsInclude)
)
}
// remove noExternals that are explicitly externalized
const _ssrExternal = options.viteUserConfig?.ssr?.external
if (_ssrExternal) {
ssrNoExternal = ssrNoExternal.filter(
(dep) => !isDepExternaled(dep, _ssrExternal)
)
}
// remove externals that are explicitly noExternal
const _ssrNoExternal = options.viteUserConfig?.ssr?.noExternal
if (_ssrNoExternal) {
ssrExternal = ssrExternal.filter(
(dep) => !isDepNoExternaled(dep, _ssrNoExternal)
)
}
}
return {
optimizeDeps: {
include: optimizeDepsInclude,
exclude: optimizeDepsExclude
},
ssr: {
noExternal: ssrNoExternal,
external: ssrExternal
}
}
/**
* crawl the package.json dependencies for framework packages. rules:
* 1. a framework package should be `optimizeDeps.exclude` and `ssr.noExternal`.
* 2. the deps of the framework package should be `optimizeDeps.include` and `ssr.external`
* unless the dep is also a framework package, in which case do no1 & no2 recursively.
* 3. any non-framework packages that aren't imported by a framework package can be skipped entirely.
* 4. a semi-framework package is like a framework package, except it isn't `optimizeDeps.exclude`,
* but only applies `ssr.noExternal`.
* @param {string} pkgJsonPath
* @param {Record<string, any>} pkgJson
* @param {string[]} [parentDepNames]
*/
async function crawl(pkgJsonPath, pkgJson, parentDepNames = []) {
const isRoot = parentDepNames.length === 0
/** @type {string[]} */
let deps = [
...Object.keys(pkgJson.dependencies || {}),
...(isRoot ? Object.keys(pkgJson.devDependencies || {}) : [])
]
deps = deps.filter((dep) => {
// skip circular deps
if (parentDepNames.includes(dep)) {
return false
}
const isFrameworkPkg = options.isFrameworkPkgByName?.(dep)
const isSemiFrameworkPkg = options.isSemiFrameworkPkgByName?.(dep)
if (isFrameworkPkg) {
// framework packages should be excluded from optimization as esbuild can't handle them.
// otherwise it'll cause https://github.com/vitejs/vite/issues/3910
optimizeDepsExclude.push(dep)
// framework packages should be noExternal so that they go through vite's transformation
// pipeline, since nodejs can't support them.
ssrNoExternal.push(dep)
} else if (isSemiFrameworkPkg) {
// semi-framework packages should do the same except for optimization exclude as they
// aren't needed to work (they don't contain raw framework components)
ssrNoExternal.push(dep)
}
// only those that are explictly false can skip crawling since we don't need to do anything
// special for them
if (isFrameworkPkg === false || isSemiFrameworkPkg === false) {
return false
}
// if `true`, we need to crawl the nested deps to deep include and ssr externalize them in dev.
// if `undefined`, it's the same as "i don't know". we need to crawl and find the package.json
// to find out.
else {
return true
}
})
const promises = deps.map(async (dep) => {
const depPkgJsonPath = await findDepPkgJsonPath(dep, pkgJsonPath)
if (!depPkgJsonPath) return
const depPkgJson = await readJson(depPkgJsonPath).catch(() => {})
if (!depPkgJson) return
// fast path if this dep is already a framework dep based on the filter condition above
const cachedIsFrameworkPkg = ssrNoExternal.includes(dep)
if (cachedIsFrameworkPkg) {
return crawl(depPkgJsonPath, depPkgJson, parentDepNames.concat(dep))
}
// check if this dep is a framework dep, if so, track and crawl it
const isFrameworkPkg = options.isFrameworkPkgByJson?.(depPkgJson)
const isSemiFrameworkPkg = options.isSemiFrameworkPkgByJson?.(depPkgJson)
if (isFrameworkPkg || isSemiFrameworkPkg) {
// see explanation in filter condition above
if (isFrameworkPkg) {
optimizeDepsExclude.push(dep)
ssrNoExternal.push(dep)
} else if (isSemiFrameworkPkg) {
ssrNoExternal.push(dep)
}
return crawl(depPkgJsonPath, depPkgJson, parentDepNames.concat(dep))
}
// if we're crawling in a non-root state, the parent is 100% a framework package
// because of the above if block. in this case, if it's dep of a non-framework
// package, handle special cases for them.
if (!isRoot) {
// deep include it if it's a CJS package, so it becomes ESM and vite is happy.
if (await pkgNeedsOptimization(depPkgJson, depPkgJsonPath)) {
optimizeDepsInclude.push(parentDepNames.concat(dep).join(' > '))
}
// also externalize it in dev so it doesn't trip vite's SSR transformation.
// we do in dev only as build cannot access deep external packages in strict
// dependency installations, such as pnpm.
if (!options.isBuild && !ssrExternal.includes(dep)) {
ssrExternal.push(dep)
}
}
})
await Promise.all(promises)
}
}
/** @type {import('..').findDepPkgJsonPath} */
export async function findDepPkgJsonPath(dep, parent) {
if (pnp) {
try {
const depRoot = pnp.resolveToUnqualified(dep, parent)
if (!depRoot) return undefined
return path.join(depRoot, 'package.json')
} catch {
return undefined
}
}
let root = parent
while (root) {
const pkg = path.join(root, 'node_modules', dep, 'package.json')
try {
await fs.access(pkg)
// use 'node:fs' version to match 'vite:resolve' and avoid realpath.native quirk
// https://github.com/sveltejs/vite-plugin-svelte/issues/525#issuecomment-1355551264
return fsSync.realpathSync(pkg)
} catch {}
const nextRoot = path.dirname(root)
if (nextRoot === root) break
root = nextRoot
}
return undefined
}
/** @type {import('..').findClosestPkgJsonPath} */
export async function findClosestPkgJsonPath(dir, predicate = undefined) {
if (dir.endsWith('package.json')) {
dir = path.dirname(dir)
}
while (dir) {
const pkg = path.join(dir, 'package.json')
try {
const stat = await fs.stat(pkg)
if (stat.isFile() && (!predicate || (await predicate(pkg)))) {
return pkg
}
} catch {}
const nextDir = path.dirname(dir)
if (nextDir === dir) break
dir = nextDir
}
return undefined
}
/** @type {import('..').pkgNeedsOptimization} */
export async function pkgNeedsOptimization(pkgJson, pkgJsonPath) {
// only optimize if is cjs, using the below as heuristic
// see https://github.com/sveltejs/vite-plugin-svelte/issues/162
if (pkgJson.module || pkgJson.exports) return false
// if have main, ensure entry is js so vite can prebundle it
// see https://github.com/sveltejs/vite-plugin-svelte/issues/233
if (pkgJson.main) {
const entryExt = path.extname(pkgJson.main)
return !entryExt || entryExt === '.js' || entryExt === '.cjs'
}
// check if has implicit index.js entrypoint to prebundle
// see https://github.com/sveltejs/vite-plugin-svelte/issues/281
// see https://github.com/solidjs/vite-plugin-solid/issues/70#issuecomment-1306488154
try {
await fs.access(path.join(path.dirname(pkgJsonPath), 'index.js'))
return true
} catch {
return false
}
}
/**
* @param {string} findDepPkgJsonPath
* @returns {Promise<Record<string, any>>}
*/
async function readJson(findDepPkgJsonPath) {
return JSON.parse(await fs.readFile(findDepPkgJsonPath, 'utf8'))
}