第十三章 实现组件库按需引入功能

这篇具有很好参考价值的文章主要介绍了第十三章 实现组件库按需引入功能。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

组件库会包含几十甚至上百个组件,但是应用的时候往往只使用其中的一部分。这个时候如果全部引入到项目中,就会使输出产物体积变大。按需加载的支持是组件库中必须考虑的问题。

目前组件的按需引入会分成两个方法:

  • 经典方法:组件单独分包 + 按需导入 + babel-plugin-component ( 自动化按需引入);
  • 次时代方法:ESModule + Treeshaking + 自动按需 importunplugin-vue-components 自动化配置)。

分包与树摇(Treeshaking)

传统的解决方案就是将组件库分包导出,比如将组件库分为 ListButtonCard,用到哪个加载哪个,简单粗暴。这样写有两个弊端:

  • 需要了解软件包内部构造 例: import "ui/xxx" or import "ui/package/xxx"
  • 需要不断手工调整组件加载预注册。
// 全部导入
const SmartyUI = require("smarty-ui-vite")

// 单独导入
const Button = require("smarty-ui-vite/button")

好在后面有 babel-plugin-component,解决了需要了解软件包构造的问题。当然你需要按照约定规则导出软件包。

// 转换前
const { Button } = require("smarty-ui-vite")
// 转换后
const Button = require("smarty-ui-vite/button")

随着时代的发展,esmodule 已经成为了前端开发的主流。esmodule 带来好处是静态编译,也就是说,在编译阶段就可以判断需要导入哪些包。

这样就给 Treeshaking 提供了可能。Treeshaking 是一种通过语法分析去除无用代码的方法。目前,Treeshaking 逐渐成为了构建工具的标配,RollupVite新版本的 Webpack 都支持了这个功能。

比如:组件库只使用了 Button

import { Button } from 'smarty-ui-vite'

使用 ES 模块并且只引用了 Button,编译器会自动将其他组件的代码去掉。


实现分包导出

分包导出相当于将组件库形成无数各子软件包,软件包必须满足一下要求:

  • 文件名即组件名;
  • 独立的 package.json 定义,包含 esmumd 的入口定义;
  • 每个组件必须以 Vue 插件形式进行加载;
  • 每个软件包还需要有单独的 css 导出。
  1. 重构代码结构

首先需要在原有代码上进行重构:

  • 首先将组件目录由 【button】 改为 【Button】

特别提醒:git 提交的时候注意,默认 git 修改的时候是忽略大小写的。需要修改一下 git 配置才可以提交。

# 禁止忽略大小写
git config core.ignorecase false
  • Button 组件入口 index.ts 默认作为插件导出。

重构前:

import Button from "./Button";

// 导出 Button 组件
export default Button

重构后:

import Button from "./Button";
import { App } from "vue";

// 导出Button组件
export { Button };

// 导出Vue插件
export default {
  install(app: App) {
    app.component(Button.name, Button);
  },
};
  1. 编写分包导出脚本

默认导出方式是通过配置 vite.config.tsbuild 属性完成。但是在分包导出的时候需要每个组件都分别配置自己的配置文件,而且需要由程序自动读取组件文件夹,根据文件夹的名字遍历打包,还需要为每个子组件包配上一个 package.json 文件。

新建一个 scripts/build.ts 文件。

首先需要学会的是如何使用代码让 vite 打包。

导入 vite.config.ts中的配置,然后调用 vitebuild api 打包。

  • 修改vite.config.ts
/// <reference types="vitest" />
import { defineConfig,UserConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// 引入重构后的unocss配置
import UnoCSS from './config/unocss'

const rollupOptions = {
  external: ['vue', 'vue-router'],
  output: {
    globals: {
      vue: 'Vue',
    },
  },
}

export const config =  {

    resolve: {
      alias: {
        vue: 'vue/dist/vue.esm-bundler.js', 
      },
    },
    plugins: [
      vue(), // VUE插件
      vueJsx({}), // JSX 插件
  
      UnoCSS(), // 这是重构后的unocss配置
  
      // 添加UnoCSS插件
      // Unocss({
      //   presets: [presetUno(), presetAttributify(), presetIcons()],
      // }),
    ],
  
    // 添加库模式配置
    build: {
      rollupOptions,
      cssCodeSplit: true,   // 追加 css代码分割
      minify: "terser",
      sourcemap: true, // 输出单独的source文件
      reportCompressedSize: true, // 生成压缩大小报告
      lib: {
        entry: './src/entry.ts',
        name: 'SmartyUI',
        fileName: 'smarty-ui',
        // 导出模块格式
        formats: ['es', 'umd', 'iife'],
      },
      outDir: "./dist"
    },
    test: {
      // enable jest-like global test APIs
      globals: true,
      // simulate DOM with happy-dom
      // (requires installing happy-dom as a peer dependency)
      environment: 'happy-dom',
      // 支持tsx组件,很关键
      transformMode: {
        web: [/.[tj]sx$/]
      }
    }
  
  }

export default defineConfig(config as UserConfig)

  • 读取vite配置

文件名:scripts/build.ts

// 读取 vite 配置
import { config } from "../vite.config";
import { build, InlineConfig, defineConfig, UserConfig } from "vite";

// 全量打包
build(defineConfig(config as UserConfig) as InlineConfig);
  • 读取组件文件夹遍历组件库文件夹

文件名:scripts/build.ts

const srcDir = path.resolve(__dirname, "../src/");
  fs.readdirSync(srcDir)
    .filter((name) => {
      // 过滤文件只保留包含index.ts
      const componentDir = path.resolve(srcDir, name);
      const isDir = fs.lstatSync(componentDir).isDirectory();
      return isDir && fs.readdirSync(componentDir).includes("index.ts");
    })
    .forEach(async (name) => {
      // 文件夹遍历
     });
  • 为每个模块定制不同的编译规则
    • 导出文件夹为 dist/ <组件名>/ 例: dist/Button
    • 导出模块名为: index.es.jsindex.umd.js
    • 导出模块名为: <组件名> iffe 中绑定到全局的名字
const outDir = path.resolve(config.build.outDir, name);
  const custom = {
    lib: {
      entry: path.resolve(srcDir, name),
      name, // 导出模块名
      fileName: `index`,
      formats: [`esm`, `umd`],
    },
    outDir,
  };

  Object.assign(config.build, custom);
  await build(defineConfig(config as UserConfig) as InlineConfig);
  • 为每个子组件包定制一个自己的 package.json

最后还需要为每个子组件包定制一个自己的 package.json。因为根据 npm 软件包规则,当你 import 子组件包的时候,会根据子包中的 package.json 文件找到对应的模块。

// 读取
import Button from "smarty-ui-vite/Button"

子包的 package.json

{
      "name": "smarty-ui-vite/Button",
      "main": "index.umd.js",
      "module": "index.umd.js"
}

生成 package.json 使用模版字符串实现。

 fs.outputFile(
        path.resolve(outDir, `package.json`),
        `{
          "name": "smarty-ui-vite/${name}",
          "main": "index.umd.js",
          "module": "index.umd.js",
        }`,
        `utf-8`
      );
  • 最终代码
import fs from "fs-extra";
import path from "path";
// 读取 vite 配置
import {config} from "../vite.config";
import { build, InlineConfig, defineConfig, UserConfig } from "vite";

const buildAll = async () => {
  
    // 全量打包
    await build(defineConfig(config as UserConfig) as InlineConfig);
    // await build(defineConfig({}))
  
    const srcDir = path.resolve(__dirname, "../src/");
    fs.readdirSync(srcDir)
      .filter((name) => {
        // 只要目录不要文件,且里面包含index.ts
        const componentDir = path.resolve(srcDir, name);
        const isDir = fs.lstatSync(componentDir).isDirectory();
        return isDir && fs.readdirSync(componentDir).includes("index.ts");
      })
      .forEach(async (name) => {
        const outDir = path.resolve(config.build.outDir, name);
        const custom = {
          lib: {
            entry: path.resolve(srcDir, name),
            name, // 导出模块名
            fileName: `index`,
            formats: [`es`, `umd`],
          },
          outDir,
        };
  
        Object.assign(config.build, custom);
        await build(defineConfig(config as UserConfig) as InlineConfig);
  
        fs.outputFile(
          path.resolve(outDir, `package.json`),
          `{
            "name": "smarty-ui-vite/${name}",
            "main": "index.umd.js",
            "module": "index.umd.js"
          }`,
          `utf-8`
        );
      });
  };

  buildAll();
  • 安装需要的依赖

由于脚本是使用typescript编写的所以需要使用 esno 运行。

pnpm i esno -D

代码中还使用的fs的功能所以需要安装fs-extra

pnpm i fs-extra -D
  • package.json中添加脚本
"scripts": {
    "build": "pnpm build:components",
    "build:all": "vite build",
    "build:components": "esno ./scripts/build.ts",
    }

运行代码

pnpm build

测试按需加载

假设页面中只使用 Button 按钮,那么只调用Button子包中的 js、css 就可以了。

文件名:demo/iffe/test_button.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test button</title>
    <link rel="stylesheet" href="../../dist/Button/assets/index.cb9ba4f4.css">
    <script src="../../node_modules/vue/dist/vue.global.js"></script>
    <script src="../../dist/Button/index.iife.js"></script>
</head>
<body>
    <div id="app"></div>
    <script>
        const { createApp } = Vue
        console.log('vue', Vue)
        console.log('SmartyUI', Button)
        createApp({
            template: `
            <div style="margin-bottom:20px;">
                <SButton color="blue">主要按钮</SButton>
                <SButton color="green">绿色按钮</SButton>
                <SButton color="gray">灰色按钮</SButton>
                <SButton color="yellow">黄色按钮</SButton>
                <SButton color="red">红色按钮</SButton>
            </div>
           
            <div style="margin-bottom:20px;">
                <SButton color="blue"  icon="search">搜索按钮</SButton>
                <SButton color="green"  icon="edit">编辑按钮</SButton>
                <SButton color="gray"  icon="check">成功按钮</SButton>
                <SButton color="yellow"  icon="message">提示按钮</SButton>
                <SButton color="red"  icon="delete">删除按钮</SButton>
            </div>
            <div style="margin-bottom:20px;">
                <SButton color="blue"  icon="search"></SButton>
                <SButton color="green"  icon="edit"></SButton>
                <SButton color="gray" icon="check"></SButton>
                <SButton color="yellow"  icon="message"></SButton>
                <SButton color="red"  icon="delete"></SButton>
            </div>
        `}).use(Button.default).mount('#app')
    
    </script>
</body>
</html>
  • 启动项目
pnpm dev

浏览器查看结果

组件按需引入,搭建Vue3组件库,ui,前端,treeshaking,vite,vue文章来源地址https://www.toymoban.com/news/detail-675220.html

到了这里,关于第十三章 实现组件库按需引入功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [C国演义] 第十三章

    力扣链接 根据题目要求: 返回的数对应的下标各不相同 三个数之和等于0 不可包含重复的三元组 – – 即 顺序是不做要求的 如: [-1 0 1] 和 [0, 1, -1] 是同一个三元组 输出答案顺序不做要求 暴力解法: 排序 + 3个for循环 + 去重 — — N^3, 肯定超时 优化: 排序 + 双指针 + 去重 — —

    2024年02月08日
    浏览(39)
  • NodeJs第十三章 cookie

    假设服务器有一个接口,通过请求这个接口,可以添加一个管理员 但是,不是任何人都有权力做这种操作的 那么服务器如何知道请求接口的人是有权力的呢? 答案是:只有登录过的管理员才能做这种操作 可问题是,客户端和服务器的传输使用的是http协议,http协议是无状态

    2024年01月19日
    浏览(34)
  • 《微服务实战》 第十三章 JWT

    【项目实战】Spring boot整合JWT、Vue案例展示用户鉴权 【微服务实战】JWT JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。 基于JSON的开发标准 用户信息加密到token里,服务器不保存任何用户信息 在传统的用户登录认证中,因为http是无状态的,所以都是采用session方式

    2024年02月06日
    浏览(71)
  • 第十三章_Redis中的BigKey

    MoreKey案例 大批量往redis里面插入2000W测试数据key  Linux Bash下面执行,插入100W # 生成100W条redis批量设置kv的语句(key=kn,value=vn)写入到/tmp目录下的redisTest.txt文件中 for((i=1;i=100*10000;i++)); do echo \\\"set k$i v$i\\\" /tmp/redisTest.txt ;done; 通过redis提供的管道--pipe命令插入100W大批量数据 结合自己

    2024年02月03日
    浏览(41)
  • 精读《图解密码技术》——第十三章 PGP

      PGP是一款由个人编写的密码软件,PGP是为了保护处于极端状况下的人们的隐私而开发的,如果这些人的信息被窃听,那么可能是性命攸关的大事件。   OpenPGP是对密文和数字签名格式进行定义的标准规格。   GNU Privacy Guard ( GnuPG、GPG)是一款基于OpenPGP标准开发的密码学

    2024年02月05日
    浏览(52)
  • 第十三章,枚举与泛型例题

    例题1 结果   例题2 结果   例题3 结果     例题4 结果 例题5  结果 例题6  结果 例题7  结果 例题8  结果

    2024年02月06日
    浏览(47)
  • 第十三章 Unity 移动和旋转(上)

    移动和旋转是游戏对象最频繁地操作。我们上个章节简单介绍了Cube的移动和旋转。移动是修改transform的position属性,旋转是修改transform的eulerAngles(欧拉角)属性,两者属性值均可以使用Vector3向量来实现。需要大家注意的是,transform.forward和Vector3.forward的区别(参考坐标系是

    2024年02月05日
    浏览(51)
  • 第十三章 opengl之模型(导入3D模型)

    使用Assimp并创建实际的加载和转换代码。Model类结构如下: Model类包含一个Mesh对象的vector,构造器参数需要一个文件路径。 构造器通过loadModel来加载文件。私有函数将会处理Assimp导入过程中的一部分,私有函数还存储了 文件路径的目录,加载纹理时会用到。 Draw函数的作用:

    2024年02月05日
    浏览(45)
  • 《c++ primer笔记》第十三章 拷贝控制

    1.1拷贝构造函数 ​ 如果一个构造函数的第一个参数是自身类类型的 引用 ,且任何额外参数都由默认值,则此构造函数成为拷贝构造函数。 拷贝构造函数在某些情况下会被隐式地使用,所以不能定义为 expicit 。 合成拷贝构造函数 ​ 合成某某函数 一般出现在我们没定义该函

    2023年04月25日
    浏览(44)
  • Vue3——第十三章(插槽 Slots)

    这里有一个 FancyButton 组件,可以像这样使用: 而 FancyButton 的模板是这样的: slot 元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。 最终渲染出的 DOM 是这样: 通过使用插槽, FancyButton 仅负责渲染外层的 button (以及相应的样式),而

    2024年02月07日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包