Description
Suggestion
im looking for a { importsNotUsedAsValues: 'preserve-values' }
compiler-option, where
1. all type-imports are removed, including "empty imports" like import "./some"
and import "./where.d.ts"
2. unused imports are preserved, including maybe-type-imports
this would make it much easier to compile code-fragments in isolation
where ts has no access to other files, and no access to the fragment's context
1. type-imports should be fully removed
import { SomeType } from "./types"
var someVar: SomeType; // here ts can see that SomeType is a type
// ... is currently transpiled to ...
import "./types";
var someVar;
2. unused imports should be preserved
import { NotUsed } from "./mixed"
// NotUsed is not used anywhere, so its value or type
// ... is currently transpiled to ...
import "./mixed";
to support the infamous export { SomeThing } from './some-where'
case
where ts.transpileModule cannot tell whether SomeThing
is a type or value:
use a fault-tolerant module loader, like
// note: top level await only works in modules
var importedModule = await (async () => {
try { return await import('./some-module.js'); }
catch (error) { console.warn(error.message); }
return {};
})();
this is needed for development mode, where tree-shaking is disabled
so imports from type files would give fatal errors
deprecated: transpile type exports to "dummy exports"
edit: this is deprecated, since it breaks with external modules
external modules are not compiled in the current build pipeline
so there is no way we can generate "dummy exports" for all maybe-type-imports
in case SomeThing
is a type
ts.transpileModule would have to generate "dummy exports"
like export const SomeThing = null;
in some-where.js
... to avoid the runtime error SomeThing is not exported by some-where
or no such file: some-where
export type SomeType = number;
// ... would be transpiled to ...
export const SomeType = null;
adding such dummy exports should be safe
since collisions between value IDs and type IDs are not possible in ts
and IDs are (usually) scoped to modules
this solution is "clean", cos the added dead-code is removed later in the build pipeline (tree-shaking in production mode)
compiler call (playground on codesandbox)
var result = ts.transpileModule(source, {
fileName: "test.ts",
compilerOptions: {
target: "es2015",
importsNotUsedAsValues: "preserve-values", // new
}
});
🔍 Search Terms
single file compilation
ts.transpileModule
remove only type imports
keep unused value imports
generate dummy exports for types
✅ Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
- yes: the "dummy exports" would be removed later in the build pipeline (dead code elimination, tree shaking)
- yes: goal 4. Emit clean, idiomatic, recognizable JavaScript code.
- yes: goal 7. Preserve runtime behavior of all JavaScript code.
- yes: goal 9. Use a consistent, fully erasable, structural type system.
- yes: non-goal 4. Provide an end-to-end build pipeline. Instead, make the system extensible so that external tools can use the compiler for more complex build workflows.
- yes: the "dummy exports" would be removed later in the build pipeline (dead code elimination, tree shaking)
⭐ Suggestion
📃 Motivating Example
💻 Use Cases
references
single file transpile: sveltejs/svelte-preprocess#318 (comment)
dummy exports: https://stackoverflow.com/a/52260432/10440128
api docs: https://www.typescriptlang.org/tsconfig#importsNotUsedAsValues