Re從零開始的組件庫構建與發佈流程

不少時候,咱們爲了複用或者概括總結,會把組件抽離出來發到npm上。可是這個過程你會發現一個問題,就是應該怎麼更好的發佈管理維護這些組件呢。最後會發現網上的其餘教程不是太零散了,就是有些細節不大到位。這裏借這個機會好好總結一下,要是看完以爲有幫助的話,不妨個,關注一下哈哈。css

理解完以後咱們就能夠

  1. 編寫發佈一個可用的組件庫
  2. 能以import { demoComponent } from 'xxxUI'方式引入
  3. 也能以import demoComponent from xxxUI/component/demoComponent方式引入
  4. 各個組件打包相對獨立,互不干擾
  5. 輸出的組件可以簡單易用和具備良好的兼容性
  6. 組件庫能根據用戶的配置實現按需加載
  7. 組件庫能根據用戶的配置實現Tree Shaking
  8. 組件經過單元測試
  9. 打包發佈到npm

就像這樣,嘿嘿。vue

image

如下都以react組件庫爲例,其實vue是也同樣的,只是babel配置有所區別node

項目結構

結構解析

先來看看組件庫的項目結構react

image

嗯,看起來非常複雜,第一印象應該都在想着都是些什麼亂七八糟的文件,下面先來解釋一下。webpack

  • src存放核心代碼
  • dist存放最後打包輸出的代碼
  • sass樣式單獨抽離放置(固然能夠跟組件放一塊兒,這裏的目的是即便不用相關的組件,單獨使用相關樣式也是沒問題)
  • __mocks__(mock對象),coverage(覆蓋率),test,jest.config.js(jest配置)這些都是與單元測試相關的下一章會有詳細介紹
  • .npmignore.gitignore做用相似
  • .babelrc大名鼎鼎的babel應該都知道的
  • 其餘應該都很是熟悉了,再介紹下去就有湊字數的嫌疑了。

關鍵目錄

咱們先把目光聚焦到src核心代碼目錄下,首先咱們將組件存放在component中,在外層用index去引用component中的組件,因爲在不提供具體路徑的狀況下,import引入時會默認找到index。這樣在打包輸出後,就能經過import { demoComponent } from 'xxxUI'這種方式去引用組件了。git

// index.jsx
import demoComponent from './component/demoComponent';

export {
 demoComponent
};
複製代碼
// demoComponent.jsx
export default class demoComponent extends Component {
    render() {
        return (
            <div> hello world </div>
        );
    }
}
複製代碼

而後使用這種形式去導出組件,就能經過import demoComponent from xxxUI/component/demoComponent這種形式單獨引入組件了。es6

組件庫按需加載

根據以上目錄結構和引入方式咱們能夠知道,經過import { demoComponent } from 'xxxUI'這種形式去引入會使得整個組件庫都引入到開發項目中,有時只須要用到其中的兩三個組件,這種狀況是咱們不想看到的。而經過import demoComponent from xxxUI/component/demoComponent這種形式去引用,就能作到只引入某個須要用到的組件,這恰好能解決這個問題。可是每次引入都要寫這麼長的一串,很不方便。這個時候就須要用到babel-plugin-import這個插件了。github

import { demoComponent, demoComponent1, demoComponent2 } from 'xxxUI'

// 使用babel-plugin-import插件能自動將以上這種調用形式在AST(抽象語法樹)中改寫成如下形式。
// 這樣就能方便地引入相關組件,又不用擔憂一次所有引入致使包過大的問題

import demoComponent from xxxUI/component/demoComponent
import demoComponent1 from xxxUI/component/demoComponent
import demoComponent2 from xxxUI/component/demoComponent
複製代碼

最後在.babelrc中配置須要轉換的路徑web

// .babelrc
{
    ...
    "plugins":[
        "import", {
            "libraryName": "xxxUI",
            "libraryDirectory": "component",
        }
    ]
}
複製代碼

須要注意的是,這裏須要組件庫的使用者去配置,而不是寫在組件庫的.babelrc中。若是組件庫支持按需加載,這個配置應該寫在README.md中交由組件庫的使用者去選擇。按需加載的好壞處是由具體的項目環境而定,須要具體狀況具體分析sql

image
沒設置按需加載時,整個組件庫都打包進去了。

image
設置了按需加載,只加載用到的組件。

就這樣,經過巧妙的文件結構,目標2,3,6已達成。

輸入

明確了項目結構,接下來就是須要收集組件源碼了。一般來說,只須要在webpackentry配置中只須要設置入口文件index就能夠了,就像這樣entry: path.resolve(__dirname, 'src', 'index.jsx')。但因爲咱們須要每一個組件互相獨立單獨打包,因此須要一個個組件去引入,同時也要保持相應的文件結構。

function getFileCollection() {
    const globPath = './src/**/*.*(jsx|js)';
    const files = glob.sync(globPath);
    return files;
}

function entryConfig() {
    let entryObj = {};
    getFileCollection().forEach(item => {
        const filePath = item.replace('./src', '');
        entryObj[filePath] = path.resolve(__dirname, item);
    });
    return entryObj;
}
複製代碼

在這裏使用了glob這個很好用的工具,能很方便匹配出對應的文件。最後返回的是一個文件路徑的映射對象,咱們能夠在控制檯看看輸入了哪些文件。

image

ok,接下來就是要怎樣處理這些源文件了。

編譯處理和組件庫Tree Shaking

這裏的處理過程很簡單,邏輯就是配置babeles6+的源碼處理成es5的兼容代碼,順便也將svg小圖標轉化爲base64格式嵌入。這樣作更可能是爲了讓用戶以儘可能小的配置,儘可能小的上手成本就能使用這個組件庫。這裏若是同時保留了es6代碼,就可以讓讓開發者能夠自由配置Tree Shaking了(好比開發者只用到了某個組件中的某個方法的場景下,就不必引入整個組件了)。關於開發者如何配置Tree Shaking最後會講到。

Es6 Modules從語法層面提供了模塊化功能,Tree Shaking就是基於ES6模塊化的,在編譯打包節點能夠在AST(抽象語法樹)中靜態分析,將沒有用到的代碼剔除掉。咱們通過編譯打包後的es5代碼是沒法進行Tree Shaking的。

// webpack.config中的loader配置
rules: [{
    test: /.jsx|.js$/,
    loader: 'babel-loader',
    exclude: /node_modules/
}, {
    test: /\.(jpg|png|gif|svg|jpeg)$/,
    loader: 'url-loader',
    exclude: /node_modules/
}]
複製代碼
// .babelrc
{
    "presets": [
        ["@babel/preset-env", {
            // 瀏覽器兼容方案配置
            "targets": {
                "browsers": [
                    ">0.25%",
                    "not ie 11",
                    "not op_mini all"
                ]
            }
        }],
        "@babel/preset-react",
    ],
    "plugins": [
        // 一些必備的轉換插件
        "@babel/plugin-proposal-function-bind",
        "@babel/plugin-proposal-class-properties",
        // 解決編譯中產生的重複的工具函數
        "@babel/plugin-transform-runtime",
        "transform-remove-console"
    ]
}
複製代碼

達成目標的第7點。

輸出

打包編譯輸出到dist目錄,要注意的是dist目錄中的結構要與src目錄保持一致才能使組件和組件間的引用路徑不會亂,就像這樣,dist目錄結構跟src類似。

image

再來看看output的配置,因爲咱們在文件輸入時保持了文件路徑信息,因此這裏直接更改後綴以後輸出到dist便可。libraryTarget的做用在於設置打包格式,這裏採用umd標準。若是設置了library,那麼將會導出成單入口的引用形式import xxxUI from 'xxxUI',這是咱們不但願的。librarylibraryTarget的取值根據項目類型的不一樣而不一樣。詳情看這裏

output: {
    filename: (chunkData) => {
        let filePath = chunkData.chunk.name;
        const filename = filePath.replace('.jsx', '.js');
        return filename;
    },
    path: __dirname + '/dist',
    libraryTarget: 'umd',
    // library: 'xxxUI'
}
複製代碼

萬事俱備了,但按照這樣打包後會發現,怎麼第三方包react,react-dom也跟着打包進來了,這會致使打包以後組件庫的體積很大。

image

咱們須要這樣去配置,過濾掉import進來的第三方包

externals: [
    function(context, request, callback) {
        // 容許編譯如下後綴文件
        if (/.jsx|.jpg|.png|.gif|.svg|.jpeg$/g.test(request)) {
            return callback();
        }
        callback(null, request);
    }
]
複製代碼

image

能夠看到變化巨大!如今整個包大小隻有120kb(除去樣式)

因爲樣式是獨立抽離出來的,只須要將樣式copy到dist目錄便可,固然可配置插件自動完成。

new CopyPlugin([{
    from: './sass',
    to: './sass'
}])
複製代碼

達成目標的4,5

最終發佈

  1. 先去官網完成註冊
  2. npm login登陸
  3. 添加.npmignore文件,將須要忽略的文件列出來
  4. 添加README.md,寫出必要的說明,這是一個好習慣
  5. package.jsonscript中添加命令webpack --mode production && npm publish ./dist。這裏意思是採用生產模式打包並將dist目錄發佈上npm

到最後README.md使用手冊能夠這樣寫

// 安裝
npm i -S xxxUI

// webpack配置處理樣式
{
    test: /\.scss$/,
    use: [MiniCssExtractPlugin.loader, 'css-loader', "postcss-loader", 'sass-loader'],
    include: [
        path.join(__dirname, 'node_modules/xxxUI/sass/')
    ]
}

// 在index.jsx中引入樣式
import "xxxUI/sass/index.scss";

// 可選項---------------
// .babelrc 配置按需加載
"plugins": [
    [
        "import",
        {
            "libraryName": "xxxUI",
            "libraryDirectory": "component",
        }
    ],
    // ...
]

// 可選項---------------
// 配置Tree Shaking
// webpack.config.js
// ...
{
    test: /\.scss$/,
    use: [MiniCssExtractPlugin.loader, 'css-loader', "postcss-loader", 'sass-loader'],
    include: [
        path.join(__dirname, 'node_modules/xxxUI/sass/')
    ],
    // 樣式無需進行Tree Shaking
    sideEffects: true
}
// ...
optimization: {
    usedExports: true,
    minimizer: [
       new TerserPlugin({})
    ]
}
// .babelrc
"presets": [
    [
        "@babel/preset-env",
        {
            // 想達到Tree Shaking效果這裏
            "modules": false,
        }
    ]
]
複製代碼

babel中modules的選項有'amd' | 'umd' | 'systemjs' | 'commonjs' | false這幾個,因爲Tree Shaking基於ES6 Modules,這裏就不能轉換成其餘標準,只能選false,即採用本來文件的模塊標準編譯。

搞定,一個實用的組件庫就發佈完成了,快來動手試試吧。

單元測試

等等,彷佛還漏了單元測試,實際上是裏面須要注意的點(keng)太多了,一次講不完,將在下一篇《Re從零開始的組件單元測試》中詳細展開。

結束

SluckyUI的源碼和項目構造就是按照這套模式去搭建的,在細節方面有其餘考量,可能會有所不一樣,但思路是不變的。SluckyUI的理念是打造一個組件庫種子,讓其餘開發者可以進行快速二次開發,減小沒必要要的造輪子,但當中的編寫還有不少尚不完善的地方,不妨點個start支持一下。

在線組件Demo&組件庫源碼

最近終於將代碼整理好了,前期寫的實在有點不大好看。 在線組件Demo&組件庫源碼

組件庫中組件怎麼寫呢?

web安全系列

相關文章
相關標籤/搜索