From 6ec64da48427c954c90771150c72d81922684c02 Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Mon, 3 Jun 2019 11:04:40 +0200 Subject: [PATCH] rip out all the `.ts` -> `.js` logic the plugin now only supports `.d.ts` files as inputs, #29 --- src/compiler.ts | 194 --------------------- src/index.ts | 103 +++++------ tests/testcases.test.ts | 21 +-- tests/testcases/react-components/meta.json | 5 - tests/testcases/remove-internal/meta.json | 7 - 5 files changed, 48 insertions(+), 282 deletions(-) delete mode 100644 src/compiler.ts delete mode 100644 tests/testcases/react-components/meta.json delete mode 100644 tests/testcases/remove-internal/meta.json diff --git a/src/compiler.ts b/src/compiler.ts deleted file mode 100644 index 3e076d9..0000000 --- a/src/compiler.ts +++ /dev/null @@ -1,194 +0,0 @@ -import * as ESTree from "estree"; -import * as ts from "typescript"; -import { Transformer } from "./Transformer"; -import fs from "fs"; -import path from "path"; - -let SOURCEMAPPING_URL = "sourceMa"; -SOURCEMAPPING_URL += "ppingURL"; - -const SOURCEMAPPING_URL_RE = new RegExp(`^//#\\s+${SOURCEMAPPING_URL}=.+\\n?`, "m"); - -export enum CompileMode { - Types = "dts", - Js = "js", -} - -interface CacheOptions { - tsconfig: string; - compilerOptions: ts.CompilerOptions; - mode: CompileMode; -} - -interface CacheEntry { - parsedCompilerOptions: ts.CompilerOptions; - compiler: ts.Program; -} - -const OPTIONS_OVERRIDES: ts.CompilerOptions = { - module: ts.ModuleKind.ES2015, - noEmitOnError: false, - noEmit: false, - skipLibCheck: true, - declaration: true, - allowJs: true, - checkJs: true, - resolveJsonModule: true, - sourceMap: true, - inlineSourceMap: false, - declarationMap: true, -}; - -const COMPILERCACHE = new Map(); - -function createCompiler(options: CacheOptions) { - const file = - !fs.existsSync(options.tsconfig) || fs.lstatSync(options.tsconfig).isFile() - ? path.basename(options.tsconfig) - : undefined; - const configFileName = ts.findConfigFile( - options.tsconfig, - ts.sys.fileExists, - path.extname(options.tsconfig) ? file : undefined, - ); - - // istanbul ignore if - if (!configFileName) { - throw new Error(`rollup-plugin-dts: Couldn't find tsconfig file`); - } - - const compilerOptions: ts.CompilerOptions = { - ...options.compilerOptions, - ...OPTIONS_OVERRIDES, - }; - - let diagnostic; - const configParseResult = ts.getParsedCommandLineOfConfigFile(configFileName, compilerOptions, { - ...ts.sys, - onUnRecoverableConfigFileDiagnostic(d) { - // istanbul ignore next - diagnostic = d; - }, - }); - // istanbul ignore if - if (!configParseResult) { - console.log(diagnostic); - throw new Error(`rollup-plugin-dts: Couldn't process compiler options`); - } - const { fileNames, options: parsedCompilerOptions } = configParseResult; - - return { - parsedCompilerOptions, - compiler: ts.createProgram(fileNames, parsedCompilerOptions), - }; -} - -interface EmitFiles { - dts: string; - dtsMap: string; - js: string; - jsMap: string; -} - -function getEmitFiles(compiler: ts.Program, fileName: string): EmitFiles { - const result: EmitFiles = { - dts: "", - dtsMap: `{"mappings": ""}`, - js: "", - jsMap: `{"mappings": ""}`, - }; - - if (fileName.endsWith(".d.ts")) { - result.dts = ts.sys.readFile(fileName, "utf-8")!; - return result; - } - const sourceFile = compiler.getSourceFile(fileName)!; - - const emitResult = compiler.emit(sourceFile, (fileName, data) => { - data = data.replace(SOURCEMAPPING_URL_RE, "").trim(); - if (fileName.endsWith(".d.ts")) { - result.dts = data; - } else if (fileName.endsWith(".js")) { - result.js = data; - // NOTE(swatinem): hm, there are still issues with this, - // I couldn’t get it to work locally - // See https://github.com/Microsoft/TypeScript/issues/25662 - // } else if (fileName.endsWith(".d.ts.map")) { - // result.dtsMap = data; - } else if (fileName.endsWith(".js.map")) { - result.jsMap = data; - } - }); - if (emitResult.emitSkipped) { - throw new Error(emitResult.diagnostics.join("\n")); - } - - return result; -} - -export function getCachedCompiler(options: CacheOptions) { - const cacheKey = JSON.stringify(options); - - function lazyCreate() { - let compiler = COMPILERCACHE.get(cacheKey); - if (!compiler) { - compiler = createCompiler(options); - COMPILERCACHE.set(cacheKey, compiler); - } - return compiler; - } - - return { - resolve(importee: string, importer: string): string | void { - const { parsedCompilerOptions } = lazyCreate(); - - const result = ts.nodeModuleNameResolver(importee, importer, parsedCompilerOptions, ts.sys); - // hm, maybe we should use `isExternalLibraryImport` at some point… - // istanbul ignore else - if (result.resolvedModule && result.resolvedModule.resolvedFileName) { - return result.resolvedModule.resolvedFileName; - } else { - return; - } - }, - load(fileName: string): { code: string; map: string; ast?: ESTree.Program } { - const { compiler } = lazyCreate(); - - const emitFiles = getEmitFiles(compiler, fileName); - - if (options.mode === CompileMode.Js) { - return { - code: emitFiles.js, - map: emitFiles.jsMap, - }; - } - - let code = emitFiles.dts; - - let dtsFileName = fileName; - if (!fileName.endsWith(".d.ts")) { - dtsFileName = fileName.slice(0, -path.extname(fileName).length) + ".d.ts"; - } - const dtsSource = ts.createSourceFile(dtsFileName, 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, - map: emitFiles.dtsMap, - }; - }, - }; -} diff --git a/src/index.ts b/src/index.ts index 026bac5..dd39f5b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,43 +1,12 @@ import * as ts from "typescript"; import { PluginImpl } from "rollup"; -import { getCachedCompiler, CompileMode } from "./compiler"; -import { version } from "../package.json"; - -export interface Options { - tsconfig?: string; - compilerOptions?: ts.CompilerOptions; - compileMode?: CompileMode; - banner?: boolean; -} - -const TSLIB_ID = "\0tslib"; - -const BANNER = - ` -// FILE GENERATED BY \`rollup-plugin-dts@${version}\` -// https://github.com/Swatinem/rollup-plugin-dts -`.trim() + "\n"; - -const plugin: PluginImpl = (options = {}) => { - const tslibFileName = require.resolve("tslib").replace("tslib.js", "tslib.es6.js"); - const tslib = ts.sys.readFile(tslibFileName, "utf-8") || null; - - const mode = options.compileMode || CompileMode.Types; - const compiler = getCachedCompiler({ - tsconfig: options.tsconfig || process.cwd(), - compilerOptions: options.compilerOptions || {}, - mode, - }); +import { Transformer } from "./Transformer"; +const plugin: PluginImpl<{}> = () => { return { name: "dts", - banner: mode === CompileMode.Types && options.banner !== false ? BANNER : undefined, - options(options) { - if (mode === CompileMode.Js) { - return options; - } return { ...options, treeshake: { @@ -48,9 +17,6 @@ const plugin: PluginImpl = (options = {}) => { }, outputOptions(options) { - if (mode === CompileMode.Js) { - return options; - } return { ...options, chunkFileNames: options.chunkFileNames || "[name]-[hash].d.ts", @@ -65,26 +31,53 @@ const plugin: PluginImpl = (options = {}) => { }; }, - resolveId(importee, importer) { - // istanbul ignore if - if (importee === "tslib") { - return TSLIB_ID; - } + resolveId(source, importer) { if (!importer) { return; } - importer = importer.split("\\").join("/"); - return compiler.resolve(importee, importer); + + // resolve this via typescript + const { resolvedModule } = ts.nodeModuleNameResolver(source, importer, {}, ts.sys); + if (!resolvedModule) { + return; + } + + // here, we define everything that comes from `node_modules` as `external`. + // 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 }; }, - async load(id) { - if (id === TSLIB_ID) { - return tslib; + transform(code, id) { + if (!id.endsWith(".d.ts")) { + this.error("`rollup-plugin-dts` can only deal with `.d.ts` files."); + return; } - if (id.endsWith(".ts") || id.endsWith(".tsx")) { - return compiler.load(id); + + 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 null; + + return { code, ast }; }, // TODO: figure out if we could use this to "fix" namespace-re-exports @@ -97,16 +90,4 @@ const plugin: PluginImpl = (options = {}) => { }; }; -const dts: PluginImpl = (options = {}) => { - options.compileMode = options.compileMode || CompileMode.Types; - return plugin(options); -}; - -const js: PluginImpl = (options = {}) => { - options.compileMode = options.compileMode || CompileMode.Js; - return plugin(options); -}; - -export { CompileMode, plugin, dts, js, js as ts }; - export default plugin; diff --git a/tests/testcases.test.ts b/tests/testcases.test.ts index 61ee154..68bf898 100644 --- a/tests/testcases.test.ts +++ b/tests/testcases.test.ts @@ -1,5 +1,5 @@ import { rollup, InputOption, RollupOptions, InputOptions } from "rollup"; -import { dts, Options } from "../src"; +import dts from "../src"; import fsExtra from "fs-extra"; import path from "path"; @@ -7,15 +7,14 @@ const TESTCASES = path.join(__dirname, "testcases"); interface Meta { rollupOptions: RollupOptions; - pluginOptions: Options; skip: boolean; expectedError?: string; } -async function createBundle(rollupOptions: InputOptions, pluginOptions: Options) { +async function createBundle(rollupOptions: InputOptions) { const bundle = await rollup({ ...rollupOptions, - plugins: [dts({ ...pluginOptions, banner: false })], + plugins: [dts()], onwarn() {}, }); return bundle.generate({ @@ -49,12 +48,9 @@ export function clean(code: string = "") { } async function assertTestcase(dir: string, meta: Meta) { - const { expectedError, pluginOptions, rollupOptions } = meta; - if (pluginOptions.tsconfig && !path.isAbsolute(pluginOptions.tsconfig)) { - pluginOptions.tsconfig = path.join(dir, pluginOptions.tsconfig); - } + const { expectedError, rollupOptions } = meta; - const creator = createBundle({ ...rollupOptions, input: withInput(dir, rollupOptions) }, pluginOptions); + const creator = createBundle({ ...rollupOptions, input: withInput(dir, rollupOptions) }); if (expectedError) { await expect(creator).rejects.toThrow(expectedError); return; @@ -85,7 +81,7 @@ async function assertTestcase(dir: string, meta: Meta) { if (hasExpected && !hasMultipleOutputs) { const { output: [sanityCheck], - } = await createBundle({ ...rollupOptions, input: expectedDts }, pluginOptions); + } = await createBundle({ ...rollupOptions, input: expectedDts }); // typescript `.d.ts` output compresses whitespace, so make sure we ignore that expect(clean(sanityCheck.code)).toEqual(expectedCode); } @@ -96,20 +92,15 @@ describe("rollup-plugin-dts", () => { for (const name of dirs) { const dir = path.join(TESTCASES, name); if (fsExtra.statSync(dir).isDirectory()) { - const pluginOptions: Options = { - tsconfig: path.join(TESTCASES, "tsconfig.json"), - }; const rollupOptions: InputOptions = { input: "index.d.ts", }; const meta: Meta = { skip: false, - pluginOptions, rollupOptions, }; try { Object.assign(meta, require(path.join(dir, "meta"))); - meta.pluginOptions = Object.assign(pluginOptions, meta.pluginOptions); meta.rollupOptions = Object.assign(rollupOptions, meta.rollupOptions); } catch {} diff --git a/tests/testcases/react-components/meta.json b/tests/testcases/react-components/meta.json deleted file mode 100644 index a71fc49..0000000 --- a/tests/testcases/react-components/meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rollupOptions": { - "external": ["react"] - } -} diff --git a/tests/testcases/remove-internal/meta.json b/tests/testcases/remove-internal/meta.json deleted file mode 100644 index d1cf052..0000000 --- a/tests/testcases/remove-internal/meta.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "pluginOptions": { - "compilerOptions": { - "stripInternal": true - } - } -}