HomeLập trìnhJavaScriptCác mô-đun trong...

Các mô-đun trong JavaScript – Giải thích về CommonJS và ESmodules


Chào mọi người! Trong bài viết này, chúng ta sẽ xem xét các mô-đun trong JavaScript.

Các mô-đun là một kỹ thuật được sử dụng nhiều trong thiết kế/kiến trúc phần mềm ngày nay.

Trước tiên, chúng ta sẽ tìm hiểu chúng là gì và các loại mô-đun khác nhau đang tồn tại. Sau đó, chúng ta sẽ thảo luận tại sao các mô-đun lại hữu ích. Sau đó, chúng ta sẽ xem các ví dụ và cú pháp cơ bản cho các loại mô-đun được sử dụng nhiều nhất và cuối cùng chúng ta sẽ thảo luận về gói, tại sao lại cần thiết và cách thực hiện.

Cháchara đủ rồi, đi nào! =D

Mục lục

Mô-đun chỉ là một đoạn mã trong một tệp mà bạn có thể gọi và sử dụng từ các tệp khác. Thiết kế kiểu mô-đun ngược lại với việc có tất cả mã dự án của bạn trong một tệp duy nhất.

Khi phát triển một dự án lớn, sẽ rất hữu ích khi chia mã của chúng ta thành các mô-đun vì những lý do sau:

  • Thật tốt khi chia các mối quan tâm và tính năng thành các tệp khác nhau, giúp trực quan hóa và tổ chức mã.
  • Mã có xu hướng dễ bảo trì hơn và ít bị lỗi khi được tổ chức rõ ràng.
  • Các mô-đun có thể được sử dụng và tái sử dụng dễ dàng trong các tệp và các phần khác nhau của dự án của chúng tôi mà không cần phải viết lại cùng một mã.

Thay vì có tất cả các thành phần của chương trình trong một tệp duy nhất, chúng tôi có thể chia nó thành các phần hoặc mô-đun và để mỗi phần chịu trách nhiệm về một tính năng/mối quan tâm duy nhất.

Nếu khái niệm này không đủ rõ ràng bây giờ, đừng lo lắng. Chúng ta sẽ thấy một số ví dụ trong giây lát.

Như với hầu hết mọi thứ trong cuộc sống và đặc biệt là trong JavaScript, có nhiều cách để chúng ta triển khai các mô-đun.

Vì JavaScript lần đầu tiên được tạo ra chỉ là một ngôn ngữ kịch bản nhỏ dành cho các trang web, một tính năng dành cho các dự án lớn như mô-đun không được hỗ trợ ngay từ đầu.

Nhưng khi ngôn ngữ và hệ sinh thái phát triển, các nhà phát triển bắt đầu nhận thấy sự cần thiết của tính năng này. Và do đó, các tùy chọn và thư viện khác nhau đã được phát triển để thêm tính năng này vào JavaScript.

Trong số nhiều cái có sẵn, chúng ta sẽ chỉ xem xét CommonJS và ESmodules, đây là những cái gần đây nhất và được sử dụng rộng rãi.

Nhận xét bên lề: Bạn có biết rằng Javascript ban đầu được tạo ra chỉ trong 10 ngày làm việc không?

Khi phân tích sự phức tạp của JavaScript và hiểu ngôn ngữ này đã phát triển như thế nào, tôi nghĩ điều quan trọng cần lưu ý là ngôn ngữ ban đầu không được tạo ra để làm những gì nó làm ngày nay. Chính sự phát triển của hệ sinh thái Javascript đã thúc đẩy nhiều thay đổi xảy ra.

mô-đun CommonJS

CommonJS là một bộ tiêu chuẩn được sử dụng để triển khai các mô-đun trên JavaScript. Dự án được bắt đầu bởi kỹ sư Mozilla Kevin Dangoor vào năm 2009.

CommonJS chủ yếu được sử dụng trong các ứng dụng JS phía máy chủ với Node, vì các trình duyệt không hỗ trợ việc sử dụng CommonJS.

Là một nhận xét phụ, trước đây Node chỉ hỗ trợ CommonJS để triển khai các mô-đun, nhưng ngày nay nó cũng hỗ trợ ESmodules, một cách tiếp cận hiện đại hơn.

Vì vậy, hãy xem CommonJS trông như thế nào trong mã thực tế.

Để triển khai các mô-đun, trước tiên bạn cần có ứng dụng Node trên máy tính của mình. Vì vậy, hãy tạo một cái bằng cách chạy npm init -y.

Đầu tiên chúng ta hãy tạo một main.js tập tin với một chức năng đơn giản trong đó.

const testFunction = () => {
    console.log('Im the main function')
}

testFunction()

Bây giờ, giả sử chúng ta muốn có một hàm khác được gọi từ tệp chính của mình, nhưng chúng ta không muốn hàm đó trong đó vì nó không phải là một phần của tính năng cốt lõi của chúng ta. Đối với điều này, hãy tạo một mod1.js tập tin và thêm mã này vào nó:

const mod1Function = () => console.log('Mod1 is alive!')
module.exports = mod1Function

module.exports là từ khóa chúng ta dùng để khai báo tất cả những gì muốn xuất ra từ file đó.

Đọc thêm  Javascript - Yêu cầu API với XMLHttpRequest, chức năng xhr.onload - lỗi bản đồ - JavaScript - Diễn đàn freeCodeCamp

Để sử dụng chức năng này trong của chúng tôi main.js tập tin, chúng ta có thể làm như thế này:

mod1Function = require('./mod1.js')

const testFunction = () => {
    console.log('Im the main function')
    mod1Function()
}

testFunction()

Thấy rằng chúng tôi khai báo bất cứ thứ gì chúng tôi muốn sử dụng và sau đó gán nó cho require của tệp chúng tôi muốn sử dụng. Miếng bánh. 😉

Nếu chúng tôi muốn xuất nhiều thứ từ một mô-đun, chúng tôi có thể làm như sau:

const mod1Function = () => console.log('Mod1 is alive!')
const mod1Function2 = () => console.log('Mod1 is rolling, baby!')

module.exports = { mod1Function, mod1Function2 }

Và trên file main.js chúng ta có thể sử dụng cả 2 hàm như sau:

({ mod1Function, mod1Function2 } = require('./mod1.js'))

const testFunction = () => {
    console.log('Im the main function')
    mod1Function()
    mod1Function2()
}

testFunction()

Và đó là khá nhiều nó. Khá đơn giản phải không? Nó đơn giản nhưng là một công cụ mạnh mẽ để sử dụng. =)

mô đun ES

Bây giờ hãy xem lại ESmodules. ESmodules là một tiêu chuẩn được giới thiệu với ES6 (2015). Ý tưởng là tiêu chuẩn hóa cách hoạt động của các mô-đun JS và triển khai các tính năng này trong trình duyệt (trước đây không hỗ trợ các mô-đun).

ESmodules là một cách tiếp cận hiện đại hơn hiện đang được hỗ trợ bởi các ứng dụng phía máy chủ và trình duyệt với Node.

Hãy xem điều này trong mã. Một lần nữa, chúng tôi bắt đầu bằng cách tạo một ứng dụng Node với npm init -y.

Bây giờ chúng tôi đi đến của chúng tôi package.json và thêm "type": "module" với nó, như thế này:

{
  "name": "modulestestapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "module"
}

Nếu chúng tôi không làm điều này và cố gắng triển khai ESmodules trên Node, chúng tôi sẽ gặp lỗi như sau:

(node:29568) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
...
SyntaxError: Cannot use import statement outside a module

Bây giờ hãy lặp lại chính xác ví dụ đó. trong chúng tôi main.js file chúng ta sẽ có đoạn mã sau:

// main.js
import { mod1Function } from './mod1.js'

const testFunction = () => {
    console.log('Im the main function')
    mod1Function()
}

testFunction()

Và hơn thế nữa mod1.js chúng ta sẽ có cái này:

// mod1.js
const mod1Function = () => console.log('Mod1 is alive!')
export { mod1Function }

thông báo thay vì require Đang sử dụng import và thay vì module.exports Đang sử dụng export. Cú pháp hơi khác một chút nhưng hành vi rất giống nhau.

Một lần nữa, nếu chúng tôi muốn xuất nhiều thứ từ cùng một tệp, chúng tôi có thể làm như sau:

// main.js
import { mod1Function, mod1Function2 } from './mod1.js'

const testFunction = () => {
    console.log('Im the main function')
    mod1Function()
    mod1Function2()
}

testFunction()
// mod1.js
const mod1Function = () => console.log('Mod1 is alive!')
const mod1Function2 = () => console.log('Mod1 is rolling, baby!')

export { mod1Function, mod1Function2 }

Một tính năng khác có sẵn trong ESmodules là đổi tên nhập khẩu, có thể được thực hiện như sau:

// main.js
import { mod1Function as funct1, mod1Function2 as funct2 } from './mod1.js'

const testFunction = () => {
    console.log('Im the main function')
    funct1()
    funct2()
}

testFunction()

Lưu ý rằng chúng tôi sử dụng as từ khóa sau mỗi chức năng, sau đó đổi tên nó theo cách chúng tôi muốn. Sau này trong mã của chúng tôi, chúng tôi có thể sử dụng tên mới đó thay vì tên ban đầu mà quá trình nhập có. 😉

Một điều khác bạn có thể làm là nhập tất cả các bản xuất cùng nhau và đặt chúng cùng nhau trong một đối tượng, như sau:

// main.js
import * as mod1 from './mod1.js' 

const testFunction = () => {
    console.log('Im the main function')
    mod1.mod1Function()
    mod1.mod1Function2()
}

testFunction()

Điều này có thể hữu ích trong các trường hợp khi, trong toàn bộ mã của chúng tôi, chúng tôi muốn nói rõ ràng về nguồn gốc của mỗi lần nhập. Xem các chức năng hiện đang được gọi như mod1.mod1Function().

Điều cuối cùng đáng nói là default từ khóa. Với nó, chúng ta có thể đặt xuất mặc định cho một mô-đun nhất định. Như thế này:

// mod1.js
const mod1Function = () => console.log('Mod1 is alive!')
const mod1Function2 = () => console.log('Mod1 is rolling, baby!')

export default mod1Function
export { mod1Function2 }

Và việc xuất mặc định có nghĩa là gì? Điều đó có nghĩa là chúng ta không phải hủy cấu trúc khi nhập. Chúng ta có thể sử dụng nó như thế này:

// main.js
import mod1Function, { mod1Function2 } from './mod1.js' 

const testFunction = () => {
    console.log('Im the main function')
    mod1Function()
    mod1Function2()
}

testFunction()

Chúng tôi thậm chí có thể đổi tên nhập bất cứ thứ gì chúng tôi muốn mà không cần as từ khóa, vì JavaScript “biết” rằng nếu chúng tôi không phá hủy, chúng tôi sẽ đề cập đến việc nhập mặc định.

// main.js
import lalala, { mod1Function2 } from './mod1.js' 

const testFunction = () => {
    console.log('Im the main function')
    lalala()
    mod1Function2()
}

testFunction()

Và điều đó cũng tổng hợp khá nhiều về ESmodules. Đơn giản tôi hy vọng. =)

Đọc thêm  Ví dụ về JavaScript toString – Cách chuyển đổi một số thành một chuỗi trong JS và hơn thế nữa

Bây giờ chúng ta đã hiểu rõ về các loại mô-đun khác nhau có sẵn và cách chúng hoạt động, hãy xem cách chúng ta có thể triển khai các mô-đun trong một trang web bằng cách sử dụng HMTL và Vanilla JS.

Hãy tạo một tệp HTML đơn giản có tiêu đề, hai nút và thẻ script liên kết với main.js tập tin.

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>I'm just a test...</h1>
    <button id="isAlive">Is mod1 alive?</button>
    <button id="isRolling">Is mod1 rolling?</button>
    <script src="https://www.freecodecamp.org/news/modules-in-javascript/./main.js" type="module"></script>
</body>
</html>

Hãy chú ý đến thực tế là tôi đang tuyên bố type="module" trên thẻ tập lệnh. Chúng ta cần làm điều này để sử dụng tính năng mô-đun JS. Nếu không, chúng ta sẽ gặp lỗi như thế này:

Uncaught SyntaxError: Cannot use import statement outside a module

Nếu chúng tôi mở tệp HTML của mình, chúng tôi sẽ nhận được một cái gì đó như thế này:
ảnh chụp màn hình-2

Của chúng ta main.js tập tin sẽ có mã này:

// main.js
import { mod1Function, mod1Function2 } from './mod1.js'

const testFunction = () => console.log('Im the main function')

document.getElementById('isAlive').addEventListener('click', () => mod1Function())
document.getElementById('isRolling').addEventListener('click', () => mod1Function2())

testFunction()

Chúng tôi chỉ thêm một trình xử lý sự kiện nhấp vào mỗi nút để các chức năng đến từ mod1.js tập tin được thực thi.

Được rồi, bây giờ chúng ta có thể phân phối tệp HTML của mình và xem nó có hoạt động không. Chúng tôi cần phân phối tệp, chúng tôi không thể mở HTML trong trình duyệt vì chúng tôi sẽ gặp lỗi CORS như thế này:

Access to script at ... from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, brave, chrome-untrusted, https.

Để phục vụ nó một cách nhanh chóng, bạn có thể sử dụng Máy chủ trực tiếp Tiện ích mở rộng mã VS hoặc tạo ứng dụng Nút bằng cách chạy npm init -y và sau đó chạy npx serve.

Dù sao, sau khi tệp được phục vụ, chúng tôi có thể nhấp vào từng nút và kiểm tra xem các chức năng của chúng tôi có thực thi chính xác hay không. Bảng điều khiển của chúng ta sẽ trông như thế này:
ảnh chụp màn hình_1-1

Nhưng có một điều nữa về điều này. Nếu chúng ta chuyển đến tab mạng của các công cụ dành cho nhà phát triển của trình duyệt và lọc theo các tệp JS, chúng ta có thể thấy rằng trang web đang tải hai tệp, main.jsmod1.js:
ảnh chụp màn hình_3

Tất nhiên, nếu chúng ta định sử dụng mã bên trong mỗi tệp, thì cả hai cần phải được tải – nhưng đây không phải là điều tốt nhất nên làm. Đó là bởi vì trình duyệt cần thực hiện hai yêu cầu khác nhau để tải tất cả JS cần thiết.

Chúng ta nên luôn cố gắng giảm các yêu cầu xuống mức tối thiểu để tăng hiệu suất cho các dự án của mình. Vì vậy, hãy xem cách chúng ta có thể làm điều này với sự trợ giúp của gói mô-đun.

Nhận xét bên lề: nếu bạn muốn có video giải thích, Kent C Dodds có một video tuyệt vời. Tôi thực sự khuyên bạn nên theo dõi anh ấy, anh ấy là một trong những giáo viên JS giỏi nhất hiện có. Và đây là một video thú vị khác của Fireship. 😉

Như đã đề cập trước đây, việc chia mã của chúng tôi thành các mô-đun là điều tốt vì cơ sở mã của chúng tôi sẽ được tổ chức tốt hơn và việc sử dụng lại mã của chúng tôi sẽ dễ dàng hơn.

Nhưng đây chỉ là những lợi thế cho giai đoạn phát triển của một dự án. Khi ở trong sản xuất, các mô-đun không phải là điều tốt nhất, vì việc buộc trình duyệt đưa ra yêu cầu cho từng tệp JS có thể ảnh hưởng đến hiệu suất của trang web.

Vấn đề này có thể được giải quyết dễ dàng bằng cách sử dụng gói mô-đun. Nói một cách đơn giản, bộ đóng gói mô-đun là các chương trình lấy các mô-đun JS làm đầu vào và kết hợp chúng thành một tệp duy nhất (nhiều bộ đóng gói mô-đun có nhiều tính năng hơn nhưng đó là khái niệm cốt lõi của chúng).

Nhờ đó, với tư cách là nhà phát triển, chúng tôi có thể viết mã dự án của mình để chia dự án thành các phần được tổ chức độc đáo, sau đó chạy trình đóng gói mô-đun để lấy mã cuối cùng sẽ được sử dụng trong sản xuất.

Đọc thêm  JavaScript 设计模式

Bước chuyển đổi “mã phát triển” thành “mã sản xuất” này thường được công nhận là “xây dựng”.

Có nhiều tùy chọn để sử dụng cho việc này (như Browserify, Parcel, Rollup.js, Snowpack…) nhưng được sử dụng rộng rãi nhất là Webpack. Vì vậy, hãy xem một ví dụ sử dụng Webpack.

  • Nhận xét bên lề 1: Nếu bạn muốn tìm hiểu sâu hơn về các gói mô-đun và cách chúng hoạt động, video tuyệt vời này của Fireship có thể là một nơi tốt để bắt đầu.
  • Nhận xét bên lề 2: Webpack là một công cụ rất mạnh mẽ và tinh vi có thể làm được nhiều việc ngoài việc đóng gói các tệp JS. Kiểm tra tài liệu của họ nếu bạn muốn tìm hiểu thêm.

Tuyệt, vậy bây giờ chúng ta có thể bắt đầu bằng cách tạo một ứng dụng Node (nếu bạn chưa có) bằng cách chạy npm init -y. Sau đó, chúng tôi sẽ cần cài đặt Webpack và Webpack CLI bằng cách chạy npm i --save-dev webpack webpack-cli.

Tiếp theo chúng ta sẽ tạo một webpack.config.js tập tin và đặt mã này bên trong nó:

/* webpack.config.js */
const path = require('path');

module.exports = {
  entry: "https://www.freecodecamp.org/news/modules-in-javascript/./main.js",
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
};

Tệp này sẽ chịu trách nhiệm về cấu hình của Webpack và cách thức hoạt động của nó trong ứng dụng của chúng ta.

Những gì chúng tôi đang làm ở đây trước tiên là thiết lập tệp mục nhập (entry: "https://www.freecodecamp.org/news/modules-in-javascript/./main.js"). Webpack sẽ bắt đầu bằng cách đọc tệp đó và sau đó phân tích tất cả các phần phụ thuộc (các mô-đun được nhập từ tệp đó). Nói cách khác, tệp mục nhập là tệp JS chính của chúng tôi, nơi tất cả các mô-đun khác được nhập.

Sau đó, chúng tôi đang khai báo đầu ra – đầu tiên khai báo đường dẫn nơi nó sẽ được lưu trữ và sau đó khai báo tên của tệp được đóng gói.

output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
},

Siêu! Bây giờ chúng ta hãy đi đến của chúng tôi package.json tập tin và thêm một build kịch bản, như thế này:

{
  "name": "testappv2",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^5.72.0",
    "webpack-cli": "^4.9.2"
  }
}

Sau đó, chúng tôi có thể quay lại thiết bị đầu cuối của mình và chạy npm run build. Điều đó sẽ tạo ra một dist thư mục trong dự án của chúng tôi, và bên trong nó một bundle.js tập tin.

Nếu bạn kiểm tra tệp đó, bạn sẽ thấy mã này trong đó:

(()=>{"use strict";document.getElementById("isAlive").addEventListener("click",(()=>console.log("Mod1 is alive!"))),document.getElementById("isRolling").addEventListener("click",(()=>console.log("Mod1 is rolling, baby!"))),console.log("Im the main function")})();

Bạn sẽ thấy rằng đó thực tế là cùng một mã mà chúng tôi đã phân phối trong các tệp của mình, nhưng tất cả được gói gọn trong một tệp duy nhất và được rút gọn.

Điều duy nhất còn lại là thay đổi thẻ script trong index.html để nó sử dụng JS được đóng gói ngay bây giờ, như thế này:

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>I'm just a test...</h1>
    <button id="isAlive">Is mod1 alive?</button>
    <button id="isRolling">Is mod1 rolling?</button>
    <script src="https://www.freecodecamp.org/news/modules-in-javascript/./dist/bundle.js" type="module"></script>
</body>
</html>

Bây giờ chúng ta có thể phục vụ lại nó, kiểm tra xem JS có còn hoạt động hoàn hảo không và nếu chúng ta mở lại tab mạng, chúng ta sẽ thấy chỉ một tệp duy nhất đang được tải! =Đ
ảnh chụp màn hình_2-1

Tôi hy vọng ví dụ đơn giản này đã giúp bạn hiểu mức độ liên quan của các gói mô-đun và cách chúng giúp chúng tôi kết hợp trải nghiệm phát triển tuyệt vời của kiến ​​trúc mô-đun với hiệu suất trang web tốt.

Vâng, chúng tôi đã hoàn thành cho ngày hôm nay. Trong bài viết này, chúng ta đã biết mô-đun là gì, tại sao chúng thú vị, các cách khác nhau mà bạn có thể triển khai mô-đun trong JavaScript và một ví dụ thực tế về gói mã của chúng tôi với Webpack.

Để có hướng dẫn đầy đủ về các mô-đun JS, bạn có thể xem bài viết này.

Như mọi khi, tôi hy vọng bạn thích bài viết này và học được điều gì đó mới. Nếu muốn, bạn cũng có thể theo dõi tôi trên Linkedin hoặc Twitter.

Chúc mừng và hẹn gặp lại bạn trong phần tiếp theo! =Đ

giphy





Zik.vn – Biên dịch & Biên soạn Lại

spot_img

Create a website from scratch

Just drag and drop elements in a page to get started with Newspaper Theme.

Buy Now ⟶

Bài viết liên quang

DMCA.com Protection Status