typescript工程编译后的输出结构之灵活多变一直是个困扰我的难题,这次通过多种试验终于搞清了基本原理:

源目录结构:

app|
   |dist
   |src
     |app.ts
   |srcjs
     |lib.js
   |node_modules
   |tsconfig.json

如果 js 文件最终“参与”编译,那么输出结构:

app|
   |dist
     |src
       |app.js
     |srcjs
       |lib.js

参与的条件为设置了 allowJs, 而与ts 文件中实际用到 js与否无关。(注意后文的扩充修正)

如果 js 文件未参与,那么

app|
   |dist
     |app.js

以上两种结构,lib.js 相对于 app.js(编译后)的路径都一样,所以只要 app.ts 中是以

1
require('../srcjs/lib')

即相对路径的形式来引用 lib,实际运行都能正确引入。

但是,如果参与编译的 ts 目录有多个,必然会在 dist 下形成相应的子目录结构
此时若 allowJs 为 false,那么 lib.js 相对于 app.js 的路径就与源码结构不一样了(多了一级子目录)
需要小心处理


## 对于多 ts 目录的情况

一般会在 tsconfig.include 中设置:
如果没有设,那么默认就是 app 目录内的所有文件,此时 js 文件是否包括就看 allowJs 是否为 true;
如果设置了,那么就只编译指定的目录,此时又有几种特殊情况:
1、如果实际上只有一个 ts 目录内有文件,最终生成的 dist 里就没有子目录结构,而是与那个唯一的 ts 目录直接对应
2、如果 include 中并没有指定 js 目录,那么即使 allowJs 为 true 也不会编译 js,若此时只有一个 ts 目录,也会导致与 1 相同情况
3、即不管什么原因,只要最终生成的 js 文件都属同一子目录,就会导致 dist 下无子目录结构(注:只在没设 rootDir 的前提下成立,下面详解)


## 其它选项的影响

参考:https://www.typescriptlang.org/docs/handbook/compiler-options.html

“sourceRoot”: “./”, /_ Specify the location where debugger should locate TypeScript files instead of source locations. /
“outFile”: “./”, /
Concatenate and emit output to single file. /
“outDir”: “./”, /
Redirect output structure to the directory. /
“rootDir”: “./”, /
Specify the root directory of input files. Use to control the output directory structure with --outDir. /
“rootDirs”: [], /
List of root folders whose combined content represents the structure of the project at runtime. _/

sourceRoot 仅仅影响调试器对源码的定位,与编译无关

rootDir 最大的变数

真正决定哪些文件参与编译的是 include,而 rootDir 只起到调节输出目录结构的作用

测试一:如果不为当前目录,有别的用处吗? 设为某一 ts 子目录,此时:
  1. include 中有其它目录(或未设则默认所有子目录),报错: 编译的文件不在 rootDir 中,但实际编译还是完成了,生成文件以 include 为准
  2. include 中也只有该目录,如果编译成功的话,在 dist 下生成该目录下所有子目录(不论各子目录是否有文件)
关于2的一点曲折   由于一开始只随便复制了几个简单的源文件到该测试目录,结果报错:所有 node_modules 中的 import 都找不到,如全删除只留一个 console.log 会报 console 都找不到,提示`target library`问题,即`compilerOptions.lib`;但将所有源文件复制进来后反而能编译通过。   经过无比繁琐的逐步删除对比检验,发现问题出在这里:是否有一个 node_modules 库包含了下面这句话,如果有的话就能编译过
1
/// <reference types="node" />

根据搜索得知这是一个3斜杠编译指令,起到告知编译器引入类型的作用
参考:https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html
细节:

  1. 该指令在哪个文件中不重要,只要参与编译的任何一个文件(不管是 ts 还是 node_modules 下面的 js)有这句话就行
  2. 必须在文件的头部,即所有代码之前才有效
测试二:设(为当前目录)与不设,对 dist 目录却有影响:

上一段提到的第 3 条,仅在 rootDir 不设时生效。若 rootDir=’.’,那么 dist 下一定呈现子目录结构(哪怕只有一个)

测试三:以下目录结构
project
   |dist
   |itest
     |xxx
       |xx.ts
     |yyy
       |yy.ts
   |src
     |app.ts
   |srcjs
     |lib.js
   |node_modules
   |tsconfig.json

include=['itest/xxx']
rootDir不设: dist 下会直接出现 xx.js,即以输出文件所属最深同根目录来动态判定,因为只有 xx.ts 被编译,所以直接生成到 dist 下面;如果此时 include 中还有 itest/yyy,那么 dist 下就会有 xxx 和 yyy,而同根目录 itest 被去掉。
rootDir=itest: 显式指定后,无论 yyy 是否参与(include),dist 下都会有 xxx,而不是 xx.js 直接出现;
rootDir=.: 也是显式指定,但同根目录更上了一层,所以哪怕即使只有 xx.js 输出,dist 下也是 itest/xxx/xx.js

rootDirs 可能是最难理解的一个

意义是定义虚拟目录结构,作用是把不同目录层次下的源文件“看成”是同一次层次的
但是这个转换仅仅影响编译时对类型的导入,完全不影响输出目录结构
举例:

project
   |dist
   |itest
     |xxx
       |xx.ts
     |yyy
       |ppp
         |p.ts
       |yy.ts
   |src
     |app.ts
   |srcjs
     |lib.js
   |node_modules
   |tsconfig.json

若 xx.ts 中有

1
import p from './p';

按一般设置,这个 p 肯定是找不到的,因为与 xx.ts 不在一个目录下。
但是若设置

1
rootDirs:['itest/xxx','itest/yyy/ppp']

那么编译时就神奇的通过了,因为编译器被通知这两个目录下的文件可以看作在同一目录里!
但是—— 在 dist 里,p.js 并没有被移到 xxx 下面,xx.js 中对 p 的 require 也并没有被调整! 要想运行时不出错,需要自己去调整输出目录结构!
另外:
1、这里 p 必须是相对路径
2、相对的程度与 rootDirs 中对应,如那边是itest/yyy/ppp的话这边就./p,如那边是itest/yyy则这边就要./ppp/p,总之凑起来要拼出正确的路径