vite + vue3 + storybook + ts 搭建组件库记录

news2024/11/15 9:57:58

目标

  • 只按需引入,不依赖babel-import-plugin 插件。
  • 第三方依赖都不打包。
  • 用原生fetch请求数据。
  • 仅支持esmodule。配置package.json type:"module"

搭建

根据storybook 官网文档,需要在已有的项目中运行 

npx storybook@latest init

 也就是事先需要通过vite创建一个项目。

npm create vite@latest

之后再运行storybook 的命令,storybook会自动分析使用的构建工具,安装好需要的依赖,并自动新增文件。

storybook 增加了/src/stories目录,用于存放组件。

默认情况下,/src/stories 中放着 .vue, .stories, .css 文件,由于我希望把组件源代码单独放在一个文件夹下

目录预设

因此在项目根目录 增加 /packages 目录,下面存放以组件名称命名的文件夹。以每个文件夹为单位表示为一个组件。

/src/stories 中.stories 文件引用/packages 目录下的.vue 用于生成storybook演示文档。

对于vite 的普通打包模式下,入口必须为index.html,因此需要使用vite 支持的lib模式打包。

lib模式下,对于图片等静态资源,处理为base64内联到代码中。且不支持配置

base64的问题在于增大js体积,且不利于相同图片的复用。这块待研究。

构建vue组件

直接使用vite build 命令,会使用项目根目录下的vite.config.js 对指定的入口进行构建。构建产物默认输出到dist目录下。

lib模式下,构建产物有

  • index.js
  • style.css

对于一个vue组件来说,这种格式正式我想要的。

而/package 目录下面会有好多组件,因此需要借助vite 的JavsScript API 进行循环

vite JS API 构建 /packages 目录

根据官网描述,使用下面js代码, 可以通过js启动vite 打包。

import { build } from 'vite';
build({
    //... vite config
})

这里的 build() 方法传入的config对象,会和项目下的 vite.config.js 合并

所以将一些公共的配置可以写在 vite.config.js下

vite.config.js

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import dts from 'vite-plugin-dts';

// https://vitejs.dev/config/
export default defineConfig({
  build: {
    rollupOptions: {
      external: ['vue'], // 排除三方包
    },
  },
  plugins: [
    vue(),
    vueJsx(),
    dts({
      outputDir: './lib',
      entryRoot: './packages',
    }),
  ],
});

 build.js 主要的构建方法

/**
 * 构建一个组件
 * @param {string} compName 组件名称,对应packages目录下的文件夹名称
 */
async function buildAComponent(compName) {
  const entry = path.join(packagesDir, compName, 'index.ts');
  const out = path.join(outDir, compName);
  await build({
    build: {
      outDir: out,
      lib: {
        entry: [entry],
        formats: ['es'],
      },
    },
  });
}

 这个是构建一个组件的方法,因此,我只要通过node 的 fs 模块读取并分析/packages 路径下的文件夹,遍历出来构建即可。

build.js代码      

import { readdirSync, writeFileSync } from 'fs';
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import { build } from 'vite';

const __dirname = dirname(fileURLToPath(import.meta.url));
const packagesDir = path.join(__dirname, './packages');
const outDir = path.join(__dirname, './lib');
async function main() {
  const packages = readdirSync(packagesDir);
  let indexFileContent = '';
  const exportNames = [];
  const promise = [];
  for (const folderName of packages) {
    // const stats = lstatSync(path.join(packagesDir, compName));
    // if (stats.isDirectory()) {
    //   // 如果是目录,则构建
    // }
    const firstLetter = folderName[0];
    const isFirstLetterUpperCase = /[A-Z]/.test(firstLetter);
    if (isFirstLetterUpperCase && folderName.indexOf('.ts') === -1) {
      promise.push(buildAComponent(folderName));
      indexFileContent += `import { ${folderName} } from './${folderName}/index.js';\n`;
      exportNames.push(folderName);
    }
  }
  await Promise.all(promise);
  indexFileContent += `export { ${exportNames.join(', ')} };`;
  writeFileSync(path.join(outDir, 'index.js'), indexFileContent);
  console.log('created: lib/index.js');

}
main();

/**
 * 构建一个组件
 * @param {string} compName 组件名称,对应packages目录下的文件夹名称
 */
async function buildAComponent(compName) {
  const entry = path.join(packagesDir, compName, 'index.ts');
  const out = path.join(outDir, compName);
  await build({
    build: {
      outDir: out,
      lib: {
        entry: [entry],
        formats: ['es'],
      },
    },
  });
}

 main 函数中判断文件夹名称前面是大写字母开头的才当作组件来处理。

构建结果

  • /package/Button
    • index.ts
    • style.less
    • Button.vue

这一个目录入口为index.ts 构建完成后,输出到lib

  • /lib/Button
    • index.js
    • style.css

至此,主要构建完成了

库入口

由于一个npm库需要有一个默认的入口js,因此需要在/lib 目录下面增加一个index.js 用于导入lib 下面所有组件,并导出。这部分代码在上面 build.js 中已经体现。 

/lib/index.js

export { Button } from './Button/index.ts';

package.json 增加配置

{
  "main": "lib/index.js",
  "module": "lib/index.js",
  "files": [
    "lib",
    "packages"
  ],
  //...
}

d.ts生成

使用vite-plugin-dts 插件

import dts from 'vite-plugin-dts';

// ... 

plugins:[
    // vue(),
    // vueJsx(),
     dts({
      outputDir: './lib',
      entryRoot: './packages',
    }),
]

构建dts速度有点慢 。

这样配置,会在lib/Button/ 下面增加d.ts 文件了。

与库的默认入口文件一样,需要指定默认declare 文件,package.json types 指定为 /packages/main.ts。(这里暂时指定.ts 文件,可能在一些构建工具中不识别,因为那些工具只识别d.ts 文件)

实现自动引入

一个组件默认的构建结果为index.js, style.css

鉴于使用的情况下引入 import { Button } from 'xxx'; 之外还要 import 'xxx/lib/Button/style.css';

使用babel-import-plugin 可以解决按需引入样式问题。

由于我的目标是不使用babel-import-pluing 因此,完成这个效果,只需要在/lib/Button/index.js 文件的最上方引入/lib/Button/style.css即可。

方案

  • 使用node fs读写文件。
  • 借助vite plugin

使用vite plugin 实现

因为不想用node

由于vite基于rollup,根据rollup 官网文档,我们可以在插件的 generateBundle 钩子中在生成产物前操作文件内容。

代码

/**
 * 在产物js上导入css
 */
function () {
  return {
    name: 'auto-import-style',
    generateBundle(options, bundle) {
      bundle['index.js'].code = 'import "./style.css";\n' + bundle['index.js'].code;
    },
  };
},

npm上也有类似的插件 vite-plugin-libcss

构建vue组件要点

不能将vue依赖打包进代码中,否则会导致组件无法使用。

打包组件的产物js中,最上方应该为 import {} from 'vue'; 这样的代码。

通过配置vite.config.js 下的rollupOptions.external即可:

build: {
    rollupOptions: {
      external: ['vue'], 
      // output: {
      //   globals: {
      //     vue: 'Vue', // umd需要
      //   },
      // },
    },
  },

 这里注释掉rollupOptions.output.globals 原因是默认构建lib会输入umd 和 es 模块的文件。我构建时会指定 build.lib.formats: ['es'] 就不需要了。

增量构建方案预设

由于现在我看使用vite-plugin-dts 生成d.ts 文件使用的时间过长。先制定一下增量构建的方案。

鉴于在正常开发过程中,始终用master分支作为正式分支。

因此在开发分支中,可以借助git 来对比当前开发分支与master分支的区别

使用node 的child_process 执行命令

git diff HEAD master --stat

可检查当前分支,对比master,哪些文件有变化。

得到控制台输出后,分析产物,确定package.json 中哪些组件有变动。以此仅构建变更过的组件。

vite可能会覆盖lib目录。构建前先保存一版lib目录?还是有官方配置。暂不研究。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/564523.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电源原理分析、波形分析、应力计算、回路布局

1、Flyback变换器工作模态分析; 2、Flyback关键波形分析; 3、RCD吸收电路设计及开关管应力; 4、从噪音回路看布线要点。 5、基于实际项目,原创反激开关电源视频教程曝光 Flyback 变换器模态分析 ​ ON:开关管导通&…

516. 最长回文子序列

516. 最长回文子序列 C代码&#xff1a;dp int longestPalindromeSubseq(char* s) {int n strlen(s);int dp[n][n];memset(dp, 0, sizeof(dp));for (int i 0; i < n; i) {dp[i][i] 1;}for (int i n - 1; i > 0; i--) {for (int j i 1; j < n; j) {if (s[i] s[…

2023最新CTF入门的正确姿势

前言 随着网络安全意识的增强&#xff0c;越来越多的人开始涉足网络安全领域&#xff0c;其中CTF比赛成为了重要的学习和竞赛平台。本人从事网络安全工作多年&#xff0c;也参加过大大小小的CTF比赛&#xff0c;今天就来详细介绍CTF的流程&#xff0c;以及需要具备的知识&…

【前端相关】elementui使用el-upload组件实现自定义上传

elementui使用el-upload组件实现自定义上传 一、问题描述二、实现方式三、实现步骤3.1 方式一&#xff1a;选择后自动上传3.2 方式二&#xff1a;选择图片后手动上传3.3 拓展&#xff1a;上传文件夹 四、服务器相关接口 一、问题描述 elmentui 中的upload默认的提交行为是通过…

[C++]哈希表实现,unordered_map\set封装

目录​​​​​​​ 前言&#xff1a; 1 哈希 1.1 为什么有哈希 1.2 哈希结构 1.3 哈希冲突 2 闭散列 2.1 闭散列结点结构和位置状态表示 2.2 哈希类结构 2.3 插入 2.4 查找 2.5 删除 3 开散列 3.1 哈希表结点结构 3.2 哈希表结构 3.3 插入 3.4 查找、删除 3.5…

Ubuntu/Debian/CentOS搭建Socks5代理一键脚本

说明 Socks5属于明文代理&#xff0c;不要用于科学上网&#xff0c;否则会被阻断端口&#xff0c;可用于正常的跳板使用&#xff1b; 比如SSH转发加速国外VPS的连接速度&#xff0c;特别是一些延迟高或者丢包高的VPS&#xff1b; 使用Socks5转发后SSH就可以快速稳定的连接了&a…

Java中的Replace和ReplaceAll的区别

replace和replaceAll是都是String类中提供的两种用于字符/字符串替换的方法&#xff0c;从字面意思理解&#xff0c;replace表示替换单个匹配项&#xff0c;而replaceAll表示替换所有匹配项&#xff1b;实际上并不是这样子的,replace和replaceAll都是替换所有匹配项,replace是非…

链式二叉树OJ题思路分享

⏩博主CSDN主页:杭电码农-NEO⏩   ⏩专栏分类:刷题分享⏪   ⏩代码仓库:NEO的学习日记⏩   &#x1f339;关注我&#x1faf5;带你刷更多C语言和数据结构的题!   &#x1f51d;&#x1f51d; 链式二叉树OJ题分享 1. 前言&#x1f6a9;2. 单值二叉树&#x1f6a9;2.1 审题…

macOS Ventura 13.5beta OpenCore黑苹果双引导分区原版镜像

镜像特点&#xff08;原文地址&#xff1a;http://www.imacosx.cn/113700.html&#xff0c;转载请注明出处&#xff09; 完全由黑果魏叔官方制作&#xff0c;针对各种机型进行默认配置&#xff0c;让黑苹果安装不再困难。系统镜像设置为双引导分区&#xff0c;全面去除clover引…

六级备考25天|CET-6|听力第五讲|演讲满分技巧|分值最高|2022年6月考题16-18题|18:15~19:00

14.2分一题 抓重点 目录 2. 听力原文复现 问题16 问题17 问题18 3. 听力原文重点词汇 问题16 问题17 问题18 2. 听力原文复现 问题16 What does the speaker say about most American hospitals&#xff1f; visit brief useless Dont challenge with the authority unle…

HEVC熵编码核心点介绍

熵编码基本原理 消息与信息 把客观物质运动和主观思维的活动状态表达出来就成为了消息&#xff1b;消息中包含信息&#xff0c;是信息的载体&#xff1b;因此&#xff0c;信息与消息既有区别又有联系的&#xff1b; 获取信息的过程就是一个消除或部分消除不确定性的过程&…

Linux网络基础-4

在之前的网络基础博客中&#xff0c;我们对网络进行了概要解释&#xff0c;了解了应用层和传输层的知名协议。接下来我们来对网络层的典型协议进行解析。 目录 1.网络层协议 2.IP协议 2.1协议格式 2.2地址管理 2.3特殊网络 2.3.1私网的组建 2.3.2特殊IP地址 2.4路由选…

ACP(MaxCompute篇)-Tunnel上传下载数据

概述 相关命令 odpscmd里面包含了tunnel test11_123>tunnel help; Usage: tunnel <subcommand> [options] [args] Type tunnel help <subcommand> for help on a specific subcommand.Available subcommands:upload (u)download (d)resume (r)show (s)purge …

探索SpringBoot 3.1的惊艳新特性

一、介绍 1.1 新特性概述 经过半年的沉淀 Spring Boot 3.1于2023年5月18日正式发布了&#xff0c;带来了许多令人兴奋的新特性和改进。本篇博客将详细介绍Spring Boot 3.1的新特性、升级说明以及核心功能的改进。 同时&#xff0c;2.6.x 版本线已经停止维护了&#xff0c;最新…

《消息队列高手课》课程笔记(二)

消息模型&#xff1a;主题和队列有什么区别&#xff1f; 两类消息模型 早期的消息队列&#xff0c;就是按照“队列”的数据结构来设计的。 生产者&#xff08;Producer&#xff09;发消息就是入队操作&#xff0c;消费者&#xff08;Consumer&#xff09;收消息就是出队也就是…

数字逻辑习题

第一次作业 第二次作业 第三次作业 卡诺图最小项 计算模数M考察74LS161的特点 计数器数据选择器 在A和C加上非门即可 求计数器的模数M 该计数器是环形计数器&#xff08;循环右移&#xff09;&#xff0c;模数根据初始状态而定 由n个移位寄存器构成的环形计数器最大可能模值为n…

韩流在元宇宙中崛起,感受韩国文化的魅力!

大获成功&#xff01;第一波 K-verse 土地全部售罄&#xff01; 多家领先韩国文化公司&#xff0c;包括 SMBM、CUBE 娱乐、LINE Studio、Gravity、乐天世界、K League 等进驻 The Sandbox&#xff0c;并在第一波 K-verse 土地销售活动上发布了邻近这些公司的 LAND。 所有土地全…

哈希表(散列表)详解

&#x1f495;**今天的每一秒都是珍贵的&#xff0c;因为它永远不会再次出现。**&#x1f495; &#x1f43c;作者&#xff1a;不能再留遗憾了&#x1f43c; &#x1f386;专栏&#xff1a;Java学习&#x1f386; &#x1f697;本文章主要内容&#xff1a;深入理解哈希表&#…

npm init和npm create、npm create vite什么意思

npm init 和npm create 之前在用npm时&#xff0c;一直都是npm init来初始化一个项目并生成package.json文件。 但是&#xff0c;今天在看vite的官方文档时&#xff0c;vite上说创建一个vite项目的命令是 npm create vitelatest之前一直没有用过npm create这个命令&#xff…

sql ---- 查询两个日期间隔的天数:Datediff(a,b)

197. 上升的温度 表&#xff1a; Weather ------------------------ | Column Name | Type | ------------------------ | id | int | | recordDate | date | | temperature | int | ------------------------ id 是这个表的主键 该表包含特定…