rollup-plugin-dts
Advanced tools
Comparing version 1.0.0 to 1.1.0
@@ -0,1 +1,6 @@ | ||
### 1.0.0 2019-06-07 | ||
- Re-add support for directly using `.ts` files. | ||
- Fix type parameters with `extends` constraints. | ||
### 1.0.0 2019-06-05 | ||
@@ -2,0 +7,0 @@ |
@@ -6,2 +6,3 @@ 'use strict'; | ||
var ts = require('typescript'); | ||
var path = require('path'); | ||
@@ -63,2 +64,184 @@ function getCodeFrame() { | ||
class NamespaceFixer { | ||
constructor(sourceFile) { | ||
this.sourceFile = sourceFile; | ||
} | ||
findNamespaces() { | ||
const namespaces = []; | ||
const itemTypes = {}; | ||
for (const node of this.sourceFile.statements) { | ||
if (ts.isClassDeclaration(node)) { | ||
itemTypes[node.name.getText()] = "class"; | ||
} | ||
else if (ts.isFunctionDeclaration(node)) { | ||
itemTypes[node.name.getText()] = "function"; | ||
} | ||
else if (ts.isInterfaceDeclaration(node)) { | ||
itemTypes[node.name.getText()] = "interface"; | ||
} | ||
else if (ts.isModuleDeclaration(node)) { | ||
itemTypes[node.name.getText()] = "namespace"; | ||
} | ||
else if (ts.isEnumDeclaration(node)) { | ||
itemTypes[node.name.getText()] = "enum"; | ||
} | ||
if (!ts.isVariableStatement(node)) { | ||
continue; | ||
} | ||
const { declarations } = node.declarationList; | ||
if (declarations.length !== 1) { | ||
continue; | ||
} | ||
const decl = declarations[0]; | ||
const name = decl.name.getText(); | ||
if (!decl.initializer || !ts.isCallExpression(decl.initializer)) { | ||
itemTypes[name] = "var"; | ||
continue; | ||
} | ||
const obj = decl.initializer.arguments[0]; | ||
if (!decl.initializer.expression.getFullText().includes("/*#__PURE__*/Object.freeze") || | ||
!ts.isObjectLiteralExpression(obj)) { | ||
continue; | ||
} | ||
const exports = []; | ||
for (const prop of obj.properties) { | ||
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || !ts.isIdentifier(prop.initializer)) { | ||
throw new UnsupportedSyntaxError(prop, "Expected a property assignment"); | ||
} | ||
exports.push({ | ||
exportedName: prop.name.getText(), | ||
localName: prop.initializer.getText(), | ||
}); | ||
} | ||
// sort in reverse order, since we will do string manipulation | ||
namespaces.unshift({ | ||
name, | ||
exports, | ||
location: { | ||
start: node.getStart(), | ||
end: node.getEnd(), | ||
}, | ||
}); | ||
} | ||
return { namespaces, itemTypes }; | ||
} | ||
fix() { | ||
let code = this.sourceFile.getText(); | ||
const { namespaces, itemTypes } = this.findNamespaces(); | ||
for (const ns of namespaces) { | ||
const codeAfter = code.slice(ns.location.end); | ||
code = code.slice(0, ns.location.start); | ||
for (const { exportedName, localName } of ns.exports) { | ||
if (exportedName === localName) { | ||
const type = itemTypes[localName]; | ||
if (type === "interface") { | ||
// an interface is just a type | ||
code += `type ${ns.name}_${exportedName} = ${localName};\n`; | ||
} | ||
else if (type === "enum" || type === "class") { | ||
// enums and classes are both types and values | ||
code += `type ${ns.name}_${exportedName} = ${localName};\n`; | ||
code += `declare const ${ns.name}_${exportedName}: typeof ${localName};\n`; | ||
} | ||
else { | ||
// functions and vars are just values | ||
code += `declare const ${ns.name}_${exportedName}: typeof ${localName};\n`; | ||
} | ||
} | ||
} | ||
code += `declare namespace ${ns.name} {\n`; | ||
code += ` export {\n`; | ||
for (const { exportedName, localName } of ns.exports) { | ||
if (exportedName === localName) { | ||
code += ` ${ns.name}_${exportedName} as ${exportedName},\n`; | ||
} | ||
else { | ||
code += ` ${localName} as ${exportedName}\n`; | ||
} | ||
} | ||
code += ` };\n`; | ||
code += `}`; | ||
code += codeAfter; | ||
} | ||
return code; | ||
} | ||
} | ||
const dts = ".d.ts"; | ||
const formatHost = { | ||
getCurrentDirectory: () => ts.sys.getCurrentDirectory(), | ||
getNewLine: () => ts.sys.newLine, | ||
getCanonicalFileName: ts.sys.useCaseSensitiveFileNames ? f => f : f => f.toLowerCase(), | ||
}; | ||
const OPTIONS_OVERRIDE = { | ||
// Ensure ".d.ts" modules are generated | ||
declaration: true, | ||
// Skip ".js" generation | ||
emitDeclarationOnly: true, | ||
// Skip code generation when error occurs | ||
noEmitOnError: true, | ||
// Avoid extra work | ||
checkJs: false, | ||
declarationMap: false, | ||
skipLibCheck: true, | ||
// Ensure TS2742 errors are visible | ||
preserveSymlinks: true, | ||
}; | ||
function getCompilerOptions(input) { | ||
let dirName = path.dirname(input); | ||
const configPath = ts.findConfigFile(path.dirname(input), ts.sys.fileExists); | ||
if (!configPath) { | ||
return { dirName, compilerOptions: Object.assign({}, OPTIONS_OVERRIDE) }; | ||
} | ||
dirName = path.dirname(configPath); | ||
const { config, error } = ts.readConfigFile(configPath, ts.sys.readFile); | ||
if (error) { | ||
console.error(ts.formatDiagnostic(error, formatHost)); | ||
return { dirName, compilerOptions: Object.assign({}, OPTIONS_OVERRIDE) }; | ||
} | ||
const { options, errors } = ts.parseJsonConfigFileContent(config, ts.sys, dirName); | ||
if (errors.length) { | ||
console.error(ts.formatDiagnostics(errors, formatHost)); | ||
return { dirName, compilerOptions: Object.assign({}, OPTIONS_OVERRIDE) }; | ||
} | ||
return { | ||
dirName, | ||
compilerOptions: Object.assign({}, options, OPTIONS_OVERRIDE), | ||
}; | ||
} | ||
function createPrograms(input) { | ||
const programs = []; | ||
let inputs = []; | ||
let dirName = ""; | ||
let compilerOptions = {}; | ||
for (let main of input) { | ||
if (main.endsWith(dts)) { | ||
continue; | ||
} | ||
main = path.resolve(main); | ||
const options = getCompilerOptions(main); | ||
if (!inputs.length) { | ||
inputs.push(main); | ||
({ dirName, compilerOptions } = options); | ||
continue; | ||
} | ||
if (options.dirName === dirName) { | ||
inputs.push(main); | ||
} | ||
else { | ||
const host = ts.createCompilerHost(compilerOptions, true); | ||
const program = ts.createProgram(inputs, compilerOptions, host); | ||
programs.push(program); | ||
inputs = [main]; | ||
({ dirName, compilerOptions } = options); | ||
} | ||
} | ||
if (inputs.length) { | ||
const host = ts.createCompilerHost(compilerOptions, true); | ||
const program = ts.createProgram(inputs, compilerOptions, host); | ||
programs.push(program); | ||
} | ||
return programs; | ||
} | ||
const MARKER = "0"; | ||
@@ -321,5 +504,6 @@ let IDs = 1; | ||
for (const node of params) { | ||
this.convertTypeNode(node.constraint); | ||
this.convertTypeNode(node.default); | ||
this.pushScope(); | ||
this.pushTypeVariable(node.name); | ||
this.convertTypeNode(node.default); | ||
} | ||
@@ -788,111 +972,52 @@ return params.length; | ||
class NamespaceFixer { | ||
constructor(sourceFile) { | ||
this.sourceFile = sourceFile; | ||
const tsx = /\.tsx?$/; | ||
// Parse a TypeScript module into an ESTree program. | ||
function transformFile(input) { | ||
const transformer = new Transformer(input); | ||
const { ast, fixups } = transformer.transform(); | ||
// NOTE(swatinem): | ||
// hm, typescript generates `export default` without a declare, | ||
// but rollup moves the `export default` to a different place, which leaves | ||
// the function declaration without a `declare`. | ||
// Well luckily both words have the same length, haha :-D | ||
let code = input.getText(); | ||
code = code.replace(/(export\s+)default(\s+(function|class))/m, "$1declare$2"); | ||
for (const fixup of fixups) { | ||
code = code.slice(0, fixup.range.start) + fixup.identifier + code.slice(fixup.range.end); | ||
} | ||
findNamespaces() { | ||
const namespaces = []; | ||
const itemTypes = {}; | ||
for (const node of this.sourceFile.statements) { | ||
if (ts.isClassDeclaration(node)) { | ||
itemTypes[node.name.getText()] = "class"; | ||
return { code, ast }; | ||
} | ||
const plugin = () => { | ||
// There exists one Program object per entry point, | ||
// except when all entry points are ".d.ts" modules. | ||
let programs = []; | ||
function getModule(fileName) { | ||
let source; | ||
let program; | ||
if (programs.length) { | ||
// Rollup doesn't tell you the entry point of each module in the bundle, | ||
// so we need to ask every TypeScript program for the given filename. | ||
for (program of programs) { | ||
source = program.getSourceFile(fileName); | ||
if (source) | ||
break; | ||
} | ||
else if (ts.isFunctionDeclaration(node)) { | ||
itemTypes[node.name.getText()] = "function"; | ||
} | ||
else if (ts.isInterfaceDeclaration(node)) { | ||
itemTypes[node.name.getText()] = "interface"; | ||
} | ||
else if (ts.isModuleDeclaration(node)) { | ||
itemTypes[node.name.getText()] = "namespace"; | ||
} | ||
else if (ts.isEnumDeclaration(node)) { | ||
itemTypes[node.name.getText()] = "enum"; | ||
} | ||
if (!ts.isVariableStatement(node)) { | ||
continue; | ||
} | ||
const { declarations } = node.declarationList; | ||
if (declarations.length !== 1) { | ||
continue; | ||
} | ||
const decl = declarations[0]; | ||
const name = decl.name.getText(); | ||
if (!decl.initializer || !ts.isCallExpression(decl.initializer)) { | ||
itemTypes[name] = "var"; | ||
continue; | ||
} | ||
const obj = decl.initializer.arguments[0]; | ||
if (!decl.initializer.expression.getFullText().includes("/*#__PURE__*/Object.freeze") || | ||
!ts.isObjectLiteralExpression(obj)) { | ||
continue; | ||
} | ||
const exports = []; | ||
for (const prop of obj.properties) { | ||
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name) || !ts.isIdentifier(prop.initializer)) { | ||
throw new UnsupportedSyntaxError(prop, "Expected a property assignment"); | ||
} | ||
exports.push({ | ||
exportedName: prop.name.getText(), | ||
localName: prop.initializer.getText(), | ||
}); | ||
} | ||
// sort in reverse order, since we will do string manipulation | ||
namespaces.unshift({ | ||
name, | ||
exports, | ||
location: { | ||
start: node.getStart(), | ||
end: node.getEnd(), | ||
}, | ||
}); | ||
} | ||
return { namespaces, itemTypes }; | ||
} | ||
fix() { | ||
let code = this.sourceFile.getText(); | ||
const { namespaces, itemTypes } = this.findNamespaces(); | ||
for (const ns of namespaces) { | ||
const codeAfter = code.slice(ns.location.end); | ||
code = code.slice(0, ns.location.start); | ||
for (const { exportedName, localName } of ns.exports) { | ||
if (exportedName === localName) { | ||
const type = itemTypes[localName]; | ||
if (type === "interface") { | ||
// an interface is just a type | ||
code += `type ${ns.name}_${exportedName} = ${localName};\n`; | ||
} | ||
else if (type === "enum" || type === "class") { | ||
// enums and classes are both types and values | ||
code += `type ${ns.name}_${exportedName} = ${localName};\n`; | ||
code += `declare const ${ns.name}_${exportedName}: typeof ${localName};\n`; | ||
} | ||
else { | ||
// functions and vars are just values | ||
code += `declare const ${ns.name}_${exportedName}: typeof ${localName};\n`; | ||
} | ||
} | ||
} | ||
code += `declare namespace ${ns.name} {\n`; | ||
code += ` export {\n`; | ||
for (const { exportedName, localName } of ns.exports) { | ||
if (exportedName === localName) { | ||
code += ` ${ns.name}_${exportedName} as ${exportedName},\n`; | ||
} | ||
else { | ||
code += ` ${localName} as ${exportedName}\n`; | ||
} | ||
} | ||
code += ` };\n`; | ||
code += `}`; | ||
code += codeAfter; | ||
// Create any `ts.SourceFile` objects on-demand for ".d.ts" modules, | ||
// but only when there are zero ".ts" entry points. | ||
else if (fileName.endsWith(dts)) { | ||
const code = ts.sys.readFile(fileName, "utf8"); | ||
if (code) | ||
source = ts.createSourceFile(fileName, code, ts.ScriptTarget.Latest, true); | ||
} | ||
return code; | ||
return { source, program }; | ||
} | ||
} | ||
const plugin = () => { | ||
return { | ||
name: "dts", | ||
options(options) { | ||
let { input } = options; | ||
if (!Array.isArray(input)) { | ||
input = !input ? [] : typeof input === "string" ? [input] : Object.values(input); | ||
} | ||
programs = createPrograms(input); | ||
return Object.assign({}, options, { treeshake: { | ||
@@ -904,4 +1029,35 @@ moduleSideEffects: "no-external", | ||
outputOptions(options) { | ||
return Object.assign({}, options, { chunkFileNames: options.chunkFileNames || "[name]-[hash].d.ts", entryFileNames: options.entryFileNames || "[name].d.ts", format: "es", exports: "named", compact: false, freeze: true, interop: false, namespaceToStringTag: false, strict: false }); | ||
return Object.assign({}, options, { chunkFileNames: options.chunkFileNames || "[name]-[hash]" + dts, entryFileNames: options.entryFileNames || "[name]" + dts, format: "es", exports: "named", compact: false, freeze: true, interop: false, namespaceToStringTag: false, strict: false }); | ||
}, | ||
load(id) { | ||
if (!tsx.test(id)) { | ||
return null; | ||
} | ||
if (id.endsWith(dts)) { | ||
const { source } = getModule(id); | ||
return source ? transformFile(source) : null; | ||
} | ||
// Always try ".d.ts" before ".tsx?" | ||
const declarationId = id.replace(tsx, dts); | ||
let module = getModule(declarationId); | ||
if (module.source) { | ||
return transformFile(module.source); | ||
} | ||
// Generate in-memory ".d.ts" modules from ".tsx?" modules! | ||
module = getModule(id); | ||
if (!module.source || !module.program) { | ||
return null; | ||
} | ||
let generated; | ||
const { emitSkipped, diagnostics } = module.program.emit(module.source, (_, declarationText) => (generated = transformFile(ts.createSourceFile(declarationId, declarationText, ts.ScriptTarget.Latest, true))), undefined, // cancellationToken | ||
true); | ||
if (emitSkipped) { | ||
const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error); | ||
if (errors.length) { | ||
console.error(ts.formatDiagnostics(errors, formatHost)); | ||
this.error("Failed to compile. Check the logs above."); | ||
} | ||
} | ||
return generated; | ||
}, | ||
resolveId(source, importer) { | ||
@@ -918,32 +1074,6 @@ if (!importer) { | ||
// maybe its a good idea to introduce an option for this? | ||
if (resolvedModule.isExternalLibraryImport) { | ||
return { id: source, external: true }; | ||
} | ||
let id = resolvedModule.resolvedFileName; | ||
const { extension } = resolvedModule; | ||
if (extension !== ".d.ts") { | ||
// ts resolves `.ts`/`.tsx` files before `.d.ts` | ||
id = id.slice(0, id.length - extension.length) + ".d.ts"; | ||
} | ||
return { id }; | ||
return resolvedModule.isExternalLibraryImport | ||
? { id: source, external: true } | ||
: { id: resolvedModule.resolvedFileName }; | ||
}, | ||
transform(code, id) { | ||
if (!id.endsWith(".d.ts")) { | ||
this.error("`rollup-plugin-dts` can only deal with `.d.ts` files."); | ||
return; | ||
} | ||
const dtsSource = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true); | ||
const converter = new Transformer(dtsSource); | ||
const { ast, fixups } = converter.transform(); | ||
// NOTE(swatinem): | ||
// hm, typescript generates `export default` without a declare, | ||
// but rollup moves the `export default` to a different place, which leaves | ||
// the function declaration without a `declare`. | ||
// Well luckily both words have the same length, haha :-D | ||
code = code.replace(/(export\s+)default(\s+(function|class))/m, "$1declare$2"); | ||
for (const fixup of fixups) { | ||
code = code.slice(0, fixup.range.start) + fixup.identifier + code.slice(fixup.range.end); | ||
} | ||
return { code, ast }; | ||
}, | ||
renderChunk(code, chunk) { | ||
@@ -950,0 +1080,0 @@ const source = ts.createSourceFile(chunk.fileName, code, ts.ScriptTarget.Latest, true); |
{ | ||
"name": "rollup-plugin-dts", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "An experiment to generate .d.ts rollup files", | ||
@@ -70,3 +70,3 @@ "keywords": [ | ||
"react": "^16.8.6", | ||
"rollup": "^1.13.1", | ||
"rollup": "^1.14.3", | ||
"ts-jest": "^24.0.2", | ||
@@ -73,0 +73,0 @@ "typescript": "3.5.1" |
@@ -40,10 +40,2 @@ # rollup-plugin-dts | ||
## Prerequisites | ||
The plugin works by consuming pre-generated `.d.ts` files. So you will need to | ||
set up your `tsc` compiler or any other tool to output `.d.ts` files. | ||
You can do so by specifying either `declaration: true` | ||
or `emitDeclarationOnly: true` in your `tsconfig.json` file. Then point | ||
rollup to the output. | ||
## Why? | ||
@@ -50,0 +42,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
83179
2149
60