TS三斜线指令详解-1

161 阅读3分钟

/// <reference path='path'/> 详解

前置知识

tsconfig.json中有一项配置outFile,该配置用于将所有的输出文件整合成一个文件 在本例中,该项值设定为./result.js,ts 文件编译后的内容将整合于该单一文件中

path 解析方式

path 解析策略同模块解析策略,其有两种:ClassicNode

可以通过配置tsconfig.json中的moduleResolution选项值为ClassicNode 来选择模块解析策略

问题

我们现有如下代码

// nameC.ts
namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }
}

// nameB.ts
namespace Validation {
  const lettersRegexp = /^[A-Za-z]+$/;
  export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
      return lettersRegexp.test(s);
    }
  }
}

// nameA.ts
let sv: Validation.StringValidator = {
  isAcceptable(s) {
    return true;
  },
};
let lov: Validation.LettersOnlyValidator =
  new Validation.LettersOnlyValidator();
console.log({ sv, lov });

在我们使用tsc进行编译过后,得到的结果如下

// result.js
"use strict";
let sv = {
  isAcceptable(s) {
    return true;
  },
};
let lov = new Validation.LettersOnlyValidator();
console.log({ sv, lov });
var Validation;
(function (Validation) {
  const lettersRegexp = /^[A-Za-z]+$/;
  class LettersOnlyValidator {
    isAcceptable(s) {
      return lettersRegexp.test(s);
    }
  }
  Validation.LettersOnlyValidator = LettersOnlyValidator;
})(Validation || (Validation = {}));

我们运行该 js 文件,发现报错:

`Class 'LettersOnlyValidator' used before its declaration.`

我们发现,类LettersOnlyValidator在其被定义前就已经使用了,这说明了一个问题 输出文件内容的顺序不对,我们发现nameA.ts文件的内容先被解析了,这是不符合我们预期的!

解决办法

ts为我们提供了三斜线指令 /// <reference path='path'/>来让我们指定文件解析的先后顺序

我们将ts文件内容添加上三斜线指令之后,内容如下

// nameC.ts
namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }
}

// nameB.ts
/// <reference path='nameC.ts' />
namespace Validation {
    const lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}

// nameA.ts
/// <reference path='nameC.ts' />
/// <reference path='nameB.ts' />
let sv:Validation.StringValidator = {
    isAcceptable(s) {
        return true
    },
}

let lov:Validation.LettersOnlyValidator = new Validation.LettersOnlyValidator()

console.log({sv,lov})

当文件内使用到了另一个文件内命名空间的内容时,需要在该文件内使用三斜线指令 来告诉编译器你引用的内容所在的位置,后继编译器就会根据三斜线指令的引用关系, 来决定整合文件的内容的顺序

编译后结果如下

// result.js
"use strict";
/// <reference path='nameC.ts' />
var Validation;
/// <reference path='nameC.ts' />
(function (Validation) {
    const lettersRegexp = /^[A-Za-z]+$/;
    class LettersOnlyValidator {
        isAcceptable(s) {
            return lettersRegexp.test(s);
        }
    }
    Validation.LettersOnlyValidator = LettersOnlyValidator;
})(Validation || (Validation = {}));
/// <reference path='nameC.ts' />
/// <reference path='nameB.ts' />
let sv = {
    isAcceptable(s) {
        return true;
    },
};
let lov = new Validation.LettersOnlyValidator();
console.log({ sv, lov });

运行result.js文件,发现能够正常运行并输出期望结果

解决逻辑

编译器在编译文件时有个步骤——预处理输入文件,该步骤会对输入文件预处理来解析所有三斜线指令,在这个过程中额外的文件会被加入到编译过程中。 以上述代码为例,编译器在解析nameA.ts文件前,会先监测其文件内容有没有三斜线指令,结果发现有

/// <reference path='nameC.ts' />
/// <reference path='nameB.ts' />

那么编译器会先去对nameC.ts进行编译,发现nameC.ts中没有三斜线指令,则先将编译结果添加到输出文件中,后面继续解析nameB.ts 发现nameB.ts也使用三斜线指令指向了nameA.ts,不过其之前已经编译过,就不再处理,而是将nameB.ts内容编译后添加到输出文件中, 此时nameC.ts的三斜线指令已经预处理完毕,那么就将nameC.ts内容编译后添加到输出文件中,此时输出文件的内容便是我们预期的输出结果。

说大白话就是,/// <reference path='nameC.ts' />就是告诉编译器:我这里使用了nameC.ts的内容,请你先去编译nameC.ts的内容后再来我这

作用

根据上述过程我们得出一个结论:

/// <reference path='path' />的作用就是当需要将ts文件编译结果整合为一个文件时,我们可以使用该三斜线指令来告诉编译器这些文件内容的依赖关系,从而使得编译输出结果符合我们的预期

#### 注意事项

  • 一个三斜线引用路径是相对于包含它的文件的
  • 如果在tsconfig.json中的noResolve值为true,则会忽略所有三斜线指令

别的解决办法

ts会影响输出文件内容顺序的方式有2:三斜线指令和import的文件顺序

现我们使用import来影响文件顺序,我们需要关闭outFile属性

我们将上述ts文件内容调整如下

// nameC.ts
export namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }

}

// nameB.ts
import {Validation as nameC} from "./nameC"
export namespace Validation {
    const lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements nameC.StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}

// nameA.ts
import {Validation as nameC} from "./nameC"
import {Validation as nameB} from "./nameB"

let sv:nameC.StringValidator = {
    isAcceptable(s) {
        return true
    },
}

let lov:nameB.LettersOnlyValidator = new nameB.LettersOnlyValidator()

console.log({sv,lov})

其输出结果如下,此时tsconfig.jsonmodule值为CommonJS

// nameC.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

// nameB.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Validation = void 0;
var Validation;
(function (Validation) {
    const lettersRegexp = /^[A-Za-z]+$/;
    class LettersOnlyValidator {
        isAcceptable(s) {
            return lettersRegexp.test(s);
        }
    }
    Validation.LettersOnlyValidator = LettersOnlyValidator;
})(Validation = exports.Validation || (exports.Validation = {}));

// nameA.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const nameB_1 = require("./nameB");
let sv = {
    isAcceptable(s) {
        return true;
    },
};
let lov = new nameB_1.Validation.LettersOnlyValidator();
console.log({ sv, lov });

我们运行 nameA.js 发现达到输出预期,我们成功使用了三斜线指令之外的方式来影响 输出文件的内容