前端組件化是當今熱議的話題之一,也是咱們在開發單頁應用常常會碰到的一個問題,如今咱們有了功能很是完善的Element-UI。各個大廠也相繼宣佈開源XXX-UI。可是也會存在一些問題,好比每一個公司可能須要的業務組件不盡相同,或者咱們想本身開發一套屬於本身的組件庫,來加強對組件的可控性。那麼咱們該如何去作呢?
這裏記錄一下我從零開始搭建起來的組件庫的過程,目前只有簡單幾個組件,不過我也會慢慢更新維護。javascript
項目github地址:github
項目演示地址: 演示css
咱們搭建組件庫,須要準備一系列環境,首先咱們要考慮一下問題:html
首先,對於腳手架環境的問題,目前已經有很是成熟的vue官方的腳手架,咱們拿來用就行了前端
# 全局安裝 vue-cli $ npm install --global vue-cli # 建立一個基於 webpack 模板的新項目 $ vue init webpack my-project # 安裝依賴,走你 $ cd my-project $ npm install $ npm run dev
接着咱們看第二個問題,如何規劃好咱們組建的目錄結構?首先咱們須要有一個目錄存放組件,有一個目錄存放示例。因此咱們要對vue-cli 生成的項目結構作一下改造:vue
. ... |-- examples // 原 src 目錄,改爲 examples 用做示例展現 |-- packages // 新增 packages 用於編寫存放組件 ... .
這樣的話 咱們須要再把咱們webpack配置文件稍做一下調整,首先是把原先的編譯指向src的目錄改爲examples,其次爲了 npm run build
能正常編譯 packages 咱們也須要爲 babel-loader 再增長一個編譯目錄:java
{ test: /\.js$/, loader: 'babel-loader', include: [resolve('examples'), resolve('test'), resolve('packages')] }
這樣咱們搭建起來一個簡易的目錄結構。
緊接着咱們須要考慮如何編寫文檔。對於文檔的編寫,天然是markdown最合適不過了,那麼怎麼讓咱們在vue下能夠去寫 markdown 文檔呢?答案固然是 vue-markdown-loader。而後咱們按照文檔配置了相關的插件信息:webpack
rules: [ { test: /\.md$/, loader: 'vue-markdown-loader' } ]
好了,咱們能夠開始嘗試寫文檔了,在 example/docs 目錄下新建 test.md。git
# test
> Hello World
同時建立一個新的路由,指向咱們的md文件:github
{ path: '/test', name: 'test', component: r => require.ensure([], () => r(require('../docs/test.md'))) }
打開咱們的瀏覽器http://localhost:8080/#/test
哈哈 真的成功了。別高興的太早.... 問題還在後面:咱們指望的文檔不只能編譯markdown,並且最好能識別demo代碼塊一方面作演示,一方面能夠顯示演示代碼最好了,就像這樣:web
那咱們須要怎麼作呢?vue-mark-down 功能確定不止這些!因而咱們繼續閱讀它的文檔,發現其實他就是封裝了 markdown-it,支持 options 選項。這樣咱們就能夠爲咱們的markdown定義獨特的標識符,這裏我用 demo 標識須要顯示代碼塊的地方,因此我須要配置options 選項 :
const vueMarkdown = { preprocess: (MarkdownIt, source) => { MarkdownIt.renderer.rules.table_open = function () { return '<table class="table">' } MarkdownIt.renderer.rules.fence = utils.wrapCustomClass(MarkdownIt.renderer.rules.fence) return source }, use: [ [MarkdownItContainer, 'demo', { // 用於校驗包含demo的代碼塊 validate: params => params.trim().match(/^demo\s*(.*)$/), render: function(tokens, idx) { var m = tokens[idx].info.trim().match(/^demo\s*(.*)$/); if (tokens[idx].nesting === 1) { var desc = tokens[idx + 2].content; // 編譯成html const html = utils.convertHtml(striptags(tokens[idx + 1].content, 'script')) // 移除描述,防止被添加到代碼塊 tokens[idx + 2].children = []; return `<demo-block> <div slot="desc">${html}</div> <div slot="highlight">`; } return '</div></demo-block>\n'; } }] ] }
這裏簡單的描述一下這段代碼是幹什麼的:首先把內容裏面vue片斷編譯成html,用於顯示,另外一方面用highlight來高亮代碼塊。demo-block
自己是咱們定義好的組件:
<template> <div class="docs-demo-wrapper"> <div :style="{maxHeight: isExpand ? '700px' : '0'}" class="demo-container"> <div span="14"> <div class="docs-demo docs-demo--expand"> <div class="highlight-wrapper"> <slot name="highlight"></slot> </div> </div> </div> </div> <span class="docs-trans docs-demo__triangle" @click="toggle">{{isExpand ? '隱藏代碼' : '顯示代碼'}}</span> </div> </template>
這樣,咱們的 test.md 即可以這麼去寫了:
環境準備完畢,緊接着要開始編寫組件,考慮的是組件庫,因此咱們竟可能讓咱們的組件支持全局引入和按需引入,若是全局引入,那麼全部的組件須要要註冊到Vue component 上,並導出:
const install = function(Vue) { if (install.installed) return; components.map(component => Vue.component(component.name, component)); }; export default { install };
接着要實現按需加載,咱們只須要單個導出組件便可:
import Button from './button/index.js'; import Row from './row/index' import Col from './col/index' const components = [ Button, Row, Col ]; const install = function(Vue) { if (install.installed) return; components.map(component => Vue.component(component.name, component)); }; if (typeof window !== 'undefined' && window.Vue) { install(window.Vue); } export default { install, Button, Row, Col };
其次,咱們還須要考慮一個問題:既然是單頁面應用,必然要去解決樣式衝突問題,若是組件內使用soped,那麼樣式就沒法從組件內抽離出來,達不到可定製化主題顏色的目的。咱們須要一套能夠分離處理的樣式,能夠自行編譯,能夠相互不污染。這時候css 的BEM規範就顯得尤其重要。若是你還不知道什麼是BEM 參考: http://www.w3cplus.com/css/css-architecture-1.html。
說到這裏,目前對BEM規範支持較好的插件就是postcss了,他容許咱們配置BEM之間的鏈接符和縮寫:
{ "browsers": ["ie > 8", "last 2 versions"], "features": { "bem": { "shortcuts": { "component": "b", "modifier": "m", "descendent": "e" }, "separators": { "descendent": "__", "modifier": "--" } } } }
這樣咱們就能夠把樣式單獨的抽離出來,經過gulp進行打包編譯:
gulp.task('compile', function() { return gulp.src('./src/*.css') .pipe(postcss([salad])) .pipe(cssmin()) .pipe(gulp.dest('./lib')); });
最後生成咱們的樣式代碼。
好了開始咱們的測試:
import VVUI from '../packages/index' import '../packages/theme-default/lib/index.css' Vue.use(VVUI)
一切顯得那麼美好....
npm run dev
的時候自動進行組件檢測,而後幫咱們寫好導出代碼。項目github地址:github
項目演示地址: 演示
歡迎 PR 一塊兒維護,歡迎 Star
做者:monkeyWang
本人主頁:monkeyWang
微信公衆號:前端知識鋪