microsoft / microsoft/TypeScript

[Proposal] Conceptual(virtual) module in .d.ts

Đang mở
#33,112 1 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Awaiting More Feedback Suggestion
Ngôn ngữ chính
Go
Star
111k
Fork
14.3k
Merge trung bình
2 ngày 4 giờ
Pull request đã merge (30 ngày)
132

Mô tả

Motivation

A common requirement during JavaScript application development is to bundle multiple modules together. There are many bundling tools to do this, such as rollup.js, webpack.

The usual case is that library maintainers organize their source in a internal structure. When publish, bundling tool is called to roll all modules up into one. Users of the library can only see the bundled module. They don't know and don't care about the original structure.

Everything works fine except when library maintainer wish to provide type declaration file(s) for the library so users can benefit from Typescript. Because Typescript compiler can only generate declaration files according to the original source structure. Even the compiler option outFile is used, it just produce a single declaration file which contains multiple "ambient" modules. For the declaration file's consumer, they can still see and refer to the original module.

For example. Say there is a library has the following source structure:
index.ts

export { Apple } from "./apple";
export { Pen } from "./pen";

apple.ts

export class Apple {}

pen.ts

export class Pen {}

Bundling tool bundle the index.ts to generated a single module "applepen.js".

Typescript compiler generate the declaration file like this:

declare module "apple" { export class Apple {} }
declare module "pen" { export class Pen {} }
declare module "index" {
  export { Apple } from "./apple";
  export { Pen } from "./pen";
}

It would be better to replace module name "index" to the library's real name. It can be done if the triple slash directive is inserted into index.ts:

/// <amd-module name="applepen"/>
export { Apple } from "./apple";
export { Pen } from "./pen";

It will infer the tsc to generate the declaration file like:

declare module "apple" { export class Apple {} }
declare module "pen" { export class Pen {} }
declare module "applepen" {
  export { Apple } from "./apple";
  export { Pen } from "./pen";
}

Now you can use the declaration file as:

import { Apple, Pen } from "applepen";

Yes, that's really what we expect. The declaration of "applepen" exactly match what bundling tool does.

Is that OK? No. Users are able, according the typing information the declaration file expresses, refer to a module called "apple". So:

import { Apple } from "apple";

is valid and cause no compiling error. But it's wrong because there is no "apple" module exists.
Even if we can carefully write any import statement, we are losing the convenience to use Typescript language server's auto-completion. That's mean when you type:

const apple = new Appl

The IDE will infer you Apple from a non-existing module "apple";


A kind of tools may helper, such as dts-bundle-generator or gift(the author is me :) ). But they are hack and problematical.


This proposal is aims to overcome the above issue.

Solution

Concept

A conceptual module is a kind of ambient module that cannot be used outside the non-conceptual module enclosing it.

Conceptual modules are only used to express structure. No any actual JS codes are generated for them.

To declare a conceptual module, you should use the module-declaration syntax, with the following restricts:

  • Conceptual modules are only valid in declaration file(*.d.ts file);
  • There shall be one non-conceptual ambient module enclosing it(ie. declare module "xx" {});
  • The name element in module-declaration AST shall obey the rule of standard module specifier(ie. shall be a string-literal);
Example
declare module "applepen" {
    module "apple" {
        export class Apple {}
    }

    module "pen" {
        export class Pen {}
    }

    export { Apple } from "apple";
    export { Pen } from "pen";
}
const apple: import("apple").Appe; // Error: cannot resolve module "apple"

In above example, there are two conceptual module "apple" and "pen" in non-conceptual module "applepen". All portion in module "applepen" can import the two conceptual module. Outside the "applepen" module can not, however:

A use case:
another file:

import { Apple } from "applepen"; // OK
import { Pen } from "pen"; // Error: cannot resolve module "pen"
Implementation

The syntax of "A module declaration is enclosed in other one" is supported already. The only things need to be done are:

  • Add codes to resolveExternalModule() in Checker.ts to support resolve conceptual modules;
  • Code-completion stuffs.

More information

The second restrict described above requires the enclosing non-conceptual module to be "ambient". It actually a restrict from current Typescript.

In current, a module in .d.ts like
xx.d.ts

module "ambient-module-with-no-declare-modifier" {
} 

is an error. The Typescript requires it have a declare keyword modifier. But it is indeed recognized as a module which just has the declare modifier in type checker.

If it is REALLY treated as an error. The second restrict can be modified as:

  • There shall be one module-declaration or source file enclosing it;

So we can define our bundle as not a "ambient" module, instead, as an "external" module:
applepen.d.ts

module "apple" {
   export class Apple {}
}

module "pen" {
    export class Pen {}
}

export { Apple } from "apple";
export { Pen } from "pen";

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu bằng cách đọc phần xử lý hiện có đối với các khai báo module lồng nhau và resolveExternalModule() trong Checker.ts, sau đó xem lại phần triển khai hoàn tất mã được đề cập trong đề xuất. Kết quả mong muốn là các module khái niệm trong các tệp .d.ts được phân giải bên trong ambient module bao quanh chúng, nhưng không thể được import từ bên ngoài module đó, với tính năng hoàn tất phản ánh phạm vi đó.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
typescript
Lĩnh vực
compilers
Loại issue
Tính năng
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
25/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.