Typescript-使用API-来做一波编译

24次阅读

共计 9275 个字符,预计需要花费 24 分钟才能阅读完成。

首发于: https://luoyangfu.com/article…

前言

下面假设在 tsc 执行目录中有如下配置:

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
    "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
    "plugins": [{"name": "typescript-tslint-plugin"}],
    // "lib": [],                             /* Specify library files to be included in the compilation. */
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
    // "checkJs": true,                       /* Report errors in .js files. */
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
    // "sourceMap": true /* Generates corresponding '.map' file. */,
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    "outDir": "dist" /* Redirect output structure to the directory. */,
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                     /* Enable project compilation */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true /* Enable all strict type-checking options. */,
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
    // "baseUrl": "." /* Base directory to resolve non-absolute module names. */,
    // "paths": {// "config/*": ["src/config/*"],
    // "controllers/*": ["./src/controllers/*"],
    // "db/*": ["./src/db/*"],
    // "middlewares/*": ["./src/middlewares/*"],
    // "routes/*": ["./src/routes/*"],
    // "services/*": ["./src/services/*"],
    // "static/*": ["./src/static/*"],
    // "templates/*": ["./src/templates/*"],
    // "tools/*": ["./src/tools/*"],
    // "src/*": ["./src/*"]
    // } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */,
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    "traceResolution": false,
    "typeRoots": [
      "node_modules/@types",
      "src/typings"
    ] /* List of folders to include type definitions from. */,
    // "types": [],                           /* Type declaration files to be included in compilation. */
    "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */

    /* Source Map Options */
    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */"inlineSourceMap": true /* Emit a single file with source maps instead of having a separate file. */,
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
  },
  "include": ["src/**/*"]
}

上面就为一个 typescript 的简单配置。(PS: 可直接复制使用哦)

在代码里面如果需要把 typescript 转化成 javascript 时候,我们需要执行下面命令

$ tsc index.ts

这里 npx 为包执行命令的 bash.

我们通过这样方式就可以把 ts 代码,转化成了 js 代码,如果说我们需要时刻监听代码修改,然后根据代码的改变来转化呢?

$ tsc index.ts --watch

通过 watch 表示来实现监听,看下图

这里会默认显示两条信息,并且会清空当前控制台。

如果说我们有多条命令需要同时执行呢?看下面图片来看:

这里就会不断闪烁。每次修改也会清空,想观察控制台就很难观察。so,需要一种方式,既要监听 typescript 自动编译,还需要能够自定义处理输出。

这里就需要去学习一下 typescript 编译的 API。

需求相关 API

findConfigFile

查找配置文件路径

代码申明:

function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName?: string): string | undefined;
  • searchPath: 查询目录
  • fileExists: 一般传递 ts.sys.fileExists, 这个函数传递一个路径判断文件是否存在
  • configName: 配置文件名称,一般都是 tsocnfig.json

createSemanticDiagnosticsBuilderProgram

这里需要说一下是的,typescript 可以使用不同的代码创造器的 策略 去创建程序

  • ts.createEmitAndSemanticDiagnosticsBuilderProgram
  • ts.createSemanticDiagnosticsBuilderProgram
  • ts.createAbstractBuilder

前两种代码创造器策略是创造可运行的程序,他们都是使用一种增量策略,仅仅检查内容发生改变并且触发的文件或者那些依赖以及改变然后影响之前产生的代码结果。最后一个使用普通程序,在每次更改后进行完整类型检查。前两种区别在于触发个点上。对于纯类型检查场景或者第三方工具、进程手动触发使用 ts.createSemanticDiagnosticsBuilderProgram 或许更合适。

代码申明:

function createSemanticDiagnosticsBuilderProgram(newProgram: Program, host: BuilderProgramHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>): SemanticDiagnosticsBuilderProgram;
 
function createSemanticDiagnosticsBuilderProgram(rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: SemanticDiagnosticsBuilderProgram, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>, projectReferences?: ReadonlyArray<ProjectReference>): SemanticDiagnosticsBuilderProgram;

这里又两种形式代码申明,但是这两种形式其实在后续都说明,又其他代码传入,不必手动传递.

createWatchCompilerHost

这里就是创建 watch 的 typescript 脚本 API 了。这里是实现了 tsc -w 这个命令.

代码申明:


function createWatchCompilerHost<T extends BuilderProgram>(configFileName: string, optionsToExtend: CompilerOptions | undefined, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHostOfConfigFile<T>;

function createWatchCompilerHost<T extends BuilderProgram>(rootFiles: string[], options: CompilerOptions, system: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, projectReferences?: ReadonlyArray<ProjectReference>): WatchCompilerHostOfFilesAndCompilerOptions<T>;

也有两种形式申明根据实际情况,我这里选择第一种函数形式:

  • configFileName: 配置文件地址
  • optionsToExtend: tsconfig 配置
  • system: 传入 ts.system 即可
  • createProgram: 上面说的不需要手动传入参数的创造代码函数就是传递到这里,可以传递进去 ts.createSemanticDiagnosticsBuilderProgram 即可.
  • reportDiagnostic: 这个就是当程序报错后,执行的函数,会传递进去一个 Diagnostic 对象,包含当前代码的诊断信息,报错的代码片段等
  • reportWatchStatusChanged: 这个就是当前编译状态信息,也会传递进去一个 Diagnostic 对象,通过 ts.formatDiagnostic 可以格式化当前对象,得到一个如下类型的消息: message TS1234: xxxx 这样的提示信息

这里返回就返回一个 WatchCompilerHostOfConfigFile 对象

createWatchProgram

创建一个程序监听器

代码申明:

function createWatchProgram<T extends BuilderProgram>(host: WatchCompilerHostOfConfigFile<T>): WatchOfConfigFile<T>;
  • host: 创建的编译宿主程序

在上面一个函数 createWatchCompilerHost 创建成功后需要有如下操作:

 const host = ts.createWatchCompilerHost(
    configPath,
    {},
    ts.sys,
    createProgram,
    reportDiagnostic,
    reportWatchStatusChanged
  );

  const origCreateProgram = host.createProgram;
  host.createProgram = (rootNames, options, host, oldProgram) => {consola.info("We're about to create the program!");
    return origCreateProgram(rootNames, options, host, oldProgram);
  };
  const origPostProgramCreate = host.afterProgramCreate;

  host.afterProgramCreate = (program) => {consola.info('We finished making the program!');
    origPostProgramCreate(program);
  };

  ts.createWatchProgram(host);

host 上有两个属性分别是: createProgramafterProgramCreate。这两个属性,我们如果要想知道对应阶段,例如创造程序开始和完成就需要使用闭包包裹一下即可。正如上面代码.

结束

typescript 监听脚本

const ts = require('typescript');
const consola = require('consola');

const formatHost = {getCanonicalFileName: (path) => path,
  getCurrentDirectory: ts.sys.getCurrentDirectory,
  getNewLine: () => ts.sys.newLine,};

function watchMain() {const configPath = ts.findConfigFile(/*searchPath*/ './', ts.sys.fileExists, 'tsconfig.json');
  if (!configPath) {throw new Error("Could not find a valid'tsconfig.json'.");
  }

  const createProgram = ts.createSemanticDiagnosticsBuilderProgram;

  const host = ts.createWatchCompilerHost(
    configPath,
    {},
    ts.sys,
    createProgram,
    reportDiagnostic,
    reportWatchStatusChanged
  );

  const origCreateProgram = host.createProgram;
  host.createProgram = (rootNames, options, host, oldProgram) => {consola.info("We're about to create the program!");
    return origCreateProgram(rootNames, options, host, oldProgram);
  };
  const origPostProgramCreate = host.afterProgramCreate;

  host.afterProgramCreate = (program) => {consola.info('We finished making the program!');
    origPostProgramCreate(program);
  };

  ts.createWatchProgram(host);
}

function reportDiagnostic(diagnostic) {
  consola.error(
    'Error',
    diagnostic.code,
    ':',
    ts.flattenDiagnosticMessageText(diagnostic.messageText, formatHost.getNewLine())
  );
}

function reportWatchStatusChanged(diagnostic) {let message = ts.formatDiagnostic(diagnostic, formatHost);
  if (message.indexOf('TS6194') > 0) {message = message.replace(/message\sTS[0-9]{4}:(.+)(\s+)$/, '$1');
    consola.ready({message, badge: true});
  }
}

watchMain();

上面就是完整的执行 typescript 编译监听脚本

consola 没写错,这是一个支持 nodejs 和 浏览器的控制台日志输入器,支持颜色输出哦。

看效果:

基本达到想要结果。后面还有什么好玩的欢迎交流。

正文完
 0