• 业务组件化 —— 创建、发布、使用业务组件


    目录

    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. ]

  • 相关阅读:
    ppt 插入柱状图及基础功能调整
    网络编程:组播发送接收
    python判断语句
    E. Binary Inversions——前缀+后缀
    我用了多年的前端框架,强烈推荐!
    服务器存储面临的两大难题
    封装了几个CAPL发送诊断相关函数,具有较高的可复用性
    Redis7.0 编译安装以及简单创建Cluster测试服务器的方法 步骤
    Managing Test Files with create_files and delete_files Bash Scripts
    计算机软件分类、编程知识体系、编程工作岗位
  • 原文地址:https://blog.csdn.net/Lyrelion/article/details/127104984