diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a4223a1..9af8c0dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [3.9.3](https://github.com/kaisermann/svelte-preprocess/compare/v3.9.2...v3.9.3) (2020-06-06) + + + ## [3.9.2](https://github.com/kaisermann/svelte-preprocess/compare/v3.7.4...v3.9.2) (2020-06-06) diff --git a/README.md b/README.md index ef6d00c3..4415a6e7 100644 --- a/README.md +++ b/README.md @@ -659,3 +659,5 @@ import preprocess from 'svelte-preprocess' } ... ``` + +Warning: If you do this, you can't import types or interfaces into your svelte component without using the new TS 3.8 `type` import modifier: `import type { SomeInterface } from './MyModule.ts'` otherwise Rollup (and possibly others) will complain that the name is not exported by `MyModule`) \ No newline at end of file diff --git a/package.json b/package.json index 0d6330d8..60689f16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte-preprocess", - "version": "3.9.2", + "version": "3.9.3", "license": "MIT", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/transformers/typescript.ts b/src/transformers/typescript.ts index 999baa65..51f82495 100644 --- a/src/transformers/typescript.ts +++ b/src/transformers/typescript.ts @@ -74,6 +74,10 @@ function isValidSvelteImportDiagnostic(filename: string, diagnostic: any) { const importTransformer: ts.TransformerFactory = (context) => { const visit: ts.Visitor = (node) => { if (ts.isImportDeclaration(node)) { + if (node.importClause?.isTypeOnly) { + return ts.createEmptyStatement(); + } + return ts.createImportDeclaration( node.decorators, node.modifiers, @@ -112,6 +116,89 @@ function isValidSvelteReactiveValueDiagnostic( return !(usedVar && proposedVar && usedVar === proposedVar); } +function createImportTransformerFromProgram(program: ts.Program) { + const checker = program.getTypeChecker(); + + const importedTypeRemoverTransformer: ts.TransformerFactory = ( + context, + ) => { + const visit: ts.Visitor = (node) => { + if (!ts.isImportDeclaration(node)) { + return ts.visitEachChild(node, (child) => visit(child), context); + } + + let newImportClause: ts.ImportClause = node.importClause; + + if (node.importClause) { + // import type {...} from './blah' + if (node.importClause?.isTypeOnly) { + return ts.createEmptyStatement(); + } + + // import Blah, { blah } from './blah' + newImportClause = ts.getMutableClone(node.importClause); + + // types can't be default exports, so we just worry about { blah } and { blah as name } exports + if ( + newImportClause.namedBindings && + ts.isNamedImports(newImportClause.namedBindings) + ) { + const newBindings = ts.getMutableClone(newImportClause.namedBindings); + const newElements = []; + + newImportClause.namedBindings = undefined; + + for (const spec of newBindings.elements) { + const ident = spec.name; + + const symbol = checker.getSymbolAtLocation(ident); + const aliased = checker.getAliasedSymbol(symbol); + + if (aliased) { + if ( + (aliased.flags & + (ts.SymbolFlags.TypeAlias | ts.SymbolFlags.Interface)) > + 0 + ) { + // We found an imported type, don't add to our new import clause + continue; + } + } + newElements.push(spec); + } + + if (newElements.length > 0) { + newBindings.elements = ts.createNodeArray( + newElements, + newBindings.elements.hasTrailingComma, + ); + newImportClause.namedBindings = newBindings; + } + } + + // we ended up removing all named bindings and we didn't have a name? nothing left to import. + if ( + newImportClause.namedBindings == null && + newImportClause.name == null + ) { + return ts.createEmptyStatement(); + } + } + + return ts.createImportDeclaration( + node.decorators, + node.modifiers, + newImportClause, + node.moduleSpecifier, + ); + }; + + return (node) => ts.visitNode(node, visit); + }; + + return importedTypeRemoverTransformer; +} + function compileFileFromMemory( compilerOptions: CompilerOptions, { filename, content }: { filename: string; content: string }, @@ -172,12 +259,17 @@ function compileFileFromMemory( }; const program = ts.createProgram([dummyFileName], compilerOptions, host); + + const transformers = { + before: [createImportTransformerFromProgram(program)], + }; + const emitResult = program.emit( + program.getSourceFile(dummyFileName), undefined, undefined, undefined, - undefined, - TS_TRANSFORMERS, + transformers, ); // collect diagnostics without svelte import errors diff --git a/test/fixtures/types.ts b/test/fixtures/types.ts new file mode 100644 index 00000000..c25ea1d9 --- /dev/null +++ b/test/fixtures/types.ts @@ -0,0 +1,7 @@ +export type AType = "test1" | "test2" +export interface AInterface { + test: string +} +export const AValue: string = "test" + +export default "String" \ No newline at end of file diff --git a/test/transformers/typescript.test.ts b/test/transformers/typescript.test.ts index 36e3eca7..e9686184 100644 --- a/test/transformers/typescript.test.ts +++ b/test/transformers/typescript.test.ts @@ -175,5 +175,49 @@ describe('transformer - typescript', () => { expect(diagnostics.some((d) => d.code === 2552)).toBe(true); }); + + it('should remove imports containing types only', async () => { + const { code } = await transpile( + ` + import { AType, AInterface } from './fixtures/types' + let name: AType = "test1"; + `, + ); + + expect(code).not.toContain('/fixtures/types'); + }); + + it('should remove type only imports', async () => { + const { code } = await transpile( + ` + import type { AType, AInterface } from './fixtures/types' + let name: AType = "test1"; + `, + ); + + expect(code).not.toContain('/fixtures/types'); + }); + + it('should remove only the types from the imports', async () => { + const { code } = await transpile( + ` + import { AValue, AType, AInterface } from './fixtures/types' + let name: AType = "test1"; + `, + ); + + expect(code).toContain("import { AValue } from './fixtures/types'"); + }); + + it('should remove the named imports completely if they were all types', async () => { + const { code } = await transpile( + ` + import Default, { AType, AInterface } from './fixtures/types' + let name: AType = "test1"; + `, + ); + + expect(code).toContain("import Default from './fixtures/types'"); + }); }); });