Begini Cara Mudah Membuat Module Bundler Sendiri

0
62
Module Bundler
Module Bundler

Kamu seorang Web Developer? Pasti suka pusing melihat banyaknya module yang digunakan. Sebelumnya mari kita cari tahu pengertiannya satu persatu.

Apa itu Module?

Module adalah sebuah berkas yang berisi kode script. Module memiliki sifat khusus, yakni dapat memuat atau dimuat oleh module lainnya. Berkat sifat inilah antar module dapat saling ekspor dan impor untuk bertukar fungsi.

Masalahnya adalah saat ini sudah banyak website-website besar menggunakan banyak module didalamnya. Dengan banyaknya module yang digunakan, pasti diantaranya ada yang mempunyai fungsi yang sama.

Masalah lainnya yaitu salah urutan dalam menempatkan script, hal itu dapat membuat fungsi-fungsi yang telah dibuat, tidak berjalan sesuai rencana.

Module Bundler

Module bundler secara otomatis akan mengumpulkan semua module-module yang digunakan, mengurutkannya dengan benar dan membungkusnya menjadi satu berkas module saja.

Salah satu tools module bundler yang sering digunakan adalah Webpack. Dalam core concepts mereka menyebutkan, Webpack akan membangun sebuah dependency graph saat dijalankan. Dependency graph ini berisi pemetaan setiap module yang dibutuhkan dalam proyek dan mengeluarkan sebuah bundle module statis.

Core Concept

Untuk lebih memahami bagaimana module bundler bekerja, perlu kita pelajari dahulu bagaimana konsep dasarnya. Menurut Webpack, ada beberapa bagian dasar dari sebuah module bundler, yaitu Entry, Output, Loaders, Plugins, Mode dan Browser Compatibility [rujukan]. Namun, pada kesempatan ini kita hanya perlu menggunakan dua (2) bagian saja, yakni Entry dan Output:

  • Entry

Sebuah entry point adalah titik permulaan yang digunakan oleh module bundler sebagai acuan script mana yang perlu dibaca pertama kali dan digunakan untuk permulaan pembuatan dependency graph.

  • Output

Properti output memberitahukan di mana module bundler harus menyimpan hasil bundler yang telah dikumpulkan dan menentukan nama bundle-nya.

Standarisasi Module

Pertama, kita perlu membuat ulang setiap module yang telah dibaca. Struktur standar sebuah module terdiri dari beberapa properti, yaitu: id modul, lokasi berkas, script kode, dependensi, peta dependensi. Perhatikan contoh module berikut:

1
2
3
4
5
6
7
8
const module = {
  id: ,
  filePath: “.src/entry.js”,
  code: `import message from ‘./message.js’;
console.log(message);`,
  dependencies: [“./message.js”],
  dependendyMap: { “./message.js”: module1 }
};

 

Karena banyaknya module yang digunakan, proses ini tidak mungkin kita lakukan secara manual. Untuk itu, pada berkas bundler.js, isikan kode berikut:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const fs = require(“fs”);
const { entry, output } = require(“./webpack.config”);
let ID = ;
createModule(entry);
function createModule(filePath) {
  const content = fs.readFileSync(filePath, “utf8”);
  console.log(content);
  return {
    id: ID++,
    filePath
    // code, Todo 1
    // dependencies, Todo 2
    // dependencyMap: {}, Todo 3
  };
}

Mengisi Code

Sampai pada tahap ini kita sudah mengisikan ID dan filePath pada module baru yang dibuat. Selanjutnya untuk bagian code isikan content file yang telah dibaca. Namun, karena content tersebut masih berformat Javascript ES6, kita perlu seragamkan menjadi format CommonJS.

ES6 Module

1
2
3
4
5
// define dependency by import statement
import { message } from “./message”;
// expose the module by export statement
export default alertBtn;

 

CommonJS Module

1
2
3
4
5
// require to define dependency
const moduleName = require(“./dependency”);
// expose module by module.exports or exports
module.exports = “something”;

Untuk mengubah format Javascript ES6 ke CommonJS, kita dapat menggunakan tools babel transformSync seperti berikut ini:

Baca juga:   5 Rekomendasi CSS Framework Terbaik Yang Bisa Kalian Gunakan
1
const { code } = transformSync(content, { presets: [‘@babel/preset-env’] })

Sehingga, hasil akhirnya menjadi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const fs = require(“fs”);
const { entry, output } = require(“./webpack.config”);
const { transformSync } = require(“@babel/core”);
let ID = ;
createModule(entry);
function createModule(filePath) {
  const content = fs.readFileSync(filePath, “utf8”);
  const { code } = transformSync(content, { presets: [“@babel/preset-env”] });
  return {
    id: ID++,
    filePath,
    code
    // dependencies, Todo 2
    // dependencyMap: {}, Todo 3
  };
}

Mengumpulkan Dependencies

Untuk mengumpulkan semua dependency apa saja yang digunakan oleh sebuah module, kita dapat memanfaatkan fitur Abstract Syntax Tree atau AST. Sebagai gambaran seperti apa itu AST, silakan lihat di website astexplorer.net melalui url berikut: https://bit.ly/2sc1PBI

Dapat kita lihat di gambar bahwa AST membaca kode yang diberikan, kemudian mendapati bahwa kode tersebut memiliki dependency ke module “./message”. Untuk mendapatkan informasi AST di script module bundler, kita dapat memanfaatkan fungsi babel parseSync. Kemudian kita juga bisa memanfaatkan babel traverse untuk mendapatkan nama module dependency-nya.

Hasilnya menjadi seperti berikut:

const fs = require("fs");
const { entry, output } = require("./webpack.config");
const { traverse, transformSync, parseSync } = require("@babel/core");
let ID = 0;
createModule(entry);
function createModule(filePath) {
const content = fs.readFileSync(filePath, "utf8");
const { code } = transformSync(content, { presets: ["@babel/preset-env"] });
const dependencies = [];
const abstractSyntaxTree = parseSync(content, { sourceType: "module" });
traverse(abstractSyntaxTree, {
ImportDeclaration: declare => {
dependencies.push(declare.node.source.value);
}
});
return {
id: ID++,
filePath,
code,
dependencies
// dependencyMap: {}, Todo 3
};
}

Menyelesaikan Module

Pada tahap ini kita sudah dapat membuat ulang sebuah module dengan struktur standar module yang sudah ditentukan sebelumnya. Namun karena dalam sebuah module bisa saja terdapat dependency ke module lainnya, kita perlu membuat ulang juga dependency module tersebut agar sesuai struktur standar.

Untuk itu, kita harus ulangi cara sebelumnya pada setiap dependency module yang ada. Jangan lupa perbaiki alamat berkas dependency module dahulu agar sesuai dengan alamat sebenarnya ya. Proses pembuatan ulang module pun dapat berjalan seperti sebelumnya.

Hasilnya sebagai berikut:

const fs = require("fs");

const { entry, output } = require("./webpack.config");

const { traverse, transformSync, parseSync } = require("@babel/core");

let ID = ;

resolveModules(entry);


function resolveModules(filePath) {

  const entryModule = createModule(filePath);

  const modules = [entryModule];

  for (const module of modules) {

    module.dependencies.forEach(dependency => {

      // resolve dependency Path

      const dependencyPath = resolveDependencyPath(module, dependency);

      // create child module

      const childModule = createModule(dependencyPath);

      // Completing todo 3: Fulfill dependencyMap

      module.dependencyMap[dependency] = childModule.id;

      // add child module to module list

      modules.push(childModule);

    });

  }

  return modules;

}

// resolve relativePath to fullPath, e.g. ./message.js => src/message.js

function resolveDependencyPath(module, dependency) {

  const dirname = path.dirname(module.filePath);

  return path.join(dirname, dependency);

}

function createModule(filePath) {

  const content = fs.readFileSync(filePath, "utf8");

  const { code } = transformSync(content, { presets: ["@babel/preset-env"] });

  const dependencies = [];

  const abstractSyntaxTree = parseSync(content, { sourceType: "module" });

  traverse(abstractSyntaxTree, {

    ImportDeclaration: declare => {

      dependencies.push(declare.node.source.value);

    }

  });


  return {

    id: ID++,

    filePath,

    code,

    dependencies,

    dependencyMap: {}

  };

}

Packaging

Sampai pada tahap ini kita hanya perlu membungkus hasil module-module yang telah kita buat ulang menjadi satu berkas bundle. Untuk itu, pertama ubah dahulu module-module tersebut menjadi javascript object.

Setelah diubah menjadi javascript object, kumpulan module ini tinggal memiliki dua (2) buah properti saja. Properti pertama yaitu factory yang berfungsi sebagai pembungkus module. Properti factory sejatinya dalah sebuah function di mana di dalamnya terdapat kode utama module. Selain itu juga pada parameternya perlu ditambahkan callback function berupa require untuk mengimpor dependency module dan exports untuk keperluan mengekspor module. Hal ini sesuai dengan prinsip module wrapper yang digunakan pada NodeJS [rujukan].

Baca juga:   Seorang Data Scientist Harus Memiliki Kemampuan Ini

Properti kedua yaitu dependencyMap. Properti ini akan berguna pada tahap selanjutnya, yaitu saat memperbaiki fungsi require().
Hasil modifikasi kodenya menjadi seperti berikut ini:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const result = packing(entry);
// stringify module into javascript object
function stringifyModule(module) {
  return `${module.id}: {
factory: function(require, module, exports) {
${module.code}
},
dependencyMap: ${JSON.stringify(module.dependencyMap)}
}`;
}
function packing(entry) {
  const modules = resolveModules(entry);
  const modulesString = modules.map(stringifyModule).join(“,”);
  return `(function(modules){
})({${modulesString}})`;
}

Memperbaiki “require”

Seperti yang telah kita bahas sebelumnya, cara menambahkan dependency pada format CommonJS yaitu menggunakan perintah require(). Perintah ini tidak dapat berjalan normal pada bundle module karena alamat file yang tertulis didalamnya tidak lagi sesuai.

Kita perlu mengarahkan alamat ini agar sesuai dengan alamat pemetaan dependency (dependency map) yang telah dibuat sebelumnya. Jalan termudah untuk melakukannya ialah dengan dengan cara menimpa (override) fungsi standar require().

Fungsi require() pada dasarnya hanya menerima satu parameter, yaitu alamat berkas dependency module berada. Karena alamat berkas ini sudah tidak sesuai, maka alamat tersebut bisa kita ubah berdasarkan dependency map.

Hasilnya seperti ini:

Menyimpan bundle

Langkah terakhir adalah menyimpan hasil bundle menjadi sebuah file. Caranya adalah sebagai berikut:

Menjalankan Module Bundler

Untuk menjalankan module bundler yang telah kita buat, silakan gunakan perintah: npm start.

Setelah proses bundling, Anda dapat menggunakannya sebagai dependency script pada index.html seperti saat Anda menggunakan Webpack module bundler.

LEAVE A REPLY

Please enter your comment!
Please enter your name here