3 điểm bởi GN⁺ 2023-07-03 | 1 bình luận | Chia sẻ qua WhatsApp
  • Một nhà phát triển JavaScript với 10 năm kinh nghiệm coi cảnh đọc package.json là 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: module gợ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, devDependencies có 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, jest cù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 engines củ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.json trê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ề
  • version vẫn chưa chạm đến 1.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âu
  • name cho 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 mainbrowser cùng tồn tại phơi bày dấu vết của JavaScript đẳng cấu
  • Ký ức từng cố require module fs trong 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: module gắn với quá trình di cư từ require sang import
  • 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

  • scripts là 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
  • 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 dependencies có 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-pad là 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
  • resolutions cò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-ignore rải khắp codebase
  • postcss khô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 install trê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ồ
  • jest khở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 engines có chỉ định node, 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

 
GN⁺ 2023-07-03
Ý 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

    • Grunt, Gulp, CoffeeScript, IIFE, undefined bị ghi đè, with, closure để giả lập private field, prototype bị chỉnh sửa, biến global mặc định, this thay đổ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ế đó
    • Tôi đồng ý về phần callback. Nhưng nhìn lại JavaScript của tôi 10 năm trước thì nỗi đau lớn nhất lại là toàn bộ những lớp giả được tạo ra bằng cách sửa SomeFunction.prototype
      Tô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ặc SomeFunction.actualFunction() như thể SomeFunction là 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ện
    • Tôi từng phát triển trên Firefox để dùng Firebug, trong khi vẫn phải hỗ trợ riêng ie6, ie7, ie8
    • Cũng có thời người ta biến header thành một applet Flash nhỏ để dùng font mong muốn thay vì bị giới hạn bởi font người dùng đã cài
    • Địa ngục callback và việc thiếu kiểu dữ liệu nói chung là rất lớn
      Hô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 .devcontainer hoặ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ẵn
    Sau đó 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?

    • Ngay cả người đã biết hết đống này cũng mất cả ngày. Là một dev backend và vận hành, tôi đã vài lần cố bước vào stack kiểu này, nhưng thành thật mà nói là phần lớn đều thất bại
      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
    • Có tận hai linter. Chỉ riêng vậy thôi đã điên rồi. Những thứ còn lại còn tệ hơn, nhưng ngay cả mảnh nhỏ nhất trong việc chọn công cụ của hệ sinh thái JavaScript cũng đầy đau khổ
    • Tôi chỉ việc copy thư mục khởi đầu TypeScript rồi đổi tên trong 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 create chẳng làm việc đó sao?
      Nếu chạy npm create vue thì 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 được
    • Có lẽ bạn chỉ cần tạo một dự án stub chứa toàn bộ cấu hình yêu thích ở trên rồi đưa lên GitHub
      Khi 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

    • Tỷ lệ lập trình viên JavaScript không dùng NodeJS là bao nhiêu nhỉ?
      Đú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
    • Đúng vậy, tức là hệ sinh thái không phải là đối tượng hợp lệ để phê phán ngôn ngữ à
    • Hãy thử thực sự viết JavaScript một lần mà không có bundler dùng 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.json trên thực tế chính là một phần của JavaScript
    • Nếu điều đó làm bạn khó chịu đến vậy, thì cứ nghĩ rằng mỗi khi ai đó nói JavaScript, thực ra họ đang muốn nói ECMAscript là được
    • Đúng. Đây là lời phê bình về hệ sinh thái JavaScript
  • Tô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

    • Đúng vậy. Điều mỉa mai là chính sự đánh đổi đó khiến ngay cả một lập trình viên senior đã làm thứ chạy trong trình duyệt suốt 20 năm cũng không thể sẵn sàng chỉ trong một ngày
      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
    • Rồi sau đó bạn vá víu để làm ra thứ gì đó trong một đến ba ngày, hối hận về nó suốt 10 năm sau, rồi viết ra những bài như thế này. Vòng lặp cứ tiếp diễn
    • Mấy junior tôi biết sẽ bảo: “hãy làm một framework để xử lý sự phức tạp này”
      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
    • Giờ thì người ta không tìm kiếm nữa mà hỏi ChatGPT
  • “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

    • Đồng ý. Tôi đang làm game và quyết định không dùng bất kỳ dependency nào, thế là việc code trở nên cực kỳ thú vị
  • 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

    • Là hài hước không có nghĩa là không nghiêm túc
  • 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

    • Vì thế tôi cố dùng Go cho gần như mọi thứ. Dù không phải lựa chọn tối ưu nhất, nó vẫn ít việc lặt vặt nhất
    • C mà phức tạp à? Đây gần như là ngôn ngữ duy nhất tôi có thể giữ trọn trong đầu dù có dùng thường xuyên hay không. Một số cú pháp JavaScript hiện đại thì dù dùng hằng ngày vẫn dễ dàng bốc hơi khỏi trí nhớ
  • 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.json dài quá 20 dòng là tôi gần như phản ứng y hệt thế này

    • Hiện đang trừng mắt nhìn frontend Angular mới đang phát triển, có bốn thành phần frontend và mỗi cái đều có package.json hơn 200 dòng
      Thà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
    • Tôi cũng vậy. Nhưng nói hơi lạc đề một chút thì liên kết trong hồ sơ bị uBlock Origin chặn và dẫn vào một hang thỏ của những trang web kỳ quặc. Tôi chưa từng dùng hackernewsers nên không biết đó có phải hoạt động bình thường không
  • 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