封装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=" attrs:包含了父组件传递给当前组件中的不识别为prop的属性(class和style除外)(即当前组件如果没有声明接收某个prop,而父组件却传递了,则不会被识别为prop),可以通过‘v−bind="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中
- 例如:从GitHub上克隆了仓库后,可以直接使用
问题
无法在包工作区启动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 build
或yarn 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模板引擎
- 可生成任何的代码