手把手构建Vue组件开发、管理工具

311 阅读7分钟

快速原型开发
Monrepo
Storybook组件管理
yarn workspaces
Lerna上传管理
Jest单元测试
Rollup打包
Plop生成模版文件

快速原型开发

  • VueCli中提供了一个插件可以进行原型快速开发
  • 需要先额外安装一个全局的扩展
    • npm install -g @vue/cli-service-global
  • 你所需要的仅仅是一个 App.vue 文件
<template>
  <h1>Hello!</h1>
</template>
  • 然后在这个 App.vue 文件所在的目录下运行:
vue serve
  • vue serve 使用了和 vue create 创建的项目相同的默认设置 (webpack、Babel、PostCSS 和 ESLint)。它会在当前目录自动推导入口文件——入口可以是 main.jsindex.jsApp.vue 或 app.vue 中的一个。你也可以显式地指定入口文件:
vue serve MyComponent.vue

Monorepo

  • 一个项目仓库中管理多个模块/包
  • 目录结构
    • 新建一个packages文件夹
    • 给每个组件创建一个文件夹
    • src 目录下存放组件源码
    • tests/stories 分别是测试及storybook所需文件,后续会提到,这里先忽略 先把文件结构搭建出来 image.png

storybook

  • 简介
    • 分开展示各个组件不同属性下的状态
    • 能追踪组件的行为并且具有属性调试功能
    • 可以为组件自动生成文档和属性列表
  • 自动安装
    • 项目根目录执行 yarn init -y,因为要用到yarn的workspace,所以统一使用yarn对依赖包进行管理
        yarn init -y
    
    • 安装storybook
        npx -p @storybook/cli sb init --type vue
    
    • 安装vue 及相关依赖
        yarn add vue
        yarn add vue-loader vue-template-compiler --dev
    
  • 修改.storybook/main.js的 入口,运行storybook脚本时,去packages文件下去找stories.js文件
       "../packages/**/*.stories.js"
    

image.png

  • 在packages中给组件文件添加stories文件及对应的js文件 image.png
  • 这里编写一个简单的demo作为参考,具体参照官方文档
       import WzjInput from '../'
        export default {
          title: 'WzjInput',
          component: WzjInput
        }
        export const Password = () => ({
          components: { WzjInput },
          template: `<wzj-input type="password" v-model="value"></wzj-input>`,
          data() {
            return {
              value: '123456'
            }
          }
        })
    
  • 运行storybook 查看效果
        yarn storybook
    
  • 页面正常访问,可以看到编写的story,此时基本的结构就搭建完成了。 image.png

yarn workspaces

  • workspace的作用:
    • 能帮助你更好地管理多个子project的repo,这样你可以在每个子project里使用独立的package.json管理你的依赖,又不用分别进到每一个子project里去yarn install/upfrade安装/升级依赖,而是使用一条yarn命令去处理所有依赖就像只有一个package.json一样
    • yarn会根据就依赖关系帮助你分析所有子project的共用依赖,保证所有的project公用的依赖只会被下载和安装一次。
  • workspace的使用
    • yarn workspace并不需要安装什么其他的包,只需在项目根目录下的package.json,添加相关配置,便可以工作。
        "private": true,
         "workspaces": [
         "packages/*"
        ],
    

image.png

  • 给项目根目录安装开发依赖, 此时不加-W安装依赖会报错,要记得加上-W
yarn add xx -D -W
  • 给制定工作区安装依赖
    yarn workspace wzj-button add lodash@4
    
    • 此时button依赖的ladash模块在根目录下的node_modules中 image.png
  • 给所有工作去初始化依赖
 yarn install

Lerna

Lerna介绍

  • Lerna是一个优化使用git和npm管理多包仓库的工作流工具
  • 用于管理具有多个包的JavaScript项目
  • 它可以一键把代码提交到git和npm仓库

Lerna使用

  • 全局安装
   yarn global add lerna
  • 初始化
    • 在lerna publish之前要初始化项目的git仓库,否则会报错
   lerna init
  • 发布
    • 在publish之前,执行 npm whoami确认npm是否登录及账号是否正确
    • 未登录执行npm login 登录你的npm账号
    • lerna publish 成功后就可以在git仓库及npm平台上看到自己所上传的代码及组件
   lerna publish

Vue组件的单元测试

  • 单元测试好处
    • 提供描述组件行为的文档
    • 节省手动测试的时间
    • 减少研发新特性时产生的bug
    • 改进设计
    • 促进重构
  • 安装依赖
yarn add jest @vue/test-utils vue-jest babel-jest -D -W
  • 配置测试脚本 package.json
"scripts": {
    "test": "jest"
 }
  • Jest配置文件,根目录下创建 jest.config.js
module.exports = {
  "testMatch": ["**/__tests__/**/*.[jt]s?(x)"],
  "moduleFileExtensions": [
    "js",
    "json",
    // 告诉 Jest 处理 `*.vue` 文件
    "vue"
  ],
  "transform": {
    // 用 `vue-jest` 处理 `*.vue` 文件
    ".*\\.(vue)$": "vue-jest",
    // 用 `babel-jest` 处理 js
    ".*\\.(js)$": "babel-jest"
  }
}

  • Babel配置文件,根目录下创建 babel.config.js
module.exports = {
  presets: [['@babel/preset-env']]
}
  • Babel的桥接
yarn add babel-core@bridge -D -W
  • 组件目录下的__tests__创建编写测试代码的js文件

image.png

  • 实例作为参考,这里不做拓展,具体使用方式查看官方文档即可:
import { mount } from '@vue/test-utils'
import WzjInput from '../src/input.vue'
describe('wzj-input', () => {
  test('input-password', () => {
    const wrapper = mount(WzjInput, {
      propsData: {
        type: 'password'
      }
    })
    // 推断type="password
    expect(wrapper.html()).toContain('input type="password"')
  })
})
  • 运行测试脚本
    yarn test

运行时如果遇到报错,可尝试在test.js文件顶部添加以下注释解决

/**
* @jest-environment jsdom
*/

image.png

Rollup

  • 安装 Rollup 以及所需的插件
yarn add rollup rollup-plugin-terser rollup-plugin-vue@5.1.9 vue-template-compiler -D -W
  • Rollup 配置文件

在 button 目录中创建 rollup.config.js

import { terser } from 'rollup-plugin-terser'
import vue from 'rollup-plugin-vue'

module.exports = [
  {
    input: 'index.js',
    output: [
      {
        file: 'dist/index.js',
        format: 'es'
      }
    ],
    plugins: [
      vue({
        // Dynamically inject css as a <style> tag
        css: true,
        // Explicitly convert template to render function
        compileTemplate: true
      }),
      terser()
    ]
  }
]
  • 配置 build 脚本并运行

找到 button 包中的 package.json 的 scripts 配置

"build": "rollup -c"

运行打包

yarn workspace lg-button run build

打包所有组件

  • 安装依赖
yarn add @rollup/plugin-json rollup-plugin-postcss @rollup/plugin-node-resolve -D -W
  • 配置文件

项目根目录创建 rollup.config.js

import fs from 'fs'
import path from 'path'
import json from '@rollup/plugin-json'
import vue from 'rollup-plugin-vue'
import postcss from 'rollup-plugin-postcss'
import { terser } from 'rollup-plugin-terser'
import { nodeResolve } from '@rollup/plugin-node-resolve'

const isDev = process.env.NODE_ENV !== 'production'

// 公共插件配置
const plugins = [
  vue({
    // Dynamically inject css as a <style> tag
    css: true,
    // Explicitly convert template to render function
    compileTemplate: true
  }),
  json(),
  nodeResolve(),
  postcss({
    // 把 css 插入到 style 中
    // inject: true,
    // 把 css 放到和js同一目录
    extract: true
  })
]

// 如果不是开发环境,开启压缩
isDev || plugins.push(terser())

// packages 文件夹路径
const root = path.resolve(__dirname, 'packages')

module.exports = fs
  .readdirSync(root)
  // 过滤,只保留文件夹
  .filter(item => fs.statSync(path.resolve(root, item)).isDirectory())
  // 为每一个文件夹创建对应的配置
  .map(item => {
    const pkg = require(path.resolve(root, item, 'package.json'))
    return {
      input: path.resolve(root, item, 'index.js'),
      output: [
        {
          exports: 'auto',
          file: path.resolve(root, item, pkg.main),
          format: 'cjs'
        },
        {
          exports: 'auto',
          file: path.join(root, item, pkg.module),
          format: 'es'
        }
      ],
      plugins: plugins
    }
  })
  • 在每一个包中设置 package.json 中的 main 和 module 字段
"main": "dist/cjs/index.js",
"module": "dist/es/index.js"
  • 根目录的 package.json 中配置 scripts
"build": "rollup -c"

设置环境变量

yarn add cross-env -D -W
  • 根目录的 package.json 中配置 scripts
  "scripts": {
    "build:prod": "cross-env NODE_ENV=production rollup -c",
    "build:dev": "cross-env NODE_ENV=development rollup -c",
  }

清理

yarn add rimraf -D -W
  • 为每一个包中的package.json添加脚本
  "scripts": {
   "del": "rimraf dist"
 },
  • 执行脚本删除每一个包中的老的dist文件
yarn workspaces run del

Plop基于模版生成组件基本结构

经过以上的配置,我们的项目结构基本已经固定下来,但是要思考一个问题,如果一个组件就要创建这么多文件,而且都是相同的结构,略微有点不妥,可以借助plop帮助我们去完成固定文件的创建

image.png

  • 安装依赖
yarn add plop -D -W
运行报错的话 尝试安装2.7.4版本
  • 根目录的 创建plopfile 中配置并做配置
module.exports = plop => {
  plop.setGenerator('component', {
    description: 'create a custom component',
    prompts: [
      {
        type: 'input',
        name: 'name',
        message: 'component name',
        default: 'MyComponent'
      }
    ],
    actions: [
      {
        type: 'add',
        path: 'packages/{{name}}/src/{{name}}.vue',
        templateFile: 'plop-template/component/src/component.hbs'
      },
      {
        type: 'add',
        path: 'packages/{{name}}/__tests__/{{name}}.test.js',
        templateFile: 'plop-template/component/__tests__/component.test.hbs'
      },
      {
        type: 'add',
        path: 'packages/{{name}}/stories/{{name}}.stories.js',
        templateFile: 'plop-template/component/stories/component.stories.hbs'
      },
      {
        type: 'add',
        path: 'packages/{{name}}/index.js',
        templateFile: 'plop-template/component/index.hbs'
      },
      {
        type: 'add',
        path: 'packages/{{name}}/LICENSE',
        templateFile: 'plop-template/component/LICENSE'
      },
      {
        type: 'add',
        path: 'packages/{{name}}/package.json',
        templateFile: 'plop-template/component/package.hbs'
      },
      {
        type: 'add',
        path: 'packages/{{name}}/README.md',
        templateFile: 'plop-template/component/README.hbs'
      }
    ]
  })
}

  • 在plop-template/component文件夹下创建模版文件,模版可参照此项目中的模版
  • 根目录的 package.json 中配置 scripts
    "scripts": {
    "test": "jest",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook",
    "lerna": "lerna publish",
    "build:prod": "cross-env NODE_ENV=production rollup -c",
    "build:dev": "cross-env NODE_ENV=development rollup -c",
    "clean": "lerna clean",
    "plop": "plop"
  }
  • 运行脚本,输入要创建的组件名即可
yarn plop
  • 在所创建的文件中,去编写你的组件即可。

总结

  • Monrepo的方式一个项目仓库中管理多个模块/包
  • Storybook有组织和高效地构建UI组件
  • yarn workspaces更好地统一管理多个子项目package.json
  • Lerna优化使用git和npm管理多包仓库
  • Jest编写组件测试脚本
  • Rollup打包
  • Plop基于模版生成组件基本结构 可能会因为包的版本问题碰到一些报错,有问题可以留言指出。