【造轮子】开发vue组件库MeowMeowUI

vue

项目示例
github

1. 创建项目

# 全局安装 vue-cli

$ npm install --global vue-cli

# 创建一个基于 webpack 模板的新项目

$ vue init webpack meowui

# 安装依赖

$ cd meowui

$ npm install

$ npm run dev

2. 规划目录结构

这里参考element-ui和iview的目录结构

|-- examples      // 原 src 目录,改成 examples 用作示例展示

|-- packages // 新增 packages 用于编写存放组件

这样需要修改webpack相关目录路径配置

{

test: /\.js$/,

loader: 'babel-loader',

include: [resolve('examples'), resolve('test'), resolve('packages')]

}

vue-markdown-loader编写md说明文档

下载安装相关依赖

# markdown-it 渲染 markdown 基本语法

# markdown-it-anchor 为各级标题添加锚点

# markdown-it-container 用于创建自定义的块级容器

# vue-markdown-loader 核心loader

# transliteration 中文转拼音

# cheerio 服务器版jQuery

# highlight.js 代码块高亮实现

# striptags 利用cheerio实现两个方法,strip是去除标签以及内容,fetch是获取第一符合规则的标签的内容

配置webpack

1. build目录下新建一个strip-tags.js文件.

// strip-tags.js

'use strict';

var cheerio = require('cheerio'); // 服务器版的jQuery

/**

* 在生成组件效果展示时,解析出的VUE组件有些是带<script>和<style>的,我们需要先将其剔除,之后使用

* @param {[String]} str 需要剔除的标签名 e.g'script'或['script','style']

* @param {[Array|String]} tags e.g '<template></template><script></script>''

* @return {[String]} e.g '<html><head><template></template></head><body></body></html>'

*/

exports.strip = function(str, tags) {

var $ = cheerio.load(str, {decodeEntities: false});

if (!tags || tags.length === 0) {

return str;

}

tags = !Array.isArray(tags) ? [tags] : tags;

var len = tags.length;

while (len--) {

$(tags[len]).remove();

}

return $.html(); // cheerio 转换后会将代码放入<head>中

};

/**

* 获取标签中的文本内容

* @param {[String]} str e.g '<html><body><h1>header</h1></body><script></script></html>'

* @param {[String]} tag e.g 'h1'

* @return {[String]} e.g 'header'

*/

exports.fetch = function(str, tag) {

var $ = cheerio.load(str, {decodeEntities: false});

if (!tag) return str;

return $(tag).html();

};

2. 修改webpack.base.conf.js

- 添加 vue-markdown-loader 并配置

// 完整 webpack.base.conf.js 文件

'use strict'

const path = require('path')

const utils = require('./utils')

const config = require('../config')

const slugify = require('transliteration').slugify; // 引入transliteration中的slugify方法

const striptags = require('./strip-tags'); // 引入刚刚的工具类

const md = require('markdown-it')()

const vueLoaderConfig = require('./vue-loader.conf')

const MarkdownItAnchor = require('markdown-it-anchor')

const MarkdownItContainer = require('markdown-it-container')

/**

* 由于cheerio在转换汉字时会出现转为Unicode的情况,所以我们编写convert方法来保证最终转码正确

* @param {[String]} str e.g &#x6210;&#x529F;

* @return {[String]} e.g 成功

*/

function convert(str) {

str = str.replace(/(&#x)(\w{4});/gi, function($0) {

return String.fromCharCode(parseInt(encodeURIComponent($0).replace(/(%26%23x)(\w{4})(%3B)/g, '$2'), 16));

});

return str;

}

/**

* 由于v-pre会导致在加载时直接按内容生成页面.但是我们想要的是直接展示组件效果,通过正则进行替换

* hljs是highlight.js中的高亮样式类名

* @param {[type]} render e.g '<code v-pre class="test"></code>' | '<code></code>'

* @return {[type]} e.g '<code class="hljs test></code>' | '<code class="hljs></code>'

*/

function wrap(render) {

return function() {

return render.apply(this, arguments)

.replace('<code v-pre class="', '<code class="hljs ')

.replace('<code>', '<code>');

};

}

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)

// MarkdownIt.renderer.rules.fence = wrap(MarkdownIt.renderer.rules.fence);

// ```html `` 给这种样式加个class hljs

// 但是markdown-it 有个bug fence整合attr的时候直接加载class数组上而不是class的值上

// markdown-it\lib\renderer.js 71行 这么修改可以修复bug

// tmpAttrs[i] += ' ' + options.langPrefix + langName; --> tmpAttrs[i][1] += ' ' + options.langPrefix + langName;

// const fence = MarkdownIt.renderer.rules.fence

// MarkdownIt.renderer.rules.fence = function(...args){

// args[0][args[1]].attrJoin('class', 'hljs')

// var a = fence(...args)

// return a

// }

// ```code`` 给这种样式加个class code_inline

const code_inline = MarkdownIt.renderer.rules.code_inline

MarkdownIt.renderer.rules.code_inline = function(...args){

args[0][args[1]].attrJoin('class', 'code_inline')

return code_inline(...args)

}

return source

},

use: [

[MarkdownItAnchor,{

level: 2, // 添加超链接锚点的最小标题级别, 如: #标题 不会添加锚点

slugify: slugify, // 自定义slugify, 我们使用的是将中文转为汉语拼音,最终生成为标题id属性

permalink: true, // 开启标题锚点功能

permalinkBefore: true // 在标题前创建锚点

}],

[MarkdownItContainer, 'demo', {

validate: params => params.trim().match(/^demo\s*(.*)$/),

render: function(tokens, idx) {

var m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);

// nesting === 1表示标签开始

if (tokens[idx].nesting === 1) {

// 获取正则捕获组中的描述内容,即::: demo xxx中的xxx

var description = (m && m.length > 1) ? m[1] : '';

// 获得内容

var content = tokens[idx + 1].content;

// 解析过滤解码生成html字符串

var html = convert(striptags.strip(content, ['script', 'style'])).replace(/(<[^>]*)=""(?=.*>)/g, '$1');

// 获取script中的内容

var script = striptags.fetch(content, 'script');

// 获取style中的内容

var style = striptags.fetch(content, 'style');

// 组合成prop参数,准备传入组件

var jsfiddle = { html: html, script: script, style: style };

// 是否有描述需要渲染

var descriptionHTML = description

? md.render(description)

: '';

// 将jsfiddle对象转换为字符串,并将特殊字符转为转义序列

jsfiddle = md.utils.escapeHtml(JSON.stringify(jsfiddle));

// 起始标签,写入pre-block模板开头,并传入参数

return `<pre-block class="demo-box" :jsfiddle="${jsfiddle}">

<div class="source" slot="desc">${html}</div>

${descriptionHTML}

<div class="highlight" slot="highlight">`;

}

//否则闭合标签

return `</div></pre-block>\n`

}

}],

[require('markdown-it-container'), 'tip'],

[require('markdown-it-container'), 'warning']

]

}

function resolve (dir) {

return path.join(__dirname, '..', dir)

}

module.exports = {

context: path.resolve(__dirname, '../'),

entry: {

app: './example/main.js'

},

output: {

path: config.build.assetsRoot,

filename: '[name].js',

publicPath: process.env.NODE_ENV === 'production'

? config.build.assetsPublicPath

: config.dev.assetsPublicPath

},

resolve: {

extensions: ['.js', '.vue', '.json'],

alias: {

'vue$': 'vue/dist/vue.esm.js',

'@': resolve('example'),

}

},

module: {

rules: [

{

test: /\.md$/,

loader: 'vue-markdown-loader',

options: vueMarkdown

},

{

test: /\.vue$/,

loader: 'vue-loader',

options: vueLoaderConfig

},

{

test: /\.js$/,

loader: 'babel-loader',

include: [resolve('example'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]

},

{

test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,

loader: 'url-loader',

options: {

limit: 10000,

name: utils.assetsPath('img/[name].[hash:7].[ext]')

}

},

{

test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,

loader: 'url-loader',

options: {

limit: 10000,

name: utils.assetsPath('media/[name].[hash:7].[ext]')

}

},

{

test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,

loader: 'url-loader',

options: {

limit: 10000,

name: utils.assetsPath('fonts/[name].[hash:7].[ext]')

}

}

]

},

node: {

// prevent webpack from injecting useless setImmediate polyfill because Vue

// source contains it (although only uses it if it's native).

setImmediate: false,

// prevent webpack from injecting mocks to Node native modules

// that does not make sense for the client

dgram: 'empty',

fs: 'empty',

net: 'empty',

tls: 'empty',

child_process: 'empty'

}

}

创建路由并测试md读写功能

import Vue from 'vue'

import Router from 'vue-router'

const _import_ = file => () => import('@/views/' + file )

import GuidLayout from '@/views/layout/guid.vue'

Vue.use(Router)

export default new Router({

routes: [

{

name: "index",

path: "/",

component: _import_('dashboard/index.vue')

},

{

path: '',

name: 'Docs',

component: GuidLayout,

children:[

{

path: '/guid',

name: 'guid',

component: _import_('docs/guid.md')

}

]

},

{path: '*', component: _import_('dashboard/index.vue'), hidden: true},

]

})

// 创建测试md文件

## demo

### 基础布局

<div class="demo-block" style="color:red">

1111

</div>

// 注意demo和html不加多空行;代码与标签需要多空行,否则解析会有问题
::: demo
```html

<div style="color:red">111</div>

```
:::

效果==》

美化代码展示,增加pre-demo 组件并全局引用

//mian.js 文件

// The Vue build version to load with the `import` command

// (runtime-only or standalone) has been set in webpack.base.conf with an alias.

import Vue from 'vue'

import App from './App'

import router from './router'

import meowUi from '../packages/index'

import preBlock from './components/pre-block.vue'

Vue.component('pre-block', preBlock)

Vue.use(meowUi)

Vue.config.productionTip = false

import 'highlight.js/styles/color-brewer.css';

import './assets/common.css';

/* eslint-disable no-new */

new Vue({

el: '#app',

router,

components: { App },

template: '<App/>'

})

4. 写组件

按需引入实现,package文件夹下创建index.js整理全部组件

/**

* @author calamus0427

* Date: 19/4/30

*/

import Button from './button/index.js'

const components = [

Button

]

const install = function(Vue) {

if (install.installed) return

components.map(component => Vue.component(component.name, component))

MetaInfo.install(Vue)

Vue.prototype.$loading = WLoadingBar

}

if (typeof window !== 'undefined' && window.Vue) {

install(window.Vue)

}

export default {

Button

}

5. 发布到npm(不做详细展开了,相关资料很多)

  1. 修改package的相关信息
  2. 发布

    npm publish

----------- ----------- ----------- ----------- ----------- 待续 ----------- ----------- ----------- ----------- -----------

以上是 【造轮子】开发vue组件库MeowMeowUI 的全部内容, 来源链接: utcz.com/z/378369.html

回到顶部