microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.24k stars 12.52k forks source link

How can I get js code by createProgram? #29226

Closed 10229428 closed 5 years ago

10229428 commented 5 years ago

I want to compile ts code to js code in the browser. I use 'transpileModule ' firstly, but I found that through this API I can't get semantic diagnostics, only some syntactic diagnostics. So I change to 'createProgram' API. Unfortunately, I found that I only can get the declaration code, '.d.ts', not js code.

    type File = { fileName: string, content: string, sourceFile?: any }

    const options = {
        module: this.typescript.ModuleKind.CommonJS,
        target: this.typescript.ScriptTarget.ES2015,
        strict: true,
        suppressOutputPathCheck: false
    };
    const file: File = {fileName: 'test', content: "let test = 'aa';"};

    const compilerHost = {
        getSourceFile: (fileName: string) => {
            file.sourceFile = file.sourceFile || this.typescript.createSourceFile(fileName, file.content, this.typescript.ScriptTarget.ES2015, true);
            return file.sourceFile;
        },
        writeFile: (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: any[]): void => {
            console.log('writeFile =====> fileName: ', fileName, ', data: ', data);
        },
        getCurrentDirectory: (): string => {
            return '';
        },
        useCaseSensitiveFileNames: (): boolean => {
            return false;
        },
        getCanonicalFileName: (fileName: string): string => {
            return fileName;
        },
        fileExists: (fileName: string): boolean => {
            return true;
        },
        getDefaultLibFileName: (options: any): string => {
            return 'lib.d.ts';
        },
        getNewLine: (): string => {
            return '\n';
        }
    };

    const program = this.typescript.createProgram([file.fileName], options, compilerHost);
    let emitResult = program.emit();

this.typescript is node_modules\typescript\lib\typescript.js.

By the function writeFile in my host, the content of data is the declare code, not js code.

AlCalzone commented 5 years ago

Take a look at how I do it in https://github.com/AlCalzone/virtual-tsc (in /src/server.ts)

DanielRosenwasser commented 5 years ago

@AlCalzone seems like your solution is partially to disable declaration emit. @sheetalkamat, any idea if this is the most optimal way?

sheetalkamat commented 5 years ago

@AlCalzone This is what I tried and seems to work correctly:

const typescript = require("typescript");
const options = {
    module: typescript.ModuleKind.CommonJS,
    target: typescript.ScriptTarget.ES2015,
    strict: true,
    suppressOutputPathCheck: false
};
const file = {fileName: 'test.ts', content: "let test = 'aa';"};

const compilerHost = typescript.createCompilerHost(options);
const originalGetSourceFile = compilerHost.getSourceFile;
compilerHost.getSourceFile = (fileName) => {
    console.log(fileName);
    if (fileName === file.fileName) {
        file.sourceFile = file.sourceFile || typescript.createSourceFile(fileName, file.content, typescript.ScriptTarget.ES2015, true);
        return file.sourceFile;
    }
    else  return originalGetSourceFile.call(compilerHost, fileName);
};
compilerHost.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
    console.log('writeFile =====> fileName: ', fileName, ', data: ', data);
};

const program = typescript.createProgram([file.fileName], options, compilerHost);
let emitResult = program.emit();

The result was:

test.ts
c:/temp/test2/node_modules/typescript/lib/lib.es6.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.es2015.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.es5.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.es2015.core.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.es2015.collection.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.es2015.generator.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.es2015.promise.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.es2015.iterable.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.es2015.symbol.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.es2015.proxy.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.es2015.reflect.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.dom.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.dom.iterable.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.webworker.importscripts.d.ts
c:/temp/test2/node_modules/typescript/lib/lib.scripthost.d.ts
writeFile =====> fileName:  test.js , data:  "use strict";
let test = 'aa';
AlCalzone commented 5 years ago

@sheetalkamat Looks like I can simplify my code a bit. Thanks!

10229428 commented 5 years ago

Take a look at how I do it in https://github.com/AlCalzone/virtual-tsc (in /src/server.ts)

Thank you very much! It works perfectly! But, I have an other question.

The compile process is in a project which based Angular running in browser. The code what I want to compile to javascript is :

import {Component, ViewChild, EventEmitter, ElementRef, NgZone} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {EventBus, DataBus, LogService, util, LoadingService} from '@rdkmaster/uid-sdk';
import {TranslateHelper, JigsawBox, TimeGr, TimeService, JigsawButton} from '@rdkmaster/jigsaw';

export class AppComponent {
    test($event: any) {
        this.aaaa = 'aaaa';
    }
}

There are some imported modules and errors in function test. The output js code is my wanted. The diagnostics are:

[
    {"sourceLine":"import {Component, ViewChild, EventEmitter, ElementRef, NgZone} from '@angular/core';","description":"Cannot find module '@angular/core'."},
    {"sourceLine":"import {HttpClient} from '@angular/common/http';","description":"Cannot find module '@angular/common/http'.",
    {"sourceLine":"import {EventBus, DataBus, LogService, util, LoadingService} from '@rdkmaster/uid-sdk';","description":"Cannot find module '@rdkmaster/uid-sdk'.",
    {"sourceLine":"import {TranslateHelper, JigsawBox, TimeGr, TimeService, JigsawButton} from '@rdkmaster/jigsaw';","description":"Cannot find module '@rdkmaster/jigsaw'.",
    {"sourceLine":"    this.aaaa = 'aaaa';","description":"Property 'aaaa' does not exist on type 'AppComponent'."
]

The first four diagnostics are about the imported modules, the last one is the semantic diagnostic which I want. How can I resolve the diagnostics about "import .... Cannot find module '....' "? I don't konw how to handle the imported module for the compile ts code.

In your project, I notices that you write some ambient declarations files into virtual file system. Is this what I want?

// provide all ambient declaration files
for (const ambientFile of Object.keys(ambientDeclarations)) {
    if (!/\.d\.ts$/.test(ambientFile)) throw new Error("Declarations must be .d.ts-files");
    fs.writeFile(ambientFile, ambientDeclarations[ambientFile], true);
}
AlCalzone commented 5 years ago

In your project, I notices that you write some ambient declarations files into virtual file system. Is this what I want?

Probably that is what you want and should be the easiest to accomplish. Or you need to somehow put the node_modules directories of all imported modules into the virtual fs aswell, so TypeScript can locate it. (Although I'm not sure right now if I added a special case to avoid looking those up)

10229428 commented 5 years ago

In your project, I notices that you write some ambient declarations files into virtual file system. Is this what I want?

Probably that is what you want and should be the easiest to accomplish. Or you need to somehow put the node_modules directories of all imported modules into the virtual fs aswell, so TypeScript can locate it. (Although I'm not sure right now if I added a special case to avoid looking those up)

Thanks! I will try it later.

karlhorky commented 5 years ago

@sheetalkamat or anyone else, do you have an idea of how to get the simple solution above working inside a create-react-app context? I seem to get errors with properties on ts.sys being undefined, such as:

Cannot read property 'useCaseSensitiveFileNames' of undefined

at this line: https://github.com/microsoft/TypeScript/blob/1cbace6eee8257309e5e8dc5a2389a20077a5c08/src/compiler/program.ts#L77


Here's an example on CodeSandbox:

https://codesandbox.io/s/create-react-app-v3-with-tscreatecompilerhost-cielb


I have a hunch that this is because running ts.createProgram in the create-react-app context is not exactly the same as running in the Node context, from this Stack Overflow question/answer:

https://stackoverflow.com/questions/52969177/typescript-createprogram-throwing-ts-sys-is-undefined


By playing around a little bit, I got something working today (by declaring the compilerHost as an object instead):

https://github.com/codesandbox/codesandbox-client/issues/2090#issuecomment-505500573 https://twitter.com/karlhorky/status/1143817823342551040