From 5fff58f4e84e496eab276d452a017bd63e65406a Mon Sep 17 00:00:00 2001 From: halfnelson Date: Fri, 5 Jun 2020 23:50:27 +1000 Subject: [PATCH 1/3] fix: use the typescript transform to remove type imports should fix https://github.com/sveltejs/svelte-preprocess/issues/153 use first param of emit to only emit for the actual source file --- README.md | 2 + src/transformers/typescript.ts | 76 +++++++++++++++++++++++++++- test/fixtures/types.ts | 7 +++ test/transformers/typescript.test.ts | 44 ++++++++++++++++ 4 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/types.ts 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/src/transformers/typescript.ts b/src/transformers/typescript.ts index 999baa65..3816e75b 100644 --- a/src/transformers/typescript.ts +++ b/src/transformers/typescript.ts @@ -74,6 +74,9 @@ 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 +115,72 @@ 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)) { + + 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 = []; + + 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) { + continue; //We found an imported type, don't add to our new import clause + } + } + newElements.push(spec) + } + + if (newElements.length > 0) { + newBindings.elements = ts.createNodeArray(newElements, newBindings.elements.hasTrailingComma); + newImportClause.namedBindings = newBindings; + } else { + newImportClause.namedBindings = undefined; + } + } + + //we ended up removing all named bindings and we didn't have a name? nothing left to import. + if (!newImportClause.namedBindings && !newImportClause.name) { + return ts.createEmptyStatement(); + } + } + + return ts.createImportDeclaration( + node.decorators, + node.modifiers, + newImportClause, + node.moduleSpecifier, + ); + } + return ts.visitEachChild(node, child => visit(child), context); + }; + + return node => ts.visitNode(node, visit); + }; + + return importedTypeRemoverTransformer; +} + + function compileFileFromMemory( compilerOptions: CompilerOptions, { filename, content }: { filename: string; content: string }, @@ -172,12 +241,15 @@ 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'"); + }); }); }); From 5e199cbbfd8eb1308b864669460802be1188c110 Mon Sep 17 00:00:00 2001 From: Christian Kaisermann Date: Sat, 6 Jun 2020 00:22:40 -0300 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20tiny=20refactor?= =?UTF-8?q?=20to=20remote=20some=20identation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/transformers/typescript.ts | 116 +++++++++++++++++++-------------- 1 file changed, 68 insertions(+), 48 deletions(-) diff --git a/src/transformers/typescript.ts b/src/transformers/typescript.ts index 3816e75b..51f82495 100644 --- a/src/transformers/typescript.ts +++ b/src/transformers/typescript.ts @@ -77,6 +77,7 @@ const importTransformer: ts.TransformerFactory = (context) => { if (node.importClause?.isTypeOnly) { return ts.createEmptyStatement(); } + return ts.createImportDeclaration( node.decorators, node.modifiers, @@ -118,69 +119,86 @@ function isValidSvelteReactiveValueDiagnostic( function createImportTransformerFromProgram(program: ts.Program) { const checker = program.getTypeChecker(); - const importedTypeRemoverTransformer: ts.TransformerFactory = context => { - const visit: ts.Visitor = node => { - if (ts.isImportDeclaration(node)) { + 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; + 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 = []; - - 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) { - continue; //We found an imported type, don't add to our new import clause - } - } - newElements.push(spec) - } + if (node.importClause) { + // import type {...} from './blah' + if (node.importClause?.isTypeOnly) { + return ts.createEmptyStatement(); + } - if (newElements.length > 0) { - newBindings.elements = ts.createNodeArray(newElements, newBindings.elements.hasTrailingComma); - newImportClause.namedBindings = newBindings; - } else { - newImportClause.namedBindings = undefined; + // 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); } - //we ended up removing all named bindings and we didn't have a name? nothing left to import. - if (!newImportClause.namedBindings && !newImportClause.name) { - return ts.createEmptyStatement(); + if (newElements.length > 0) { + newBindings.elements = ts.createNodeArray( + newElements, + newBindings.elements.hasTrailingComma, + ); + newImportClause.namedBindings = newBindings; } } - return ts.createImportDeclaration( - node.decorators, - node.modifiers, - newImportClause, - node.moduleSpecifier, - ); + // 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.visitEachChild(node, child => visit(child), context); + + return ts.createImportDeclaration( + node.decorators, + node.modifiers, + newImportClause, + node.moduleSpecifier, + ); }; - return node => ts.visitNode(node, visit); + return (node) => ts.visitNode(node, visit); }; return importedTypeRemoverTransformer; } - function compileFileFromMemory( compilerOptions: CompilerOptions, { filename, content }: { filename: string; content: string }, @@ -242,14 +260,16 @@ function compileFileFromMemory( const program = ts.createProgram([dummyFileName], compilerOptions, host); - const transformers = { before: [createImportTransformerFromProgram(program)] } + const transformers = { + before: [createImportTransformerFromProgram(program)], + }; const emitResult = program.emit( program.getSourceFile(dummyFileName), undefined, undefined, undefined, - transformers + transformers, ); // collect diagnostics without svelte import errors From 6a284672fd52c0c8fdb433b1070088641248b205 Mon Sep 17 00:00:00 2001 From: Christian Kaisermann Date: Sat, 6 Jun 2020 00:31:10 -0300 Subject: [PATCH 3/3] chore(release): v3.9.3 :tada: --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) 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/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",