Shimming: bản chất, phương pháp tiếp cận và nguyên lý hoạt động

Tác giả: IT Sectr Đã đăng: 2026-05-19 Thời gian đọc: 8 phút

Shimming là kỹ thuật đảm bảo tính tương thích của các module dự kiến các biến toàn cục hoặc API nhất định. Trong hệ sinh thái Webpack, shimming được triển khai thông qua ProvidePlugin, imports-loader và exports-loader, cho phép kết nối các thư viện legacy mà không cần thay đổi mã nguồn của chúng. Theo Webpack Documentation (2026), shimming vẫn là công cụ chính để tích hợp các plugin jQuery và các phụ thuộc khác không hỗ trợ hệ thống module.

Điều chính yếu

  • Shimming là kỹ thuật thay thế các biến toàn cục và API để đảm bảo tính tương thích của module trong bản build.
  • ProvidePlugin tự động import module khi phát hiện tham chiếu đến một biến toàn cục trong mã.
  • imports-loaderexports-loader quản lý phạm vi của các module, thêm hoặc thay đổi giao diện của chúng.
  • Shim khác polyfill ở chỗ nó không triển khai chức năng còn thiếu, mà chuyển hướng các lệnh gọi hiện có.
  • Webpack cung cấp các cơ chế shimming tích hợp mà không cần cài đặt thêm gói nào.

Shimming là gì?

Shimming là một kỹ thuật phần mềm nhúng một lớp tương thích giữa mã và môi trường mà không thay đổi mã nguồn của module. Trong bối cảnh bản build JavaScript, shimming giải quyết vấn đề khi một module truy cập vào các biến toàn cục (window.$, global.process) không tồn tại trong môi trường module.

Shim và polyfill: những khác biệt chính

Polyfill triển khai chức năng còn thiếu từ đầu, thêm các khả năng mới vào môi trường. Ví dụ, core-js thêm Array.prototype.flatMap cho các trình duyệt cũ. Còn Shim chuyển hướng các lệnh gọi hiện có đến các triển khai sẵn có hoặc thay thế các đối tượng toàn cục dự kiến. Trong Webpack, ProvidePlugin tự động chèn import $ from 'jquery' ở mọi nơi gặp tham chiếu đến biến toàn cục $, mà không yêu cầu thay đổi mã.

Khác biệt chính nằm ở mục đích. Polyfill thêm thứ không tồn tại, còn shim làm cho mã hiện có tương thích với môi trường mà nó chạy. Việc lựa chọn giữa chúng phụ thuộc vào vấn đề cần giải quyết: thiếu API hay không tương thích giao diện.

Shimming hoạt động như thế nào trong Webpack

Webpack xử lý mỗi module như một đơn vị cô lập với phạm vi riêng. Nếu một thư viện truy cập biến toàn cục jQuery dưới dạng window.$, bản build sẽ kết thúc bằng lỗi, vì biến này không tồn tại trong ngữ cảnh module. ProvidePlugin giải quyết vấn đề ở giai đoạn biên dịch: khi phát hiện định danh $ trong mã, plugin tự động chèn import $ from 'jquery' vào đầu tệp.

js
// Mã nguồn (module legacy truy cập jQuery toàn cục)
$('.element').hide();

// Sau khi xử lý bởi ProvidePlugin (Webpack chèn import)
import $ from 'jquery';
$('.element').hide();

Ngoài ra, imports-loader cho phép chỉ định rõ ràng module nên nhận những phụ thuộc nào. Điều này hữu ích khi thư viện sử dụng this ở cấp cao nhất, mong đợi this tham chiếu đến window thay vì module.exports.

ProvidePlugin: biến toàn cục cho các module

ProvidePlugin là plugin tích hợp của Webpack tự động tải các module khi phát hiện tham chiếu đến các định danh được chỉ định. Cấu hình là một đối tượng trong đó khóa là tên biến, còn giá trị là đường dẫn đến module và trường được xuất.

Cấu hình plugin

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

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      _: 'lodash',
      'window.$': 'jquery',
    }),
  ],
};

ProvidePlugin hỗ trợ import từng phần thông qua cú pháp mảng. Ví dụ, [lodash, debounce] chỉ import hàm debounce từ lodash, giúp giảm kích thước bundle cuối cùng. Điều này đặc biệt quan trọng với các dự án di động, nơi mỗi kilobyte đều ảnh hưởng đến thời gian tải.

imports-loader và exports-loader

imports-loader thêm các import cần thiết vào đầu module, còn exports-loader xác định các giá trị xuất cho các module không sử dụng module.exports một cách rõ ràng. Các loader này hoạt động ở cấp tệp riêng lẻ, không mang tính toàn cục như ProvidePlugin.

Sửa phụ thuộc bằng imports-loader

js
// webpack.config.js — cấu hình imports-loader
module.exports = {
  module: {
    rules: [
      {
        test: /legacy-module\.js$/,
        use: [
          {
            loader: 'imports-loader',
            options: {
              imports: [
                'jquery',
                '$',
              ],
            },
          },
        ],
      },
    ],
  },
};

exports-loader được sử dụng khi thư viện gán giá trị cho một biến toàn cục nhưng không xuất giá trị đó qua hệ thống module. Loader trích xuất giá trị và biến nó thành một phần xuất module, cho phép các module khác import nó qua import.

Cấu hình shimming trong cấu hình Webpack

Shimming được cấu hình trong webpack.config.js thông qua sự kết hợp giữa plugin và loader. Một kịch bản điển hình bao gồm ProvidePlugin cho các biến toàn cục và imports-loader cho các module cụ thể yêu cầu thay đổi phạm vi.

Cấu hình Webpack cơ bản cho shimming

js
const webpack = require('webpack');
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
    globalObject: 'this',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules\/(?!legacy-lib)/,
        use: [
          {
            loader: 'imports-loader',
            options: {
              type: 'commonjs',
              imports: ['jquery', '$'],
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
    }),
  ],
};

Trường globalObject trong output xác định ngữ cảnh cho các tham chiếu this ở cấp cao nhất. Đối với môi trường trình duyệt, giá trị 'this' tham chiếu đến window, còn với React Native hoặc Node.js — đến global. Việc chọn đúng giá trị ngăn ngừa lỗi thực thi trong môi trường đích.

Các lỗi điển hình khi shimming

Shimming là công cụ mạnh mẽ nhưng nguy hiểm. Cấu hình sai dẫn đến trùng lặp mã trong bundle, xung đột tên và các lỗi thực thi bất ngờ. Các nhà phát triển thường quên rằng ProvidePlugin hoạt động ở giai đoạn biên dịch và không thể xử lý các tham chiếu động đến biến.

Xung đột biến toàn cục

Nếu hai plugin sử dụng các phiên bản jQuery khác nhau, ProvidePlugin sẽ chỉ thay thế một trong số chúng — phiên bản được chỉ định đầu tiên trong cấu hình. Thư viện thứ hai sẽ nhận phiên bản không tương thích, gây ra các lỗi khó gỡ lỗi. Giải pháp — sử dụng exports-loader cho mỗi thư viện với phiên bản rõ ràng hoặc áp dụng webpack.IgnorePlugin để loại trừ các module trùng lặp.

Một lỗi phổ biến khác — cố gắng shim các module sử dụng lệnh gọi require đồng bộ của CommonJS trong ngữ cảnh động. ProvidePlugin chỉ xử lý các định danh tĩnh, do đó các tham chiếu động cần được thay thế thủ công hoặc bằng NormalModuleReplacementPlugin.

Vấn đề hiệu suất khi shimming sai cách

Cấu hình sai shimming có thể dẫn đến tăng đáng kể kích thước bundle. Nếu ProvidePlugin được cấu hình cho hàng chục biến toàn cục, Webpack sẽ chèn các import tương ứng vào tất cả các tệp của dự án, bất kể các biến đó có được sử dụng trong từng tệp cụ thể hay không. Điều này tạo ra mã thừa, đặc biệt trong các dự án lớn với hàng nghìn module.

Để chẩn đoán vấn đề với shimming, hãy sử dụng webpack-bundle-analyzer — công cụ trực quan hóa thành phần bundle. Nếu jQuery hoặc thư viện khác xuất hiện trong bundle nhiều lần, có thể các phiên bản khác nhau đang xung đột hoặc ProvidePlugin được cấu hình cho nhiều định danh dẫn đến các phiên bản gói khác nhau. Giải pháp — thống nhất các phiên bản phụ thuộc qua resolve.alias và kiểm tra rằng mọi định danh đã shim đều trỏ đến cùng một module.

Các lựa chọn thay thế shimming: tái cấu trúc và cập nhật phụ thuộc

Trước khi áp dụng shimming, hãy đánh giá khả năng nâng cấp thư viện lên phiên bản hỗ trợ hệ thống module. Nhiều gói legacy có các lựa chọn hiện đại không cần shimming. Ví dụ, các plugin jQuery có thể được thay thế bằng API gốc của trình duyệt: $.ajaxfetch, $.eachArray.forEach. Tái cấu trúc mang lại lợi ích lâu dài trong bảo trì, còn shimming là giải pháp tạm thời làm phức tạp cấu hình.

Nếu không thể cập nhật, hãy cân nhắc NormalModuleReplacementPlugin, cho phép thay thế một module bằng module khác ở cấp phân giải mà không thay đổi mã nguồn. Plugin này hoạt động ở giai đoạn xây dựng đồ thị phụ thuộc, trước khi áp dụng các loader, và xử lý mọi tham chiếu đến module bất kể ngữ cảnh. Đây là giải pháp sạch hơn để thay thế toàn bộ thư viện so với các loader điểm.

Shimming trong JavaScript hiện đại: ESM và import maps

Với sự phát triển của ES modules gốc trong trình duyệt và sự xuất hiện của import maps, một số kịch bản shimming có thể được giải quyết mà không cần Webpack. Import maps cho phép ánh xạ lại tên module một cách linh hoạt ở cấp trình duyệt, không cần giai đoạn build. Tuy nhiên, cách tiếp cận này không được hỗ trợ trong React Native và các môi trường khác không có ESM trình duyệt, vì vậy shimming qua Webpack vẫn cần thiết cho các bản build production đòi hỏi kiểm soát hoàn toàn đối với các phụ thuộc và phiên bản của chúng. Việc lựa chọn giữa import maps và Webpack shims phụ thuộc vào nền tảng mục tiêu và yêu cầu tương thích với các trình duyệt cũ.

Các câu hỏi thường gặp

Shimming khác tree shaking như thế nào?

Shimming thêm mã để đảm bảo tương thích, còn tree shaking loại bỏ mã không sử dụng. Hai kỹ thuật này đối lập về mục đích: shimming tăng kích thước bundle, tree shaking giảm. Trong bản build production, cả hai được áp dụng tuần tự.

Có thể sử dụng shimming mà không cần Webpack không?

Có, shimming tồn tại như một kỹ thuật độc lập với Webpack — ví dụ, thông qua các script toàn cục trong HTML hoặc qua các ES module có tái xuất. Tuy nhiên, Webpack cung cấp các công cụ tự động hóa thuận tiện nhất: ProvidePlugin và các loader không yêu cầu thay đổi mã thủ công.

Shimming ảnh hưởng đến hiệu suất build như thế nào?

ProvidePlugin không ảnh hưởng đến tốc độ build vì nó hoạt động ở giai đoạn biên dịch AST. imports-loaderexports-loader thêm một chút thời gian xử lý cho mỗi tệp. Khi được sử dụng trên hàng trăm tệp, chênh lệch có thể là 5–15% tổng thời gian build.

Khi nào nên từ bỏ shimming?

Nếu mọi phụ thuộc đều hỗ trợ ES modules và hệ thống module, thì shimming là thừa. Từ bỏ shimming giúp đơn giản hóa cấu hình, giảm kích thước bundle và hạ thấp rủi ro xung đột tên. Nên kiểm tra các phụ thuộc trên caniuse.com.

Shimming hoạt động với TypeScript như thế nào?

TypeScript yêu cầu các khai báo kiểu bổ sung cho các biến đã shim. Cần thêm declare const $: any hoặc cài đặt các kiểu qua @types/jquery. ProvidePlugin chèn các import ở cấp JavaScript sau khi biên dịch TypeScript, vì vậy các kiểu được kiểm tra riêng.

Tổng kết

  • Shimming là kỹ thuật đảm bảo tính tương thích của module với môi trường bằng cách thay thế các biến toàn cục và API.
  • ProvidePlugin tự động import các module khi phát hiện tham chiếu đến các định danh được chỉ định trong mã.
  • imports-loader thêm import vào đầu các tệp cụ thể, còn exports-loader xác định các giá trị xuất.
  • Shim khác polyfill ở chỗ nó không triển khai chức năng, mà chuyển hướng các lệnh gọi đến các triển khai hiện có.
  • ProvidePlugin hoạt động ở giai đoạn biên dịch và không xử lý các tham chiếu động đến biến.
  • Trường globalObject trong output xác định ngữ cảnh đúng cho cấp cao nhất trong môi trường đích.
  • Chỉ sử dụng shimming cho các module không hỗ trợ hệ thống module hiện đại, và từ bỏ nó khi ES modules được hỗ trợ đầy đủ.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm