封装Vue组件库

封装Vue组件

组件库开发

很多项目都会用到界面和功能相似的组件,为了能在不同项目中最大程度重用组件,所以会进行组件库开发,以提供不同项目基础的组件

开源组件库

  • Element-UI

  • iView

组件驱动开发CDD

Component-Driven Development

  • 自下而上构建UI
  • 从组件开始,到页面结束:先隔离开发组件,再组合成页面
    • 组件最大程度重用
    • 并行开发
    • 可视化测试

组件边界情况的处理

  • 组件可以访问$root属性,获取到当前组件树的根Vue实例,如果当前组件没有父实例,则$root指向自己。

    • 可以在根实例中存储一些共享数据
  • $parent/$children属性,获取当前组件的父组件实例或子组件实例

  • $refs属性,持有注册过ref属性的DOM元素和组件实例

  • 依赖注入provide/inject,provide与inject是成对出现

    • 父组件(或祖先组件)使用provide属性来声明自己的数据

      provide () {
        // 声明要提供的数据对象
        return {
          a: 'a',
          b: 'b'
        }
      }
      
    • 子组件(或子孙组件)使用inject属性来声明要从祖先组件的数据中注入自己的数据,注入到子孙组件的数据并不是响应式的,应该避免直接修改注入的数据

      // 注入要使用的数据属性
      inject: ['a', 'b']
      
  • a t t r s : 包 含 了 父 组 件 传 递 给 当 前 组 件 中 的 不 识 别 为 p r o p 的 属 性 ( c l a s s 和 s t y l e 除 外 ) ( 即 当 前 组 件 如 果 没 有 声 明 接 收 某 个 p r o p , 而 父 组 件 却 传 递 了 , 则 不 会 被 识 别 为 p r o p ) , 可 以 通 过 ‘ v − b i n d = " attrs:包含了父组件传递给当前组件中的不识别为prop的属性(class和style除外)(即当前组件如果没有声明接收某个prop,而父组件却传递了,则不会被识别为prop),可以通过`v-bind=" attrspropclassstyleproppropvbind="attrs"将这些非props的属性绑定到当前组件内部(可以绑定到非根元素上,如果不适用v-bind="$attrs"`,则默认绑定到组件根元素上)

    • 父组件传递的class和style属性会与当前组件的class和style进行合并处理
    • class和style是保留字,不允许被当前组件设置为prop
  • $listeners:包含了父组件为当前组件绑定的所有事件监听器(因为父子组件通信是通过自定义事件触发的形式,所以事件监听器通常是自定义事件监听器),是一个Object类型,key是事件名,value是事件处理函数。

    • 当前组件可以将$listeners绑定给内部元素,从而使得父组件的事件监听器直接作用到当前组件内部元素(类似于React把父组件的事件处理函数作为prop传递给子组件)。
    • 父组件的事件监听器的key如果与DOM原生事件名相同,则使用v-on="$listeners"将事件监听器绑定到当前组件的内部元素时,不需要声明.native修饰符

快速原型开发

快速开发和运行单文件组件

Vue-cli提供一个插件可以进行快速原型开发,查看单文件组件的运行效果

安装一个全局扩展npm install -g @vue/cli-service-global

  • 全局安装后没办法保证所有设备上的版本一致

使用vue serve快速查看单文件组件的运行效果,只需要有一个能够运行的单文件组件即可,vue serve会负责将该组件挂载到DOM,不需要手动实例化根Vue实例实现挂载

vue serve命令

  • 如果不指定参数,则默认在当前目录下寻找以下入口文件:main.js、index.js、App.vue、app.vue
  • 可以指定参数来指定要加载的组件vue serve ./src/login.vue,此时不需要有入口文件,vue serve会自己创建入口文件来加载指定的组件

基于第三方组件库二次开发

比如基于Element-UI进行二次开发

安装Element-UI
  • 初始化package.json:npm init -y
  • 安装Element-UI:vue add element
    • 同时会安装其他的包(如babel),根据与用户的交互来选择安装包(这里也会同时安装element-ui)
    • 自动在package.json中配置babel
    • vue add命令用来给@vue/cli创建的项目添加插件
    • @vue/cli使用了一套基于插件的架构,当创建项目时,会预先安装一些插件。每个插件都包含一个生成器(创建文件及目录结构)和一个运行时插件(调整 webpack 核心配置和注入命令)
    • 推荐在运行vue add 命令前先提交一次状态,因为该命令可能会生成文件
指定入口文件

导入ElementUI或ElementUI提供的组件,使用Vue.use()安装ElementUI插件,使用vue serve在当前目录下启动

// main.js,入口文件,使用vue serve时会自动创建index.html
import Vue from 'vue'
import ElementUI from 'element-ui'
// 导入ElementUI提供的样式
import 'element-ui/lib/theme-chalk/index.css'
// 导入自己基于ElementUI开发的组件Login
import Login from './src/Login.vue'
// 安装ElementUI插件
Vue.use(ElementUI)

new Vue({
  el: '#app',
  render (h) {
    return h(Login)
  }
})

组件开发

组件分类

第三方组件

基础组件:通用型

业务组件:具体业务型

表单验证库:async-validator

仓库管理:Monorepo

一个项目仓库(我们的组件库)中管理多个包(这里每个包就是我们自定义的组件),根目录放置脚手架,packages目录下放置组件包

每个包都是独立的npm模块,拥有独立的package.json文件

称为:多包单仓库管理模式Monorepo

很多知名的开源库都采用了Monorepo的管理模式,如vue、react等

Monorepo的工作区管理方式:yarn workspace
  • 集中管理Monorepo仓库中的依赖,无论是根项目依赖,还是packages各个包的依赖

  • 集中管理Monrepo仓库中的npm scripts,无论是根项目,还是package各个包的npm scripts

Monorepo仓库的结构一般在根目录下存放packages中所有包的开发依赖(如babel、jest等等),各个包会有自己的运行依赖记录在各自的package.json中。

一般情况下,各个包需要下载各自的运行依赖,但这些依赖很有可能存在公共的情况,如果每个包都下载这些公共依赖,会重复下载占用大量的硬盘资源。

通过开启yarn workspace功能(npm不支持),可以在工作区根目录下,使用yarn来为这些公共的运行依赖统一下载,并提升到工作区根目录的node_modules目录下,减少重复下载。如果依赖的公共模块版本不同,则只会将相同版本的依赖提升,单一版本的依赖还会继续下载并存放在各个包工作区下。

开启
// 根目录下的package.json, 这里将仓库根目录设置为yarn的workspace根目录,然后设置了packages目录下的任意目录作为workspace目录
// 私有仓库,这是为了意外暴露根目录下的脚手架资源,因为这些资源没必要发布到npm
"private": true,
// 设置workspace工作区,这里将packages/目录下的各个包的目录作为工作区
"workspaces": [
  "./packages/*"
]
使用
  • 给工作区根目录安装开发依赖
    • 例如:yarn add jest -D -W,其中-D是开发依赖,-W表示安装到工作区根目录
  • 给指定工作区安装依赖
    • 例如:yarn workspace <package-name> add package,其中,<package-name>是我们的packages中的某个包的包名(在该包的package.json中指定的名称,不是文件夹名称),package是要安装的依赖包
  • 给所有的工作区安装依赖
    • 例如:从GitHub上克隆了仓库后,可以直接使用yarn install来安装所有的依赖
    • 使用yarn install优先将依赖安装到工作区根目录的node_modules,除非某个包的依赖有不同版本,则安装到该工作区(包)自己目录的node_modules中
问题
无法在包工作区启动npm scripts
  • 在使用的过程中,通过yarn workspace <package-name> run command来运行某个包的package.json中指定的npm scripts命令时,由于scritps命令对应的包安装到了工作区根目录下的node_modules目录中,导致运行时找不到该命令。即使是在该包的目录内使用yarn run command也是同样的问题
  • 在工作区根目录下,使用yarn workspaces run command运行所有包的npm scripts,命令找不到

组件可视化:Storybook

  • 可视化展示组件的平台
  • 在隔离开发环境中,交互式展示组件
  • 独立开发组件
  • 支持框架:React、Vue、React-Native等等
安装并初始化
npx sb init

本地局部安装了@storybook/cli,并调用sb init添加storybook到当前项目

注:sb init 不能在空项目下使用,因为storybook要读取项目的package.json来确定项目使用的框架和其他信息

空项目下安装

在空项目下,可以强制指定项目框架来安装,并初始化项目

npx -p @storybook/cli sb init --type vue

npx -p表示要下载安装某个模块,接着使用sb init --type vue来告诉storybook当前项目是基于Vue框架,这样就不需要storybook检查package.json来确定项目使用的框架,storybook就可以基于该框架来生成对应的配置。

  • 下载安装框架对应的Storybook
  • 添加Storybook支持
  • 下载依赖项
    • 包含storybook对框架的支持@storybook/vue@6.0.20
    • babel相关:@babel/core@7.11.4,babel-loader@8.1.0
    • storybook的必需插件:
      • @storybook/addon-essentials@6.0.20
      • @storybook/addon-actions@6.0.20
      • @storybook/addon-links@6.0.20
添加依赖

如果是强制指定vue框架,并且项目没有使用vue-cli初始化时,则需要安装vue和vue相关的依赖

yarn add vue
yarn add vue-loader vue-template-compiler --dev
项目目录
.storybook目录:storybook的配置文件所在目录
main.js配置文件

main.js文件,storybook的配置文件

main.js配置文件是一组预设preset,有一些对外的接口字段,包含如下

module.exports = {
  // stories的路径
  stories: ['../stories/**/*.stories.js'],
  // 插件
  // actions快速注册事件
  // links设置链接
  addons: ['@storybook/addon-essentials', '@storybook/addon-actions', '@storybook/addon-links'],
  // 可选的webpack自定义配置
  // 该字段是一个异步函数,接收默认的webpack config,返回一个webpack config
  // 默认的webpack配置允许以下操作
  /*
  	1. 导入图片和其他静态文件
  	2. 导入JSON文件作为JavaScript文件(即作为对象形式)
  */
  // 这里的webpack配置用于添加Sass支持
  webpackFinal: async (config, { configType }) => {
    // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
    // You can change the configuration based on that.
    // 'PRODUCTION' is used when building the static version of storybook.

    // Make whatever fine-grained changes you need
    config.module.rules.push({
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
      include: path.resolve(__dirname, '../'),
    });

    // Return the altered config
    return config;
  // babel自定义配置
  babel:
}
  • webpack的配置仅仅用于在preview iframe中渲染组件,storybook对于UI(即manager)还有一个完全隔离的webpack配置。因此,小心谨慎改写配置,确保保留entry和output字段
preview.js配置文件:可选
  • preview.js配置文件是配置stories渲染的方式,并且可以添加全局decorators和parameters

  • 这些配置会加载到Canvas选项卡中,preview iframe元素内(隔离渲染组件)

  • 使用preview.js来应用全局代码(如css imports或javascript mocks)给所有的stories

  • preview.js是一个ES模块,对外暴露以下变量

    • decorators:全局decorators组成的数据
    • parameters:全局parameters组成的对象
      • layout属性:控制stories在Canvas中的位置
    • globalTypes:globalTypes的定义
manager.js文件:可选

配置UI(manager)的表现

stories目录

组件文件和.stories.js文件,其中组件是需要自己开发,然后将组件导入.stories.js文件中,通过.stories.js文件来可视化查看组件的效果

scripts命令
storybook
"storybook": "start-storybook -p 6006",

启动storybook开发模式,端口号6006

build-storybook
"build-storybook": "build-storybook"

打包storybook项目,打包结果是一个静态网站,可以直接发布到服务器上

如何写stories

除了开发组件外,stories脚本文件如何撰写是storybook的一个核心

story是UI组件状态的快照,描述组件的某个状态。开发者可以针对每个组件编写stories来查看感兴趣的组件状态,因此story本身的含义就是将关于组件状态的故事

storybook的官网是基本是都是react的例子,没有Vue例子

撰写stories的依据是Component Story Format(CSF)
  • stories文件需要先导入要描述的组件

  • stories文件默认导出一个对象,包含了title和component属性,title属性用来给stories命名,component属性说明要描述的组件

  • stories文件需要命名导出每个story,每个story都是一个函数,用来描述如何渲染一个组件(提供渲染组件所需的数据),函数名会作为story的名称

    • 可以通过story的storyName属性来重命名story
  • 使用args:args对应于React的props、Vue的slots和Angular的@input。将stories的主要结构定义为Template函数(该函数接收args作为参数),对于每个story而言,重复使用该Template函数,传入不同的args即可

    • args也支持导入使用,如从一个stories文件中导入到另一个stories文件中复用,这通常用于两个stories文件之间有某种联系,如ButtonGroup和Button之间,ButtonGroup复用Button的args,这样Button的args更新时,ButtonGroup也会自动更新
    • args可以通过ES6的扩展语法来在另一个stories中复用
    • args还可以通过storybook的页面上的control面板实现热更新(实时编辑)live edit component
    • addons可以用来增强args。例如,Actions会自动检测到哪个args是回调函数,给这些回调函数添加一个logging函数,这样可以在Actions面板中记录这些交互行为
    // Button.stories.js, 这里假设使用Vue框架
    const Template = (args) => ({
      template: `<my-component />`
      component: { MyComponent }
    })
    export Primay = Template.bind({})
    // story层面的args
    // Vue框架下的args暂时没有见过如何处理,这里的写法是一个猜测
    Primary.args = (`<div>slot content</div>`)
    
    export Secondary = Template.bind({})
    Secondary.args = (`<p>slot content</p>`)
    
    // Button.stories.js,这里是react框架
    import React from 'react';
    import Button from './Button';
    
    export default {
      title: "Button",
      component: Button,
      argTypes: {
        backgroundColor: { control: 'color' },
      },
      // 组件层面的args
      args: {
        // Now all Button stories will be primary.
        primary: true,
      },
    };
    
    
  • 使用parameters:storybook用来为stories定义静态元数据的方法,一个story的parameters可以被用来为一组stories(或一个story)层面上提供多个addon的配置信息。

    • 例如,你想为Button组件(而不是其他组件)测试在一组不同背景下的效果,你可以为这个组件添加一个组件层面上的parameter。该parameter会引导backgrounds addon在选中Button组件时重新配置自身

    • 大多数addons通过一个parameter-based API 来配置,并且会被一个全局、组件或story层面的parameter影响配置。

    • 全局层面的parameters在preview.js中设置

      // Button.stories.js
      
      import React from 'react';
      import Button from './Button';
      
      export default {
        title: 'Button',
        component: Button,
        // 添加一个组件层面的parameter
        parameters: {
          backgrounds: {
            values: [
               { name: 'red', value: '#f00', },
               { name: 'green', value: '#0f0', },
               { name: 'blue', value: '#00f', },
            ]
          }
        }
      }
      
  • 使用decorators:decorators是在渲染story时将组件包裹在任意标签中的一种机制,因为组件往往是在假想的地方来创建和渲染的,通过decorators机制,可以让组件在一个上下文中渲染,并且还可以通过该上下文传递数据

    • 例如,给一个组件的stories添加padding,可以通过将组件渲染时包裹在一个<div><component /></div>里,给包裹的div设置一个padding即可。

    • decorators可以很复杂,且常常由addons提供。

    • 可以在全局、组件或story层面上设置decorators

      // Button.stories.js
      
      import React from 'react';
      import Button from './Button';
      
      export default {
        title: "Button",
        component: Button,
        decorators:  [(Story) => <div style={{ margin: '3em' }}><Story/></div>]
      };
      
  • 多个组件的stories:有时候在渲染组件时,会有依赖的子组件,此时需要将依赖的多个组件也一起添加到stories中工作。

    • 导入依赖组件:List组件依赖于ListItem组件

      // List.stories.js
      
      import React from 'react';
      import List from './List';
      import ListItem from './ListItem';
      
      export default {
        component: List,
        title: 'List',
      };
      
      export const Empty = (args) => <List {...args} />;
      
      export const OneItem = (args) => (
        <List {...args}>
          <ListItem />
        </List>
      );
      
      export const ManyItems = (args) => (
        <List {...args}>
          <ListItem />
          <ListItem />
          <ListItem />
        </List>
      );
      
    • 导入依赖组件的stories,复用stories

      // List.stories.js
      
      import React from 'react';
      import { Selected, Unselected } from './ListItem.stories';
      
      export const ManyItems = (args) => (
        <List {...args}>
          <Selected {...Selected.args} />
          <Unselected {...Unselected.args} />
          <Unselected {...Unselected.args} />
        </List>
      );
      

将之前使用monorepo模式的packages目录拷贝到storybook的项目中,给packages中的包目录下创建stories目录存放要写的stories文件,注意修改一下storybook里的配置文件.storybook/main.js的stories路径

给input包编写stories

input包目录如下

--input
  -- __test__
  --dist
  --src
    -- input.vue
  --stories
  --index.js
  --LICENSE
  --package.json
  --README.md
// index.js文件
import MxyInput from './src/input.vue'

// 可以让Input作为插件使用,作为插件使用时,Input组件变为全局组件
MxyInput.install = (Vue) => {
  Vue.component(MxyInput.name, MxyInput)
}

export default MxyInput

在stories目录下新建input.stories.js文件

// 从input目录下的index.js文件中导入input.vue组件
import MxyInput from '../index.js'

export default {
  title: 'MxyInput',
  component: MxyInput,
}

// 每个导出函数都返回一个选项对象,该选项对象会创建一个组件实例
export const Text = () => ({
  components: {
    MxyInput
  },
  template: '<mxy-input v-model="value"></mxy-input>',
  data () {
    return {
      value: 'admin'
    }
  }
})

export const Password = () => ({
  components: {
    MxyInput
  },
  template: '<mxy-input type="password" v-model="value"></mxy-input>',
  data () {
    return {
      value: 'admin'
    }
  }
})

发布管理:Lerna

简介

babel维护自己的Monorepo而开源的项目,用于管理和发布项目到GitHub和npm

  • 优化使用git和npm管理多包仓库的工作流工具
  • 管理具有多个包的JavaScript项目
  • 一键提交代码到git和npm仓库
  • 选择使用npm还是yarn来管理,需要单独配置
安装

全局安装yarn global add lerna

初始化lerna init,如果当前项目没有被git管理,则会先进行git初始化

发布lerna publish

配置

初始化后,在项目根目录下生成一个lerna.json的配置文件

{
  // 需要lerna管理的包的目录,这里是默认的packages,如果当前目录没有该文件夹,则会自动创建
  "packages": [
    "packages/*"
  ],
  // lerna管理的版本号
  "version": "0.0.0"
}
配置文件中的其他字段
{
  "version": "1.1.3",
  "npmClient": "npm",
  "command": {
    "publish": {
      "ignoreChanges": ["ignored-file", "*.md"],
      "message": "chore(release): publish",
      "registry": "https://npm.pkg.github.com"
    },
    "bootstrap": {
      "ignore": "component-*",
      "npmClientArgs": ["--no-package-lock"]
    }
  },
  "packages": ["packages/*"]
}
  • Version:当前仓库的版本号
  • npmClient:使用的客户端,默认npm,可以更改为yarn
  • command.publish.ignoreChanges:由globs组成的数组,用于忽略这些文件的更新造成不必要的版本更新
  • command.publish.message:当发布更新版本时,自定义的提交信息commit message
  • command.publish.registry:用于设定自定义的发布地址,来代替npm官方地址
  • command.bootstrap.ignore:当运行lerna bootstrap命令时,要忽略的文件,这里是globs组成的数组
  • command.bootstrap.npmClientArgs:当lerna bootstrap命令运行期间,传递给npm install的参数数组
  • command.bootstrap.scope:运行lerna bootstrap命令时,限制哪些packages会被bootstrapped,是一个globs组成的数组
  • Packages:指定package的路径的globs数组

lerna通过packages指定的路径中的package.json文件来识别哪些是package,并识别为叶子package(与根package相对,即仓库本身的package,用于安装开发依赖)

工作模式
固定模式(Fixed/Locked mode)

默认使用固定模式,即单一版本线。

在lerna.json配置文件中,有一个version字段记录单一的版本线

如果一个包在上次版本后被更新,只要运行lerna publish命令,就会在发布后更新version字段

当你希望所有的包的版本都绑定在一起时,使用该模式,这也是Babel正在使用的模式

唯一的不足就是,当一个包的大版本更新时,所有包都会更新大版本

独立模式(independent mode)

顾名思义,独立模式就是每个包的版本都是独立的,没有互相关联

每次发布时,都会向作者提问每个更新的包是否是patch、minor、major或者自定义更新

切换

lerna init --independent

发布仓库和包

将仓库托管到GitHub上之后,准备发布包到npmjs上

npm操作

添加npm账号:npm adduser

查看当前登录的npm账号:npm whoami

查看当前的npm源:npm config get registry

更改当前的npm源:npm config set registry https://registry.npmjs.org/

发布包到npmjs

使用lerna publish命令发布当前Monorepo仓库的包packages到npmjs

当使用lerna publish发布时,会将当前版本号和gitHead(git当前版本的hash)记录到各个包的package.json文件中

删除包

使用npm unpublish <package-name> --force来删除发布的包,npm有如下要求:

  • 删除的版本24小时后方可重发!
  • 只有发布72小时之内的包可以删除!

组件单元测试

在发布包之前,需要先在本地做单元测试

对函数的输入和输出,使用断言的方式来做测试

使用单元测试,对组件的状态和行为进行测试

单元测试的好处
  • 提供描述组件行为的文档
  • 节省手动测试的时间
  • 减少研发新特性时产生的bug
  • 改进设计,促进重构
配置单元测试环境

Vue Test Utils:Vue提供的组件单元测试库

Jest:测试框架,默认安装了 JSDOM,内置断言且命令行的用户体验非常好。DOM特性的测试用 JSDOM 在 Node 虚拟浏览器环境运行测试。

vue-jest:Vue提供的单文件组件编译预处理器,将编译结果交给Jest处理,但是vue-jest功能并不全面,缺乏对样式块和自定义块的支持

  • 如果需要全面的测试单文件组件,则需要基于Mocha运行测试, webpack + vue-loader 进行编译

babel-jest:对测试代码进行语法降级处理

安装

在当前工作区安装yarn add jest @vue/test-utils vue-jest babel-jest -D -W,如果不使用-W,会发生错误

配置测试脚本
  • package.json中添加npm scripts

    "scripts": {
      // ...
      "test": "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)$": "babel-jest"
      },
      // 开启测试覆盖率
      "collectCoverage": true,
      // 从哪些文件中收集测试覆盖率,也就是说,这些文件需要被测试
      "collectCoverageFrom": ["**/*.{js,vue}", "!**/node_modules/**"],
      // 定制测试覆盖率报告的格式,可以不添加该字段,则为默认格式
      "coverageReporters": ["html", "text-summary"]
    }
    
  • babel配置babel.config.js

    module.exports = {
      presets: [
        ['@babel/preset-env']
      ]
    }
    

    可能会在运行测试时提示找不到babel的情况,原因在于storybook安装了babel@7版本,而Vue Test Utils依赖的是babel@6版本,解决办法是安装一个桥接包yarn add babel-core@bridge -D -W

编写测试脚本

Jest框架提供测试用例和断言函数的API

Vue Test Utils库提供组件的一系列API,包括DOM操作和组件实例的方法等等

快照测试
自定义序列化工具改进被保存的快照

yarn add jest-serializer-vue -D -W

在jest.config.js中配置

"snapshotSerializers": ["jest-serializer-vue"]

打包组件:Rollup

开发框架或者组件库时,使用Rollup打包更合适,Rollup打包的体积小,默认支持Tree-shaking

安装Rollup及打包所需依赖
  • rollup
  • rollup-plugin-terser:代码压缩
  • rollup-plugin-vue@5.1.9:把单文件组件编译为JavaScript代码,之所以指定版本,是因为最新版本是要编译vue3.0
  • vue-template-compiler:rollup-plugin-vue@5.1.9使用编译器来编译
配置Rollup

如果是打包某个package时,在该包的根目录下创建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({
        // 把样式使用<style>标签嵌入HTML文件中
        css: true,
        // 组件转换为render函数
        compileTemplate: true
      }),
      terser()
    ]
  }
]
指定要打包的模块

在packages目录下,如果要针对某个包进行Rollup打包操作,可以在该包下的package.json中添加npm scripts,加入build命令

// package.json
"scripts": {
  // ...
  // rollup -c命令会在当前目录下查找默认的配置文件
  "build": "rollup -c"
}
启动打包
  • 可以进入该包的目录下,使用npm run buildyarn run build来打包
  • 可以使用yarn workspace的方式,在仓库根目录下使用yarn workspace <package-name> run build来执行package-name包的build命令,注意,package-name不是包的目录名称,而是package.json中指定的包的名称
一次性打包所有package
安装依赖
  • @rollup/plugin-json:允许rollup将json文件作为模块加载,就像webpack的loader一样,在配置文件中会用到
  • rollup-plugin-postcss:postcss对应的rollup插件
  • rollup/plugin-node-resolve:把依赖的第三方包也打包进来,比如组件依赖于async-validator包,把该包也一起打包
创建配置文件

在工作区根目录下,创建一个配置文件rollup.config.js,该配置文件本质上是一个node脚本,用来给packages中的各个包生成rollup的配置文件(但不是生成在硬盘上,而是返回一个数组,数组元素为针对各个包的打包配置对象)

设置环境变量,区分开发环境和生产环境

安装cross-env包来进行跨平台设置环境变量

在项目根目录的package.json中设置

"scripts": {
  "build:prod": "cross-env NODE_ENV=production rollup -c",
  "build:dev": "cross-env NODE_ENV=development rollup -c"
}
清理工作
清理所有包中的node_modules

使用lerna clean命令,在工作区根目录的package.json中添加"clean": "lerna clean"

清理所有包中的dist

安装rimraf

给packages中的每个包的package.json中配置del命令,rimraf后可以接目录名称表示要删除的目录

"del": "rimraf dist"

基于模板生成组件结构:Plop

组件之间有很多相似的文件或文件内容,比如每个包的package.json中都有很多相似的字段,每个组件包中的大部分文件都一致,因此,我们需要基于这些相同的文件结构或内容来生成组件,然后在此基础上修改得到不同的组件

使用Plop
安装

yarn add plop -D -W

撰写模板

在工作区根目录下,创建plop-template/components目录,该目录是组件的模板目录

模板目录中的文件,如果有动态信息,则使用模板语法来撰写文件内容,这里选择了handlebars模板语法

配置Plop

在工作区根目录下创建plopfile.js文件

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

添加scripts字段"plop": "plop"

命令行运行yarn plop

如果直接使用plop不带参数时,则Plop会在命令行提示用户选择哪个generator

也可以使用plop <generator-name>来直接触发指定的generator

传递参数

在命令行启动plop时,可以传递prompts的参数

  • 直接传递 plop <generator-name> <value1> <value2>
  • 通过名字传递 plop <generator-name> -- --<name1> <value1> --<name2> <value2>
Plop
  • 基于handlebars模板引擎
  • 可生成任何的代码
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值