Skip to content

Commit 6eaa090

Browse files
committed
Remove type-checking
1 parent 366b420 commit 6eaa090

File tree

6 files changed

+255
-407
lines changed

6 files changed

+255
-407
lines changed

README.md

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -495,12 +495,6 @@ const options = {
495495
compilerOptions: {
496496
module: 'es2015',
497497
},
498-
499-
/**
500-
* Type checking can be skipped by setting 'transpileOnly: true'.
501-
* This speeds up your build process.
502-
*/
503-
transpileOnly: true,
504498
},
505499

506500
/** Use a custom preprocess method by passing a function. */
@@ -560,14 +554,16 @@ The SCSS/SASS processor accepts the default sass options alongside two other pro
560554

561555
### `typescript`
562556

563-
Since `typescript` is not officially supported by `svelte` for its template language, `svelte-preprocess` only type checks code in the `<script></script>` tag.
564-
565557
The following compiler options are not supported:
566558

567559
- `noUnusedLocals`
568560
- `noEmitOnError`
569561
- `declarations`
570562

563+
Since `v4`, `svelte-preprocess` doesn't type-check your component, its only concern is to transpile it into valid Javascript for the compiler. If you want to have your components type-checked, you can use [svelte-check](https://github.com/sveltejs/language-tools/blob/master/packages/svelte-check/README.md).
564+
565+
As we're only transpiling, it's not possible to import types or interfaces into your svelte component without using the new TS 3.8 `type` import modifier: `import type { SomeInterface } from './MyModule'` otherwise bundlers will complain that the name is not exported by `MyModule`.
566+
571567
### `pug`
572568

573569
#### Template blocks
@@ -614,27 +610,3 @@ Since `coffeescript` transpiles variables to `var` definitions, it uses a safety
614610
![image](https://user-images.githubusercontent.com/2388078/63219174-8d4d8b00-c129-11e9-9fb0-56260a125155.png)
615611

616612
If you have configured `svelte-preprocess` to use some kind of preprocessor and `svelte-vscode` is displaying errors like it's ignoring your preprocess configuration, that's happening because `svelte-vscode` needs to know how to preprocess your components. `svelte-vscode` works by having a svelte compiler running on the background and you can configure it by [creating a `svelte.config.js`](#with-svelte-vs-code) file on your project's root. Please check this document [With Svelte VS Code](#with-svelte-vs-code) section.
617-
618-
### My `typescript` compilation is sloooooooow
619-
620-
If you have a medium-to-big project, the typescript processor might start to get slow. If you already have an IDE type checking your code, you can speed up the transpilation process by setting `transpileOnly` to `true`:
621-
622-
```js
623-
import preprocess from 'svelte-preprocess'
624-
...
625-
{
626-
...svelteOptions,
627-
preprocess: preprocess({
628-
typescript: {
629-
// skips type checking
630-
transpileOnly: true,
631-
compilerOptions: {
632-
...
633-
},
634-
},
635-
})
636-
}
637-
...
638-
```
639-
640-
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`)

src/autoProcess.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ export function autoPreprocess(
8484
): PreprocessorGroup {
8585
markupTagName = markupTagName.toLocaleLowerCase();
8686

87-
const optionsCache: Record<string, any> = {};
8887
const transformers = rest as Transformers;
8988
const markupPattern = new RegExp(
9089
`<${markupTagName}([\\s\\S]*?)(?:>([\\s\\S]*)<\\/${markupTagName}>|/>)`,
@@ -94,6 +93,7 @@ export function autoPreprocess(
9493
addLanguageAlias(aliases);
9594
}
9695

96+
const optionsCache: Record<string, any> = {};
9797
const getTransformerOptions = (
9898
lang: string,
9999
alias: string,
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
import { existsSync } from 'fs';
2+
import { dirname, basename, resolve } from 'path';
3+
4+
import ts from 'typescript';
5+
6+
import { Options } from '../../types';
7+
8+
type CompilerOptions = Options.Typescript['compilerOptions'];
9+
10+
function getFilenameExtension(filename: string) {
11+
filename = basename(filename);
12+
const lastDotIndex = filename.lastIndexOf('.');
13+
14+
if (lastDotIndex <= 0) return '';
15+
16+
return filename.substr(lastDotIndex + 1);
17+
}
18+
19+
function isSvelteFile(filename: string) {
20+
const importExtension = getFilenameExtension(filename);
21+
22+
return importExtension === 'svelte' || importExtension === 'html';
23+
}
24+
25+
const IMPORTEE_PATTERN = /['"](.*?)['"]/;
26+
27+
function isValidSvelteImportDiagnostic(filename: string, diagnostic: any) {
28+
// TS2307: 'cannot find module'
29+
if (diagnostic.code !== 2307) return true;
30+
31+
const importeeMatch = diagnostic.messageText.match(IMPORTEE_PATTERN);
32+
33+
// istanbul ignore if
34+
if (!importeeMatch) return true;
35+
36+
let [, importeePath] = importeeMatch;
37+
38+
/** if we're not dealing with a relative path, assume the file exists */
39+
if (importeePath[0] !== '.') return false;
40+
41+
/** if the importee is not a svelte file, do nothing */
42+
if (!isSvelteFile(importeePath)) return true;
43+
44+
importeePath = resolve(dirname(filename), importeePath);
45+
46+
return existsSync(importeePath) === false;
47+
}
48+
49+
const TS2552_REGEX = /Cannot find name '\$([a-zA-Z0-9_]+)'. Did you mean '([a-zA-Z0-9_]+)'\?/i;
50+
51+
function isValidSvelteReactiveValueDiagnostic(
52+
filename: string,
53+
diagnostic: any,
54+
): boolean {
55+
if (diagnostic.code !== 2552) return true;
56+
57+
/** if the importee is not a svelte file, do nothing */
58+
if (!isSvelteFile(filename)) return true;
59+
60+
/** if error message doesn't contain a reactive value, do nothing */
61+
if (!diagnostic.messageText.includes('$')) return true;
62+
63+
const [, usedVar, proposedVar] =
64+
diagnostic.messageText.match(TS2552_REGEX) || [];
65+
66+
return !(usedVar && proposedVar && usedVar === proposedVar);
67+
}
68+
69+
function createImportTransformerFromProgram(program: ts.Program) {
70+
const checker = program.getTypeChecker();
71+
72+
const importedTypeRemoverTransformer: ts.TransformerFactory<ts.SourceFile> = (
73+
context,
74+
) => {
75+
const visit: ts.Visitor = (node) => {
76+
if (!ts.isImportDeclaration(node)) {
77+
return ts.visitEachChild(node, (child) => visit(child), context);
78+
}
79+
80+
let newImportClause: ts.ImportClause = node.importClause;
81+
82+
if (node.importClause) {
83+
// import {...} from './blah'
84+
if (node.importClause?.isTypeOnly) {
85+
return ts.createEmptyStatement();
86+
}
87+
88+
// import Blah, { blah } from './blah'
89+
newImportClause = ts.getMutableClone(node.importClause);
90+
91+
// types can't be default exports, so we just worry about { blah } and { blah as name } exports
92+
if (
93+
newImportClause.namedBindings &&
94+
ts.isNamedImports(newImportClause.namedBindings)
95+
) {
96+
const newBindings = ts.getMutableClone(newImportClause.namedBindings);
97+
const newElements = [];
98+
99+
newImportClause.namedBindings = undefined;
100+
101+
for (const spec of newBindings.elements) {
102+
const ident = spec.name;
103+
104+
const symbol = checker.getSymbolAtLocation(ident);
105+
const aliased = checker.getAliasedSymbol(symbol);
106+
107+
if (aliased) {
108+
if (
109+
(aliased.flags &
110+
(ts.SymbolFlags.TypeAlias | ts.SymbolFlags.Interface)) >
111+
0
112+
) {
113+
// We found an imported type, don't add to our new import clause
114+
continue;
115+
}
116+
}
117+
newElements.push(spec);
118+
}
119+
120+
if (newElements.length > 0) {
121+
newBindings.elements = ts.createNodeArray(
122+
newElements,
123+
newBindings.elements.hasTrailingComma,
124+
);
125+
newImportClause.namedBindings = newBindings;
126+
}
127+
}
128+
129+
// we ended up removing all named bindings and we didn't have a name? nothing left to import.
130+
if (
131+
newImportClause.namedBindings == null &&
132+
newImportClause.name == null
133+
) {
134+
return ts.createEmptyStatement();
135+
}
136+
}
137+
138+
return ts.createImportDeclaration(
139+
node.decorators,
140+
node.modifiers,
141+
newImportClause,
142+
node.moduleSpecifier,
143+
);
144+
};
145+
146+
return (node) => ts.visitNode(node, visit);
147+
};
148+
149+
return importedTypeRemoverTransformer;
150+
}
151+
152+
export function compileFileFromMemory(
153+
compilerOptions: CompilerOptions,
154+
{ filename, content }: { filename: string; content: string },
155+
) {
156+
let code = content;
157+
let map;
158+
159+
const realHost = ts.createCompilerHost(compilerOptions, true);
160+
const dummyFileName = ts.sys.resolvePath(filename);
161+
162+
const isDummyFile = (fileName: string) =>
163+
ts.sys.resolvePath(fileName) === dummyFileName;
164+
165+
const host: ts.CompilerHost = {
166+
fileExists: (fileName) =>
167+
isDummyFile(fileName) || realHost.fileExists(fileName),
168+
getCanonicalFileName: (fileName) =>
169+
isDummyFile(fileName)
170+
? ts.sys.useCaseSensitiveFileNames
171+
? fileName
172+
: fileName.toLowerCase()
173+
: realHost.getCanonicalFileName(fileName),
174+
getSourceFile: (
175+
fileName,
176+
languageVersion,
177+
onError,
178+
shouldCreateNewSourceFile,
179+
// eslint-disable-next-line max-params
180+
) =>
181+
isDummyFile(fileName)
182+
? ts.createSourceFile(dummyFileName, code, languageVersion)
183+
: realHost.getSourceFile(
184+
fileName,
185+
languageVersion,
186+
onError,
187+
shouldCreateNewSourceFile,
188+
),
189+
readFile: (fileName) =>
190+
isDummyFile(fileName) ? content : realHost.readFile(fileName),
191+
writeFile: (fileName, data) => {
192+
if (fileName.endsWith('.map')) {
193+
map = data;
194+
} else {
195+
code = data;
196+
}
197+
},
198+
directoryExists:
199+
realHost.directoryExists && realHost.directoryExists.bind(realHost),
200+
getCurrentDirectory: realHost.getCurrentDirectory.bind(realHost),
201+
getDirectories: realHost.getDirectories.bind(realHost),
202+
getNewLine: realHost.getNewLine.bind(realHost),
203+
getDefaultLibFileName: realHost.getDefaultLibFileName.bind(realHost),
204+
resolveModuleNames:
205+
realHost.resolveModuleNames && realHost.resolveModuleNames.bind(realHost),
206+
useCaseSensitiveFileNames: realHost.useCaseSensitiveFileNames.bind(
207+
realHost,
208+
),
209+
};
210+
211+
const program = ts.createProgram([dummyFileName], compilerOptions, host);
212+
213+
const transformers = {
214+
before: [createImportTransformerFromProgram(program)],
215+
};
216+
217+
const emitResult = program.emit(
218+
program.getSourceFile(dummyFileName),
219+
undefined,
220+
undefined,
221+
undefined,
222+
transformers,
223+
);
224+
225+
// collect diagnostics without svelte import errors
226+
const diagnostics = [
227+
...emitResult.diagnostics,
228+
...ts.getPreEmitDiagnostics(program),
229+
].filter(
230+
(diagnostic) =>
231+
isValidSvelteImportDiagnostic(filename, diagnostic) &&
232+
isValidSvelteReactiveValueDiagnostic(filename, diagnostic),
233+
);
234+
235+
return { code, map, diagnostics };
236+
}

0 commit comments

Comments
 (0)