前端私有化部署方案_前端开发框架

(68) 2024-08-09 21:01:01

前言

本文的代码部分基于vue2 + ts ,最终的文件目录如下
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第1张

一、让vue能够正确use组件

1.1 准备工作

首先用cli,根据自己的需求创建一个项目,然后根据自己习惯或团队的规范可以做一些项目初始化配置,比如husky和eslint等等。

第一步:把src文件夹重命名为examples,然后修改vue.config.js文件,把构建入口改为examples路径下,改完执行下serve命令,看看开发环境还能不能正常启动
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第2张
第二步:创建packages文件夹,存放组件文件
第三步:创建src文件夹,然后在该文件夹下创建index.ts文件,该文件为组件库打包的入口文件。

1.2 简单介绍下vue是怎么把外部组件加载进来的

还记得我们用elementui的时候,需要在main.ts文件中使用下面的代码吗

// 整体引入 Vue.use(ElementUI); // 部分引入 Vue.use(Pagination); 

vue的 官方文档 是这么写的
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第3张

如果我们想让插件可以正确被vue.use加载,插件就必须暴露一个install方法,在使用时,通过调用Vue.use(插件),然后插件的install方法就会被调用,我们就可以拿到传入的Vue实例,再然后就可以对这个Vue实例做各种骚操作,实现各种功能。

想更深入了解的推荐去网上搜一下Vue.use和install的实现,有很多优秀的博客。

1.3 单个导入

准备步骤和知识铺垫完成后,可以来搞组件了,我的组件文件目录是这样的
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第4张
如果你不喜欢像我这样把文件拆分开,可以按正常的写法,src文件放一个.vue文件和单元测试文件就够了,另外单元测试文件你可以也拿出来按默认的放到单独的tests文件夹内。

index文件是必须的,注意层级,index文件跟src文件夹属于同级。

组件没什么好讲的,就正常组件的写法,关键点在于index.ts文件,还记得我们上面说的必须要暴露一个install方法吗

import OsPagination from './src/os-pagination.vue'; (OsPagination as any).install = (Vue: any): void => { 
    Vue.component((OsPagination as any).extendOptions.name, OsPagination); }; export default OsPagination; 

这里大量用了any类型,是因为我偷懒了,懒得补全Vue的类型…

踩坑点: 这里有个坑,如果你的组件库是用 class component 的写法,这里就必须用extendOptions.name 来指定组件的标签名,不然该组件就无法被使用,控制台直接报这个标签没有被注册(直接.name拿到的是undefined,导致组件没有被注册进去),正常使用vue的同学直接.name即可。

 Vue.component((OsPagination as any).extendOptions.name, OsPagination); 

现在我们通过为组件暴露install方法,达到了让vue能正常使用我们封装的组件的成果。

接着找到我们之前新建的src文件夹下的index文件,添加如下代码

import OsPagination from '../packages/os-pagination'; export { 
    OsPagination, }; 

组件使用方式:
在你要使用该组件的项目中

// main.ts 文件 import { 
    OsPagination } from 'os-ui'; Vue.use(OsPagination); 

目前的暴露方式是单独为每个组件提供一份暴露install的代码,使用的时候也是一个一个通过Vue.use来使用的。

1.4 整体导入

下面来介绍一下统一暴露,使用时整体引入的实现方式

修改 src/index.ts 文件,我们循环全局注册组件即可

import OsPagination from '../packages/os-pagination'; // 存储组件列表  const components = [OsPagination]; // 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,则所有的组件都将被注册 const install: any = function (Vue: any, opts: any): void { 
    // 判断是否安装 if (install.installed) return; // 遍历注册全局组件 components.map((component: any) => Vue.component(component.extendOptions.name, component)); }; // 判断是否是直接引入文件 if (typeof window !== 'undefined' && window.Vue) { 
    install(window.Vue); } export { 
    install as OSUI, // 以下是单个导出的组件 OsPagination }; 

使用方式

import { 
    OSUI } from 'os-ui'; Vue.use(OSUI); 

二、打包

2.1 使用cli提供的打包类库命令

官方文档 是这么写的
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第5张
所以我们直接根据官方说明在package.json的scripts里,加一条构建库的命令

 "lib": "vue-cli-service build --target lib --name os-ui ./src/index.ts", 

然后执行yarn lib或者npm run lib即可。

用这种方式打包的优势在于省心省力,因为vue cli内部已经针对构建lib的webpack做好了配置,属于傻瓜式操作。

但对应缺陷就是无法加入自己的需求,cli并没有像构建应用那样,为我们打包提供针对构建类库的链式操作,这也就意味着我们不能加入自己的打包需求或者针对打包做优化。

比如我需要打esm的包,我需要组件库按需引入,vue cli并不能支持。这个在后面按需引入的那一节会再谈到。

2.2 使用rollup打包

具体可以参考这篇文章
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第6张
选择rollup,主要原因是 更小支持输出esm支持tree-shaking,下面是我的rollup配置文件

// rollup.config.js import { 
    terser } from 'rollup-plugin-terser'; import { 
    nodeResolve } from '@rollup/plugin-node-resolve'; import vue from 'rollup-plugin-vue'; import scss from 'rollup-plugin-scss'; import babel from 'rollup-plugin-babel'; import typescript from 'rollup-plugin-typescript2'; import commonjs from '@rollup/plugin-commonjs'; import replace from 'rollup-plugin-replace'; export default { 
    input: 'src/index.ts', // external: ['vue', 'lodash-es', './lang/zh'], external: ['vue', 'lodash-es'], output: [ { 
    file: 'dist/os-ui.esm.js', format: 'esm', }, { 
    file: 'dist/os-ui.umd.js', format: 'umd', name: 'os-ui', globals: { 
    vue: 'Vue', 'lodash-es': 'lodashEs', }, }, ], plugins: [ nodeResolve({ 
    extensions: ['.js', '.ts'], }), commonjs(), replace({ 
    'process.env.NODE_ENV': JSON.stringify('production'), }), typescript(), vue(), babel({ 
    extensions: ['.vue', '.ts', '.js', '.tsx', '.jsx'], exclude: 'node_modules/**', }), scss(), terser(), ], }; 

配置文件中输出esm和umd两种模块文件,相当于在一个包内同时发布了两种模块规范的版本。具体使用哪一种,交由使用组件库的应用打包时自动判断,我们只需要在package.json文件中配置main 和 module属性即可。
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第7张
当打包工具遇到我们的模块时:

  • 如果它已经支持 package.module 字段则会优先使用 es6 模块规范的版本,这样可以启用 tree-shaking 机制。
  • 如果它还不识别 package.module 字段则会使用我们已经编译成 common js 规范的版本,也不会阻碍打包流程。
    具体的使用优先级可以参考这篇文章

三、如何为组件库提供一份类型文件

3.1 编写类型文件

一个好的组件库,必然不能缺少一份类型文件,详细的类型文件,可以帮助使用者快速上手,参考element ui 的类型文件
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第8张

在根目录新建一个types文件夹,下面新建一个对应组件文件名的文件,但是文件的后缀名为d.ts(d.ts结尾的文件,会被认为是类型文件,在ts编译时会被排除在外),以文中的os-pagination为例,新建文件os-pagination.d.ts。
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第9张
下面是我的类型文件,类型定义中包含了一些public 属性,除此之外,如果组件内需要暴露一些方法供外部使用,也需要在类型定义中体现出来,详细的可以参考element ui

export interface Paging { 
    currentPage: number; showCount: number; } export declare class OsPagination { 
    /** * Prop 分页对象 * required */ public paging: Paging; /** * Prop 数据总条数 * required */ public total: number; /** * Prop 分页尺码可选项配置 * 默认为 [50, 100, 200],如果paging.showCount不为该配置的首位,paging.showCount会自动加入到该数组的头部 */ public pageSizeOption: Array<number>; } 

3.2 让编辑器可以识别类型文件,提供引用提示

现在类型文件有了,我们怎么才能在实际项目中使用呢

  1. 首先在types文件夹下新建index.d.ts文件,统一导出类型
// index.d.ts import OsPagination from './os-pagination'; import OsTable from './os-table'; .... export { 
    OsPagination, OsTable ...}; 
  1. 然后修改package.json文件的files字段和typings字段,
    前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第10张
  • files支持配置一个数组,作用是指定发布包时,要包含哪些文件,这里我们选择包含disttypes文件夹。

  • typings的作用是指定包的类型文件,当别人install了你的包之后,编辑器会根据typings指定的类型文件,提供智能类型提示。

四、使用verdaccio搭建npm私服,发布组件库

4.1 思路

我们的组件库或者其他公共的包可能包含一些业务信息,所以组件库肯定不能放到npm仓库,那么就要搭建一个私服。
搭建npm私服的方案有挺多的,比如nexus或者sinopia(仓库已经很多年不维护了)等等,不过我最终选择了verdaccio,这里是verdacio官方文档。
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第11张

4.2 安装和配置

1、首先准备一台服务器,安装好node
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第12张
2、安装

npm install -g verdaccio 

或者

yarn global add verdaccio 

3、执行verdaccio,启动服务

verdaccio 

前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第13张
这里要注意第一行打印的信息,这个yaml文件就是verdaccio的配置文件,后面我们需要修改该文件进行相关配置。

 warn --- config file - /root/.config/verdaccio/config.yaml 

另外建议在服务器装一个pm2(进程守护),使用pm2启动verdaccio,或者使用其他进程守护方案

4、启动成功后,直接在浏览器输入服务器的ip地址 + 最后一行打印出的端口号,看到这样的页面就说明安装成功了。如果访问不了可能是因为服务器防火墙没关,可以检查下防火墙。
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第14张

5、修改配置文件,如果是linux服务器,直接执行

vi /root/.config/verdaccio/config.yaml 

vi 后面的地址就是启动verdaccio服务时,第一行打印出的地址,下面是具体的配置文件内容,verdaccio的作者对配置文件做了很详细的注释

# # This is the default config file. It allows all users to do anything, # so don't use it on production systems. # # Look here for more config file examples: # https://github.com/verdaccio/verdaccio/tree/master/conf # # path to a directory with all packages storage: ./storage # path to a directory with plugins to include plugins: ./plugins # 是否开启检索功能 search: true web: # 私有仓库的标题 title: Os-Component # comment out to disable gravatar support # gravatar: true # by default packages are ordercer ascendant (asc|desc) # sort_packages: asc # convert your UI to the dark side # darkMode: true # logo: http://somedomain/somelogo.png # favicon: http://somedomain/favicon.ico | /path/favicon.ico # translate your registry, api i18n not available yet # i18n: # list of the available translations https://github.com/verdaccio/ui/tree/master/i18n/translations # web: zh-CN auth: htpasswd: file: ./htpasswd # 允许注册的用户最大数量, 默认值是 "+inf",即不限制 # 可以将此值设置为-1 以禁用新用户注册。此时npm adduser被禁用 max_users: -1 # 如果你要安装的包不在该私有库中,会自动去url配置的地址中查找 uplinks: npmjs: url: https://registry.npmjs.org/ packages: '@*/*': # scoped packages access: $all publish: $authenticated unpublish: $authenticated proxy: npmjs '**': # 设为$all为允许所有用户(包括未经身份验证的用户)访问和发布包 # 您可以指定用户名/组名(取决于您的身份验证插件) # 和三个关键字:“$all”、“$anonymous”、“$authenticated” # $all 表示不限制,任何人可访问;$anonymous 表示未经认证的可访问(其实等同于all);$authenticated 表示只有经过认证的用户可访问 access: $all # 设置允许发包的权限 publish: $authenticated # 设置删除包的权限 unpublish: $authenticated # if package is not available locally, proxy requests to 'npmjs' registry proxy: npmjs # You can specify HTTP/1.1 server keep alive timeout in seconds for incoming connections. # A value of 0 makes the http server behave similarly to Node.js versions prior to 8.0.0, which did not have a keep-alive timeout. # WORKAROUND: Through given configuration you can workaround following issue https://github.com/verdaccio/verdaccio/issues/301. Set to 0 in case 60 is not enough. server: keepAliveTimeout: 60 # 服务启动时的端口配置 listen: 0.0.0.0:8080 middlewares: audit: enabled: true # log settings logs: { 
    type: stdout, format: pretty, level: http } #experiments: # # support for npm token command # token: false # # disable writing body size to logs, read more on ticket 1912 # bytesin_off: false # # enable tarball URL redirect for hosting tarball with a different server, the tarball_url_redirect can be a template string # tarball_url_redirect: 'https://mycdn.com/verdaccio/${packageName}/${filename}' # # the tarball_url_redirect can be a function, takes packageName and filename and returns the url, when working with a js configuration file # tarball_url_redirect(packageName, filename) { 
    # const signedUrl = // generate a signed url # return signedUrl; # } # This affect the web and api (not developed yet) #i18n: #web: en-US 

6、关于权限配置
建议把 max_users 设为 -1,禁止注册用户,一般情况下都应该由管理员派发账号,禁用后再添加用户会直接报错
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第15张

禁用后我们可以通过这个网站 添加账号密码,然后分发给对应的人
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第16张
把生成的密码复制出来,添加到htpasswd文件中即可
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第17张
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第18张
verdaccio 还支持和其他更多的权限配置方式,有需求的可以去网上找下资料。

4.3 发布

1、切换npm源到我们私有的仓库地址

你可能会好奇,我切换了源之后,要装其他包怎么办,私有仓库又没有这些包!其实不是这样的,前面的verdaccio配置文件里有一个配置项,可以配置一个或多个其他的源。当我们私有仓库里找不到包时,会自动去配置的其他源里面找。

推荐安装nrm,管理npm registry,

npm install -g nrm // 添加自定义的源 源就是启动verdaccio时打印出来的地址 nrm add os-ui http://xxxx:8080/ // 查看所有可用的源 nrm ls // 切换源到我们的私有仓库 nrm use os-ui 

2、package.json文件

前面的步骤已经介绍了关于package.json需要修改的地方,这里贴一下我的package.json文件,mainmodule一定不能配置错,不然会导致组件库安装后无法使用。

 "name": "这里是组件库的名字", "version": "0.1.30", "private": false, "description": "这里写你组件库的描述", // umd入口 "main": "./dist/os-ui.umd.js", // esm入口 "module": "./dist/os-ui.esm.js", // 关键字,方便搜索 "keywords": [ "vue", "typescript", "os-ui", "osui", "element-ui" ], // 发布时包含哪些文件夹 "files": [ "dist", "types", "src/local" ], // 类型文件 "typings": "./types/index.d.ts", // 作者 "author": "james", 

3、登录到我们的私有仓库,发布包

一定要先用nrm,把源切换到我们的私有仓库

// 执行完login后,输入之前加入到htpasswd文件内的账户登录 npm login // 更新版本号 npm version patch // 发布包 npm publish 

发布成功后,再访问私有仓库地址,就会发现自己的包已经发上去了。

前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第19张
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第20张
4、新建一个用于测试的项目,就像安装elementui那样,直接npm install 你的组件库,就可以安装使用了。

5、具体使用方式:

// main.ts import { 
    OsPagination, OSUI } from 'os-ui'; // 按需引入 Vue.use(OsPagination); // 整体引入 Vue.use(OSUI); 

五、组件库国际化问题

5.1 前言

关于组件库国际化,有下面几点要思考的问题

  1. 组件库的国际化方案和实际项目中的方案可能不同,比如组件库为了更轻,自己简单封装了一份国际化实现,而项目则选择了vue-i18n,或者其他国际化插件、或者干脆也自己封装了一份实现,那怎么才能保证在语言环境切换时,项目和组件库的文案都能及时响应变更呢。
  2. 有时组件库内的组件,会接收实际项目使用时传进来的国际化文案key,在组件库内做翻译的情况,比如我封装的基于el-table的 table组件。这时组件库内又没有你项目里那份国际化文件,肯定会导致文案翻译失败的。
    前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第21张

5.2 实现思路

5.2.1

上面那两个问题,对于第一个,我可以让组件库暴露一个方法,接收一个语言环境的参数,去动态改变组件库当前的语言环境。在实际项目中,在切换语言后,动态调用这个方法。

对于第二个问题,必须要做到合并组件库和项目两者的语言文件,在解决第一个问题的基础上,我们在捕捉到语言环境变更时,把本地项目的语言文件传入到组件库中去,在组件库中合并文件。

5.2.2

在有了思路之后我突然又想到可以再去扒一下饿了么ui的实现,看看有没有更好的实现方式。
看了饿了么ui源码后,我发现思路有相似之处,但又不完全一致。饿了么ui是这么玩的:

你可以使用任何i18n插件,只需要传入项目本地使用插件具体的翻译方法即可,这个方法传入进去之后,会替换掉饿了么ui默认的国际化实现方法,从而达到统一国际化的目的(除此之外你还需要手动把饿了么ui内部的翻译文件与项目中的合并一下。)
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第22张

除此之外你也可以使用vue-i18n,这里他不是暴露一个方法去修改语言环境,他是通过vue.config.lang去设置的,然后把组件库本身的语言文件与项目的合并即可,他直接用了vue-i18n提供的方法去实现。
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第23张
当前你项目中如果不需要进行国际化,也不影响,饿了么ui自身也实现了一套国际化方案,他默认语言环境中文,你想用英文直接设置一下即可

下面是饿了么ui的部分源码
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第24张

综上所述,我最终选择抄一下饿了么ui的解决方案,毕竟有源码可搬,不搬白不搬

5.3 具体实现

在src文件夹下新建这些文件
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第25张
lang文件夹下是文案的翻译文件,没什么好说的,形如下图
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第26张
src / local / format.ts 该文件是组件库本身国际化方案的核心实现

import { 
    hasOwn } from '../utils'; const RE_NARGS = /(%|)\{([0-9a-zA-Z_]+)\}/g; /** * String format template * - Inspired: * https://github.com/Matt-Esch/string-template/index.js */ // eslint-disable-next-line @typescript-eslint/no-unused-vars export default function (Vue: any): any { 
    /** * template * * @param {String} string * @param {Array} ...args * @return {String} */ function template(string: string, ...args: any): any { 
    if (args.length === 1 && typeof args[0] === 'object') { 
    // eslint-disable-next-line no-param-reassign args = args[0]; } if (!args || !args.hasOwnProperty) { 
    // eslint-disable-next-line no-param-reassign args = { 
   }; } // eslint-disable-next-line max-params return string.replace(RE_NARGS, (match, prefix, i, index) => { 
    let result; if (string[index - 1] === '{' && string[index + match.length] === '}') { 
    return i; } else { 
    result = hasOwn(args, i) ? args[i] : null; if (result === null || result === undefined) { 
    return ''; } return result; } }); } return template; } // 关于hasOwn方法 export function hasOwn(obj: any, key: any): any { 
    const hasOwnProperty = Object.prototype.hasOwnProperty; return hasOwnProperty.call(obj, key); } 

src / local / index.ts 对外暴露一些方法

import defaultLang from './lang/zh'; import Vue from 'vue'; import deepmerge from 'deepmerge'; import Format from './format'; const format = Format(Vue); let lang: { 
    [P: string]: any } = defaultLang; let merged = false; /** * i18n适配器 */ let i18nHandler = function (this: any): string | undefined { 
    // 如果存在 vue-i18n 组件,那么就将本ui组件的多语言文件合并到 i18n 组件对应的语言文件中,这样子后面要用本ui组件的词条的时候,就直接调用 i18n 组件的方法就行了 const vuei18n = Object.getPrototypeOf(this || Vue).$t; // 有 Vue.locale 这个方法,那么就直接内置兼容 // 其实就是用他原有的 lang 对象再跟 os-ui 的 lang 对象进行覆盖合并 if (typeof vuei18n === 'function' && !!Vue.locale) { 
    if (!merged) { 
    merged = true; Vue.locale(Vue.config.lang, deepmerge(lang, Vue.locale(Vue.config.lang) || { 
   }, { 
    clone: true })); } return vuei18n.apply(this, arguments); } return undefined; }; /** * 对外暴露的翻译服务方法 */ export const t = function (this: any, path: string, options: any): string { 
    // 先从 i18n 里面找,找不到再从 本ui组件的语言文件中查找 let value = i18nHandler.apply<any, any, any>(this, arguments); if (value !== null && value !== undefined) return value; const array = path.split('.'); let current: any = lang; for (let i = 0, j = array.length; i < j; i++) { 
    const property = array[i]; value = current[property]; if (i === j - 1) return format(value, options); if (!value) return ''; current = value; } return ''; }; /** * 设置默认语言的方法 */ export const use = function (l: any): void { 
    lang = l || lang; }; /** * 兼容其他 i18n 插件,替换默认的翻译实现 */ export const i18n = function (fn: () => string): void { 
    i18nHandler = fn || i18nHandler; }; export default { 
    use, t, i18n }; 

src / mixins / local.ts 这里选择使用mixin,方便组件使用

import { 
    Vue, Component } from 'vue-property-decorator'; import { 
    t } from '../local'; @Component export class I18nMixin extends Vue { 
    public t(...args: any): string { 
    return t.apply(this, args); } } 

src / index.ts 需要修改下入口文件

import OsPagination from '../packages/os-pagination'; import localeUtil from './local/index'; // 存储组件列表 const components = [OsPagination]; // 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,则所有的组件都将被注册 const install: any = function (Vue: any, opts: any): void { 
    // 添加语言参数,让其初始化组件的时候,可以传进去其他的语言 localeUtil.use(opts.locale); localeUtil.i18n(opts.i18n); // 判断是否安装 if (install.installed) return; // 遍历注册全局组件 components.map((component: any) => Vue.component(component.extendOptions.name, component)); }; const i18n = localeUtil.i18n; export { 
    install as OSUI, localeUtil, i18n, // 导出的对象必须具有 install,才能被 Vue.use() 方法安装 // 以下是单个导出的组件 OsPagination, }; 

发布组件库的时候还需要把local文件包含在里面,修改package.json文件
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第27张

5.4 在组件库的使用方式

就正常使用mixin,
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第28张
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第29张

5.5 在实际项目中使用

使用方式于饿了么ui基本一致,可以参考饿了么ui的使用方法 地址

按需引入有些区别

import Vue from 'vue'; import i18n from './lang'; import { 
    Pagination, localeUtil } from 'os-ui'; Vue.use(Pagination); localeUtil.i18n((key: string, value: string) => i18n.t(key, value)); 

六、为组件支持按需引入

研究了很久,发现如果以vue cli的lib模式打包,暂时不支持多个出口,没法打包成每个组件一个文件的形式,除非自己配置webpack进行打包,网上有很多教程。

另外一个思路是使用rollup进行打包,相比webpack来说,rollup更适合打包库,打包后的文件也会小很多,同时rollup可以配置输出为代码es版本,天生支持tree shaking,相比每个组件打包成一个文件的做法,使用组件库的人也免除了再使用babel-plugin-import 完成导入语句的转换的步骤
但要注意为了避免treeshaking莫名失效,尽量使用rollup 7以及以上的版本进行打包

七、组件库主题定制

八、为组件库编写文档

VuePress
前端私有化部署方案_前端开发框架 (https://mushiming.com/)  第30张

九、优化包体积大小

THE END

发表回复