Công cụ Rút Gọn JavaScript

Nén mã JavaScript bằng cách loại bỏ các comment, khoảng trắng và các ký tự không cần thiết.

Về việc minify JavaScript

JavaScript là loại tài nguyên nặng nhất trên hầu hết các trang web, và không chỉ vì nó có xu hướng là tệp lớn nhất. Chi phí ở năm giai đoạn: tải xuống (qua mạng), phân tích cú pháp (engine đọc các byte và xây dựng AST), biên dịch (V8 hoặc JavaScriptCore biên dịch AST sang bytecode), thực thi (bytecode chạy), và trong các lần truy cập tiếp theo, khởi động ấm từ bytecode được cache nếu engine giữ nó. Brotli ở rìa CDN xử lý chi phí tải xuống. Minification cũng giúp với việc tải xuống, nhưng nó cũng giúp với chi phí phân tích cú pháp và biên dịch trên mọi thiết bị tải xuống script của bạn, và trên điện thoại Android cấp thấp, phân tích cú pháp và biên dịch có thể mất nhiều thời gian hơn so với truyền tải mạng. Các bài viết Cost of JavaScript của Addy Osmani đã liên tục cho thấy rằng những gì trông giống như vấn đề mạng thường là vấn đề CPU, và rằng giảm 20-40% byte của minification chuyển thành các mili giây bị cạo bớt khỏi thời gian phân tích cú pháp trên đuôi dài của các thiết bị chậm khá trực tiếp.

Tối ưu hóa phân tích cú pháp lười của V8 hoãn phân tích cú pháp đầy đủ của bất kỳ hàm nào cho đến khi nó thực sự được gọi, có nghĩa là một script lớn với nhiều hàm không được sử dụng có chi phí thấp hơn so với số byte của nó gợi ý. Cache HTTP của Chrome cũng lưu trữ bytecode V8 ("Code Cache") vì vậy các lần truy cập lặp lại bỏ qua hoàn toàn giai đoạn phân tích cú pháp và biên dịch. Không có điều gì trong số này thay đổi phương trình cơ bản: các script nhỏ hơn đánh trúng cache nóng nhanh hơn, phân tích cú pháp nhanh hơn trên đường lạnh, và biên dịch nhanh hơn. Minification là nơi rẻ nhất trên toàn bộ stack để giành lại các mili giây.

Bốn (hoặc Năm) Cấp độ Minification JavaScript

Không phải tất cả minifier đều giống nhau, và cấp độ bạn cần phụ thuộc vào codebase của bạn. Cấp 1: loại bỏ khoảng trắng và bình luận. Pass đơn giản nhất, loại bỏ khoảng trắng, tab, dòng mới và các bình luận // ... / /* ... */. Đây là những gì một pass regex có thể làm an toàn (với sự cẩn trọng xung quanh chuỗi, template literal và regex literal). Giảm điển hình: 20-40% trong byte thô. Cấp 2: đổi tên ký hiệu (mangling). Tên biến cục bộ, tham số hàm và (với sự cẩn trọng) tên hàm được viết lại thành các chữ cái đơn: function calculateTotal(itemList) trở thành function a(b). Điều này yêu cầu Abstract Syntax Tree và phân tích scope đầy đủ để biết tên nào an toàn để đổi tên, tên toàn cục, export, tham chiếu đến các thuộc tính của this, và bất cứ điều gì đạt được qua truy cập khóa-chuỗi (obj['name']) đều phải giữ nguyên. Giảm bổ sung điển hình: 10-25%. Cấp 3: loại bỏ mã chết (tree shaking). Phân tích tĩnh cấp mô-đun xác định các import và đường dẫn mã không bao giờ được thực thi và loại bỏ chúng. Yêu cầu thông tin loại cấp mô-đun và hiểu rõ về các tác dụng phụ. Giảm bổ sung điển hình: thay đổi, nhưng có thể to lớn trên các thư viện gửi nhiều tính năng. Cấp 4: nội tuyến và gấp hằng số. Các biểu thức đơn giản như 2 * 60 * 60 được đánh giá tại thời điểm build thành 7200; các hàm nhỏ được gọi một lần có thể được nội tuyến vào nơi gọi chúng. Cấp 5: mangling thuộc tính. Tối ưu hóa hung hăng nhất, các tên thuộc tính đối tượng cũng được viết lại. Phá vỡ bất kỳ mã nào sử dụng khóa chuỗi (obj['name'] vs obj.name) hoặc lộ ra các tên thuộc tính như một phần của hợp đồng công khai của nó. Hầu như luôn opt-in và hầu như luôn giới hạn cho các định danh cụ thể thông qua regex --mangle-props.

Một Lịch sử Ngắn về Công cụ Minification JavaScript

JSMin (2003). Douglas Crockford, vâng, cùng một Crockford đã quảng bá JSON, đã viết JSMin bằng C vào năm 2003. Đó là một chương trình tệp đơn nhỏ thực hiện việc loại bỏ khoảng trắng và bình luận cơ bản mà không có AST, không có phân tích scope, và với cách tiếp cận có chủ ý bảo thủ đối với các trường hợp tới hạn của ASI (Automatic Semicolon Insertion). Nó đã đặt ra thanh chuẩn cho "điều đơn giản nhất hoạt động" và là tổ tiên tinh thần của mọi minifier JS dựa trên regex kể từ đó. YUI Compressor (2007). Julien Lecomte của Yahoo đã công bố YUI Compressor vào ngày 11 tháng 8 năm 2007. Nó sử dụng tokenizer Rhino để thực hiện đổi tên ký hiệu an toàn, công cụ Java đầu tiên được sử dụng rộng rãi để thực hiện mangling dựa trên AST thực sự cho JavaScript. Closure Compiler (2009). Google đã sử dụng nó nội bộ từ năm 2005; họ đã open-source nó dưới Apache 2.0 vào ngày 5 tháng 11 năm 2009. Closure là trình tối ưu hóa hung hăng nhất của thời đại, nhận biết loại thông qua các chú thích JSDoc, với chế độ "advanced" có thể viết lại các tên thuộc tính dựa trên suy luận loại. Sự đánh đổi là bạn phải viết mã của mình theo cách thân thiện với Closure; các thất bại chế độ advanced rất khét tiếng.

UglifyJS (~2010-2012). UglifyJS của Mihai Bazon là minifier JavaScript-native đầu tiên, được viết bằng JS, chạy trên Node.js, và nó trở thành mặc định npm trong một thập kỷ. UglifyJS 2 đã thêm hỗ trợ source map và các tính năng ES5; UglifyJS 3 theo sau với việc tiếp tục cải tiến ES5 nhưng không bao giờ giành được hỗ trợ ES6+ đầy đủ. Terser (Tháng 8 năm 2018). Fabricio Matté đã fork UglifyJS thành Terser cụ thể để thêm hỗ trợ ES6+ mà không làm gián đoạn API UglifyJS đã ổn định lâu. Terser hiện là minifier JS mặc định trong webpack 5, Rollup, Parcel 1, và các phiên bản Next.js cũ hơn. swc (2017/2019). swc của Donny "kdy1" Choi ("Speedy Web Compiler") là một trình biên dịch JavaScript/TypeScript dựa trên Rust với một minifier tích hợp nhanh hơn Terser 20-70 lần. Next.js đã chuyển minifier mặc định của mình từ Terser sang swc bắt đầu từ phiên bản 12 vào tháng 10 năm 2021. esbuild (mùa đông 2019-2020). Evan Wallace, đồng sáng lập Figma, đã phát hành esbuild trong kỳ nghỉ đông 2019-2020. Được viết bằng Go, nó nhanh hơn 10-100 lần so với các bundler dựa trên JavaScript của thời đó và gửi minifier của riêng nó. esbuild hiện là minifier cơ bản trong Vite, trong tsup, và trong nhiều template framework. Hướng chung trong năm năm qua là: trình phân tích cú pháp được viết bằng ngôn ngữ hệ thống nhanh (Rust hoặc Go), tối ưu hóa dựa trên AST, tree shaking thông minh nhận biết mô-đun ES. Minifier regex dán vào trình duyệt, như công cụ này, nằm ở dưới cùng của cái thang đó, thực hiện công việc đơn giản nhất vẫn hữu ích.

Source Map cho JavaScript đã Minify

Một source map là một tệp sidecar JSON ánh xạ các vị trí trong đầu ra đã minify trở lại các vị trí trong nguồn gốc. Đặc tả Source Map V3 được viết bởi John Lenz (Google) và Nick Fitzgerald (Mozilla) vào năm 2011, và được TC39 áp dụng làm ECMA-426 vào tháng 6 năm 2024, cùng định dạng source-map áp dụng cho cả JavaScript và CSS. Các trình duyệt tiêu thụ map qua bình luận trailing trong tệp đã minify: //# sourceMappingURL=app.js.map. Khi DevTools mở và map được lấy, bảng Sources hiển thị nguồn gốc, với các điểm dừng, lỗi console và stack trace đều tham chiếu lại nó. Các minifier sản xuất (Terser, swc, esbuild, Closure) đều phát ra source map theo yêu cầu. Công cụ này thì không, đối với một công cụ một-lần trong trình duyệt trả về văn bản chứ không phải cặp tệp có thể tải xuống, source map thêm độ phức tạp đáng kể cho lợi ích cận biên. Tiết lộ trung thực là công cụ này là một pass một chiều; các minifier build-pipeline có một trường hợp mạnh hơn nhiều cho source map vì các nguồn gốc nằm trên đĩa và developer cần debug khi runtime.

Mặc định Bundler Hiện đại, Hầu hết Mọi người Đã có một Minifier

Nếu bạn đang sử dụng một pipeline build hiện đại, minifier của bạn đã chạy. webpack 5 sử dụng terser-webpack-plugin với Terser theo mặc định. Vite sử dụng esbuild cho minification theo mặc định; Lightning CSS cho CSS. Parcel sử dụng swc. Next.js đã chuyển từ Terser sang swc trong v12 (tháng 10 năm 2021), và từ Babel sang swc cho toàn bộ pipeline build trong cùng phiên bản đó. Remix, Astro, SvelteKit, Nuxt, Rollup, esbuild độc lập, tất cả đều đóng gói minification vào các build sản xuất mà không cần can thiệp của developer. Kết quả là đối với bất kỳ ai sử dụng pipeline build hiện đại, minification JS xảy ra tự động với các tối ưu hóa vượt xa những gì một công cụ regex tệp đơn có thể làm. Các trường hợp khi minifier trong trình duyệt này có giá trị: các trang HTML viết tay với các khối <script> nội tuyến; các theme WordPress được gửi mà không có toolchain Node; các trình tạo trang tĩnh không đóng gói minification; các snippet một lần dán vào CMS hoặc template email; các thử nghiệm nhanh nơi việc thiết lập pipeline build sẽ mất nhiều thời gian hơn so với chính script.

Phạm vi Trung thực: Công cụ này Làm gì và Không làm gì

Công cụ này là một minifier dựa trên regex, khoảng 30 dòng JavaScript. Nó tokenize các literal chuỗi, template literal và literal regex thành các placeholder để các biến đổi tiếp theo không thể làm hỏng nội dung của chúng; loại bỏ các bình luận // .../* ... */; thu gọn các chuỗi khoảng trắng; loại bỏ khoảng trắng xung quanh dấu chấm câu không cần nó; và khôi phục các literal đã tokenize. Đầu ra điển hình nhỏ hơn đầu vào 20-40% trong byte thô. Những gì công cụ này không làm, và mà các minifier sản xuất (Terser, swc, esbuild, Closure) xử lý: nó không đổi tên các biến cục bộ thành các chữ cái đơn (không có mangling nhận biết scope); nó không thực hiện loại bỏ mã chết hoặc tree-shaking; nó không thực hiện gấp hằng số hoặc đơn giản hóa biểu thức; nó không phát ra source map; nó không hiểu cú pháp TypeScript (chỉ dán JavaScript thuần); nó không tree-shake các import mô-đun ES; nó không viết lại các tên thuộc tính. Khung trung thực: dán JavaScript ra khỏi editor hoặc bàn tay của bạn, nhận lại một phiên bản được lột bỏ thường nhỏ hơn 20-40% trong byte thô, và sử dụng nó như một artifact triển khai nhanh. Đối với các dự án có pipeline build, hãy sử dụng Terser, swc hoặc esbuild trong pipeline đó; các tối ưu hóa nhận biết AST là sự khác biệt giữa việc giảm 20-40% của công cụ này và 60-80% của một minifier sản xuất.

Cạm bẫy của Minifier Regex

Một pass dựa trên regex không hiểu ngữ pháp JavaScript có thể làm hỏng mã theo những cách tinh vi. Các cạm bẫy kinh điển: template literal sử dụng dấu nháy ngược và có thể chứa các biểu thức nội suy (`Hello ${name}!`) trông giống như các ứng cử viên loại bỏ bình luận nếu regex không cẩn thận. Literal regex như /^\/\*/g chứa dấu gạch chéo về phía trước, các chuỗi giống bình luận và nội dung giống chuỗi; xử lý sai chúng biến regex thành mã bị vỡ về mặt cú pháp. Chuỗi chứa văn bản giống bình luận (const url = "// example.com"), việc loại bỏ bình luận ngây thơ sẽ loại bỏ mọi thứ sau //. Bẫy ASI, Automatic Semicolon Insertion là tính năng JS cho phép bạn bỏ qua dấu chấm phẩy hầu hết thời gian, nhưng nó tương tác xấu với việc loại bỏ khoảng trắng khi token tiếp theo bắt đầu bằng dấu ngoặc đơn, dấu ngoặc vuông hoặc toán tử ((/regex/), [arr], +1), việc thu gọn khoảng trắng bất cẩn có thể thay đổi "hai câu lệnh" thành "một câu lệnh có lỗi phân tích cú pháp". Sự giảm thiểu trong công cụ này là pass tokenize literal chạy trước, thay thế mỗi chuỗi và regex bằng một placeholder duy nhất, làm công việc bình luận+khoảng trắng trên mã đã được dọn dẹp, sau đó khôi phục các placeholder. Nó không hoàn hảo, nhưng nó bao phủ các trường hợp phổ biến. Nếu bạn nghi ngờ minifier đã phá vỡ mã của bạn, điều đầu tiên cần kiểm tra là một literal regex chứa một chuỗi dấu gạch chéo mà tokenizer đã bỏ lỡ.

Quyền riêng tư: Tại sao chỉ-Trình duyệt Quan trọng ở đây

Các minifier JS phía máy chủ (các công cụ trực tuyến POST mã của bạn lên một máy chủ, chạy nó qua Terser ở đó, và trả về kết quả) yêu cầu tải lên nguồn của bạn. Đối với mã thư viện thông thường điều này là vô hại. Đối với các công cụ nội bộ, mã sản phẩm chưa phát hành, JavaScript chứa các khóa API nội tuyến hoặc thông tin xác thực dịch vụ bên thứ ba, hoặc bất kỳ mã nào tiết lộ các thuật toán độc quyền hoặc logic kinh doanh, thì không. Một minifier dựa hoàn toàn vào trình duyệt, JavaScript chạy trong tab của bạn không bao giờ thực hiện yêu cầu mạng sau lần tải trang đầu tiên, tránh vấn đề. Bạn có thể xác minh bằng cách mở tab Network của DevTools, dán mã, nhấp Minify, và xem bất kỳ yêu cầu đi ra nào. Tốt hơn nữa, ngắt kết nối khỏi internet (hoặc bật chế độ máy bay) sau khi trang tải và công cụ vẫn sẽ hoạt động, đó là bằng chứng thực nghiệm mạnh nhất rằng không có gì đang được tải lên.

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

Mã của tôi sẽ nhỏ hơn bao nhiêu?

Đối với mã được định dạng bằng tay với bình luận và thụt lề, hãy mong đợi giảm kích thước 20-40% trong byte thô. Các minifier sản xuất (Terser, swc, esbuild) với các tối ưu hóa dựa trên AST đầy đủ đạt được 60-80%, khoảng cách là đổi tên ký hiệu, loại bỏ mã chết và gấp hằng số, không có cái nào mà một công cụ chỉ-regex có thể làm an toàn. Sau khi nén Brotli ở rìa CDN, tiết kiệm bổ sung từ minification khiêm tốn hơn (5-15% ngoài Brotli trên bản gốc không minify) nhưng nó không bằng không, và ở quy mô lớn nó cộng dồn.

Đầu ra đã minify có làm hỏng mã của tôi không?

Đối với phần lớn mã, không. Các trường hợp tới hạn đã biết là xung quanh các literal regex với các chuỗi dấu gạch chéo (/foo\/bar/), template literal với các nội suy nhúng kéo dài các dòng, và các ngữ cảnh Automatic Semicolon Insertion nơi loại bỏ một dòng mới thay đổi việc phân tích cú pháp. Pass tokenize literal của công cụ xử lý các trường hợp phổ biến, nhưng nếu mã của bạn nặng bất thường trong bất kỳ mẫu nào trong số đó, hãy kiểm tra đầu ra đã minify trong trình duyệt trước khi triển khai. Đối với pipeline build sản xuất, hãy sử dụng Terser hoặc swc, chúng có nhận thức AST đầy đủ và xử lý các trường hợp này một cách chính xác.

Công cụ này có đổi tên các biến không?

Không. Mangling biến, biến function calculateTotal(itemList) thành function a(b), yêu cầu phân tích scope đầy đủ trên một AST để biết tên nào an toàn đổi tên. Một pass regex không thể làm điều này an toàn. Đối với đổi tên ký hiệu, hãy sử dụng Terser, UglifyJS hoặc swc trong một pipeline build; chúng triển khai mangling nhận biết-scope một cách chính xác. Giảm kích thước bổ sung 10-25% mà nó tạo ra là thực và xứng đáng làm cho mã sản xuất.

Tôi có thể dán TypeScript không?

Không, công cụ này là một minifier chỉ JavaScript. TypeScript thêm chú thích loại (function add(a: number, b: number): number), interface, enum, decorator và cú pháp khác không phải JavaScript hợp lệ. Biên dịch TypeScript của bạn sang JavaScript trước sử dụng tsc, swc, esbuild hoặc Babel, sau đó dán đầu ra JavaScript ở đây. Hầu hết các dự án TypeScript đã đi qua một trong các trình biên dịch đó như một phần của build, vì vậy JavaScript tồn tại ở đâu đó.

Tôi có nên sử dụng điều này nếu tôi đã có một pipeline build không?

Có lẽ không, bundler của bạn đang làm điều này cho bạn, với các tối ưu hóa dựa trên AST vượt xa những gì một công cụ regex có thể cung cấp. webpack 5 gửi terser-webpack-plugin với Terser; Vite sử dụng esbuild cho JS theo mặc định; Parcel sử dụng swc; Next.js đã sử dụng swc kể từ v12 (tháng 10 năm 2021). Công cụ này dành cho các trường hợp pipeline build của bạn không bao phủ: các trang HTML viết tay, các theme WordPress được gửi mà không có toolchain Node, các trình tạo trang tĩnh không đóng gói minification, các snippet một lần, hoặc các thử nghiệm nhanh nơi việc thiết lập một build sẽ mất nhiều thời gian hơn so với chính script.

Các tệp của tôi có được tải lên không?

Không. Minifier là JavaScript chạy trong trình duyệt của bạn. Mã bạn dán không bao giờ băng qua mạng, xác minh trong tab Network của DevTools khi bạn nhấp Minify, hoặc đưa trang offline sau khi tải và xác nhận công cụ vẫn hoạt động. Các công cụ nội bộ, mã sản phẩm chưa phát hành, các script chứa khóa API nội tuyến hoặc logic kinh doanh độc quyền vẫn ở trên thiết bị của bạn.

Công cụ liên quan