前言
本项目技术栈为vue3+typescript
,PC端组件库。 项目gitee地址
本文将从环境搭建开始,一步步完成组件库的打包工作。组件库内组件不定期更新。
1. 代码管理方式
组件库采用monorepo
的代码管理方式。
1)概念
monorepo
是管理项目代码的一种方式,指在一个项目仓库 (repo) 中管理多个模块/包 (package),不同于常见的每个模块建一个repo。
2)优劣势
monorepo
最主要的优势是统一的工作流
和Code Sharing
。一套代码管理多个package包的构建和发布。劣势是如果每个package都有自己的独立的依赖,频繁的install包,node_modules会变得很大。
2. monorepo的解决方案
本项目使用lerna
和yarn
和workspaces
特性,来管理monorepo
。
项目中lerna.json
声明packages
后,lerna
为项目提供统一的依赖安装 (lerna bootstrap)
,统一的执行package scripts(lerna run)
,统一的npm发版 (lerna publish)
等特性。
使用yarn
作为包管理器,在package.json
中用workspaces
字段声明packages
,yarn
就会以monorepo
的方式管理packages
。
相比lerna
,yarn
突出的是对依赖的管理,包括packages
的相互依赖、packages
对第三方的依赖,yarn会以semver
约定来分析dependencies
的版本,安装依赖时更快、占用体积更小;但欠缺了统一工作流
方面的实现。
大多monorepo
即会使用lerna
又会在package.json
声明workspaces
。这样无论你的包管理器是npm
还是yarn
,都能发挥monorepo
的优势;要是包管理是yarn
,lerna
就会把依赖安装交给yarn
处理。怎么交给yarn
处理呢?配置lerna.json
的npmClient
字段为yarn
。
关于monorepo更多的了解,请看好文Monorepo-大型项目代码管理方式。本文关于monorepo的介绍都取自该文。
一、环境搭建
1. lerna安装
全局安装lerna
,注意node
版本要在v10.4
以上。
npm install lerna -g
2. 生成lerna.json和package.json文件
lerna init
// lerna.json
{
"packages": [
"packages/*" // 表示管理packages下的所有模块
],
"npmClient": "yarn", // 依赖包的安装交给yarn
"useWorkspaces": true, // 是否使用空间,lerna本身不使用workspace,yarn要使用。
"version": "0.0.0"
}
// package.json
{
"name": "root",
"private": true,
"workspaces": [ // yarn管理packages下所有模块
"packages/*"
],
"devDependencies": {
"lerna": "^4.0.0"
}
}
// 安装本地依赖 lerna
yarn install
3. vue@next安装
yarn add vue@next -W # -W忽略提示,默认安装到根目录全局使用
编写vue声明文件
// typings/vue-shim.d.ts
declare module '*.vue' {
// 取defineComponent的返回值,标识组件类型
import { defineComponent, App } from 'vue';
const component: ReturnType<typeof defineComponent> & { install(app: App): void }
// 导出组件类型
export default component;
}
4. typescript安装,tsconfig.json生成
yarn add typescript -W
npx tsc --init # 生成tsconfg.json配置文件
// tsconfig.json
{
"compilerOptions": {
"target": "ESNext", # 打包的目标语法
"module": "ESNext", # 模块转化后的格式
"esModuleInterop": true, # 支持模块转化 import fs from 'fs'; 编译前 let fs = require('fs'); fs.default编译后,fs无default属性,所引引用时会出问题
"skipLibCheck": true, # 跳过类库检测
"forceConsistentCasingInFileNames": true, # 强制区分大小写
"moduleResolution": "node", # 模块解析方式
"jsx": "preserve", # 不转化jsx
"declaration": true, # 生成声明文件
"sourceMap": true # 生成映射文件
}
}
5. 初始化组件
lerna create button
├─button
│ │ package.json
│ │ README.md
│ ├─src
| ├─button.vue # 组件实现
│ ├─index.ts # 组件入口
│ └─__tests__ # 测试相关
<template>
<button>按钮</button>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "WButton"
});
</script>
// 入口 index.ts 对应的install方法
import { App, defineComponent, createApp } from 'vue';
import Button from './src/button.vue'; // .vue后缀的类型声明在 vue-shim.d.ts中。定义了.vue后缀的组件的类型
Button.install = (app: App): void => {
// 全局注册组件
console.log('button组件注册全局');
app.component(Button.name, Button);
}
type IWithInstall = ReturnType<typeof defineComponent> & { install(app: App): void };
let _Button: IWithInstall = Button;
export default _Button;
// createApp({}).use(_Button); // use调用,需要组件提供install方法
6. 整合所有组件
lerna create will-ui
// will-ui/index.ts
import { App } from 'vue';
import Button from '@will-ui/button';
const components = [
Button
];
const install = (app: App): void => {
components.forEach(component => {
// 遍历组件,挂载到全局
app.component(component.name, component);
})
}
export default {
install // 导出install方法。createApp({}).use() 需要install方法
}
7. theme-chalk样式管理
样式遵循BEM
样式规范。
lerna create theme-chalk
// BEM规范
$namespace: 'w'; // 命名空间
$state-prefix: 'is-'; // 状态
$modifier-separator: '--'; // 元素修饰作用
$element-separator: '__'; // 元素直接的分割
// 示例:
<div class="w-xxx">
<button class="w-button--primary"></button>
<button class="is-readonly is-disabled"></button>
<div class="w-xxx__header"></div>
<div class="w-xxx__body"></div>
<div class="w-xxx__footer"></div>
</div>
yarn install
在node_modules
下生成包的软链。
二、搭建文档
1. 安装webpack及编译打包依赖
yarn add webpack webpack-cli webpack-dev-server vue-loader@next @vue/compiler-sfc -D -W
webpack-cli # webpack命令行解析工具
webpack-dev-server # 启动静态服务
vue-loader # 处理.vue文件,解析vue模板
@vue/compiler-sfc # 解析vue模板
yarn add babel-loader @babel/core @babel/preset-env @babel/preset-typescript @babel/plugin-transform-typescript babel-plugin-module-resolver url-loader file-loader html-webpack-plugin css-loader sass-loader style-loader sass -D -W
babel-loader # babel解析js语法
@babel/core # babel-loader默认会调babel-core(babel核心包)
@babel/preset-env # 将高级语法转成低级预发
@babel/preset-typescript # babel转化解析ts
@babel/plugin-transform-typescript # .vue文件中使用ts,对ts代码进行转化
babel-plugin-module-resolver # babel模块解析插件
url-loader # url-loader解析文件资源(编译成base64)
file-loader # file-loader解析文件资源(生成真实文件)
html-webpack-plugin # 调用html的插件
css-loader sass-loader style-loader sass # 处理css样式
// babel.config.js
module.exports = {
presets: [ // babel解析的预设,是反着执行的
'@babel/preset-env',
'@babel/preset-typescript'
],
overrides: [{ // .vue文件中使用了ts,对ts代码进行转化
test: /\.vue$/,
plugins: [
'@babel/transform-typescript',
]
}]
}
// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: path.resolve(__dirname, 'main.ts'),
output: {
path: path.resolve(__dirname, '../website-dist'),
filename: 'bundle.js'
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.vue']
},
module: {
rules: [
{ test: /\.(ts|js)x?$/, exclude: /node_modules/, loader: 'babel-loader' },
{ test: /\.vue$/, loader: 'vue-loader' },
{ test: /\.(svg|otf|ttf|woff|woff2|eot|gif|png)$/, loader: 'url-loader' },
{
test: /\.(scss|css)$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin(), // vue-loader 解析.vue文件
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'template.html')
})
]
}
// package.json
"scripts": {
"website-dev": "webpack serve --config ./website/webpack.config.js"
}
// main.ts
mport { createApp } from 'vue';
import App from './App.vue';
// 引入组件库
import WillUI from 'will-ui'; // 开发阶段
import 'theme-chalk/src/index.scss'; // 开发阶段引入样式
// 创建应用并使用组件库
createApp(App).use(WillUI).mount('#app');
配置好website
后,执行npm run website-dev
,启动本地服务,之后看效果就在这儿了。
三、组件库打包
1. 打包umd格式组件库
使用webpack
打包成umd
格式。
// builds/webpack.config.js
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
mode: 'production',
entry: path.resolve(__dirname, '../packages/will-ui/index.ts'),
output: {
path: path.resolve(__dirname, '../lib'),
filename: 'index.js',
libraryTarget: 'umd', // umd打包格式,支持commonjs和amd,不支持es6,可以在浏览器直接使用。
library: 'will-ui' // 全局名称
},
externals: { // 打包忽略vue,防止把vue源码打进去
vue: {
root: 'Vue', // 根下的Vue
commonjs: 'vue', // commonjs规范的(import {xxx} from 'vue')
commonjs2: 'vue'
}
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.vue']
},
module: {
rules: [
{ test: /\.(ts|js)x?$/, exclude: /node_modules/, loader: 'babel-loader' },
{ test: /\.vue$/, loader: 'vue-loader' }
]
},
plugins: [
new VueLoaderPlugin() // vue-loader 解析.vue文件
]
}
// package.json
"scripts": {
"website-dev": "webpack serve --config ./website/webpack.config.js",
"build": "webpack --config builds/webpack.config.js",
},
2. 全量打包esModel格式组件库
使用rollup
打包esmodel
格式。
yarn add rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve rollup-plugin-vue -D -W
@rollup/plugin-node-resolve # 解析第三方模块
rollup-plugin-vue # 支持vue
// builds/rollup.config.bundle.js
// 全量打包
import typescript from 'rollup-plugin-typescript2';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import path from 'path';
import vue from 'rollup-plugin-vue'
export default {
input: path.resolve(__dirname, `../packages/will-ui/index.ts`),
output: {
format: 'es',
file: `lib/index.esm.js`,
},
plugins: [
nodeResolve(), // 支持第三方模块,vue,typescript
vue({
target: 'browser'
}),
typescript({ // 默认调用tsconfig.json 帮我们生成声明文件
tsconfigOverride: {
exclude: [
'node_modules',
'website'
]
}
})
],
external(id) { // 排除vue本身
return /^vue/.test(id)
}
}
// package.json
"scripts": {
"website-dev": "webpack serve --config ./website/webpack.config.js",
"build:esm-bundle": "rollup -c ./builds/rollup.config.bundle.js"
},
3. 组件单独打包esModel格式组件库(按需加载)
// rollup.config.js
import typescript from 'rollup-plugin-typescript2';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import path from 'path';
import { getPackagesSync } from '@lerna/project';
import vue from 'rollup-plugin-vue'
// 获取package.json,找到以 @will-ui 开头的
const inputs = getPackagesSync().map(pck => pck.name).filter(name => name.includes('@will-ui'));
export default inputs.map(name => {
const pckName = name.split('@will-ui')[1] // button icon
return {
input: path.resolve(__dirname, `../packages/${pckName}/index.ts`),
output: {
format: 'es',
file: `lib/${pckName}/index.js`,
},
plugins: [
nodeResolve(),
vue({
target: 'browser'
}),
typescript({
tsconfigOverride: {
compilerOptions: { // 打包单个组件的时候不生成ts声明文件
declaration: false,
},
exclude: [
'node_modules'
]
}
})
],
external(id) { // 对vue本身 和 自己写的包 都排除掉不打包
return /^vue/.test(id) || /^@will-ui/.test(id)
},
}
})
"scripts": {
"website-dev": "webpack serve --config ./website/webpack.config.js",
"build:esm": "rollup -c ./builds/rollup.config.js"
},
4. 组件样式单独打包
yarn add gulp gulp-autoprefixer gulp-cssmin gulp-dart-sass gulp-rename -D -W
gulp-autoprefixer # 加前缀
gulp-cssmin # 压缩css
gulp-dart-sass # 处理sass
gulp-rename # 重命名
// gulp.config.js
const { series, src, dest } = require('gulp')
const sass = require('gulp-dart-sass')
const autoprefixer = require('gulp-autoprefixer')
const cssmin = require('gulp-cssmin')
function compile() { // 处理scss文件
return src('./src/*.scss')
.pipe(sass.sync())
.pipe(autoprefixer({}))
.pipe(cssmin())
.pipe(dest('./lib'))
}
function copyfont() { // 拷贝字体样式
return src('./src/fonts/**').pipe(cssmin()).pipe(dest('./lib/fonts'))
}
exports.build = series(compile, copyfont);
四、组件编写
稍微复杂一点的组件,会有流程图,帮助理解,简单组件大家看代码就懂了。
持续更新中。。。
1. checkbox、checkbox-group
checkbox:
checkbox-group:
参考资料
- Element-plus
- Monorepo-大型项目代码管理方式
- lerna官网
- Workspaces | Yarn
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!