openplanning

Module trong TypeScript

  1. Module là gì?
  2. Ý tưởng về module trước ES6
  3. Từ khoá export
  4. Từ khoá import
  5. Từ khoá module/namespace
  6. Sử dụng module trong HTML

1. Module là gì?

Bắt đầu từ đặc tả ECMAScript 2015 (ES6), JavaScript có thêm một khái niệm mới đó là module. TypeScript cũng chia sẻ khái niệm này.
Các Module với các tài nguyên mã chỉ được sử dụng trong một phạm vi đóng kín, không phải trong phạm vi toàn cục. Điều này có nghĩa là các biến, hàm, lớp,.. được khai báo trong module sẽ không hiển thị ra bên ngoài module, trừ khi chúng được xuất khẩu một cách rõ ràng thông qua biểu thức export. Ngoài ra, để sử dụng tài nguyên được xuất khẩu của một module khác bạn phải nhập khẩu một cách rõ ràng thông qua biểu thức import.
Các lập trình viên có xu hướng tổ chức mỗi file là một module, điều này dễ dàng hơn cho việc quản lý code. Mặc dù bạn có thể định nghĩa hai hay nhiều module trong một file với từ khoá module hoặc namespace. Nếu một file không bao gồm các từ khoá modulenamespace, có nghĩa là nó là một module duy nhất.

2. Ý tưởng về module trước ES6

Ý tưởng về module đã xuất hiện từ lâu trong JavaScript, nhưng tới phiên bản ES6 (2015) nó mới được hỗ trợ ở mức độ ngôn ngữ bằng cách thêm vào các từ khoá mới như import, export, module. Trước phiên bản ES6 (2015) đã có rất nhiều đặc tả module được tạo ra như CommonJS, AMD, UMD,.. mục đích của chúng là thiết lập các quy ước về hệ sinh thái module. Sau đó, đã có rất nhiều các thư viện triển khai các đặc tả nói trên ra đời.
Một đoạn code tạo và sử dụng module theo tiêu chuẩn CommonJS:
// package/lib is a dependency we require
var lib = require( "package/lib" );

// behaviour for our module
function foo(){
    lib.log( "hello world!" );
}
// export (expose) foo to other modules
exports.foo = foo;
Các đặc tả và thư viện cũ đôi khi làm chúng ta bối rối, giống như đang đi lạc vào một mê cung. Bài viết dưới đây giới thiệu về lịch sử module trong JavaScript. Nó giúp bạn có cái nhìn tổng quan về module và tránh xa việc học các thư viện đã lỗi thời:

3. Từ khoá export

Cũng giống như JavaScript, mã của TypeScript có phạm vi toàn cục theo mặc định. Nếu bạn có nhiều file trong một dự án. Các biến, các hàm,... được viết trong một file có thể được nhận biết và sử dụng trong các file khác. Để đơn giản hãy xem một ví dụ:
file1.ts
var greeting : string = "Hello World!";

function sayHello(name: string) {
    console.log(`Hello $name`);
}
file2.ts
sayHello('Tom');
console.log(greeting);
// Set new value to greeting
greeting = 'Hi';
Trong ví dụ trên file file1.ts định nghĩa biến greeting và hàm sayHello(string), chúng có thể được được nhận biết và sử dụng trong file file2.ts. Ngoài ra, biến greeting có thể được gán một giá trị mới tại bất kỳ nơi nào trong ứng dụng, điều này có thể dẫn tới xung đột hoặc lỗi cho ứng dụng.
Từ khoá export
Từ khoá export được sử dụng để ngăn chặn "phạm vi toàn cục mặc định" (default global scope) của mã TypeScript. Cách sử dụng là: Đặt từ khoá export ở phía trước những "thứ" được định nghĩa ở Top-level của file.
Chúng ta viết lại mã trong file1.ts với sự tham gia của từ khoá export:
file1.ts (*)
export var greeting : string = "Hello World!";

export function sayHello(name: string) {
    console.log(`Hello $name`);
}
Sau khi sửa đổi mã, file1.ts được coi là một module.
Lúc này, trình biên dịch sẽ thông báo lỗi trên file2.ts:
file2.ts (*)
sayHello('Tom');  // ERROR: Cannot find name 'sayHello'.

console.log(greeting);  // ERROR: Cannot find name 'greeting'.
// Set new value to greeting
greeting = 'Hi'; // ERROR: Cannot find name 'greeting'.

4. Từ khoá import

Từ khoá import được sử dụng để nhập khẩu những "thứ" đã được định nghĩa với từ khoá export của một file nào đó.
Cú pháp:
// Import multiple 'things':
import { export_name1, export_name2 } from "file_path_without_extension"

// Import only one 'thing':
import  export_name1 from "file_path_without_extension"

// Import all 'things' into a variable.
import  * as variable_name from "file_path_without_extension"

// Import with alias name:
import { export_name1 as alias_name1, export_name2 } from "file_path_without_extension"
Ví dụ:
file1.ts
export var greeting : string = "Hello World!";

export function sayHello(name: string) {
    console.log(`Hello $name`);
}
file2.ts
import {sayHello, greeting} from './file1';

sayHello('Tom');   
console.log(greeting);
Ví dụ: Nhập khẩu tất cả vào một biến:
file2b.ts
import * as f1 from './file1';

f1.sayHello('Tom');   
console.log(f1.greeting);
Ví dụ: Nhập khẩu với tên bí danh:
file2c.ts
import {sayHello as hello, greeting} from './file1';

hello('Tom');   
console.log(greeting);

5. Từ khoá module/namespace

Trong JavaScript ES6, từ khoá module được sử dụng để định nghĩa một module, điều này cho phép bạn định nghĩa nhiều module trong một file. TypeScript sử dụng từ khoá namespace với cú pháp và mục đích tương tự.
Chú ý: Cả hai từ khoá "module""namespace" đều có thể sử dụng trong TypeScript với cú pháp và mục đích sử dụng tương tự nhau. Tuy nhiên các tài liệu TypeScript không khuyến khích sử dụng từ khoá "module" trong mã TypeScript, nó chỉ nên dùng trong mã JavaScript.
Trong TypeScript từ khoá "namespace" mạnh hơn một chút so với từ khoá "module". Bạn có thể định nghĩa một module trên nhiều file với từ khoá "namespace" và sử dụng cờ "--outFile" nếu muốn chúng ghép lại với nhau thành một file duy nhất tại thời điểm biên dịch.
Trong ví dụ dưới đây, chúng ta định nghĩa 2 module trong một file.
my_common_libs.ts
export namespace MyMathLib  {
    export function minus(a: number, b: number)  {
        return a - b;
    }
    export function sum(a: number, b: number)  {
        return a + b;
    }
}

export namespace MyStringLib {
    export var LIB_INFO = "String Lib";
    var AUTHOR = 'tran'; // Use only in module.

    export function toUpperCase(s:string)  {
        return s.toUpperCase;
    }
}
Trong một file khác, chúng ta sử dụng các module nói trên.
use_my_common_libs_ex1.ts
import {MyMathLib, MyStringLib} from './my_common_libs';

let value = MyMathLib.minus(1, 2);
console.log(`value = ${value}`);
console.log('MyStringLib info: ' + MyStringLib.LIB_INFO);
Xem bài viết chuyên sâu về TypeScript Namespaces:

6. Sử dụng module trong HTML

Về cơ bản, hầu hết các trình duyệt hiện đại đều hỗ trợ module và tích hợp sẵn một Module Loader, vì vậy chúng ta không phải sử dụng thêm bất kỳ một thư viện bổ xung nào. Đoạn mã dưới đây là cách để thông báo với trình duyệt rằng nguồn JavaScript này là một module:
test.html
<html>
   <head>
      <!-- Important: type='module' -->
      <script type="module" src="./dist/file2.js"></script>
   </head>
   <body>
      <h3>To test, you need to run this file on an HTTP or HTTPS server.
         And see the results on the browser Console.
      </h3>
   </body>
</html>
Để test thành công ví dụ trên, bạn phải chạy file test.html trên một máy chủ HTTP hoặc HTTPS, và xem kết quả trên cửa sổ Console của trình duyệt.