JavaScript Gom Jabbar
(frantic.im)- Một nhà phát triển JavaScript với 10 năm kinh nghiệm coi cảnh đọc
package.jsonlà một phép thử của đau đớn, cho thấy có bao nhiêu sự phức tạp tích lũy của cả hệ sinh thái bị dồn vào chỉ một tệp cấu hình - Các trường như
main,browser,type: modulegợi lại những vết sẹo di cư cũ của JavaScript đẳng cấu, bundle cho trình duyệt, và quá trình chuyển đổi giữa CommonJS với ESM - Trong
scripts,dependencies,devDependenciescó sự đan xen của các biến thể lint,watchman, gulp,left-pad, moment.js và cả những vấn đề quản lý gói cũ nhưresolutions eslint,postcss,jestcùng các công cụ build và test làm tăng chi phí duy trì môi trường phát triển thông qua các quy tắc quá nghiêm ngặt, lỗi cài đặt theo từng nền tảng, và độ khó khi cấu hình ESM cùng TypeScript- Dù nhìn vào
enginescủa Node, lý do không chuyển sang Bun hay Deno là vì tin chắc rằng nỗi đau sẽ không biến mất mà còn lớn hơn, nên nhà phát triển đóng tệp lại và trước mắt là sống sót
Phép thử khi đọc package.json
- Câu chuyện bắt đầu từ tình huống một nhà phát triển đã dùng JavaScript suốt 10 năm mở tệp
package.jsontrên máy tính và phải đọc hết từ đầu đến cuối - Tệp này không chỉ là một tệp cấu hình đơn thuần, mà là thứ khiến ký ức và nỗi đau từng trải qua trong hệ sinh thái JavaScript cùng lúc ùa về
versionvẫn chưa chạm đến1.0, và SemVer còn lại như một ký ức khó chịu về việc đã học cách phớt lờ nó từ rất lâunamecho thấy tình cảnh không thể dùng cái tên mong muốn chỉ vì một gói đã bị ai đó chiếm từ 7 năm trước rồi không hề cập nhật nữa
Những vết thương cũ của module và bundling
- Việc
mainvàbrowsercùng tồn tại phơi bày dấu vết của JavaScript đẳng cấu - Ký ức từng cố
requiremodulefstrong bundle trình duyệt, cùng những cú hack phải dùng để làm cho nó chạy được, vẫn còn là một trải nghiệm khó chịu type: modulegắn với quá trình di cư từrequiresangimport- Trải nghiệm cố import qua lại giữa CommonJS và ESM đã để lại nỗi đau lớn
Sự hỗn loạn của script và dependency
scriptslà trung tâm của một mớ hỗn loạn nóng bỏng và đau đớn- Có các biến thể như
lint,lintall,lintfast,lintdiff - Xuất hiện chạy song song, đối số không rõ ràng, và cả việc escape kép cho đối số định dạng JSON
- Dù đã chuyển sang yarn và pnpm, một số lệnh con vẫn tiếp tục gọi npm
- Có các biến thể như
- Một số script vẫn còn dùng
watchman- Phải nhớ rằng không nên dùng nó vì nó không hỗ trợ symlink
- Issue liên quan đã được mở từ năm 2015
- Các script dựa trên gulp vẫn còn tồn tại; ai cũng muốn thay bằng thứ hiện đại hơn nhưng không ai dám đứng ra thay thế
Danh sách package gợi lại chấn thương
- Trong
dependenciescó rất nhiều package, và mỗi package lại khơi dậy một ký ức chấn thương riêng left-padlà một package nhỏ nhưng vẫn được nhớ như package huyền thoại từng “làm hỏng cả internet”- Mỗi lần sửa danh sách dependency, một số package lại in ra console những lời kêu gọi quyên góp hoặc cảnh báo breaking change rất dài
- Nhà phát triển rơi vào trạng thái chỉ mong các package độc hại đừng đánh cắp bí mật hay phá hỏng máy tính của mình
- moment.js được nhớ đến như một thư viện có API tốt, nhưng internet đã kết luận rằng nó quá mutable, quá lớn và không hỗ trợ treeshaking, nên giờ phải di cư sang date-fns
- Phiên bản package đã tụt hậu khá xa so với hiện tại và cần nâng cấp, nhưng kinh nghiệm nâng cấp trong quá khứ khiến người ta dự đoán sẽ có đau đớn lớn
resolutionscòn lại như một nỗi đau khác được chọn để tránh phải nâng cấp package
Một tầng phức tạp khác do công cụ phát triển tạo ra
- Khi nhìn vào
devDependencies, thật khó nhớ nổi thời mà chỉ cần dependency không phải dev dependency - Cấu hình
eslintđã trở nên quá nghiêm ngặt, đến mức chỉ một sai sót nhỏ cũng bị gạch đỏ, và CI coi vấn đề lint như ngày tận thế- Điều đó tạo ra cảm giác an toàn sai lầm cho kỹ sư junior trong nhóm
- Đã có nhiều cuộc tranh luận về việc nên bật quy tắc nào
- Nỗi đau tăng lên tương ứng với số lượng
eslint-ignorerải khắp codebase
postcsskhông được dùng trực tiếp, nhưng tồn tại vì dependency của dependency yêu cầu nó- Mỗi lần thử
npm installtrên một nền tảng mới, nó lại gây ra những lỗi biên dịch C++ mơ hồ
- Mỗi lần thử
jestkhởi đầu là một test runner nhanh, nhưng giờ đã trở nên lớn và nặng nề, đồng thời phụ thuộc vào một vài package Babel- Phần còn lại của ứng dụng được transpile bằng sự kết hợp giữa esbuild và swc
- Việc cấu hình nó cho đúng với ESM và TypeScript khó đến mức như một “dự án khoa học cấp tiến sĩ”
- Trong codebase có rất nhiều công cụ và parser tham gia như TypeScript, esbuild, swc, babel, eslint, prettier, jest, webpack, rollup, terser
- Trong
enginescó chỉ địnhnode, và ngay cả khi chuyển sang Bun hay Deno thì nỗi đau được cho là sẽ không dừng lại mà còn tệ hơn - Sau khi đọc đến dấu ngoặc nhọn đóng cuối cùng, người đó đóng tab, hít thở lại, và tạm thời sống sót với tay và não vẫn còn nguyên vẹn
1 bình luận
Ý kiến trên Hacker News
Khá buồn cười. Tôi đã viết JavaScript khoảng 18 năm và bỏ nó chừng 3 năm trước, mà hầu hết những thứ đáng lo trong danh sách này đều là các vấn đề của 10 năm gần đây
Điều đặc biệt dễ thấy là thiếu polyfill, thứ chắc chắn phải có nếu viết vào năm 2013. Còn cơn ác mộng JavaScript nào của năm 2013 mà giờ gần như đã biến mất nữa nhỉ? Dĩ nhiên là đã được thay bằng danh sách hài hước này
Đoạn tôi thích nhất là lúc đếm các công cụ và parser đang hoạt động trong codebase rồi nhớ ra TypeScript, esbuild, swc, babel, eslint, prettier, jest, webpack, rollup, terser, và đau đớn đến mức thậm chí không muốn biết mình còn sót cái nào nữa
undefinedbị ghi đè,with, closure để giả lập private field, prototype bị chỉnh sửa, biến global mặc định,thisthay đổi tùy vị trí gọi, ép kiểu tự động khi so sánh…Đúng vậy, JavaScript đã trôi qua hơn 10 năm như thế đó
SomeFunction.prototypeTôi còn bọc nó trong một hàm ẩn danh tự thực thi rồi trả về thành object, để có thể gọi
new SomeFunction()hoặcSomeFunction.actualFunction()như thểSomeFunctionlà một class. Vì chưa có arrow function, tôi còn sửa cảFunction.prototypeđể dùng.apply()nhằm buộc event vào scope, hoặc rơi vào địa ngục.bind(this)bất tận trong mọi callback sự kiệnHôm nay tôi lại xui xẻo phải làm một bài test cho công ty vẫn dùng JavaScript thuần không có Promise hay file khai báo kiểu, và cảm giác như quay về lúc mới gặp Node năm 2012. Yêu cầu phải dùng JavaScript thì tôi lách bằng JSDoc, nhưng mọi thứ còn tệ hơn tôi nhớ rất nhiều
Mỗi lần bắt đầu một dự án JavaScript mới, chính xác hơn là dự án TypeScript, tôi luôn trải qua cùng một quy trình
Thường là frontend nên tôi chọn React, dù cũng muốn thử Svelte. Bundler trước đây thường là create-react-app hoặc wrapper của Webpack, còn bây giờ tốt nhất là Vite vì ít cấu hình và hỗ trợ mọi framework chính
Tôi cài và cấu hình trình biên dịch TypeScript, thường là copy-paste từ template. Tôi nhắm tới JavaScript ổn định mới nhất và module ESM, rồi căn chỉnh eslint với prettier, thêm cả plugin cho TypeScript và React. Tôi cũng gắn hook để chạy Prettier và eslint trước khi commit
Tôi còn cài extension cho IDE, tạo
.devcontainerhoặc danh sách extension được đề xuất. Bản thân tôi không quan tâm đến style, cái quan trọng là tính nhất quán của code và phát hiện lỗi, nên eslint thường dùng cấu hình có sẵnSau đó tôi cấu hình unit test như jest hoặc vitest, rồi lại kéo theo cấu hình liên quan đến TypeScript và các rule eslint. Tôi thích tailwind làm framework CSS nên còn cài styled-components và twin.macro để phối hợp tốt với React, thêm plugin Google Fonts hoặc thư viện font nữa
Phải mất cỡ một ngày như vậy tôi mới bắt đầu lập trình được. Sao lại có quá nhiều việc thế? Có quá nhiều người dùng tổ hợp Vite + TypeScript + React/Vue/Svelte + eslint/prettier + tailwind/bootstrap + jest/vitest, vậy tại sao lại không có một thư viện không cần cấu hình nào khớp ngay với 90% trường hợp sử dụng?
Dù có dựng được toolchain lên bằng cách nào đó, tôi vẫn hoàn toàn không có cảm giác mình kiểm soát được nó, và chỉ cần rung nhẹ một chút là mọi thứ như thể sẽ sụp đổ mà tôi cũng không biết sửa ra sao
Gần đây tôi quyết định xem phần lớn stack JavaScript này là chướng ngại vật đối với việc tôi thực sự muốn làm, và quay lại xây ứng dụng server-side rendering kiểu cũ với Flask và Jinja2. Nó không bóng bẩy hiện đại, giờ còn khá xấu vì chưa có CSS, nhưng tôi đã được khuyên thử dùng CSS hiện đại thuần túy. Dù vậy, tốc độ tạo tính năng với stack đó thật sự đáng kinh ngạc
package.json. Dĩ nhiên tôi chỉ làm backend, nhưng chẳng phải frontend cũng có thể làm một template cá nhân như thế sao?npm createchẳng làm việc đó sao?Nếu chạy
npm create vuethì bạn sẽ có một cấu hình Vue khá ổn và chạy được với hầu hết thiết lập đã xong sẵn. Tôi đồng ý là có nhiều boilerplate và đủ thứ linh tinh, nhưng các ứng dụng web chúng ta làm bây giờ cũng lớn hơn ngày xưa rất nhiều. Bạn vẫn có thể dùng JavaScript chỉ với ESModules mà không cần công cụ, nhưng người ta ít làm vậy vì các công cụ đó thật sự có ích. Không phải cách gọn gàng nhất, nhưng dù sao nó vẫn chạy đượcKhi bắt đầu dự án mới, chỉ cần clone về và đổi tên vài chỗ là xong
Đây là một trường hợp khác khi người ta nói JavaScript nhưng thực ra lại có ý là “đống tạp nham trong hệ sinh thái Node”
Ngay từ đầu đã bảo phải mở và đọc file
package.json, rồi sau đó vẫn cứ theo mạch đó. Trong số những thứ được nhắc đến, hầu như chẳng có gì thực sự liên quan đến bản thân JavaScriptĐúng là JavaScript và NodeJS không phải một, nhưng đa số chắc vẫn hiểu tác giả muốn nói gì. Viết chính xác hơn thì tốt, nhưng cũng khó nói là hoàn toàn sai
package.jsonđi. Cho đến khi ECMA đưa trình quản lý gói hay bundler vào đặc tả chính thức,package.jsontrên thực tế chính là một phần của JavaScriptTôi thích giọng điệu đùa vui này. Sau khi da và các đầu dây thần kinh đã bị thiêu cháy vài lớp, việc nhớ lại những điểm đau kiểu này lại thấy có ích
Nói ngược lại thì, bạn có thể đưa cho một dev junior đầy nhiệt huyết một chiếc MacBook mới rồi quẳng xuống nước sâu, mà trong vòng một ngày họ vẫn có thể làm ra thứ có năng suất. Không cần biết về container, .NET, nginx hay 100 công cụ dòng lệnh hay dùng; có vấn đề thì cứ tìm kiếm là xong
Dù biết rằng nếu được chạm tay trực tiếp thì chỉ vài ngày là có thể tự đóng cả con tàu, họ vẫn phải vật lộn với từng lớp phức tạp không cần thiết khi sửa một con tàu trong chai bằng những công cụ thô sơ được cung cấp. Đó đúng là cảm giác khi đang làm nhanh một dự án cá nhân chỉ với JavaScript, HTML và CSS, rồi bị ném vào công việc frontend hiện đại
Các bạn ấy ngây thơ không biết rằng sẽ mất vài tháng để làm ra nó, và một phần của vấn đề chính là framework quá nhiều đến mức sang hôm sau đã lỗi thời
“Tôi làm theo hướng dẫn thì nó bảo chưa cài grunt. Giờ tôi là người có tuổi rồi nên cũng không rõ grunt là cái gì, mà file grunt của tôi cũng có vẻ không đúng lắm. Thế là tôi tìm thử một chút và biết được grunt là gì. Grunt là… thật ra tôi vẫn chưa rõ nó là cái gì.”
--Joe Armstrong, https://www.youtube.com/watch?v=lKXe3HUG2l4
Vài ngày trước tôi đã chạy
gem install wayback_machine_downloader. Tôi chưa bao giờ thích Ruby đến mức muốn bước hẳn vào hệ sinh thái của nó, nhưng nhìn từ bên ngoài thì hệ sinh thái đó có vẻ được duy trì ổn định đến đáng kinh ngạc. gem vẫn là công cụ người ta còn dùng, và Rails vẫn vẫn còn đóTôi đang chờ một dự án mới thực sự cần frontend phức tạp, đủ để vượt quá 0~30 dòng JavaScript. Khi đó có lẽ tôi sẽ hạnh phúc hơn với HTMX thay vì framework khổng lồ đang hot nhất hiện nay
Đây không phải bài về JavaScript mà là về npm
Tôi ghét npm vì đống tạp nham kiểu này và tránh nó hết mức có thể. Cá nhân tôi dùng Deno, còn ở công ty thì dùng một thứ kiểu bazel dựa trên monorepo giúp khỏi cần npm
Nhưng tôi thấy JavaScript/TypeScript là ngôn ngữ rất dễ chịu để lập trình, và đó là lựa chọn đầu tiên của tôi cho hầu như mọi thứ. Tôi thấy buồn khi người ta nói “JavaScript dở tệ” nhưng thực ra ý họ là việc build app React bằng npm mới dở tệ. Viết JavaScript/TypeScript hiện đại thực sự rất vui
Có cảm giác JavaScript và hệ sinh thái của nó đã để lại PTSD cho tất cả chúng ta đến mức không còn thưởng thức được chất hài của bài này nữa
Có quá nhiều tranh luận “nghiêm túc” về một bài viết hài hước. Cứ phân tích mổ xẻ thoải mái, nhưng nên nhớ rằng ý định thực sự là gây cười
Thành thật mà nói, hệ sinh thái đầu tiên tôi bắt đầu là Node, và những thứ tôi học sau này cũng đều có những vấn đề điên rồ của riêng chúng
Haskell rất khó dùng trên nhiều máy khác nhau, cài trên MacOS năm 2019 cũng khổ sở, mà quản lý hệ sinh thái cũng vất vả. Macro và extension gây rối kinh khủng, còn khả năng tương thích ngược của ngôn ngữ cũng không tốt. Trong khi đó, JavaScript năm 2010 thì thường vẫn có thể kỳ vọng là chạy nguyên vẹn vào năm 2023
Ngay cả việc dựng tử tế một Docker image hay môi trường như codespace/gitpod cũng đã khó, mà image mặc định GitHub cung cấp cũng không hay và có vấn đề
C nhìn chung là ổn trên đa số nền tảng, nhưng bản thân ngôn ngữ này thật sự khá phức tạp và đòi hỏi lượng kiến thức khổng lồ về toàn bộ hệ thống. Kết quả là rất dễ sinh ra mã kém tính di động, không phù hợp với phần lớn nhu cầu lập trình thông thường
Tôi còn có thể nói tiếp, nhưng ai đã dùng qua nhiều ngôn ngữ đều biết mọi hệ sinh thái đều có đủ kiểu khiếm khuyết và tất cả chúng ta cứ thế tiếp tục chịu khổ. Với PHP, tôi chưa từng hoàn tất thiết lập dự án trong vòng vài ngày; Elm thì đỡ đau hơn, nhưng lại có những vấn đề riêng về kiểu “nhà độc tài nhân từ” và tốc độ phát triển
Tôi là một lập trình viên đa ngôn ngữ thấy thoải mái với nhiều mô hình lập trình và ngôn ngữ, cả frontend lẫn backend
Cứ mỗi lần nhìn thấy
package.jsondài quá 20 dòng là tôi gần như phản ứng y hệt thế nàypackage.jsonhơn 200 dòngThành tiếng đọc như thể đang cố xua đuổi ma quỷ. “Không được sợ hãi. Sợ hãi là kẻ giết chết tâm trí. Sợ hãi là cái chết nhỏ bé dẫn đến sự diệt vong hoàn toàn. Tôi sẽ đối mặt với nỗi sợ của mình. Tôi sẽ để nó đi xuyên qua tôi. Và khi nó đi qua, tôi sẽ dùng con mắt nội tâm để nhìn lại con đường của nó. Nơi nỗi sợ đã đi qua sẽ không còn gì cả. Chỉ còn lại tôi.”
Đang khóc
Một lập trình viên JavaScript kiểu cũ đọc bài này và chịu đựng PTSD cùng những cơn hồi tưởng
Không được sợ hãi. Sợ hãi là kẻ giết chết tâm trí. Sợ hãi là cái chết nhỏ bé dẫn đến sự diệt vong hoàn toàn
Tôi sẽ đối mặt với nỗi sợ của mình. Tôi sẽ để nó đi xuyên qua tôi. Và khi nó đi qua, tôi sẽ dùng con mắt nội tâm để nhìn lại con đường của nó
Nơi nỗi sợ đã đi qua sẽ không còn gì cả. Chỉ còn lại tôi
Nhận ra mình đã đọc xong bài và sống sót, nhưng đã mồ hôi nhễ nhại