Monday, April 23, 2018

Hiểu về Module trong ES6

Bài viết được dịch từ : https://www.sitepoint.com/understanding-es6-modules/

Bài viết này tìm hiểu về module của ES6, cách chúng đang được sử dụng ngày nay với sự trợ giúp của trình chuyển đổi(transpiler).
Hầu như mọi ngôn ngữ đều có khái niệm về các module - một cách để bọc các function được khai báo trong một file khác. Thông thường, một developer tạo ra một thư viện mã hóa đóng gói chịu trách nhiệm xử lý các nhiệm vụ liên quan. Thư viện đó có thể được tham chiếu bởi các ứng dụng hoặc các module khác.

Những lợi ích:

  1. Code thể được chia thành các file nhỏ hơn với những chức năng độc lập.

  2. Các module tương tự có thể được chia sẻ trong rất nhiều ứng dụng khác nhau.

  3. Lý tưởng nhất, các module không bao giờ được kiểm tra bởi một nhà phát triển khác, bởi vì chúng đã được chứng minh là hoạt động.

  4. Code đã chiếu một module được hiểu rằng đó là sự phụ thuộc. Nếu tập tin module được thay đổi hoặc di chuyển, sẽ xảy ra vấn đề ngay lập tức.

  5. Code module (thường) giúp xóa bỏ xung đột đặt tên. Function x () trong module1 không thể đụng độ với function x () trong module2. Các tùy chọn như namespace được sử dụng , và ta sẽ có module1.x ()module2.x ().

Vậy các module trong JavaScript ở đâu?


Bất cứ ai bắt đầu phát triển Web một vài năm trước đây sẽ bị sốc khi phát hiện ra không có khái niệm về các module trong JavaScript. Không thể trực tiếp tham chiếu (reference) hoặc bao gồm (include) một file JavaScript trong một file Javascript khác. Do đó, các developer đã sử dụng các tùy chọn thay thế.

Nhiều thẻ <script> HTML


HTML có thể tải bất kỳ tệp JavaScript số nào bằng cách sử dụng nhiều thẻ <script>:





Trung bình trong năm 2018 một trang web sử dụng 25 tập lệnh riêng biệt, nhưng đó không phải là giải pháp tối ưu:

  • Mỗi tập lệnh khởi tạo một yêu cầu HTTP mới, ảnh hưởng đến hiệu suất trang. HTTP/2 làm giảm bớt vấn đề ở một mức độ nào đó, nhưng nó không giúp các tập lệnh được tham chiếu trên các tên miền khác như CDN.

  • Mỗi tập lệnh tạm dừng xử lý thêm trong khi tập lệnh chạy.

  • Quản lý phụ thuộc (dependencies) là một quy trình thủ công. Trong đoạn mã trên, nếu lib1.js tham chiếu mã trong lib2.js, mã sẽ thất bại vì nó chưa được tải. Điều đó có thể phá vỡ quá trình thực thi của JavaScript.

  • Chức năng có thể ghi đè lên những người khác trừ khi những mô hình module( module patterns) thích hợp được sử dụng. Ban đầu các thư viện JavaScript nổi tiếng hay sử dụng các tên hàm toàn cục hoặc ghi đè các phương thức nguyên gốc.

Nối các file script lại ( Script Concatenation )


Một giải pháp cho các vấn đề của nhiều thẻ <script> là ghép tất cả các tệp JavaScript vào một tệp lớn duy nhất. Điều này giải quyết một số vấn đề về hiệu suất và quản lý phụ thuộc, nhưng nó có thể phải qua một bước xây dựng và thử nghiệm thủ công.

Trình tải module (Module Loaders)


Các hệ thống như RequireJSSystemJS cung cấp một thư viện để tải và đặt tên các thư viện JavaScript khác khi chạy. Các module được tải bằng cách sử dụng các hàm của Ajax khi được yêu cầu. Các hệ thống trợ giúp, nhưng có thể trở nên phức tạp đối với các cơ sở mã lớn hơn hoặc các trang web thêm các thẻ <script> chuẩn vào hỗn hợp.

Module Bundlers, Preprocessors và Transpilers


Bundlers (trình đóng gói) giới thiệu một cách để biên dịch mã JavaScript được tạo ra tại quá trình built. Mã được xử lý để đóng gói các thư viện và tạo ra một tệp ghép nối tương thích với trình duyệt của ES5. Các tùy chọn phổ biến bao gồm Babel, Browserify, webpack và các trình chạy tác vụ phổ biến hơn như GruntGulp.

Quá trình tạo ra một file JavaScript đòi hỏi một số nỗ lực, nhưng có những lợi ích:

  • Quá trình xử lý được tự động hóa do đó ít có khả năng xảy ra lỗi của con người hơn.

  • Xử lý thêm có thể linter( kiểm tra chính tả và ngữ pháp), gỡ bỏ các lệnh gỡ lỗi ( debugger), rút ​​gọn tệp kết quả, v.v.

  • Transpiling cho phép bạn sử dụng các cú pháp thay thế như TypeScript hoặc CoffeeScript.

Module ES6


Các tùy chọn ở trên giới thiệu một loạt các định dạng định nghĩa module cạnh tranh. Cú pháp được chấp nhận rộng rãi bao gồm:

  • CommonJS - module.exports và required được sử dụng trong Node.js

  • Định nghĩa module bất đồng bộ (AMD).

  • Định nghĩa module toàn cục (UMD).

Vì thế, một tiêu chuẩn về module được hỗ trợ mặc định trong ES6 (ES2015) đã được đề xuất.

Mọi thứ bên trong module ES6 đều được đặt ở chế độ riêng tư theo mặc định và chạy ở chế độ nghiêm ngặt (không cần sử dụng 'use restrict'). Các biến, hàm và lớp công muốn được công khai phải sử dụng từ khóa export.

Ví dụ:



Một ví dụ khác chỉ sử dụng một từ khóa export  duy nhất:



Import sẽ được sử dụng để load những thành phần từ file khác vào.



Trong trường hợp này, lib.js nằm trong cùng thư mục với main.js. Tham chiếu tệp tuyệt đối (bắt đầu bằng /), các tham chiếu tệp tương đối (bắt đầu ./ hoặc ../) hoặc URL đầy đủ có thể được sử dụng.

Nhiều thành phần có thể được import vào cùng một lúc:



import có thể được bí danh (allias) để giải quyết vấn đề trùng tên:



Cuối cùng, tất cả các thành phần công khai có thể được import bằng cách cung cấp một không gian tên:


Sử dụng ES6 Modules trên những trình duyệt


Tại thời điểm viết bài, ES6 đã hỗ trợ module trên các trình duyệt dựa trên Chromium (v63 +), Safari 11+ và Edge 16+.Firefox sẽ hỗ trợ mặc định từ phiên bản 60 (phải bật nó lên bằng mở about:config đối với các phiên bản v58 +).

Các tập lệnh sử dụng mô-đun phải được tải bằng cách đặt thuộc tính type = "module" trong thẻ <script>. Ví dụ:



Hoặc inline script



Module được phân tích cú pháp (parse) một lần, bất kể số lần chúng được tham chiếu trong trang hoặc các mô-đun khác.

Những vấn đề có thể gặp với server


module phải được phân phát bằng application/javascript MIME. Hầu hết các máy chủ sẽ tự động thực hiện việc này, nhưng hãy cảnh giác với các tập lệnh được tạo động hoặc tệp .mjs (xem phần Node.js bên dưới).

Các thẻ <script> thông thường có thể tìm nạp tập lệnh trên các tên miền khác nhưng các mô-đun được tìm nạp bằng cách sử dụng chia sẻ tài nguyên gốc (CORS). Do đó, các mô-đun trên các miền khác nhau phải đặt tiêu đề HTTP thích hợp, chẳng hạn như Access-Control-Allow-Origin: *.

Cuối cùng, các mô-đun sẽ không gửi cookie hoặc thông tin đăng nhập khác trừ khi thuộc tính crossorigin = "use-credentials" được thêm vào thẻ <script> và phản hồi chứa tiêu đề Access-Control-Allow-Credentials: true.



Sự thực thi mô-đun  đã được hoãn lại

Việc thực thi kịch bản lệnh <script defer> được trì hoãn cho đến khi file được tải và phân tích cú pháp. module - bao gồm cả tập lệnh inline script - trì hoãn theo mặc định. Thí dụ:


Chưa hỗ trợ module


Các trình duyệt không có hỗ trợ module sẽ không chạy tập lệnh type = "module". Một kịch bản dự phòng có thể được cung cấp với một thuộc tính nomodule mà các trình duyệt tương thích module bỏ qua. Ví dụ:


Bạn có nên sử dụng các mô-đun trong trình duyệt không?


Những trình duyệt hỗ trợ đang nhiều dần lên, nhưng có thể hơi sớm để chuyển sang module ES6. Hiện tại, có lẽ tốt hơn nên sử dụng trình đóng gói module để tạo tập lệnh hoạt động ở mọi nơi.

Sử dụng mô-đun ES6 trong Node.js


Khi Node.js được phát hành vào năm 2009, sẽ không để tưởng tượng ra được là nó không có module. CommonJS đã được thông qua, có nghĩa là trình quản lý gói Node, npm, có thể được phát triển. Mức sử dụng tăng theo cấp số nhân từ thời điểm đó.

Một module CommonJS có thể được mã hóa theo cách tương tự với module ES2015. module.exports được sử dụng thay export:



require (thay vì import) được sử dụng để kéo module này vào một tập lệnh hoặc module khác



require có thể được dùng để import tất cả các thành phần công khai:



Vì vậy, các module ES6 dễ thực hiện trong Node.js, phải không? Er, không.

Module ES6 nằm phía sau cờ trong Node.js 9.8.0+ và sẽ không được triển khai đầy đủ cho đến ít nhất là phiên bản 10. Trong khi các module CommonJS và ES6 chia sẻ cú pháp tương tự, chúng hoạt động theo các cách khác nhau cơ bản:

  • Các module ES6 được phân tích cú pháp trước để phân tích thêm các thành phần cần import trước khi mã được thực hiện.

  • Các module CommonJS tải các phụ thuộc theo yêu cầu trong khi thực thi mã.

Nó sẽ không tạo sự khác biệt trong ví dụ trên, nhưng hãy xem xét mã module ES2015 sau:



Thứ tự thực thi có thể quan trọng trong một số ứng dụng, và điều gì sẽ xảy ra nếu các mô đun ES2015 và CommonJS được trộn lẫn trong cùng một tệp? Để giải quyết vấn đề này, Node.js sẽ chỉ cho phép các module ES6 trong các tệp có phần mở rộng .mjs. Các tệp có phần mở rộng .js sẽ mặc định là CommonJS. Đó là một tùy chọn đơn giản, loại bỏ phần lớn sự phức tạp và phải hỗ trợ các trình chỉnh sửa mã và các trình soạn thảo.

Bạn có nên sử dụng module ES6 trong Node.js không?


Module ES6 chỉ thực tế từ Node.js v10 trở đi (được phát hành vào tháng 4 năm 2018). Việc chuyển đổi một dự án hiện có không có khả năng dẫn đến bất kỳ lợi ích nào và sẽ khiến cho một ứng dụng không tương thích với các phiên bản trước của Node.js.

Đối với các dự án mới, các mô đun ES6 cung cấp một thay thế cho CommonJS. Cú pháp giống hệt với mã hóa phía máy khách và có thể cung cấp một tuyến đường dễ dàng hơn cho những ửng dụng được viết bằng JavaScript từ server đến client (isomorphic JavaScript).

Tương lai gần của module


Một hệ thống module JavaScript chuẩn hóa mất nhiều năm để đến hoàn thành, và thậm chí còn lâu hơn để thực hiện, nhưng các vấn đề đã được sửa chữa. Tất cả các trình duyệt chính và Node.js từ giữa năm 2018 đều hỗ trợ các module ES6, mặc dù sự chậm trễ chuyển đổi sẽ được mong đợi trong khi mọi người nâng cấp.

Tìm hiểu các module ES6 sẽ giúp ích cho con đường Javascript Developer của bạn trong tương lai.