UNPKG

@plugjs/plug

Version:
110 lines 5.57 kB
import { EOL } from 'node:os'; import { sep } from 'node:path'; import { assert } from "../asserts.js"; import { Files } from "../files.js"; import { readFile, writeFile } from "../fs.js"; import { $p } from "../logging.js"; import { assertRelativeChildPath, getAbsoluteParent } from "../paths.js"; import { install } from "../pipe.js"; install('exports', class Exports { _packageJson; _outputPackageJson; _cjsExtension; _esmExtension; constructor(...args) { const options = args[0] || {}; const { packageJson = 'package.json', outputPackageJson = packageJson, cjsExtension, esmExtension, } = options; this._packageJson = packageJson; this._outputPackageJson = outputPackageJson; this._cjsExtension = cjsExtension; this._esmExtension = esmExtension; } async pipe(files, context) { // read up our package.json, we need it to figure out the default `type` const incomingFile = context.resolve(this._packageJson); const incomingData = await readFile(incomingFile, 'utf8'); const packageData = JSON.parse(incomingData); // exports must be relative to the _output_ package.json const outgoingFile = context.resolve(this._outputPackageJson); const outgoingDirectory = getAbsoluteParent(outgoingFile); // type here determines the extension of commonjs or ecmascript modules const type = packageData.type === 'module' ? 'module' : packageData.type === 'commonjs' ? 'commonjs' : packageData.type == null ? 'commonjs' : undefined; assert(type, `Unknown module type "${packageData.type}" in ${$p(incomingFile)}`); context.log.debug(`Package file ${$p(incomingFile)} declares module type "${type}"`); const cjsExtension = this._cjsExtension || (type === 'commonjs' ? '.js' : '.cjs'); const esmExtension = this._esmExtension || (type === 'module' ? '.js' : '.mjs'); // reject when commonjs and ecmascript modules have the same extension assert(cjsExtension !== esmExtension, `CommonJS and EcmaScript modules both resolve to same extension "${cjsExtension}"`); const exports = {}; function addExport(name, type, file) { if (!exports[name]) exports[name] = {}; // if (! exports[name]![type]) exports[name]![type] = {} exports[name][type] = file; } // all extensions to match in the incoming files const exts = ['.d.ts', '.d.cts', '.d.mts', cjsExtension, esmExtension]; // look up all the files we were piped in for (const [name, absolute] of files.pathMappings()) { const relative = assertRelativeChildPath(outgoingDirectory, absolute); for (const ext of exts) { if (!relative.endsWith(ext)) continue; const base = `.${sep}${name.slice(0, -ext.length)}`; const exp = base.endsWith(`${sep}index`) ? base.slice(0, -6) : base; switch (ext) { case cjsExtension: addExport(exp, type === 'commonjs' ? 'default' : 'require', `.${sep}${relative}`); break; case esmExtension: addExport(exp, type === 'module' ? 'default' : 'import', `.${sep}${relative}`); break; case '.d.cts': addExport(exp, 'types', `.${sep}${relative}`); break; case '.d.mts': addExport(exp, 'types', `.${sep}${relative}`); break; case '.d.ts': addExport(exp, 'types', `.${sep}${relative}`); break; } } } // if we have a "." export, inject the "main", "module" and "types" fields if ('.' in exports) { const rootExport = exports['.']; packageData['module'] = undefined; // wipe existing... packageData['main'] = type === 'commonjs' ? rootExport?.require || rootExport?.default : type === 'module' ? rootExport?.import || rootExport?.default : undefined; packageData['types'] = rootExport?.types; } // correctly order the exports record (e.g. types comes before default) packageData['exports'] = Object.keys(exports).sort().reduce((obj, name) => { const current = exports[name]; if (!current) return obj; // json serialization will scrub all undefined... here we export the types // only if the "default" export is available, or we scrub the whole thing! obj[name] = { types: current.types, require: current.require, import: current.import, default: current.default, }; return obj; }, {}); // convert back our package data into a json and write it const outgoingData = JSON.stringify(packageData, null, 2); context.log.info(`Writing new ${$p(outgoingFile)}`, outgoingData); await writeFile(outgoingFile, outgoingData + EOL, 'utf8'); // return a `Files` instance with our `package.json` in there return Files.builder(getAbsoluteParent(outgoingFile)).add(outgoingFile).build(); } }); //# sourceMappingURL=exports.js.map