Skip to content

Commit

Permalink
Remove type-checking
Browse files Browse the repository at this point in the history
  • Loading branch information
kaisermann committed Jun 17, 2020
1 parent fe6b689 commit 9bd1a88
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 401 deletions.
36 changes: 4 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -515,12 +515,6 @@ const options = {
compilerOptions: {
module: 'es2015',
},
/**
* Type checking can be skipped by setting 'transpileOnly: true'.
* This speeds up your build process.
*/
transpileOnly: true,
},
/** Use a custom preprocess method by passing a function. */
Expand Down Expand Up @@ -580,14 +574,16 @@ The SCSS/SASS processor accepts the default sass options alongside two other pro
### `typescript`
Since `typescript` is not officially supported by `svelte` for its template language, `svelte-preprocess` only type checks code in the `<script></script>` tag.
The following compiler options are not supported:
- `noUnusedLocals`
- `noEmitOnError`
- `declarations`
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).
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`.
### `pug`
#### Template blocks
Expand Down Expand Up @@ -634,27 +630,3 @@ Since `coffeescript` transpiles variables to `var` definitions, it uses a safety
![image](https://user-images.githubusercontent.com/2388078/63219174-8d4d8b00-c129-11e9-9fb0-56260a125155.png)
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.
### My `typescript` compilation is sloooooooow
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`:
```js
import preprocess from 'svelte-preprocess'
...
{
...svelteOptions,
preprocess: preprocess({
typescript: {
// skips type checking
transpileOnly: true,
compilerOptions: {
...
},
},
})
}
...
```
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`)
2 changes: 1 addition & 1 deletion src/autoProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ export function autoPreprocess(
): PreprocessorGroup {
markupTagName = markupTagName.toLocaleLowerCase();

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

const optionsCache: Record<string, any> = {};
const getTransformerOptions = (
lang: string,
alias: string,
Expand Down
236 changes: 236 additions & 0 deletions src/modules/typescript/compileFromMemory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import { existsSync } from 'fs';
import { dirname, basename, resolve } from 'path';

import ts from 'typescript';

import { Options } from '../../types';

type CompilerOptions = Options.Typescript['compilerOptions'];

function getFilenameExtension(filename: string) {
filename = basename(filename);
const lastDotIndex = filename.lastIndexOf('.');

if (lastDotIndex <= 0) return '';

return filename.substr(lastDotIndex + 1);
}

function isSvelteFile(filename: string) {
const importExtension = getFilenameExtension(filename);

return importExtension === 'svelte' || importExtension === 'html';
}

const IMPORTEE_PATTERN = /['"](.*?)['"]/;

function isValidSvelteImportDiagnostic(filename: string, diagnostic: any) {
// TS2307: 'cannot find module'
if (diagnostic.code !== 2307) return true;

const importeeMatch = diagnostic.messageText.match(IMPORTEE_PATTERN);

// istanbul ignore if
if (!importeeMatch) return true;

let [, importeePath] = importeeMatch;

/** if we're not dealing with a relative path, assume the file exists */
if (importeePath[0] !== '.') return false;

/** if the importee is not a svelte file, do nothing */
if (!isSvelteFile(importeePath)) return true;

importeePath = resolve(dirname(filename), importeePath);

return existsSync(importeePath) === false;
}

const TS2552_REGEX = /Cannot find name '\$([a-zA-Z0-9_]+)'. Did you mean '([a-zA-Z0-9_]+)'\?/i;

function isValidSvelteReactiveValueDiagnostic(
filename: string,
diagnostic: any,
): boolean {
if (diagnostic.code !== 2552) return true;

/** if the importee is not a svelte file, do nothing */
if (!isSvelteFile(filename)) return true;

/** if error message doesn't contain a reactive value, do nothing */
if (!diagnostic.messageText.includes('$')) return true;

const [, usedVar, proposedVar] =
diagnostic.messageText.match(TS2552_REGEX) || [];

return !(usedVar && proposedVar && usedVar === proposedVar);
}

function createImportTransformerFromProgram(program: ts.Program) {
const checker = program.getTypeChecker();

const importedTypeRemoverTransformer: ts.TransformerFactory<ts.SourceFile> = (
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 {...} 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;
}

export function compileFileFromMemory(
compilerOptions: CompilerOptions,
{ filename, content }: { filename: string; content: string },
) {
let code = content;
let map;

const realHost = ts.createCompilerHost(compilerOptions, true);
const dummyFileName = ts.sys.resolvePath(filename);

const isDummyFile = (fileName: string) =>
ts.sys.resolvePath(fileName) === dummyFileName;

const host: ts.CompilerHost = {
fileExists: (fileName) =>
isDummyFile(fileName) || realHost.fileExists(fileName),
getCanonicalFileName: (fileName) =>
isDummyFile(fileName)
? ts.sys.useCaseSensitiveFileNames
? fileName
: fileName.toLowerCase()
: realHost.getCanonicalFileName(fileName),
getSourceFile: (
fileName,
languageVersion,
onError,
shouldCreateNewSourceFile,
// eslint-disable-next-line max-params
) =>
isDummyFile(fileName)
? ts.createSourceFile(dummyFileName, code, languageVersion)
: realHost.getSourceFile(
fileName,
languageVersion,
onError,
shouldCreateNewSourceFile,
),
readFile: (fileName) =>
isDummyFile(fileName) ? content : realHost.readFile(fileName),
writeFile: (fileName, data) => {
if (fileName.endsWith('.map')) {
map = data;
} else {
code = data;
}
},
directoryExists:
realHost.directoryExists && realHost.directoryExists.bind(realHost),
getCurrentDirectory: realHost.getCurrentDirectory.bind(realHost),
getDirectories: realHost.getDirectories.bind(realHost),
getNewLine: realHost.getNewLine.bind(realHost),
getDefaultLibFileName: realHost.getDefaultLibFileName.bind(realHost),
resolveModuleNames:
realHost.resolveModuleNames && realHost.resolveModuleNames.bind(realHost),
useCaseSensitiveFileNames: realHost.useCaseSensitiveFileNames.bind(
realHost,
),
};

const program = ts.createProgram([dummyFileName], compilerOptions, host);

const transformers = {
before: [createImportTransformerFromProgram(program)],
};

const emitResult = program.emit(
program.getSourceFile(dummyFileName),
undefined,
undefined,
undefined,
transformers,
);

// collect diagnostics without svelte import errors
const diagnostics = [
...emitResult.diagnostics,
...ts.getPreEmitDiagnostics(program),
].filter(
(diagnostic) =>
isValidSvelteImportDiagnostic(filename, diagnostic) &&
isValidSvelteReactiveValueDiagnostic(filename, diagnostic),
);

return { code, map, diagnostics };
}
Loading

0 comments on commit 9bd1a88

Please sign in to comment.