javascript - วิธีการสร้างไลบรารีโมดูลของ React อย่างถูกต้อง
ฉันกำลังพยายามสร้างไลบรารีคอมโพเนนต์ของ React ซึ่งอิงจาก Typescript และ SASS ไลบรารีคอมโพเนนต์นี้จะถูกใช้ในโปรเจกต์ Typescript หลายโปรเจกต์ ดังนั้นจึงจำเป็นต้องมีการส่งออกชนิดข้อมูลด้วย ในอุดมคติ ฉันต้องการเลียนแบบบางสิ่งบางอย่างเช่นการแก้ไขผลลัพธ์ dist ของไลบรารี "Material-UI"/"React-Bootstrap"
โครงสร้างโครงการตัวอย่าง:
|Tabs
+--Tabs.tsx
+--Tabs.scss
+--index.tsx
index.tsx
index.tsx
export { Tabs } from './Tabs/Tabs';
แท็บ/ดัชนี.tsx
import React from 'react';
import './Tabs.scss';
interface TabsProps {
...
}
export const Tabs: React.FC<TabsProps> = (props) => <div>...</div>
แท็บ/ดัชนี.tsx
export { Tabs } from './Tabs';
โครงสร้าง dist ที่สร้างขึ้นตามที่คาดหวังควรเลียนแบบโครงสร้าง src:
|Tabs
+--Tabs.js
+--Tabs.d.ts
+--index.js
+--index.d.ts
index.js
index.tsx
ฉันพยายามวิเคราะห์โครงการโอเพ่นซอร์สและดูว่าพวกเขาสร้างไลบรารีอย่างไร แต่ฉันไม่สามารถหาไลบรารีที่ใช้วิธีเดียวกันซึ่งฉันสามารถนำกลับมาใช้ใหม่ได้
วิธีแก้ปัญหาที่ฉันได้ลอง:
Webpack: แม้ว่าฉันจะสามารถคอมไพล์ไฟล์ typescript และ sass ได้ แต่ webpack มักจะสร้างไฟล์เพียงไฟล์เดียวตามที่ระบุในส่วน output ซึ่งโดยปกติจะถูกบันเดิล และฉันจะสูญเสียความสามารถในการนำเข้าคอมโพเนนต์เดี่ยวจากโมดูลของคอมโพเนนต์เฉพาะ ฉันทราบว่าฉันสามารถระบุจุดเข้า (entry points) หลายจุดได้ แต่โปรเจกต์จะมีการส่งออกจำนวนมากและการระบุด้วยมือไม่ใช่ตัวเลือก
ตัวอย่างการตั้งค่าที่ฉันลอง:
const path = require('path');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
entry: './src/index.tsx',
module: {
rules: [
// sass-loader is not used here yet, but should be once desired structure can be reached
{
test: /\.tsx?$/,
loader: 'babel-loader',
},
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ test: /\.js$/, loader: "source-map-loader" }
]
},
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
resolve: {
extensions: [".tsx", ".ts", ".js"],
plugins: [
new TsconfigPathsPlugin({ configFile: "./tsconfig.build.json" })
]
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
};
Rollup: สถานการณ์คล้ายกับ webpack
ตัวอย่างการตั้งค่าที่ฉันทดลอง:
// rollup.config.js
import babel from 'rollup-plugin-babel';
import sass from 'rollup-plugin-sass';
import nodeResolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import react from 'react';
import reactDom from 'react-dom';
const babelOptions = {
exclude: /node_modules/,
// We are using @babel/plugin-transform-runtime
runtimeHelpers: true,
extensions: ['.js', '.ts', '.tsx'],
configFile: './babel.config.js',
};
const nodeOptions = {
extensions: ['.js', '.tsx', '.ts'],
};
const commonjsOptions = {
ignoreGlobal: true,
include: /node_modules/,
namedExports: {
react: Object.keys(react),
'react-dom': Object.keys(reactDom)
},
};
export default {
input: 'src/index.tsx',
output: {
name: '[name].js',
dir: 'dist',
format: 'umd',
sourcemap: true,
},
plugins: [
nodeResolve(nodeOptions),
sass(),
commonjs(commonjsOptions),
babel(babelOptions)
],
};
Babel: ฉันสามารถคอมไพล์โค้ด TypeScript ได้สำเร็จ แต่เมื่อฉันใกล้จะทรานสไพล์ไฟล์ SASS ฉันกลับได้รับคำแนะนำให้ใช้ webpack สำหรับสิ่งนั้น...
TSC: ฉันสามารถรันตัวคอมไพเลอร์ของ TypeScript ได้สำเร็จและมันคอมไพล์ไฟล์ทั้งหมดโดยไม่มีปัญหาและสามารถรักษาโครงสร้างเดิมไว้ได้ อย่างไรก็ตาม TSC ไม่รองรับตัวเลือกการแปลงโค้ดอื่น ๆ ดังนั้นหลังจากค้นหามากมาย ฉันก็ลงเอยด้วยข้อเสนอให้ใช้ webpack และ "ts-loader" หรือ "babel-loader"
tsconfig :
{
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"lib": [ "es2015", "dom" ],
"outDir": "dist",
"baseUrl": ".",
"declaration": true,
"composite": true,
"module": "commonjs",
"target": "es5"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
]
}
วิธีแก้ปัญหาที่ต้องการ: ฉันควรจะสามารถหลังจากคอมไพล์ไลบรารีและติดตั้งมันในโปรเจกต์อื่น สามารถรันสิ่งต่อไปนี้ได้:
import { Tabs } from 'my-lib/Tabs';
import { Tabs } from 'my-lib';
คำตอบ #1
หลังจากทดลองเล่นหลายครั้ง ผมสามารถสร้างผลลัพธ์ที่ต้องการด้วย rollup ได้ ข้อเสียเพียงข้อเดียวของการกำหนดค่าปัจจุบันคือมันไม่รองรับไฟล์ที่เพิ่มเข้ามาใหม่ในโหมด --watch การตั้งค่ามหัศจรรย์อยู่ภายใต้ output.preserveModules
การตั้งค่า :
// rollup.config.js
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import postcss from 'rollup-plugin-postcss';
import postcssUrl from 'postcss-url';
import resolve from "@rollup/plugin-node-resolve";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
export default {
input: 'src/index.tsx',
output: {
dir: 'dist',
format: 'es',
preserveModules: true,
sourcemap: true,
},
plugins: [
resolve(),
peerDepsExternal(),
commonjs(),
typescript({
tsconfig: 'tsconfig.build.json'
}),
postcss({
minimize: true,
modules: {
generateScopedName: "[hash:base64:5]"
},
plugins: [
postcssUrl({
url: "inline"
})
]
}),
],
};
ฉันหวังว่าการตั้งค่านี้จะช่วยผู้อื่นได้เช่นกัน
คำตอบ #2
คุณสามารถตรวจสอบรีโปนี้ได้ ฉันได้ทำการเปลี่ยนแปลงบางส่วนสำหรับการสร้างไลบรารี
https://github.com/21paradox/react-webpack-typescript-starter
เพื่อใช้ห้องสมุดแบบด้านล่าง:
import Input from 'my-custom-ui/entry/Input';
import { Input } from 'my-custom-ui';
หลังจากที่ค้นหามามากมาย ในที่สุดฉันก็เขียนปลั๊กอินเพื่อสร้างโค้ด entry ของ webpack ด้วยตัวเองที่ webpack ต้องการ (สำหรับการสร้างไลบรารี UI)
ไฟล์หลายรายการ + ไฟล์รายการที่สร้างด้วยมือดูเหมือนจะทำงานได้ดีสำหรับ ส่วนประกอบแยก & ไม่มีโค้ดซ้ำซ้อน นี่ก็มีประโยชน์มากหากคุณต้องการสร้างไลบรารีที่ใช้ Vue
