前端公共组件库优化2.0

前言

在之前的文章中,笔者根据原先的团队内部的组件库使用情况,做了一次优化的处理,解决了前端开发公共组件的一些问题,提高了规范。但是依旧存在一些可优化的点,例如以submoudle的方式接入存在挺多问题。所以在经历一些版本使用之后,开始尝试使用npm包的方式来接入。

搭建内部私仓

因为是公司内部的项目,公共组件要限制在公司内部使用,所以不能直接发布到npm官方的库中,而是要选择搭建公司内部的npm私仓

这里内部私仓选型笔者选择了Verdaccio,可以直接用docker在内部部署一个,或者根据自己的情况部署一个即可,因为篇幅问题,这里就不展开Verdaccio的搭建过程,部署完成之后可以看到大概的样子如下

试用

创建账户

Verdaccio 跑起来之后首先要创建一个账户,有了账户我们才可以进行后续的一些publish install等操作,这里我们就直接按Verdaccio的提示直接创建就好了,这里不再补充。

本地登录

我们可以用上面一步创建的账密来登录我们的私仓,一方面校验下我们的账密是否正确,另外也是为下面的发包先做好准备,我们运行下面的命令来登录,然后根据提示输入账密即可

arduino 复制代码
npm login --registry http://x.x.x.x:4873/

设置镜像源

如果项目中有.npmrc文件,可以在文件中设置registry为我们的私仓,发布的时候就可以识别到私仓的地址。例如.npmrc

ini 复制代码
registry=http://x.x.x.x:4873/

手动发包

我们在自己的终端中执行命令

npm publish

如果是发布成功的话,命令行会有提示,最后是这样的

我们看下Verdaccio上面有没有更新到,可以看到这时候我们的新发布的这个版本已经可以查看到,到这一步我们完整的本地手动发包流程就算正常完成了

发包产物调整

在上面的发的版本中,我们没有设置其他的内容,所以npm publish的时候是把整个项目目录都一股脑发布上去了。如果我们尝试安装这个包就可以看到产物结构,基本上就和我们的整个项目一致

原先使用submoudle的方式引入的时候,宿主的项目可以随意引用组件库这个代码仓库里面的任意一个文件,现在用npm包的情况下,要做好这个隔离,哪些可以暴露出来给项目引用的,哪些文件无需暴露无需保留。另外是否需要对产物进行打包处理,这个也是需要进行考虑。针对笔者的这个项目,大概有下面这些判断

调整思路

需要暴露出来的内容有下面这些:

  • vue组件,这个也是代码仓库中最多的内容
  • 颜色变量等样式文件,这些需要在各种项目中决定是否引入,所以需要暴露出来使用
  • 一些通用的js文件,例如做一些初始化,一些封装好的通用方法等
  • 多语言的词条,如果项目有使用国际化的话可以获取这些组件内部的词条然后做处理,类似一些ui组件库的处理

产物处理,针对上面的暴露的内容,我们可以对不同类型在发包前做一些处理

  • vue组件,这个可以考虑直接暴露源码或者打包之后导出,这里笔者个人选择打包成产物暴露出组件,主要是考虑直接引用产物减少引入项目打包的时间
  • 颜色变量等样式文件,这里就直接导出了,如果是要使用颜色变量,直接引入这个文件即可
  • 通用的js文件,因为js直接可以被引用,就不做太多处理,可以做压缩和混淆
  • 多语言的词条,直接导出json文件或者js文件即可

增加webpack打包

因为我们需要对vue组件等进行打包,所以肯定要选择打包工具,因为内部都是webpack的,原先的代码片段也是submoudle引入到宿主项目后用webpack打包到一起的,所以这里我们还是选择使用webpack。

我们在原先的基础上需要增加webpack的包,因为涉及到vue,所以也需要对应的vue-loader,可以安装下面的包

json 复制代码
"vue-loader": "^15.10.2",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"

然后在项目根目录下新建webpack.config.js用来编写我们的webpack配置

另外因为项目中用到了样式、svg、图片等资源,也是要增加对应的loader,每个项目都不太一样,这里笔者就大概贴一下自己项目的配置

javascript 复制代码
const { VueLoaderPlugin } = require('vue-loader');
const path = require('path');

module.exports = {
    mode: 'production',
    entry: path.resolve(__dirname, 'index.js'),
    output: {
        path: path.resolve(__dirname, 'dist'),
        publicPath: '/dist/',
        filename: 'index.js',
        library: 'Frontend-common',
        libraryTarget: 'umd',
        umdNamedDefine: true,
        globalObject: 'this'
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            },
            {
                test: /\.scss$/,
                use: ['style-loader', 'css-loader', 'sass-loader']
            },
            {
                test: /\.svg$/,
                loader: 'svg-sprite-loader'
            },
            {
                test: /\.(png|jpe?g|gif)$/,
                loader: 'file-loader'
            }
        ]
    },
    plugins: [
        new VueLoaderPlugin(),
    ]
};

简单走读下,这部分的配置还是相对比较简单,就是针对不同的资源用不同的loader来处理,另外设置打包的入口文件还有导出的目录等配置,不同资源的处理可以根据自己实际项目来调整。

增加入口文件

在上面的入口文件中我们指定了打包的入口文件,一开始其实是没有这个入口文件的,用submodule的方式可以随意引用里面的任意一个文件,这样其实不太规范,笔者是希望约定好暴露出来的文件,而不是给使用者随意引用这个包里面所有的内容。另外指定入口文件可以去除一些无用的代码,减少打包出来产物的大小。

这里也是在项目根目录下增加一个index.js文件,这个文件就export我们要暴露出来的组件

如下:

javascript 复制代码
import FcommonNoFound from './components/commonPage/FcommonNoFound/index.vue';
import FcommonNoPermission from './components/commonPage/FcommonNoPermission/index.vue';

export {
    FcommonNoFound,
    FcommonNoPermission
};

拷贝文件

如上面所说,我们除了vue组件之外,还有涉及js文件、样式文件、json文件等,这些其实我们不需要进行编译,就是直接导出即可,这个笔者选择直接拷贝在源码中对应的目录,然后暴露出来即可

这里我们需要加多一个copy-webpack-plugin的包用来拷贝资源,用shell脚本也可以,这里笔者就统一在webpack里面配置了,在上面的配置的基础上增加配置

css 复制代码
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
    ...
    plugins: [
        new VueLoaderPlugin(),
        new CopyPlugin({
            patterns: [
                { from: './output/*.js', to: '' },
                {
                    from: './styles/themes/*',
                    to: ''
                },
                {
                    from: './lang/*/*',
                    to: ''
                }
            ]
        })
    ]
}

这里我们就是指定源码中的几个目录,直接拷贝到指定的位置即可

增加构建命令

上面我们已经增加了webpack配置,为了方便我们要增加一个命令来运行编译打包出产出,所以我们在package.json中增加一个命令

json 复制代码
"build": "webpack --config webpack.config.js"

当我们要编译打包的时候直接npm run build即可

看下我们现在打包出来的情况,这种格式就比较符合我们的诉求

设置npmignore

在上面的publish中我们基本上是把整个项目发布上去了,现在我们打包出了产物,还是要设置一下,只发布产物的内容,其他源码的不publish上去,这时候就需要npmignore文件了,在根目录创建.npmignore文件,设置如下

csharp 复制代码
# 忽略所有的文件
**
# 除了 dist 目录以及其中的内容
!dist/**/*

设置exports

我们发布上去的包因为带着/dist目录,如果要引用的话是frontend-common/dist/xxx.js这样,笔者希望抹除这个dist,这里就需要设置package.json里面的exports配置,例如下面这样

json 复制代码
"exports": {
    ".": "dist/index.js",
    "./styles/": "dist/styles/",
    "./output/": "dist/output/"
},

自动发包

现状

我们在上面的步骤已经可以构建出产物,然后通过在本地运行publish的方式来发包,但是考虑到很多步骤都是在本地执行,这个过程还是存在一些可以优化的点,主要考虑有

  • 需要构建出产物,频繁在本地执行会占用本地机器的资源
  • 比较依赖开发手动改版本号,如果多个开发同时改版本号的话不好维护,也存在冲突的风险,版本号的改动也无法控制
  • 需要有账户密码才可以登录私仓,如果账号密码给到所有的开发的话,一旦权限下放就比较难管控
  • 镜像源是处于内网环境,结合笔者的实际情况,在外网要访问这个镜像源需要各种跳板机,对团队成员来说上手成本比较高

目标

根据上面的现状,我们可以有针对性的列出我们要优化的目标

  • 能够自动构建产物,不占用本地机器的资源
  • 可以自动生成版本号
  • 能够不依赖账密和访问网络进行发包

总结,就是要能够自动化发包,减少开发的发包成本,并且约束好规范

方案

根据笔者的认识,可以用CI或者jenkins来做这些事情,因为笔者用的代码仓库里面没有CI功能,所以采用了jenkins来做这个事情,大概的步骤是这样,以发alpha版本为例

  • 前端正常开发版本,如果想发一个验证的版本给到其他人使用,就提PR到alpha分支
  • PR审核通过之后触发jenkins的任务
  • jenkins里面拉取仓库代码,安装依赖,执行命令增加一个alpha的版本号,提交改动推到远程,构建产物,用账密登录私仓,最后publish我们这个包,完成通知到某个群

因为笔者这里不展开jenkins相关的知识,就是以前端发包的相关来展开,其他读者可以根据自己团队的情况自行调整

设置alpha版本号

在上面的步骤中我们是手动改版本号,为了让版本号比较规范,所以这里增加了一个命令来生成新的版本号

json 复制代码
"scripts": {
    "alpha": "npm version prerelease --preid=alpha"
}

如果这时候本地还有没有提交的改动会有上面这种报错,我们要保证本地提交好了才执行。如果提交成功,会有一个新的commit推上去

另外,因为笔者的commit会触发一系列的commitlint,所以这里就直接跳过就好,完整的命令如下:

json 复制代码
"scripts": {
    "alpha": "cross-env HUSKY_SKIP_HOOKS=1 npm version prerelease --preid=alpha"
}

登录私仓

在上面的本地的例子中,我们是通过跑命令来登录私仓的,输入login命令后需要键盘录入账户名和密码,如果我们要在jenkins上面做这种输入比较麻烦 ,笔者也试过挺多次,没有能够解决,所以只能曲线救国,使用其他的方式。

这里介绍笔者目前的解决方案,就是用npm-cli-login这个包来做登录,避开需要键盘录入账户名密码的操作

在jenkins的环境中我们全局安装这个工具

npm install -g npm-cli-login

然后我们只需要用这个工具在后面带着我们的参数即可登录

bash 复制代码
npm-cli-login -u ${NPM_USERNAME} -p ${NPM_PASSWORD} -e ${NPM_EMAIL} -r ${NPM_REGISTRY}

完整命令

这里补充下笔者的完整命令,可以作为一个参考

perl 复制代码
npm-cli-login -u ${NPM_USERNAME} -p ${NPM_PASSWORD} -e ${NPM_EMAIL} -r ${NPM_REGISTRY}
npm ci
npm run alpha
git push origin HEAD:alpha
npm run build
npm publish

简单走读一下每个命令的作用

  • npm-cli-login:登录私仓,账密登时在jenkins里面作为一个常量声明了
  • npm ci:根据package-lcok.json 安装依赖
  • npm run alpha: 升级alpha的版本号,并生成一个commit
  • git push: 这里推送到远程是为了后续的合入都能追加版本号,所以上面生成的版本号commit要推送到远程,这样也方便大家看记录
  • npm run build: 构建产物
  • npm publish: 发包到我们的私仓

使用流程

发布alpha版本流程

alpha版本作为我们开发联调阶段发布的包版本,是为了方便提供给其他项目做验证,注意:在项目的正式环境中不要使用alpha版本,一定要使用正式的版本,总体流程如下

  • alpha分支reset成为和稳定的版本(例如master)一样,例如这时候的版本是(2.0.0)
  • 从稳定的版本(例如master)拉取分支做开发,正常开发和提交,不需要手动改package.json里面的版本号
  • 需要发布alpha版本的时候,提PR到alpha分支,审核通过自动触发流水线更新
  • 流水线自动生成一个alpha版本(例如2.0.1-alpha.0)并提交到alpha分支
  • 流水线运行成功会收到消息提醒,在私仓里面的versions里面看到新增的版本
  • 需要继续更新版本的话继续提PR合并,会自动追加alpha版本号,重复上面的这些步骤

发布正式版本流程

同发布alpha版本基本一致,部分细节有调整

  • 用alpha版本验证过后,用功能分支(不是alpha分支)提PR到master分支进行带code review
  • 如果code review有修改,继续发alpha版本验证完成,保证后续的改动也是正常的
  • 审核通过之后合并代码,触发流水线
  • 流水线自动生成一个正式的版本号和变更记录等(例如2.0.1这样,并提交到master分支
  • 流水线运行成功发布完成,在私仓里面的versions里面看到新增的版本
  • 重置alpha分支为现在的master分支
  • 发送企业微信的消息提醒,包括变更记录等

总结

本文主要罗列了笔者在将公共组件库作为npm包发布的整个过程,涉及了私仓搭建,产物调整,还有增加了自动发包的流程。提高了团队内部使用公共组件的效率,制定了对应的发包规范。当然,优化是不断进行的,笔者会持续根据团队情况去优化整个过程,做出相应的调整。

相关推荐
雯0609~15 分钟前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ18 分钟前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z23 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
天天扭码38 分钟前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
彭世瑜1 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4041 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish1 小时前
Token刷新机制
前端·javascript·vue.js·typescript·vue
小五Five1 小时前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序1 小时前
vue3 封装request请求
java·前端·typescript·vue
临枫5411 小时前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript