แนวทางปฏิบัติที่ดีที่สุดของไลบรารีองค์ประกอบที่ใช้ร่วมกัน


12

ฉันกำลังสร้างไลบรารีคอมโพเนนต์ React ที่แชร์ได้

ไลบรารีมีส่วนประกอบมากมาย แต่ผู้ใช้อาจจำเป็นต้องใช้เพียงไม่กี่องค์ประกอบเท่านั้น

เมื่อคุณรวมรหัสกับ Webpack (หรือพัสดุหรือ Rollup) มันจะสร้างไฟล์เดียวหนึ่งไฟล์ที่มีรหัสทั้งหมด

ด้วยเหตุผลด้านประสิทธิภาพฉันไม่ต้องการให้เบราว์เซอร์ดาวน์โหลดรหัสทั้งหมดเว้นแต่จะใช้งานจริง ฉันคิดถูกหรือไม่ว่าไม่ควรรวมส่วนประกอบต่างๆ การรวมกลุ่มจะถูกทิ้งไว้กับผู้บริโภคของส่วนประกอบหรือไม่ ฉันจะปล่อยให้สิ่งอื่นใดกับผู้บริโภคส่วนประกอบหรือไม่ ฉันเพิ่งจะส่งผ่าน JSX และมันได้หรือไม่

หาก repo เดียวกันมีส่วนประกอบต่าง ๆ มากมายสิ่งที่ควรอยู่ใน main.js?


1
ถ้าผมเข้าใจคำถามของคุณได้อย่างถูกต้องคุณกำลังมองหาวิธีการเช่นนี้หนึ่งจะดูที่รหัสที่มาของพวกเขาและคุณจะเห็นว่าพวกเขาส่งออกส่วนประกอบทั้งหมดเช่นเดียวกับคนของแต่ละบุคคลและเมื่อ app ลูกค้าใช้ส่วนประกอบของพวกเขา (และการนำเข้าของแต่ละบุคคล ส่วนประกอบแทนโมดูลทั้งหมด) webpack จะดึงเฉพาะไฟล์ที่อยู่importedในรหัสจึงลดขนาดมัด
Edward Chopuryan

คำตอบ:


5

นี่เป็นคำตอบที่ยาวมากเพราะคำถามนี้สมควรได้รับคำตอบที่ยาวและละเอียดมากเพราะวิธี "การปฏิบัติที่ดีที่สุด" นั้นซับซ้อนกว่าการตอบกลับเพียงไม่กี่บรรทัด

Iv'e รักษาห้องสมุดในบ้านของเราเป็นเวลา 3.5 ปีในเวลานั้น iv'e ตัดสินด้วยสองวิธีที่ฉันคิดว่าห้องสมุดควรจะรวมเอาการแลกเปลี่ยนขึ้นอยู่กับว่าห้องสมุดของคุณใหญ่แค่ไหนและโดยส่วนตัวเรารวบรวมทั้งสองวิธี ผู้บริโภค

วิธีที่ 1: สร้างไฟล์ index.ts พร้อมทุกสิ่งที่คุณต้องการให้ส่งออกและการยกเลิกเป้าหมายที่ไฟล์นี้เป็นอินพุต รวมไลบรารีทั้งหมดของคุณเป็นไฟล์ index.js และไฟล์ index.css ไฟล์เดียว ด้วยการพึ่งพาจากภายนอกที่สืบทอดมาจากโครงการผู้บริโภคเพื่อหลีกเลี่ยงการทำซ้ำรหัสห้องสมุด (สรุปสาระสำคัญรวมอยู่ที่ด้านล่างของตัวอย่างการกำหนดค่า)

  • ข้อดี: ง่ายต่อการบริโภคเนื่องจากผู้บริโภคในโครงการสามารถนำเข้าทุกสิ่งจากเส้นทางของไลบรารีแบบสัมพันธ์ import { Foo, Bar } from "library"
  • จุดด้อย: นี่จะไม่มีต้นไม้สั่นไหว และก่อนที่ผู้คนจะบอกว่าทำสิ่งนี้กับ ESM และมันจะเป็นสิ่งที่หลีกเลี่ยงไม่ได้ NextJS ไม่สนับสนุน ESM ในขั้นตอนนี้และไม่มีการตั้งค่าโครงการจำนวนมากดังนั้นจึงเป็นความคิดที่ดีในการรวบรวมโครงสร้างนี้เป็นเพียง CJS หากมีคนนำเข้าส่วนประกอบ 1 รายการของคุณพวกเขาจะได้รับ CSS และจาวาสคริปต์ทั้งหมดสำหรับส่วนประกอบทั้งหมดของคุณ

วิธีที่ 2: สิ่งนี้สำหรับผู้ใช้ขั้นสูง: สร้างไฟล์ใหม่สำหรับทุกการส่งออกและใช้ rollup-plugin-multi-input พร้อมกับตัวเลือก "maintainModules: true" ขึ้นอยู่กับว่าระบบ css ที่คุณใช้อยู่นั้นเป็นอย่างไร css ของคุณไม่ได้รวมอยู่ในไฟล์เดียว แต่ไฟล์ css แต่ละไฟล์ต้องการคำสั่ง (". css") ที่อยู่ในไฟล์เอาต์พุตหลังการยกเลิกและไฟล์ css นั้นมีอยู่

  • ข้อดี: เมื่อผู้ใช้นำเข้า {Foo} จาก "library / dist / foo" พวกเขาจะได้รับรหัสสำหรับ Foo เท่านั้นและ css สำหรับ Foo และไม่มีอะไรเพิ่มเติม
  • ข้อด้อย: การตั้งค่านี้เกี่ยวข้องกับคอนซูเมอร์ที่ต้องจัดการกับคำสั่ง node_modules ต้องการ (". css") ในการกำหนดค่าบิลด์ด้วย NextJS ซึ่งทำได้ด้วยnext-transpile-modulesแพ็กเกจ npm
  • ข้อแม้: เราใช้ปลั๊กอินบาเบลของเราเองที่นี่: https://www.npmjs.com/package/babel-plugin-qubicเพื่ออนุญาตให้ผู้คนimport { Foo,Bar } from "library"เปลี่ยน Babel เป็น ...
import { Foo } from "library/dist/export/foo"
import { Bar } from "library/dist/export/bar"

เรามีการกำหนดค่าการยกเลิกหลายที่ที่เราใช้จริงทั้งสองวิธี ดังนั้นสำหรับผู้ใช้ห้องสมุดที่ไม่สนใจการเขย่าต้นไม้สามารถทำได้"Foo from "library"และนำเข้าไฟล์ css เดียว และสำหรับผู้ใช้ห้องสมุดที่สนใจการเขย่าต้นไม้และใช้ CSS ที่สำคัญเท่านั้นพวกเขาสามารถเปิดปลั๊กอินของ Babel ได้

คู่มือการยกเลิกสำหรับการปฏิบัติที่ดีที่สุด:

ไม่ว่าคุณจะใช้ typescript หรือไม่สร้างเสมอกับ"rollup-plugin-babel": "5.0.0-alpha.1" ตรวจสอบว่า. babelrc ของคุณมีลักษณะเช่นนี้

{
  "presets": [
    ["@babel/preset-env", {
      "targets": {"chrome": "58", "ie": "11"},
      "useBuiltIns": false
    }],
    "@babel/preset-react",
    "@babel/preset-typescript"
  ],
  "plugins": [
    ["@babel/plugin-transform-runtime", {
      "absoluteRuntime": false,
      "corejs": false,
      "helpers": true,
      "regenerator": true,
      "useESModules": false,
      "version": "^7.8.3"
    }],
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-transform-classes",
    ["@babel/plugin-proposal-optional-chaining", {
      "loose": true
    }]
  ]
}

และด้วยปลั๊กอิน babel ในการยกเลิกเช่นนี้ ...

        babel({
            babelHelpers: "runtime",
            extensions,
            include: ["src/**/*"],
            exclude: "node_modules/**",
            babelrc: true
        }),

และ package.json ของคุณดู ATLEAST เช่นนี้:

    "dependencies": {
        "@babel/runtime": "^7.8.3",
        "react": "^16.10.2",
        "react-dom": "^16.10.2",
        "regenerator-runtime": "^0.13.3"
    },
    "peerDependencies": {
        "react": "^16.12.0",
        "react-dom": "^16.12.0",
    }

และในที่สุดคุณภายนอกก็จะมอง ATLEAST แบบนี้

const makeExternalPredicate = externalArr => {
    if (externalArr.length === 0) return () => false;
    return id => new RegExp(`^(${externalArr.join('|')})($|/)`).test(id);
};

//... rest of rollup config above external.
    external: makeExternalPredicate(Object.keys(pkg.peerDependencies || {}).concat(Object.keys(pkg.dependencies || {}))),
// rest of rollup config below external.

ทำไม?

  • สิ่งนี้จะรวมอึของคุณเป็นโดยอัตโนมัติเพื่อสืบทอดการตอบสนอง / react-dom และการพึ่งพาเพื่อน / ภายนอกอื่น ๆ ของคุณจากโครงการผู้บริโภคซึ่งหมายความว่าพวกเขาจะไม่ซ้ำในกลุ่มของคุณ
  • สิ่งนี้จะรวมเข้ากับ ES5
  • สิ่งนี้จะต้องการ (".. ") โดยอัตโนมัติในฟังก์ชัน babel helper สำหรับ objectSpread, คลาสและอื่น ๆ จากโครงการผู้บริโภคซึ่งจะล้างอีก 15-25KB จากขนาดชุดรวมของคุณและหมายความว่าฟังก์ชันผู้ช่วยสำหรับ objectSpread จะไม่ซ้ำกันในห้องสมุดของคุณ เอาท์พุท + โครงการบริโภคเอาท์พุทรวม
  • ฟังก์ชั่น Async จะยังคงใช้งานได้
  • externals จะจับคู่สิ่งที่เริ่มต้นด้วยส่วนต่อท้ายของการพึ่งพาเพียร์นั้นเช่น babel-helpers จะจับคู่กับภายนอกสำหรับ Babel-helpers / helpers / object-spread

ในที่สุดนี่คือส่วนสำคัญสำหรับไฟล์การกำหนดค่า rollup การส่งออกไฟล์เดียว index.js https://gist.github.com/ShanonJackson/deb65ebf5b2094b3eac6141b9c25a0e3 ที่เป้าหมาย src / export / index.ts มีลักษณะเช่นนี้ ...

export { Button } from "../components/Button/Button";
export * from "../components/Button/Button.styles";

export { Checkbox } from "../components/Checkbox/Checkbox";
export * from "../components/Checkbox/Checkbox.styles";

export { DatePicker } from "../components/DateTimePicker/DatePicker/DatePicker";
export { TimePicker } from "../components/DateTimePicker/TimePicker/TimePicker";
export { DayPicker } from "../components/DayPicker/DayPicker";
// etc etc etc

แจ้งให้เราทราบหากคุณประสบปัญหากับ Babel, Rollup หรือมีคำถามใด ๆ เกี่ยวกับการรวม / ห้องสมุด


3

เมื่อคุณรวมรหัสกับ Webpack (หรือพัสดุหรือ Rollup) มันจะสร้างไฟล์เดียวหนึ่งไฟล์ที่มีรหัสทั้งหมด

ด้วยเหตุผลด้านประสิทธิภาพฉันไม่ต้องการให้เบราว์เซอร์ดาวน์โหลดรหัสทั้งหมดเว้นแต่จะใช้งานจริง

เป็นไปได้ที่จะสร้างไฟล์แยกกันสำหรับแต่ละองค์ประกอบ Webpack มีความสามารถดังกล่าวโดยการกำหนดหลายรายการและผลลัพธ์ สมมติว่าคุณมีโครงสร้างของโครงการดังต่อไปนี้

- my-cool-react-components
  - src // Folder contains all source code
    - index.js
    - componentA.js
    - componentB.js
    - ...
  - lib // Folder is generated when build
    - index.js // Contains components all together
    - componentA.js
    - componentB.js
    - ...

ไฟล์ Webpack จะมีลักษณะเช่นนี้

const path = require('path');

module.exports = {
  entry: {
    index: './src/index.js',
    componentA: './src/componentA.js',
    componentB: './src/componentB.js',
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'lib'),
  },
};

ข้อมูลเพิ่มเติมเกี่ยวกับ "การแยกรหัส" อยู่ที่นี่ในเอกสาร Webpack

หาก repo เดียวกันมีส่วนประกอบต่าง ๆ มากมายสิ่งที่ควรอยู่ใน main.js?

มีเขตข้อมูลเดียวในpackage.jsonชื่อไฟล์mainเป็นการดีที่จะใส่ค่าlib/index.jsตามโครงสร้างโครงการด้านบน และในindex.jsไฟล์มีการส่งออกส่วนประกอบทั้งหมด ในกรณีที่ผู้บริโภคต้องการใช้องค์ประกอบเดียวก็สามารถเข้าถึงได้โดยเพียงแค่ทำ

const componentX = require('my-cool-react-components/lib/componentX');

ฉันคิดถูกหรือไม่ว่าไม่ควรรวมส่วนประกอบต่างๆ การรวมกลุ่มจะถูกทิ้งไว้กับผู้บริโภคของส่วนประกอบหรือไม่ ฉันจะปล่อยให้สิ่งอื่นใดกับผู้บริโภคส่วนประกอบหรือไม่ ฉันเพิ่งจะส่งผ่าน JSX และมันได้หรือไม่

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

หวังว่าทุกคำถามของคุณจะได้รับคำตอบ :)


ขอบคุณสำหรับคำตอบ ฉันไม่ต้องการอัปเดตการกำหนดค่า Webpack ของฉันทุกครั้งที่ฉันเพิ่มส่วนประกอบใหม่ตามตัวอย่างของคุณ "ขึ้นอยู่กับคุณฉันพบว่าห้องสมุดปฏิกิริยาบางแห่งได้รับการเผยแพร่ในลักษณะดั้งเดิม นี่เป็นการพิสูจน์ว่าไม่เป็นอย่างนั้น สร้างแอป React ใช้งานได้กับส่วนประกอบที่ไม่ได้รวมของฉันตกลง แต่ JS ถัดไปกำลังโยนข้อผิดพลาดและทำงานกับส่วนประกอบที่รวมเท่านั้นการตัดสินใจออกจากมือของฉันอย่างชัดเจน
otw

ฉันพยายามค้นคว้าอย่างดีที่สุด :) "ฉันไม่ต้องการอัปเดต Webpack config ทุกครั้งที่ฉันเพิ่มองค์ประกอบใหม่" - เป็นไปได้ที่จะใช้ glob-wildcard บางรายการเพื่อไม่แสดงส่วนประกอบทั้งหมดมันแก้ปัญหาของ อัปเดตการกำหนดค่า webpack สำหรับทุกองค์ประกอบใหม่ "JS ถัดไปกำลังโยนข้อผิดพลาด" - ดีจากนั้นให้แพคเกจของคุณ :) แพคเกจดิบจะใช้งานได้หากรวมอยู่ในการรวมกลุ่มจากโครงการผู้บริโภคเท่านั้น รุ่นที่แถมจะใช้งานได้ 100%
Rashad Ibrahimov

1

คุณสามารถแยกส่วนประกอบของคุณเช่นlodashทำเพื่อวิธีการของพวกเขา

สิ่งที่คุณอาจมีคือส่วนประกอบแยกต่างหากที่คุณสามารถอนุญาตให้นำเข้าแยกจากกันหรือผ่านส่วนประกอบหลัก

จากนั้นผู้บริโภคสามารถนำเข้าแพคเกจทั้งหมด

import {MyComponent} from 'my-components';

หรือแต่ละส่วน

import MyComponent from 'my-components/my-component';

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


1

คุณควรดูที่Bitฉันคิดว่านี่เป็นวิธีแก้ปัญหาที่ดีในการแบ่งปันนำมาใช้ซ้ำและองค์ประกอบภาพ

มันง่ายมากที่จะติดตั้ง คุณสามารถติดตั้งไลบรารีบิตของคุณหรือเพียงแค่ส่วนประกอบที่มี:

npm i @bit/bit.your-library.components.buttons

จากนั้นคุณสามารถนำเข้าส่วนประกอบในแอปของคุณด้วย:

import Button3 from '@bit/bit.your-library.components.buttons';

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


0

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.