Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions .rollup/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,137 @@ import commonjs from '@rollup/plugin-commonjs'
import nodeResolve from '@rollup/plugin-node-resolve'
import replace from '@rollup/plugin-replace'
import fs from 'fs'
import path from 'path'

// Plugin to add .js extensions to imports in .d.ts files for ESM compatibility
function fixEsmTypeImports() {
return {
name: 'fix-esm-type-imports',
writeBundle(options) {
const outDir = options.dir
if (!outDir || !outDir.includes('esm')) return

const fixImportsInFile = (filePath) => {
let content = fs.readFileSync(filePath, 'utf8')

// Replace relative imports in 'from' statements: from './file' or from "./file"
content = content.replace(
/from\s+(['"])(\.\.[/\\].*?|\.\/.*?)\1/g,
(match, quote, importPath) => {
// Don't add extension if it already has one
if (/\.[a-z]+$/.test(importPath)) {
return match
}
return `from ${quote}${importPath}.js${quote}`
}
)

// Replace relative imports in inline import() statements: import("./file")
content = content.replace(
/import\((['"])(\.\.[/\\].*?|\.\/.*?)\1\)/g,
(match, quote, importPath) => {
// Don't add extension if it already has one
if (/\.[a-z]+$/.test(importPath)) {
return match
}
return `import(${quote}${importPath}.js${quote})`
}
)

fs.writeFileSync(filePath, content, 'utf8')
}

const processDirectory = (dirPath) => {
const entries = fs.readdirSync(dirPath, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name)
if (entry.isDirectory()) {
processDirectory(fullPath)
} else if (entry.name.endsWith('.d.ts')) {
fixImportsInFile(fullPath)
}
}
}

processDirectory(outDir)
}
}
}

// Plugin to convert 'export default' to 'export =' in CJS .d.ts files for proper typing
function fixCjsDefaultExport() {
return {
name: 'fix-cjs-default-export',
writeBundle(options) {
const outDir = options.dir
if (!outDir || !outDir.includes('cjs')) return

const fixExportInFile = (filePath) => {
let content = fs.readFileSync(filePath, 'utf8')

// Check if there are any other exports (named exports, export interface, etc.)
const hasOtherExports = /^export (?!default)/m.test(content)

if (hasOtherExports) {
// Don't convert to 'export =' if there are other exports
// Just leave it as 'export default' for compatibility
return
}

// Handle 'export default class/function' declarations
// These need to be converted to 'declare class/function' + 'export ='
content = content.replace(
/^export default (class|function)\s+(\w+)/gm,
(match, keyword, name) => {
return `declare ${keyword} ${name}`
}
)

// Now add 'export =' for any class/function that was a default export
// Look for 'declare class X' or 'declare function X' and add export after the closing brace/declaration
const classOrFunctionPattern = /^declare (class|function)\s+(\w+)[\s\S]*?^}/gm
const matches = [...content.matchAll(classOrFunctionPattern)]

// Process matches in reverse order to maintain correct positions
for (let i = matches.length - 1; i >= 0; i--) {
const match = matches[i]
const name = match[2]
const endIndex = match.index + match[0].length

// Check if this class/function doesn't already have an export
const afterDeclaration = content.substring(endIndex, endIndex + 100)
if (!afterDeclaration.match(/^\s*export\s*=/)) {
content = content.substring(0, endIndex) +
'\nexport = ' + name + ';' +
content.substring(endIndex)
}
}

// For simple default exports (like 'export default identifier'), convert directly
content = content.replace(
/^export default (?!class|function)(\w+);?$/gm,
'export = $1;'
)

fs.writeFileSync(filePath, content, 'utf8')
}

const processDirectory = (dirPath) => {
const entries = fs.readdirSync(dirPath, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name)
if (entry.isDirectory()) {
processDirectory(fullPath)
} else if (entry.name.endsWith('.d.ts')) {
fixExportInFile(fullPath)
}
}
}

processDirectory(outDir)
}
}
}

const defaultOptions = () => ({
// additional variables to define with '@rollup/plugin-replace'
Expand Down Expand Up @@ -52,6 +183,17 @@ function createRollupConfig (options = defaultOptions()) {
outDir: 'dist/esm',
noEmitOnError: true
}),
fixEsmTypeImports(),
{
name: 'emit-esm-package-json',
generateBundle() {
this.emitFile({
type: 'asset',
fileName: 'package.json',
source: JSON.stringify({ type: 'module' }, null, 2)
});
}
},
...(options.plugins ?? [])
]
},
Expand Down Expand Up @@ -89,6 +231,7 @@ function createRollupConfig (options = defaultOptions()) {
outDir: 'dist/cjs',
noEmitOnError: true
}),
fixCjsDefaultExport(),
{
name: 'emit-cjs-package-json',
generateBundle() {
Expand Down
Loading
Loading