npm เป็นเครื่องมือการจัดการแพ็คเกจที่ใช้กันอย่างแพร่หลายโดยนักพัฒนาส่วนหน้า ในโปรเจ็กต์นั้น package.json ใช้เพื่อจัดการการกำหนดค่าของแพ็คเกจ npm ที่โปรเจ็กต์ต้องพึ่งพา package.json เป็นไฟล์ json นอกเหนือจากการอธิบายการขึ้นต่อกันของแพ็คเกจของโปรเจ็กต์แล้ว ยังช่วยให้เราใช้ "กฎการกำหนดเวอร์ชันเชิงความหมาย" เพื่อระบุเวอร์ชันของแพ็คเกจที่ขึ้นต่อกันของโปรเจ็กต์ของคุณ ทำให้สามารถแชร์ build ของคุณกับนักพัฒนารายอื่น ๆ ได้ง่ายขึ้น นำมาใช้ใหม่ บทความนี้ส่วนใหญ่เริ่มต้นจากแนวปฏิบัติล่าสุด รวมกับเวอร์ชัน npm และโหนดล่าสุด เพื่อแนะนำการกำหนดค่าทั่วไปบางอย่างใน package.json และวิธีเขียน package.json ที่เป็นมาตรฐาน
ในโปรเจ็กต์ nodejs นั้น package.json เป็นไฟล์คอนฟิกูเรชันที่จัดการการขึ้นต่อกัน โดยปกติเมื่อเราเริ่มต้นโปรเจ็กต์ nodejs เราจะผ่าน:
npm init
จากนั้น 3 จะถูกสร้างขึ้นใน ไดเร็กทอรีของคุณ ไดเร็กทอรี/ไฟล์, node_modules, package.json และ package.lock.json เนื้อหาของ package.json คือ:
{ "name": "ชื่อโครงการของคุณ", "เวอร์ชัน": "1.0.0", "description": "คำอธิบายโครงการของคุณ", "main": "app.js", "สคริปต์": { "test": "echo "ข้อผิดพลาด: ไม่ได้ระบุการทดสอบ" && ออก 1", - "ผู้เขียน": "ชื่อผู้เขียน", "ใบอนุญาต": "ISC", "การอ้างอิง": { "dependency1": "^1.4.0", "การพึ่งพา2": "^1.5.2" - }
ดังที่เห็นได้จากด้านบน package.json มีข้อมูลเมตาของโครงการเอง เช่นเดียวกับข้อมูลการขึ้นต่อกันย่อยของโครงการ (เช่น การขึ้นต่อกัน ฯลฯ)
เราพบว่าในระหว่าง npm init ไม่เพียงสร้างไฟล์ package.json เท่านั้น แต่ยังสร้างไฟล์ package-lock.json ด้วย เหตุใดเราจึงยังต้องสร้างไฟล์ package-lock.json เมื่อทำการล้าง package.json โดยพื้นฐานแล้ว ไฟล์ package-lock.json ใช้สำหรับล็อคเวอร์ชัน แพ็คเกจย่อย npm ที่ระบุใน package.json มีลักษณะดังนี้: react: "^16.0.0" ในการติดตั้งจริง ตราบใดที่เวอร์ชันนั้นสูงกว่า ตอบสนอง package.json เป็นไปตามข้อกำหนด ซึ่งหมายความว่าตามไฟล์ package.json เดียวกัน เวอร์ชันการพึ่งพาย่อยที่ติดตั้งสองครั้งไม่สามารถรับประกันได้ว่าสอดคล้องกัน
ไฟล์ package-lock ดังที่แสดงด้านล่าง และการพึ่งพาย่อย 1 จะระบุเวอร์ชันโดยละเอียด รับบทเป็นเวอร์ชั่นล็อค
- "name": "ชื่อโครงการของคุณ", "เวอร์ชัน": "1.0.0", "lockfileVersion": 1, "ต้องการ": จริง "การอ้างอิง": { "การพึ่งพา 1": { "เวอร์ชัน": "1.4.0", "แก้ไขแล้ว": "https://registry.npmjs.org/dependency1/-/dependency1-1.4.0.tgz", "ความซื่อสัตย์": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" - "การพึ่งพา2": { "เวอร์ชัน": "1.5.2", "แก้ไขแล้ว": "https://registry.npmjs.org/dependency2/-/dependency2-1.5.2.tgz", "ความซื่อสัตย์": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==" - - }
บทนี้จะพูดถึงคุณลักษณะการกำหนดค่าที่ใช้โดยทั่วไปใน package.json คุณลักษณะต่างๆ เช่น ชื่อ เวอร์ชัน และอื่นๆ นั้นเรียบง่ายเกินไป และจะไม่ถูกนำมาใช้ทีละรายการ บทนี้จะแนะนำคุณสมบัติของสคริปต์ bin และพื้นที่ทำงานเป็นหลัก
ใช้แท็กสคริปต์ใน npm เพื่อกำหนดสคริปต์ เมื่อใดก็ตามที่ระบุการรัน npm เชลล์สคริปต์จะถูกสร้างขึ้นโดยอัตโนมัติ ไดเร็กทอรีท้องถิ่น เพิ่มไดเร็กทอรีย่อยให้กับตัวแปร PATH
ซึ่งหมายความว่าสคริปต์ทั้งหมดในไดเร็กทอรีย่อย node_modules/.bin ของไดเร็กทอรีปัจจุบันสามารถเรียกได้โดยตรงด้วยชื่อสคริปต์โดยไม่ต้องเพิ่มพาธ ตัวอย่างเช่น หากการขึ้นต่อกันของโปรเจ็กต์ปัจจุบันมี esbuild ให้เขียน esbuild xxx โดยตรง
- - "สคริปต์": { "build": "esbuild index.js", - }
{ - "สคริปต์": { "build": "./node_modules/.bin/esbuild index.js" - }
การเขียนสองวิธีข้างต้นเทียบเท่ากัน
แอ็ตทริบิวต์ bin ใช้เพื่อโหลดไฟล์ปฏิบัติการลงสู่สภาพแวดล้อมโกลบอล เมื่อระบุแพ็คเกจ npm พร้อมฟิลด์ bin แล้ว มันจะถูกโหลดลงสู่สภาพแวดล้อมโกลบอลและไฟล์สามารถดำเนินการผ่านนามแฝงได้
ตัวอย่างเช่น แพ็กเกจ npm ของ @bytepack/cli:
"bin": { "bytepack": "./bin/index.js" }
เมื่อติดตั้ง @bytepack/cli ทั่วโลกแล้ว คุณสามารถดำเนินการคำสั่งที่เกี่ยวข้องได้โดยตรงผ่าน bytepack เช่น
bytepack -v //แสดง 1.11.0
หากไม่ได้ติดตั้งแบบโกลบอล มันจะเชื่อมต่อกับไดเร็กทอรี node_module/.bin ของโปรเจ็กต์โดยอัตโนมัติ เพื่อให้สอดคล้องกับสิ่งที่กล่าวไว้ในแท็กสคริปต์ที่แนะนำก่อนหน้านี้ สามารถใช้กับนามแฝงได้โดยตรง
เมื่อโครงการมีขนาดใหญ่เกินไป monorepo ก็ได้รับความนิยมมากขึ้นเรื่อยๆ ในช่วงนี้ เมื่อพูดถึง monorepo เราไม่ได้ดูที่พื้นที่ทำงาน ในช่วงแรกๆ เราจะใช้พื้นที่ทำงานของ Yarn ตอนนี้ npm รองรับพื้นที่ทำงานอย่างเป็นทางการ แก้ปัญหาวิธีจัดการแพ็คเกจย่อยหลายรายการภายใต้แพ็คเกจรูทระดับบนสุด ในระบบไฟล์โลคัล ในไดเร็กทอรีการประกาศพื้นที่ทำงาน แพ็กเกจจะถูกซอฟต์ลิงก์ไปยัง node_modules ของแพ็กเกจรูทระดับบนสุด
มาอธิบายโดยตรงจากตัวอย่างจากเว็บไซต์อย่างเป็นทางการ:
{ "name": "โครงการของฉัน", "พื้นที่ทำงาน": [ "แพ็คเกจ/ก" - }
ในแพ็คเกจ npm ชื่อ my-project มีไดเร็กทอรีที่กำหนดค่าโดยพื้นที่ทำงาน
- +-- แพ็คเกจ json +--index.js `-- แพ็คเกจ +--ก |. `-- package.json
และแพ็คเกจรูทระดับบนสุดที่ชื่อ my-project มีแพ็คเกจ/แพ็คเกจย่อย ในเวลานี้ หากเราติดตั้ง npm แพ็คเกจ npm a ที่ติดตั้งใน node_modules ในแพ็คเกจรูทจะชี้ไปที่แพ็คเกจโลคัล/
a +-- node_modules |. `-- แพ็คเกจ/a -> ../แพ็คเกจ/a +-- แพ็คเกจ-lock.json +-- แพ็คเกจ json `-- แพ็คเกจ +--ก |. `-- package.json
ด้านบน
--packages/a -> ../packages/a
อ้างถึงซอฟต์ลิงก์จาก a ใน node_modules ไปยังแพ็คเกจ npm ท้องถิ่น
สภาพแวดล้อมทั่วไปที่มีคุณลักษณะที่เกี่ยวข้องกับสภาพแวดล้อมของ package.json โดยทั่วไป ข้างต้นแบ่งออกเป็นสองประเภท: เบราว์เซอร์และสภาพแวดล้อมของโหนด ต่อไปเรามาดูคุณสมบัติการกำหนดค่าที่เกี่ยวข้องกับสภาพแวดล้อมใน package.json คำจำกัดความของสภาพแวดล้อมสามารถเข้าใจได้ง่าย ๆ ดังนี้:
js ประกอบด้วยโมดูล commonjs, CMD, UMD, AMD และ ES ในตอนแรก เฉพาะฟิลด์ commonjs เท่านั้นที่ได้รับการสนับสนุนในโหนด อย่างไรก็ตาม เริ่มต้นจาก node13.2.0 โหนดสนับสนุนข้อกำหนดโมดูล ES อย่างเป็นทางการ สามารถใช้ฟิลด์ใน package.json เพื่อประกาศข้อกำหนดโมดูลาร์ที่แพ็คเกจ npm ปฏิบัติตาม
//package.json - ชื่อ: "บางแพ็คเกจ" ประเภท: "โมดูล"||"commonjs" }
ควรสังเกตว่า
เมื่อไม่ได้ระบุประเภท ค่าเริ่มต้นของประเภทคือ commonjs อย่างไรก็ตาม ขอแนะนำให้แพ็กเกจ npm ทั้งหมดระบุประเภท
เมื่อฟิลด์ประเภทระบุค่าเป็นโมดูล ข้อกำหนด ESModule จะถูก
ใช้ ฟิลด์ประเภทถูกระบุ .js ทั้งหมดในไดเร็กทอรีจะถูกใช้ ไฟล์ที่ลงท้ายด้วยส่วนต่อท้ายเป็นไปตามข้อกำหนดการทำให้เป็นโมดูลที่ระบุตาม
ประเภท ส่วนต่อท้ายของไฟล์ ไฟล์ที่ลงท้ายด้วย .mjs คือข้อกำหนด ESModule ที่ใช้ ส่วนท้ายของ .cjs เป็นไปตามข้อกำหนด commonjs
นอกเหนือจากประเภทแล้ว package.json ยังมีสามฟิลด์: main, module และ browser เพื่อกำหนดไฟล์รายการของแพ็คเกจ npm
มาดูสถานการณ์การใช้งานของทั้งสามฟิลด์นี้และลำดับความสำคัญเมื่อมีทั้งสามฟิลด์นี้อยู่ในเวลาเดียวกัน สมมติว่ามีแพ็คเกจ npm ชื่อ demo1,
----- dist |--index.browser.js |--index.browser.mjs |--index.js |--
package.json ของ index.mjs ระบุสามฟิลด์ main, module และ browser ในเวลาเดียวกัน,
"main": "dist/index.js", // main "module": "dist/index.mjs", // โมดูล // เบราว์เซอร์สามารถกำหนดเป็นวัตถุการแมปที่สอดคล้องกับฟิลด์หลัก/โมดูล หรือสามารถกำหนดโดยตรงเป็นสตริง "เบราว์เซอร์": { "./dist/index.js": "./dist/index.browser.js", // เบราว์เซอร์ + cjs "./dist/index.mjs": "./dist/index.browser.mjs" // เบราว์เซอร์ + mjs - // "browser": "./dist/index.browser.js" // เบราว์เซอร์
ถูกสร้างและใช้งานตามค่าเริ่มต้น ตัวอย่างเช่น หากเราอ้างอิงแพ็คเกจ npm นี้ในโครงการ:
นำเข้าการสาธิตจาก 'demo'
หลังจากสร้างข้างต้น โค้ดผ่านเครื่องมือ build โมดูล ลำดับการโหลดคือ:
browser
+mjs > module > browser+cjs > main
ลำดับการโหลดนี้สามารถแก้ไขได้ผ่านการกำหนดค่าที่เกี่ยวข้อง แต่ในสถานการณ์ส่วนใหญ่ เราจะยังคงปฏิบัติตามลำดับการโหลดเริ่มต้น
หากกำหนดฟิลด์การส่งออกใน package.json เนื้อหาที่กำหนดไว้ในฟิลด์นี้จะเป็นการส่งออกแพ็คเกจ npm ที่แท้จริงและสมบูรณ์ และลำดับความสำคัญจะสูงกว่าฟิลด์หลักและฟิลด์ไฟล์
ตัวอย่างเช่น:
{ "ชื่อ": "pkg", "ส่งออก": { ".": "./main.mjs", "./foo": "./foo.js" - }
นำเข้า { บางอย่าง } จาก "pkg"; // จาก "pkg/main.mjs"
const { บางอย่าง } = need("pkg/foo"); // need("pkg/foo.js")
จากตัวอย่างข้างต้น ดูเหมือนว่าการส่งออกสามารถกำหนดการส่งออกด้วยเส้นทางที่ต่างกัน หากมีการส่งออก ไดเร็กทอรีไฟล์ที่ถูกต้องก่อนหน้านี้จะใช้ไม่ได้ในทุกที่ เช่น need('pkg/package.json') เนื่องจากไม่ได้ระบุไว้ในการส่งออก และจะมีการรายงานข้อผิดพลาด
คุณสมบัติที่ใหญ่ที่สุดอีกประการหนึ่งของการส่งออกคือการอ้างอิงแบบมีเงื่อนไข ตัวอย่างเช่น เราสามารถระบุแพ็คเกจ npm เพื่ออ้างอิงไฟล์รายการต่างๆ ตามวิธีอ้างอิงที่แตกต่างกันหรือประเภทโมดูลาร์
// package.json - "ชื่อ": "pkg", "main": "./main-require.cjs", "ส่งออก": { "นำเข้า": "./main-module.js", "ต้องการ": "./main-require.cjs" - "ประเภท": "โมดูล" }
ในตัวอย่างข้างต้น หากเรา
อ้างถึง "./main-require.cjs"
ถึงconst p = need('pkg')
หากคุณผ่าน:
import p จาก 'pkg'
การอ้างอิงคือ "./main-module.js"
สิ่งสุดท้ายที่ควรทราบคือ: หากมีแอตทริบิวต์การส่งออกอยู่ คุณลักษณะการส่งออกจะไม่เพียงแต่มีลำดับความสำคัญสูงกว่าหลักเท่านั้น แต่ยังสูงกว่าฟิลด์โมดูลและเบราว์เซอร์ด้วย
คุณสมบัติการกำหนดค่าที่เกี่ยวข้องกับการพึ่งพาใน package.json ประกอบด้วยการพึ่งพา, devDependencies, peerDependencies, peerDependenciesMeta เป็นต้น
การขึ้นต่อกันคือการขึ้นต่อกันของโปรเจ็กต์ และ devDependencies คือโมดูลที่จำเป็นสำหรับการพัฒนา ดังนั้นเราจึงสามารถติดตั้งได้ตามต้องการในระหว่างกระบวนการพัฒนาเพื่อปรับปรุงประสิทธิภาพการพัฒนาของเรา สิ่งที่ต้องสังเกตที่นี่คือพยายามใช้ให้เป็นมาตรฐานที่สุดเท่าที่จะเป็นไปได้ในโปรเจ็กต์ของคุณ ตัวอย่างเช่น webpack, babel ฯลฯ เป็นการพึ่งพาการพัฒนา ไม่ใช่การพึ่งพาของโปรเจ็กต์เอง
การขึ้นต่อกัน นอกเหนือจากการขึ้นต่อกันและ devDependencies แล้ว บทความนี้ยังเน้นที่ peerDependencies และ peerDependenciesMeta
peerDependencies คือการขึ้นต่อกันใน package.json ซึ่งสามารถแก้ปัญหาไลบรารีหลักที่ถูกดาวน์โหลดหลายครั้ง และรวมเวอร์ชันไลบรารีหลักเข้าด้วยกัน
//แพ็คเกจ/แพ็ค -----node_modules |-- npm-a -> ขึ้นอยู่กับ react, react-dom |-- npm-b -> ขึ้นอยู่กับ react, react-dom |-- index.js
ตัวอย่างเช่น ในตัวอย่างข้างต้น หากแพ็กเกจย่อย npm a และ b มาจาก react และ react-dom ทั้งคู่ ถ้าเราประกาศ PeerDependicies ใน package.json ของแพ็กเกจย่อย npm a และ b การขึ้นต่อกันที่เกี่ยวข้องจะไม่ถูกติดตั้งใหม่
มีสองประเด็นที่ควรทราบ:
"peerDependencies": { "ตอบสนอง": "^16.8.3 || ^17 || ^18" - "เพียร์พึ่งพาเมตา": { "ปฏิกิริยาโดม": { "ไม่จำเป็น": จริง - "ตอบสนองพื้นเมือง": { "ไม่จำเป็น": จริง - }
"react-dom" และ "react-native" ได้รับการระบุไว้ที่นี่ใน peerDependenciesMeta และเป็นทางเลือก ดังนั้นหากไม่ได้ติดตั้ง "react-dom" และ "react-native" ในโปรเจ็กต์ จะไม่มีการรายงานข้อผิดพลาด
เป็นที่น่าสังเกตว่าเราได้ยกเลิกข้อจำกัดจริง ๆ ผ่าน peerDependenciesMeta แต่มักจะมีสถานการณ์ที่เป็น A หรือ B ตัวอย่างเช่น ในตัวอย่างข้างต้น สิ่งที่เราต้องการคือ "react-dom" และ "react-native" และเราจำเป็นต้องติดตั้งอันหนึ่ง แต่อันที่จริง เราไม่สามารถบรรลุพร้อมท์นี้ผ่านข้อความข้างต้นได้
ยังมีคุณลักษณะของบุคคลที่สามอีกมากมายใน package.json เช่น ประเภทที่ใช้ใน tsc, ผลข้างเคียงที่ใช้ในเครื่องมือสร้าง, husky ที่ใช้ใน git และ eslintIgnore ที่ใช้ใน eslint ส่วนขยายมีไว้สำหรับเครื่องมือการพัฒนาเฉพาะเจาะจงที่มีความหมาย และฉันจะไม่ยกตัวอย่างที่นี่