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


    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')]
    }
    

    下载安装相关依赖

    
    # 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 class="hljs">');
      };
    }
    
    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-itlib
    enderer.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(/^demos*(.*)$/),
          render: function(tokens, idx) {
            var m = tokens[idx].info.trim().match(/^demos*(.*)$/);
            // 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>
    `
          }
        }],
        [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
      

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

  • 相关阅读:
    Oracle约束详解
    查看oracle数据库中表是否被锁
    Oracle安装EMCC
    Hbuilder和夜神模拟器的使用
    Python3 进制表示、进制转换
    Python3制作图片缩略图
    flask 异步接口
    git——一段代码将本地的代码提交至远程
    centos7防火墙放开某一端口
    CentOS7安装docker
  • 原文地址:https://www.cnblogs.com/calamus/p/10796462.html
Copyright © 2020-2023  润新知