printWidthความกว้างที่ Prettier พยายามทำให้ได้ ไม่ใช่ขีดจำกัดตายตัว

ตัวเลือก Print Width ของ Prettier

printWidth เป็นออปชันของ Prettier ที่คนเข้าใจผิดบ่อยที่สุด มันไม่ใช่ความยาวบรรทัดสูงสุดที่ Prettier รับปากว่าจะไม่มีวันเกิน แต่เป็นความกว้างที่ตัวพิมพ์ใช้วัดตอนตัดสินว่านิพจน์หนึ่งยังอยู่ในบรรทัดเดียวได้หรือไม่ Prettier จะยอมพิมพ์บรรทัดที่ยาวกว่านี้เมื่อไม่มีวิธีขึ้นบรรทัดใหม่ที่ถูกต้อง และจะขึ้นบรรทัดใหม่ตั้งแต่ยังไม่ถึงขีดจำกัดเมื่อโครงสร้างบังคับ

The line length where Prettier will try wrap.
คำอธิบายของ Prettier เอง จากเอกสารตัวเลือกอย่างเป็นทางการ
ค่าเริ่มต้น
80
ชนิด
int
แฟล็ก CLI
--print-width

printWidth ควบคุมอะไรจริง ๆ

Prettier ไม่ได้จัดรูปแบบด้วยการนับตัวอักษรแล้วแทรกบรรทัดใหม่ แต่แปลงโค้ดของคุณเป็นโครงสร้างกลางที่ประกอบด้วยกลุ่ม (group) แล้วถามแต่ละกลุ่มด้วยคำถามเดียวว่า กลุ่มนี้ยังพอดีกับพื้นที่ที่เหลือบนบรรทัดนี้หรือไม่ printWidth คือตัวเลขที่ใช้วัดคำถามนั้น กลุ่มที่พอดีจะถูกพิมพ์เป็นบรรทัดเดียว ส่วนกลุ่มที่ไม่พอดีจะถูกแตกออก แล้วทุกกลุ่มข้างในก็จะถูกถามคำถามเดิมซ้ำที่ระดับย่อหน้าใหม่

ด้วยเหตุนี้ การเพิ่ม printWidth จึงไม่ได้แค่ทำให้บรรทัดยาวขึ้นเฉย ๆ แต่เปลี่ยนจุดที่โครงสร้างทั้งก้อนจะยุบหรือกางออก ทั้งอ็อบเจกต์ลิเทอรัล ลายเซ็นฟังก์ชัน และเชนของการเรียกเมท็อด แต่ละอย่างจะสลับไปมาระหว่างแบบบรรทัดละรายการกับแบบรวมบรรทัดเดียวที่ค่าขีดแบ่งบางค่า และการจัดรูปแบบใหม่ที่ตามมาหลังแก้ออปชันนี้มักใหญ่กว่าขนาดของการแก้ไขมาก

ผลที่ตามมาโดยตรงมีสามข้อ และแต่ละข้อก็ทำให้บางคนแปลกใจเสมอ

  • สตริงยาว ๆ พาธของ import หรือ URL ในคอมเมนต์ จะเลย printWidth ออกไป เพราะ Prettier จะไม่ตัดสตริงให้คุณ เนื่องจากนั่นจะเปลี่ยนค่าของมัน
  • นิพจน์ที่ซ้อนลึกอาจขึ้นบรรทัดใหม่ตั้งแต่ยังห่างจาก printWidth มาก เพราะระยะย่อหน้าถูกนับรวมเป็นส่วนหนึ่งของความกว้าง ที่ความลึกแปดชั้น คุณอาจเหลือพื้นที่ใช้งานจริงแค่สี่สิบคอลัมน์
  • ข้อความร้อยแก้วใน Markdown ไม่ได้ถูกควบคุมด้วยออปชันนี้เลย นั่นเป็นหน้าที่ของ proseWrap ส่วน printWidth จำกัดเฉพาะโค้ดที่ Prettier พิมพ์ออกมา

ทำไมค่าเริ่มต้นถึงเป็น 80

แปดสิบคอลัมน์เป็นมรดกตกทอดจากบัตรเจาะรูซึ่งเก็บได้แปดสิบตัวอักษร แล้วส่งต่อมายังเทอร์มินัลที่เลียนแบบมันอีกที ทั้งสองอย่างล้าสมัยไปแล้ว แต่ตัวเลขนี้ยังอยู่ด้วยเหตุผลสองข้อที่ยังใช้ได้จริง

ข้อแรกคือความอ่านง่าย นักออกแบบตัวพิมพ์วางความยาวบรรทัดที่สบายตาสำหรับข้อความต่อเนื่องไว้ราวหกสิบถึงเก้าสิบตัวอักษรมานาน ถ้ากว้างกว่านั้นมาก สายตาจะหาจุดเริ่มต้นของบรรทัดถัดไปไม่เจอ โค้ดไม่ใช่ร้อยแก้ว แต่ก็ยังถูกอ่านจากซ้ายไปขวาด้วยสายตาคู่เดียวกัน

ข้อสองคือ diff และนี่คือเหตุผลที่สำคัญที่สุดเมื่อทำงานเป็นทีม ความกว้างที่แคบบังคับให้ขึ้นบรรทัดใหม่บ่อยขึ้น และการขึ้นบรรทัดใหม่ที่บ่อยขึ้นทำให้การเปลี่ยนแปลงแต่ละครั้งเล็กลง เมื่ออาร์กิวเมนต์แต่ละตัวอยู่บรรทัดของตัวเอง การแก้อาร์กิวเมนต์หนึ่งตัวจะกระทบแค่บรรทัดเดียว รีวิวเห็นบรรทัดเดียว git blame ชี้ไปที่บรรทัดเดียว และ conflict ตอน merge ก็เกี่ยวข้องแค่บรรทัดเดียว พอขยายขีดจำกัดจนการเรียกฟังก์ชันนั้นยุบเหลือบรรทัดเดียว ทุกอย่างที่ว่ามาก็จะครอบคลุมทั้งการเรียกนั้นแทน

ควรเปลี่ยนเมื่อไร และเมื่อไรไม่ควร

คำตอบตรงไปตรงมาคือ 80 เป็นค่าเริ่มต้นที่ดี และโปรเจกต์ส่วนใหญ่ควรปล่อยไว้อย่างนั้น เหตุผลที่จะเพิ่มค่านั้นมีจริง แต่แคบกว่าที่เห็นตอนแรก

เพิ่มค่าเมื่อภาษาที่ใช้ยืดยาวพอจนแปดสิบคอลัมน์ไม่เหลือที่ให้เนื้อหา ทั้ง TypeScript ที่ใช้ generic ซ้อนลึก JSX ที่มี prop จำนวนมาก และโค้ดที่ตั้งชื่อยาวเพื่อสื่อความหมาย ล้วนใช้ความกว้างไปกับไวยากรณ์ก่อนจะถึงสิ่งที่มีความหมายจริง ค่า 100 คือก้าวแรกที่คนนิยมและเป็นค่าเริ่มต้นที่ปกป้องได้สำหรับโค้ดเบส TypeScript สมัยใหม่ ส่วน 120 เหมาะกับจอกว้างและการรีวิวทีละไฟล์ แต่แลกมาด้วยต้นทุนจริงเมื่อดู diff แบบเทียบข้างกัน

อย่าเพิ่มค่าเพียงเพื่อปิดเสียงบ่นของ linter ถ้า max-len กำลังบ่น ทางแก้คือหยุดไม่ให้ max-len ทำงานเลย ดูหัวข้อด้านล่าง การเพิ่ม printWidth ให้ตรงกับกฎ lint คือการผูกสองการตั้งค่าที่ตอบคำถามคนละเรื่องเข้าด้วยกัน แล้วสุดท้ายคุณจะแก้อันหนึ่งแล้วลืมอีกอัน

{
  "printWidth": 100
}
ตัวเลือกที่พบบ่อยสำหรับโปรเจกต์ TypeScript ที่มีชื่อชนิดข้อมูลยาว

ข้อแลกเปลี่ยนและข้อตกลงของทีม

ไม่ว่าจะเลือกค่าไหน ให้เลือกครั้งเดียว ต้นทุนของออปชันนี้เกือบทั้งหมดอยู่ที่การเปลี่ยนค่า ไม่ใช่ที่ตัวค่าเอง เพราะการเปลี่ยนแต่ละครั้งจะจัดรูปแบบโค้ดเบสใหม่เป็นสัดส่วนใหญ่ ซึ่งกลบประวัติการแก้ไขจริงไว้ใต้คอมมิตที่เป็นแค่การจัดรูปแบบ

  • เปลี่ยนในคอมมิตแยกที่ไม่ทำอย่างอื่นเลย เพื่อให้ผู้รีวิวข้ามได้ และเพื่อให้สอน git blame ให้ข้ามได้ด้วย
  • ใส่คอมมิตนั้นในไฟล์ .git-blame-ignore-revs แล้วชี้ blame.ignoreRevsFile มาที่ไฟล์นี้ เพื่อให้ข้อมูลผู้เขียนรอดจากการจัดรูปแบบใหม่
  • คิดถึงวิธีที่ทีมรีวิวโค้ด diff แบบเทียบข้างกันบนแล็ปท็อปให้พื้นที่แต่ละฝั่งราวครึ่งหน้าจอ ค่า 120 จะถูกตัดบรรทัดในเครื่องมือรีวิวถึงแม้จะพอดีในเอดิเตอร์
  • ให้ดูที่ที่แคบที่สุดที่โค้ดถูกอ่าน ไม่ใช่ที่ที่กว้างที่สุดที่โค้ดถูกเขียน

ESLint เอดิเตอร์ และ CI

กฎ max-len ของ ESLint กับ printWidth จะขัดกันเอง เพราะ max-len เป็นขีดจำกัดตายตัวส่วน printWidth ไม่ใช่ บรรทัดที่ Prettier ตัดไม่ได้ตามกฎไวยากรณ์ เช่นสตริงยาวหรือ import ยาว จะผ่าน Prettier แต่ตก ESLint ไปตลอดกาล

ทางแก้คือ eslint-config-prettier ซึ่งปิดกฎ ESLint ทุกข้อที่ทับซ้อนกับการจัดรูปแบบ รวมถึง max-len ด้วย วางไว้ท้ายสุดในคอนฟิกเพื่อให้มันมีผลเหนือกว่า

import prettier from 'eslint-config-prettier';

export default [
  // ...กฎของคุณ
  prettier,
];
eslint.config.mjs — คอนฟิกของ Prettier ต้องอยู่ท้ายสุด

ในเอดิเตอร์ ให้ตั้งเส้นไกด์ที่ตัวเลขเดียวกันเพื่อให้เห็นขีดจำกัดขณะพิมพ์ ใช้ editor.rulers ใน VS Code เส้นนั้นเป็นแค่ตัวช่วยมอง ไม่ใช่กฎ Prettier ยังคงเป็นสิ่งเดียวที่ตัดสินว่าบรรทัดจะขึ้นใหม่ตรงไหนจริง ๆ

ข้อผิดพลาดที่พบบ่อย

  • มองว่ามันเป็นการรับประกัน จริง ๆ มันเป็นเป้าหมาย บรรทัดจะเกินค่านี้ได้ และนั่นคือพฤติกรรมที่ถูกต้อง ไม่ใช่บั๊กที่ต้องไปรายงาน
  • ตั้งค่าให้ตรงกับ max-len แทนที่จะปิด max-len ไปเลย กลายเป็นมีแหล่งความจริงสองแหล่งสำหรับการตัดสินใจเดียว
  • เปลี่ยนค่าบนโค้ดเบสขนาดใหญ่ในคอมมิตที่มีงานจริงปนอยู่ด้วย ซึ่งทำให้งานจริงนั้นรีวิวไม่ได้
  • คาดหวังว่ามันจะตัดบรรทัดย่อหน้าใน Markdown ให้ ใช้ proseWrap แทน
  • ปรับค่าแยกรายไฟล์ด้วย overrides ซึ่งทำให้โค้ดเบสดูไม่สม่ำเสมอโดยที่ผู้อ่านไม่ได้ประโยชน์อะไร

ใช้ใน .prettierrc

ใส่ printWidth ลงในไฟล์ตั้งค่า Prettier ของคุณ:

{
  "printWidth": 80
}
ลองใช้ในตัวสร้าง

ตัวอย่างจริง

โค้ดเดียวกันที่จัดรูปแบบด้วยแต่ละค่าของ printWidth

printWidth: 40

const user = {
  name: "Ada",
  "user-id": 7,
  roles: ["admin", "editor"],
  active: true,
};
const greet = (name) => `Hello ${name}`;
const label = user.active
  ? "active member of the team"
  : "inactive member of the team";
export function summarize(items) {
  return items
    .filter((i) => i.active)
    .map((i) => i.name)
    .join(", ");
}

printWidth: 120

const user = { name: "Ada", "user-id": 7, roles: ["admin", "editor"], active: true };
const greet = (name) => `Hello ${name}`;
const label = user.active ? "active member of the team" : "inactive member of the team";
export function summarize(items) {
  return items
    .filter((i) => i.active)
    .map((i) => i.name)
    .join(", ");
}

คำถามที่พบบ่อย

printWidth รับประกันว่าจะไม่มีบรรทัดไหนเกินค่านี้ใช่ไหม
ไม่ใช่ Prettier ถือว่ามันเป็นความกว้างที่พยายามทำให้ได้ ไม่ใช่เพดาน เมื่อไม่มีจุดขึ้นบรรทัดใหม่ที่ถูกต้อง เช่นภายในสตริง พาธ import ยาว ๆ หรือ URL ในคอมเมนต์ บรรทัดนั้นก็จะยาวเกิน และไม่มีการตั้งค่าใดเปลี่ยนเรื่องนี้ได้
ควรเลือกค่าเท่าไร
ปล่อยไว้ที่ 80 เว้นแต่มีเหตุผลชัดเจน ค่า 100 เป็นก้าวถัดไปที่นิยมสำหรับโค้ดเบส TypeScript ที่มีชื่อชนิดข้อมูลยาว ส่วน 120 เหมาะกับจอกว้างแต่ทำให้การรีวิวแบบเทียบข้างกันแย่ลง ตัวเลขสำคัญน้อยกว่าการเลือกสักค่าแล้วไม่กลับมารื้ออีก
ทำไมเปลี่ยน printWidth แล้วโค้ดถูกจัดรูปแบบใหม่เยอะขนาดนี้
เพราะมันไม่ได้ทำให้บรรทัดยาวขึ้น แต่เปลี่ยนว่าโครงสร้างไหนพอดี ทุกอ็อบเจกต์ รายการอาร์กิวเมนต์ และเชนการเรียกที่อยู่ใกล้ขีดเดิมจะสลับระหว่างกางกับยุบ ให้แยกการเปลี่ยนนี้เป็นคอมมิตของตัวเองแล้วเพิ่มลงใน .git-blame-ignore-revs
printWidth ทำงานร่วมกับ tabWidth อย่างไร
ระยะย่อหน้าถูกนับรวมในความกว้างที่วัด และหนึ่งแท็บถูกนับเป็น tabWidth คอลัมน์ การเพิ่ม tabWidth จึงเหลือที่ให้โค้ดน้อยลงบนบรรทัดที่ซ้อนลึก เท่ากับบีบ printWidth ให้แคบลงตรงจุดที่ซ้อนลึกที่สุด

ตัวเลือก Global อื่น ๆ

สร้างจาก Prettier 3.9.6