当前位置:   article > 正文

功能/业务模块 组件化 —— 创建、发布、使用 功能/业务组件

业务组件

目录

1. 创建 功能/业务组件

1.1 开发组件

1.1.1 添加基本结构

1.1.2 组件内的 package.json

1.1.3 组件内的 index.ts

1.1.4 在 src 下开发组件时要注意

1.2 打包组件

1.2.1 修改项目 package.json

1.2.2 添加 rollup.config.install.js

1.2.3 打包全部组件

1.2.4 打包指定组件

1.2.5 打包失败的几种原因 ★★★

1.3 将打包好的组件拷贝到 node_modules 中

1.3.1 修改项目 package.json

1.3.2 添加 rollup.config.deploy.js

1.3.3 拷贝组件

2. 使用 Lerna 管理组件发布

2.1 发布组件的过程

2.1.1 发布组件前,需要提交代码

2.1.2 选择组件版本

2.1.3 确认发布

2.1.4 发布成功效果

2.1.5 检查制品库 Nexus

2.1.6 检查 Gitlab 中的 Tag

2.2 移除本地打包生成的 lib、es 目录

3. 使用业务组件的两种方式

3.1 使用正在开发的组件(package 中的直接引用)

3.2 使用制品库的组件(制品库中的安装使用)


业务组件 —— 开发过程中,整个 功能/业务模块 被抽出成一个组件,并发布;

使用场景 —— 若其他系统中有类似的 功能/业务模块,直接采用 npm 安装引入即可;

1. 创建 功能/业务组件

新建 package 文件夹(与 src 同级),该文件夹包含:

  • 功能/业务组件列表(lyrelion/components)
  • 打包方法(build) 

关于功能/业务组件:

  • 组件命名采用 中划线 的方式,例如 c-common-base-anchor
  • 每个组件,都有自己的 页面 views、公共接口 service、公共方法 utils、公共数据类型接口 types、入口文件 index.ts、package.json 等文件/文件夹

1.1 开发组件

1.1.1 添加基本结构

在 package/lyrelion/components 目录下,新增 功能/业务组件 文件夹(例如:c-example-module-crud)

在 c-example-module-crud 目录下,新增 src 文件夹,在 src 下,开发具体的 功能/业务

最终,每个 功能/业务组件 的结构,都类似这样:

1.1.2 组件内的 package.json

该文件需要修改以下内容:

  • 名称 name
  • 版本号 version
  • 要打包的文件 files
  • 组件发布地址 publishConfig.registry

1.1.3 组件内的 index.ts

需要进行以下操作:

  • 引入组件的入口文件 —— xxx.vue,并修改名字
  • 注册组件 install
  • 导出组件 export
  1. import { App } from 'vue';
  2. import CcommonBaseAnchor from './src/view/anchor-point.vue';
  3. CcommonBaseAnchor.install = (app: App): void => {
  4. app.component(CcommonBaseAnchor.name, CcommonBaseAnchor);
  5. };
  6. export { CcommonBaseAnchor };

1.1.4 在 src 下开发组件时要注意

组件中如果使用了公共的 hooks 方法、services 服务的话,也需要将他们引入哦

1.2 打包组件

1.2.1 修改项目 package.json

此处的项目指的是组件库所处的项目,也就是 package 所处的项目 microApp

在项目根目录下的 package.json 中,新增下方的 scripts

"installc": "rollup -c package/build/rollup.config.install.js",

也就是说,当执行 npm run installc 时,会执行 一个 js 文件(该文件是对 rollup 制定的打包配置)

1.2.2 添加 rollup.config.install.js

上面说过 package 下的 build,用于存放打包配置

新建 rollup.config.install.js 文件,添加下方内容:

  1. /* eslint-disable import/no-dynamic-require */
  2. import fs from 'fs';
  3. import path from 'path';
  4. import vue from 'rollup-plugin-vue';
  5. import json from '@rollup/plugin-json';
  6. import images from '@rollup/plugin-image';
  7. import postcss from 'rollup-plugin-postcss';
  8. import { terser } from 'rollup-plugin-terser';
  9. import typescript from 'rollup-plugin-typescript2';
  10. import resolve, { nodeResolve } from '@rollup/plugin-node-resolve';
  11. import commonjs from '@rollup/plugin-commonjs'; // 将CommonJS模块转换为 ES2015 供 Rollup 处理
  12. import alias from '@rollup/plugin-alias';
  13. // 路径分隔符 - windows和linux分隔符不同
  14. const { sep } = require('path');
  15. /**
  16. * 获取命令行中 -- 后面的字符串,确定要对哪个组件进行操作
  17. * 比如 npm run installc -- --anchor,就会在这里输出 anchor
  18. */
  19. let cPartPath = process.argv[4];
  20. if (!cPartPath) {
  21. cPartPath = 'lyrelion';
  22. } else {
  23. cPartPath = cPartPath.replace('--', '');
  24. }
  25. console.log('安装包含' + cPartPath + '路径的组件');
  26. const config = {
  27. // 获取 lyrelion 文件夹路径,作为处理的根路径
  28. root: path.resolve(__dirname, '../lyrelion', ''),
  29. src: path.resolve(__dirname, '../../', 'src/*'),
  30. // 路径分隔符 - windows和linux分隔符不同
  31. sep,
  32. // 判断环境,生产环境下,开启代码压缩
  33. isDev: process.env.NODE_ENV !== 'production',
  34. // 要编译的组件数组,做为全局变量使用
  35. buildComponentsMessage: [],
  36. // 要编译的组件数组,做为全局变量使用
  37. buildComponentsConfig: [],
  38. };
  39. /**
  40. * 初始化 rollup 插件
  41. * @param {*} componentSourcePath
  42. * @returns
  43. */
  44. function initPlugins(componentSourcePath) {
  45. // 公共插件配置
  46. const plugins = [
  47. vue({
  48. css: false,
  49. compileTemplate: true,
  50. }),
  51. images({ include: ['**/*.png', '**/*.jpg'] }),
  52. postcss({
  53. extensions: ['.css', '.scss'],
  54. extract: true,
  55. }),
  56. resolve(),
  57. // css(),
  58. commonjs(),
  59. /*
  60. * babel({
  61. * runtimeHelpers: true,
  62. * }),
  63. */
  64. json(),
  65. // 支持TS
  66. typescript({
  67. tsconfig: 'tsconfig-build.json',
  68. tsconfigOverride: {
  69. compilerOptions: {
  70. declaration: true,
  71. types: ['lyrelion-common-base', 'lyrelion-common-ou', 'lyrelion-common-bpm'],
  72. // declarationDir: path.join(__dirname, `../lyrelion/${folder}/types`),
  73. },
  74. include: ['types',
  75. path.resolve(componentSourcePath, 'src'),
  76. path.resolve(componentSourcePath, '*/*.ts'),
  77. path.resolve(componentSourcePath, '*/*.d.ts'),
  78. ],
  79. },
  80. }),
  81. nodeResolve({
  82. extensions: ['.js', '.jsx', '.ts', '.tsx'],
  83. modulesOnly: true,
  84. }),
  85. alias({
  86. '@/*': ['./src/*'],
  87. }),
  88. ];
  89. return plugins;
  90. }
  91. // 公用方法
  92. const commonFunction = {
  93. /**
  94. * 字符串转大驼峰
  95. * @param {*} string
  96. */
  97. stringToCamel(string) {
  98. const arr = string.split(sep);
  99. let resStr = arr.reduce((prev, cur) => {
  100. const str = prev + cur.slice(0, 1).toUpperCase() + cur.slice(1);
  101. return str;
  102. });
  103. resStr = resStr.slice(0, 1).toUpperCase() + resStr.slice(1);
  104. return resStr;
  105. },
  106. /**
  107. * 字符串转中划线拼接
  108. * @param {*} string
  109. */
  110. stringToDash(string) {
  111. const arrList = string.split(sep);
  112. let resStr = '';
  113. arrList.forEach((one) => {
  114. if (resStr && one) {
  115. resStr = resStr + '-' + one;
  116. } else {
  117. resStr = one;
  118. }
  119. });
  120. return resStr;
  121. },
  122. };
  123. function create(componentSourcePath, camelName) {
  124. console.log('componentSourcePath:' + componentSourcePath);
  125. /*
  126. * 获取包的 package.json 文件
  127. * @rollup/plugin-json 使 rollup 可以使用 require 的方式将 json 文件作为模块加载
  128. * 它返回 json 对象
  129. */
  130. // eslint-disable-next-line global-require
  131. const pkg = require(path.resolve(componentSourcePath, 'package.json'));
  132. // eslint-disable-next-line global-require
  133. const tsconfigJson = require(path.resolve(__dirname, 'tsconfig-build.json'));
  134. tsconfigJson.compilerOptions.declarationDir = path.resolve(componentSourcePath, 'types');
  135. tsconfigJson.include = [path.resolve(componentSourcePath, 'src')];
  136. // 初始化 rollup 插件
  137. const plugins = initPlugins(componentSourcePath);
  138. /* 如果时生产环境,开启代码压缩 */
  139. if (!config.isDev) plugins.push(terser());
  140. // 返回 rollup 的配置对象
  141. return {
  142. // 打包入口:拼接绝对路径
  143. input: path.resolve(componentSourcePath, 'index.ts'),
  144. /*
  145. * 配置打包出口
  146. * 分别打包两种模块类型 cjs 和 es
  147. * 路径使用业务组件的 package.json 中配置的 main 和 module
  148. */
  149. output: [
  150. {
  151. name: camelName,
  152. file: path.resolve(componentSourcePath, pkg.main),
  153. format: 'umd',
  154. sourcemap: true,
  155. globals: {
  156. vue: 'Vue',
  157. 'vue-i18n': 'VueI18n',
  158. appConfig: 'appConfig',
  159. '@lyrelion/u-common-base': '@lyrelion/u-common-base',
  160. },
  161. palyrelion: {
  162. vue: 'https://unpkg.com/vue@next',
  163. },
  164. },
  165. {
  166. exports: 'auto',
  167. file: path.join(componentSourcePath, pkg.module),
  168. format: 'es',
  169. globals: {
  170. vue: 'Vue',
  171. 'vue-i18n': 'VueI18n',
  172. appConfig: 'appConfig',
  173. '@lyrelion/u-common-base': '@lyrelion/u-common-base',
  174. },
  175. },
  176. ],
  177. // 配置插件
  178. plugins: [
  179. ...plugins,
  180. ],
  181. // 指出应将哪些模块视为外部模块
  182. external: [
  183. 'vue',
  184. 'vue-i18n',
  185. 'echarts',
  186. 'echarts-liquidfill',
  187. '@lyrelion/c-common-base-table',
  188. '@lyrelion/c-common-base-col',
  189. '@lyrelion/c-common-base-paging',
  190. '@lyrelion/c-common-base-button',
  191. '@lyrelion/c-common-base-code',
  192. '@lyrelion/c-common-base-upload',
  193. '@lyrelion/c-common-ou-form',
  194. '@lyrelion/c-common-base-tree',
  195. '@lyrelion/s-common-base',
  196. '@lyrelion/u-common-base',
  197. 'crypto-js',
  198. 'jsonwebtoken',
  199. 'axios',
  200. 'js-cookie',
  201. 'element-plus',
  202. ],
  203. };
  204. }
  205. /**
  206. * 遍历编译所有组件
  207. * @param {*} folder
  208. */
  209. function readDirRecur(folder) {
  210. const files = fs.readdirSync(folder);
  211. if (files.length > 0) {
  212. files.forEach((file) => {
  213. const fullPath = folder + sep + file;
  214. const isFile = fs.statSync(fullPath);
  215. if (isFile && isFile.isDirectory()) {
  216. readDirRecur(fullPath);
  217. } else if (fullPath.endsWith('package.json')) {
  218. // 业务组件源文件位置,例如:D:\lyrelionPlatform\microapp8\package\lyrelion\components\c-common-base-button
  219. const componentSourcePath = path.resolve(fullPath, '../');
  220. // 业务组件中间位置,例如:D:\lyrelionPlatform\microapp8\package\lyrelion
  221. const lyrelionRoot = path.resolve(__dirname, '../', 'lyrelion');
  222. const arrList = componentSourcePath.split(sep);
  223. // 中划线拼接 name,例如:demo-button
  224. const dashName = arrList[arrList.length - 1];
  225. // 大驼峰 name,例如:DemoButton
  226. const camelName = commonFunction.stringToCamel(dashName);
  227. // 包含输入路径
  228. if (cPartPath && componentSourcePath.indexOf(cPartPath) > -1) {
  229. // 排除lyrelion\types目录
  230. if (fullPath.indexOf(`lyrelion${sep}types`) === -1) {
  231. config.buildComponentsMessage.push({
  232. componentSourcePath,
  233. camelName,
  234. });
  235. }
  236. }
  237. }
  238. });
  239. }
  240. }
  241. console.log(`1/2:遍历路径中包含${cPartPath}的组件`);
  242. readDirRecur(config.root);
  243. console.log(`2/2:共找到${config.buildComponentsMessage.length}个要编译的组件`);
  244. if (config.buildComponentsMessage && config.buildComponentsMessage.length > 0) {
  245. // 第二步:编译组件
  246. config.buildComponentsMessage.forEach((componentMessage) => {
  247. config.buildComponentsConfig.push(create(componentMessage.componentSourcePath, componentMessage.camelName));
  248. });
  249. module.exports = config.buildComponentsConfig;
  250. /*
  251. * 第三步:复制组件到 node_modules 目录下
  252. * console.log('4/5:开始复制组件到 node_modules 目录下');
  253. * config.buildComponentsMessage.forEach((componentMessage) => {
  254. * fsExtra.copy(componentMessage.componentSourcePath, componentMessage.componentClassPath, (err) => {});
  255. * });
  256. */
  257. // console.log('5/5:组件编译完成,并复制到 node_modules 路径下');
  258. } else {
  259. console.log(`没有找到${cPartPath}路径下的组件,路径用${sep}分隔`);
  260. }

1.2.3 打包全部组件

在项目根目录下,执行下方命令,会打包 package 下的所有组件

npm run installc

1.2.4 打包指定组件

打包指定组件(也可以打包 组件名中包含某个字符串 的组件),和打包全部组件的区别:增加了文件名

npm run installc -- --example

example 可以是指定组件的文件夹完整名称,也可以是指定组件的文件夹名中的某部分字符串(只要检测到对应的字符串,就会打包)

1.2.5 打包失败的几种原因 ★★★

在 vue 文件中,使用了 scss 语法,或者样式中的注释是 // xxx

使用了 package 下面不存在的文件,比如 hooks

1.3 将打包好的组件拷贝到 node_modules 中

1.3.1 修改项目 package.json

此处的项目指的是组件库所处的项目,也就是 package 所处的项目 microApp

在项目根目录下的 package.json 中,新增下方的 scripts

"deployc": "node package/build/rollup.config.deploy.js"

也就是说,当执行 npm run deployc 时,会执行 一个 js 文件(该文件复制 打包文件 到依赖中)

1.3.2 添加 rollup.config.deploy.js

上面说过 package 下的 build,用于存放打包配置

新建 rollup.config.deploy.js 文件,添加下方内容:

  1. /* eslint-disable import/no-dynamic-require */
  2. const fs = require('fs');
  3. const fsExtra = require('fs-extra');
  4. const path = require('path');
  5. // 路径分隔符 - windows和linux分隔符不同
  6. const { sep } = require('path');
  7. let cPartPath = process.argv[2];
  8. if (!cPartPath) {
  9. cPartPath = 'lyrelion';
  10. } else {
  11. cPartPath = cPartPath.replace('--', '');
  12. }
  13. console.log('安装包含' + cPartPath + '路径的组件');
  14. const config = {
  15. // 获取 lyrelion 文件夹路径,作为处理的根路径
  16. root: path.resolve(__dirname, '../lyrelion', ''),
  17. src: path.resolve(__dirname, '../../', 'src/*'),
  18. // 路径分隔符 - windows和linux分隔符不同
  19. sep,
  20. // 判断环境,生产环境会开启代码压缩
  21. isDev: process.env.NODE_ENV !== 'production',
  22. // 要编译的组件数组,做为全局变量使用
  23. buildComponentsMessage: [],
  24. // 要编译的组件数组,做为全局变量使用
  25. buildComponentsConfig: [],
  26. };
  27. // 公用方法
  28. const commonFunction = {
  29. /**
  30. * 字符串转大驼峰
  31. * @param {*} string
  32. */
  33. stringToCamel(string) {
  34. const arr = string.split(sep);
  35. let resStr = arr.reduce((prev, cur) => {
  36. const str = prev + cur.slice(0, 1).toUpperCase() + cur.slice(1);
  37. return str;
  38. });
  39. resStr = resStr.slice(0, 1).toUpperCase() + resStr.slice(1);
  40. return resStr;
  41. },
  42. /**
  43. * 字符串转中划线拼接
  44. * @param {*} string
  45. */
  46. stringToDash(string) {
  47. const arrList = string.split(sep);
  48. let resStr = '';
  49. arrList.forEach((one) => {
  50. if (resStr && one) {
  51. resStr = resStr + '-' + one;
  52. } else {
  53. resStr = one;
  54. }
  55. });
  56. return resStr;
  57. },
  58. };
  59. /**
  60. * 遍历编译所有组件
  61. * @param {*} folder
  62. */
  63. function readDirRecur(folder) {
  64. const files = fs.readdirSync(folder);
  65. if (files.length > 0) {
  66. files.forEach((file) => {
  67. const fullPath = folder + sep + file;
  68. const isFile = fs.statSync(fullPath);
  69. if (isFile && isFile.isDirectory()) {
  70. readDirRecur(fullPath);
  71. } else if (fullPath.endsWith('package.json')) {
  72. // 业务组件源文件位置 D:\lyrelionPlatform\microapp8\package\lyrelion\components\c-common-base-button
  73. const componentSourcePath = path.resolve(fullPath, '../');
  74. // 中间位置:D:\lyrelionPlatform\microapp8\package\lyrelion
  75. const lyrelionRoot = path.resolve(__dirname, '../', 'lyrelion');
  76. const arrList = componentSourcePath.split(sep);
  77. // 中划线拼接name,例如:example-demo-button
  78. const dashName = arrList[arrList.length - 1];
  79. // 大驼峰name,例如:DemoButton
  80. const camelName = commonFunction.stringToCamel(dashName);
  81. // 业务组件编译后根位置D:\lyrelionPlatform\microApp\node_modules\@lyrelion
  82. let componentClassRootPath = path.resolve(__dirname, '../../', 'node_modules', '@lyrelion');
  83. if (fullPath.indexOf(`lyrelion${sep}types`) > -1) {
  84. componentClassRootPath = path.resolve(__dirname, '../../', 'node_modules', '@types');
  85. }
  86. // 业务组件编译后组件绝对位置 D:\lyrelionPlatform\microApp\node_modules\@lyrelion\demo-button
  87. const componentClassPath = path.resolve(componentClassRootPath, dashName);
  88. if (cPartPath && componentSourcePath.indexOf(cPartPath) > -1) {
  89. config.buildComponentsMessage.push({
  90. componentSourcePath,
  91. componentClassPath,
  92. camelName,
  93. });
  94. }
  95. }
  96. });
  97. }
  98. }
  99. console.log(`1/4:遍历路径中包含${cPartPath}的组件`);
  100. readDirRecur(config.root);
  101. console.log(`2/4:共找到${config.buildComponentsMessage.length}个要部署的组件`);
  102. if (config.buildComponentsMessage && config.buildComponentsMessage.length > 0) {
  103. // 第三步:复制组件到 node_modules 目录下
  104. console.log('3/4:开始复制组件到node_modules目录下');
  105. config.buildComponentsMessage.forEach((componentMessage) => {
  106. console.log(componentMessage.componentClassPath);
  107. fsExtra.removeSync(componentMessage.componentClassPath, (err) => {});
  108. fsExtra.copy(componentMessage.componentSourcePath, componentMessage.componentClassPath, (err) => {});
  109. });
  110. console.log('4/4:组件复制到 node_modules 路径下,完成');
  111. } else {
  112. console.log(`没有找到${cPartPath}路径下的组件,路径用${sep}分隔`);
  113. }

1.3.3 拷贝组件

在项目根目录下,执行下方命令,会把 package 文件下的内容都放到 node_modules 中

npm run deployc

拷贝指定组件(也可以拷贝 组件名中包含某个字符串 的组件),和拷贝全部组件的区别:增加了文件名 

npm run deployc -- --example

2. 使用 Lerna 管理组件发布

使用管理工具 Lerna 来管理组件包,全局安装 Lerna 命令如下

npm i -g lerna

安装不上的话,考虑切换 cnpm 吧,我切换了 yarn/npm 两个源感觉都下载不下载...

2.1 发布组件的过程

2.1.1 发布组件前,需要提交代码

项目根目录下,执行下方命令

lerna publish

注意:需要在执行 lerna publish 前,把所有修改过的文件提交,否则会报错

2.1.2 选择组件版本

执行完 lerna publish 命令后,会出现选择版本的提示

选择正确版本,并回车

2.1.3 确认发布

选择完版本后,会出现 是否确认 的提示

如下图,输入 y 之后,就会执行发布包的操作了

2.1.4 发布成功效果

出现以下信息,代表发布好了

2.1.5 检查制品库 Nexus

检查 制品库(Nexus)- Browser 中,是否已经出现最新的包

2.1.6 检查 Gitlab 中的 Tag

检查 Gitlab 仓库 中,是否已经有对应版本的 Tag

使用 Tag,可以标记提交历史上的重要提交

2.2 移除本地打包生成的 lib、es 目录

lerna exec -- yarn del

3. 使用业务组件的两种方式

3.1 使用正在开发的组件(package 中的直接引用)

适用于正在开发中的组件,想实时看效果的

在路由文件中,引入开发环境的业务组件路径

import CdemoCrudList from 'package/xxx/components/c-example-module-crud/src/view/list.vue';

使用组件,配置业务组件的跳转路径

  1. // 示例路由
  2. const demoRoutes = [
  3. ....
  4. // 组件化开发-demo-增删改查
  5. {
  6. name: 'CdemoCrudList',
  7. path: '/layout/demo/crud/list', // 路径
  8. component: CdemoCrudList,
  9. },
  10. .....
  11. ]

3.2 使用制品库的组件(制品库中的安装使用)

适用于已经开发好的组件,存在于制品库中的

在项目根目录下的 package.json 中添加依赖,依赖名称要与包名一致

在路由文件中,引入制品库组件

import { CdemoCrudList, CdemoCrudView } from '@xxx/c-example-module-crud';

使用组件,配置业务组件的跳转路径

  1. // 示例路由
  2. const demoRoutes = [
  3. ....
  4. // 组件化开发-demo-增删改查
  5. {
  6. name: 'CdemoCrudList',
  7. path: '/layout/demo/crud/list', // 路径
  8. component: CdemoCrudList,
  9. },
  10. .....
  11. ]

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/283934
推荐阅读
相关标签
  

闽ICP备14008679号