UNPKG

@growing-web/esm-pack-core

Version:

esm pack build core.

196 lines (178 loc) 5.93 kB
import type { Plugin } from 'rollup' import fs from 'fs' import path from 'path' import execa from 'execa' import isValidIdentifier from 'is-valid-identifier' import resolve from 'resolve' import { APP_NAME } from '../constants' // Use CJS intentionally here! ESM interface is async but CJS is sync, and this file is sync const { parse } = require('cjs-module-lexer') function createTransformTarget(specifier: string, all = true) { return { specifier, all, default: false, namespace: false, named: [], } } function normalizePath(p: string) { return p.split(path.win32.sep).join(path.posix.sep) } function isJavaScript(pathname: string): boolean { const ext = path.extname(pathname).toLowerCase() return ext === '.js' || ext === '.mjs' || ext === '.cjs' } function isRemoteUrl(val: string): boolean { return /\w+:\/\//.test(val) || val.startsWith('//') } function isTruthy<T>(item: T | false | null | undefined): item is T { return Boolean(item) } function isValidNamedExport(name: string): boolean { return name !== 'default' && name !== '__esModule' && isValidIdentifier(name) } export function rollupPluginWrapTargets( isTreeshake = false, target?: string, ): Plugin | null { if (!target) { return null } const installTargetSummaries: { [loc: string]: any } = {} const cjsScannedNamedExports = new Map<string, string[]>() const transformTarget = createTransformTarget(target) function cjsAutoDetectExportsStatic( filename: string, visited = new Set(), ): string[] | undefined { const isMainEntrypoint = visited.size === 0 // Prevent infinite loops via circular dependencies. if (visited.has(filename)) { return [] } else { visited.add(filename) } const fileContents = fs.readFileSync(filename, 'utf8') try { // Attempt 1 - CJS: Run cjs-module-lexer to statically analyze exports. const { exports, reexports } = parse(fileContents) // If re-exports were detected (`exports.foo = require(...)`) then resolve them here. let resolvedReexports: string[] = [] if (reexports.length > 0) { resolvedReexports = ([] as string[]).concat.apply( [], reexports .map((e) => cjsAutoDetectExportsStatic( resolve.sync(e, { basedir: path.dirname(filename) }), visited, ), ) .filter(isTruthy), ) } // If nothing was detected, return undefined. // Otherwise, resolve and flatten all exports into a single array, remove invalid exports. const resolvedExports = Array.from( new Set([...exports, ...resolvedReexports]), ).filter(isValidNamedExport) return isMainEntrypoint && resolvedExports.length === 0 ? undefined : resolvedExports } catch (err: any) { return undefined } } function cjsAutoDetectExportsRuntimeTrusted( normalizedFileName: string, ): string[] | undefined { try { const { stdout } = execa.sync( `node`, ['-p', `JSON.stringify(Object.keys(require('${normalizedFileName}')))`], { cwd: __dirname, extendEnv: false, }, ) return JSON.parse(stdout).filter(isValidNamedExport) } catch (err: any) { return undefined } } function cjsAutoDetectExportsRuntimeUntrusted(): string[] | undefined { try { return exports.filter((identifier) => isValidIdentifier(identifier)) } catch (err: any) { return undefined } } return { name: 'rollup-plugin-wrap-exports', // Mark some inputs for tree-shaking. buildStart(inputOptions) { const input = inputOptions.input as { [entryAlias: string]: string } for (const [key, val] of Object.entries(input)) { if (isRemoteUrl(val)) { continue } if (!isJavaScript(val)) { continue } installTargetSummaries[val] = transformTarget const normalizedFileLoc = normalizePath(val) const cjsExports = // If we can trust the static analyzer, run that first. cjsAutoDetectExportsStatic(val) || // Otherwise, run our more powerful, runtime analysis. // Attempted trusted first (won't run in untrusted environments). cjsAutoDetectExportsRuntimeTrusted(normalizedFileLoc) || cjsAutoDetectExportsRuntimeUntrusted() if (cjsExports && cjsExports.length > 0) { cjsScannedNamedExports.set(normalizedFileLoc, cjsExports) input[key] = `${APP_NAME}:${val}` } } }, resolveId(id) { if (id.startsWith(`${APP_NAME}:`)) { return id } return null }, load(id) { if (!id.startsWith(`${APP_NAME}:`)) { return null } const fileLoc = id.substring(`${APP_NAME}:`.length) const installTargetSummary = installTargetSummaries[fileLoc] let uniqueNamedExports = Array.from(new Set(installTargetSummary.named)) const normalizedFileLoc = normalizePath(fileLoc) const scannedNamedExports = cjsScannedNamedExports.get(normalizedFileLoc) if ( scannedNamedExports && (!isTreeshake || installTargetSummary.namespace) ) { uniqueNamedExports = scannedNamedExports || [] installTargetSummary.default = true } const result = ` ${ installTargetSummary.namespace ? `export * from '${normalizedFileLoc}';` : '' } ${ installTargetSummary.default ? `import _web_default_export_for_treeshaking__ from '${normalizedFileLoc}'; export default _web_default_export_for_treeshaking__;` : '' } ${`export {${uniqueNamedExports.join( ',', )}} from '${normalizedFileLoc}';`} ` return result }, } }