Open fantasticsoul opened 1 year ago
声明:此文章来源于hel交流群的网友实践,仅供参考
我们平时使用npm publish进行发布时,上传的仓库默认地址是npm,通过Verdaccio工具在本地新建一个仓库地址,再把本地的默认上传仓库地址切换到本地仓库地址即可。当npm install时没有找到本地的仓库,则Verdaccio默认配置中会从npm中央仓库下载。
npm : https://registry.npmjs.org/
cnpm : http://r.cnpmjs.org/
taobao: https://registry.npm.taobao.org/
私密性高,仅团队共享。
安全性高,能够有效的防治恶意代码攻击。
使用局域网,传输速度快。
兵马未动,粮草先行,既然是搭建私有仓库应用,基础环境得备好。
npm install -g verdaccio
// 访问http://localhost:4837 verdaccio
// 最后面添加以下配置 listen: 0.0.0.0:4873
// 访问http://ip:port/ // 不要访问本地localhost下的 verdaccio
nrm add <registry> http://localhost:4873
nrm use <registry>
nrm ls
npm adduser // 输入账号和密码
// 登录 npm login // 发布 npm publish
上一章节将npm搭建到服务器了,接下来就是要搭建unpkg cdn服务,并且将上一章节搭建的npm私有仓库连接到unpkg私服上 首先搭建unpkg私服
git clone https://github.com/mjackson/unpkg.git # 安装依赖 $ npm i
在package.json的script添加start命令:
"scripts": { "build": "rollup -c", ... "watch": "rollup -c -w", "start":"set NODE_ENV=production&&node server.js" },
执行编译命令:
npm run build
命令运行完后会在根目录生成server.js文件; 启动服务:
npm run start
我们自己搭建的unpkg已经可以正常的使用了,但是目前我们私库的npm包还是不能访问,记下来就是添加私库支持了;
根目录新建npmConfig.js来存放私库包的命名空间:
//存放私库包的命名空间 export const scopes = [ '@cz','@syl' ]; /**** * 私库地址,代理端口会解析url的端口号 * const privateNpmRegistryURLArr = privateNpmRegistryURL.split(":"); * const privateNpmPort = privateNpmRegistryURLArr[privateNpmRegistryURLArr.length - 1] * 拉取一些npm的包会返回302的情况,unpkg暂时没有处理,会不会和本地的npm源有关? ***/ export const privateNpmRegistryURL = 'http://10.250.4.121:8088'; //互联网npm地址 export const publicNpmRegistryURL = 'http://registry.npmjs.org'; export default scopes;
接下来就是修改修改modules/utils/npm.js文件了,思路大概如下:
import url from 'url'; import http from 'http'; import gunzip from 'gunzip-maybe'; import LRUCache from 'lru-cache'; import bufferStream from './bufferStream.js'; const npmRegistryURL = 'http://10.250.4.121:8088' || 'https://registry.npmjs.org'; const oneMegabyte = 1024 * 1024; const oneSecond = 1000; const oneMinute = oneSecond * 60; const cache = new LRUCache({ max: oneMegabyte * 40, length: Buffer.byteLength, maxAge: oneSecond }); const notFound = ''; function get(options) { return new Promise((accept, reject) => { http.get(options, accept).on('error', reject); }); } function isScopedPackageName(packageName) { return packageName.startsWith('@'); } function encodePackageName(packageName) { return isScopedPackageName(packageName) ? `@${encodeURIComponent(packageName.substring(1))}` : encodeURIComponent(packageName); } async function fetchPackageInfo(packageName, log) { const name = encodePackageName(packageName); const infoURL = `${npmRegistryURL}/${name}`; log.debug('Fetching package info for %s from %s', packageName, infoURL); const { hostname, pathname,port } = url.parse(infoURL); const options = { hostname: hostname, path: pathname, port:port, headers: { Accept: 'application/json' } }; const res = await get(options); if (res.statusCode === 200) { return bufferStream(res).then(JSON.parse); } if (res.statusCode === 404) { return null; } const content = (await bufferStream(res)).toString('utf-8'); log.error( 'Error fetching info for %s (status: %s)', packageName, res.statusCode ); log.error(content); return null; } async function fetchVersionsAndTags(packageName, log) { const info = await fetchPackageInfo(packageName, log); return info && info.versions ? { versions: Object.keys(info.versions), tags: info['dist-tags'] } : null; } /** * Returns an object of available { versions, tags }. * Uses a cache to avoid over-fetching from the registry. */ export async function getVersionsAndTags(packageName, log) { const cacheKey = `versions-${packageName}`; const cacheValue = cache.get(cacheKey); if (cacheValue != null) { return cacheValue === notFound ? null : JSON.parse(cacheValue); } const value = await fetchVersionsAndTags(packageName, log); if (value == null) { cache.set(cacheKey, notFound, 5 * oneMinute); return null; } cache.set(cacheKey, JSON.stringify(value), oneMinute); return value; } // All the keys that sometimes appear in package info // docs that we don't need. There are probably more. const packageConfigExcludeKeys = [ 'browserify', 'bugs', 'directories', 'engines', 'files', 'homepage', 'keywords', 'maintainers', 'scripts' ]; function cleanPackageConfig(config) { return Object.keys(config).reduce((memo, key) => { if (!key.startsWith('_') && !packageConfigExcludeKeys.includes(key)) { memo[key] = config[key]; } return memo; }, {}); } async function fetchPackageConfig(packageName, version, log) { const info = await fetchPackageInfo(packageName, log); return info && info.versions && version in info.versions ? cleanPackageConfig(info.versions[version]) : null; } /** * Returns metadata about a package, mostly the same as package.json. * Uses a cache to avoid over-fetching from the registry. */ export async function getPackageConfig(packageName, version, log) { const cacheKey = `config-${packageName}-${version}`; const cacheValue = cache.get(cacheKey); if (cacheValue != null) { return cacheValue === notFound ? null : JSON.parse(cacheValue); } const value = await fetchPackageConfig(packageName, version, log); if (value == null) { cache.set(cacheKey, notFound, 5 * oneMinute); return null; } cache.set(cacheKey, JSON.stringify(value), oneMinute); return value; } /** * Returns a stream of the tarball'd contents of the given package. */ export async function getPackage(packageName, version, log) { const tarballName = isScopedPackageName(packageName) ? packageName.split('/')[1] : packageName; const tarballURL = `${npmRegistryURL}/${packageName}/-/${tarballName}-${version}.tgz`; log.debug('Fetching package for %s from %s', packageName, tarballURL); const { hostname, pathname,port } = url.parse(tarballURL); const options = { hostname: hostname, path: pathname, port:port }; const res = await get(options); if (res.statusCode === 200) { const stream = res.pipe(gunzip()); // stream.pause(); return stream; } if (res.statusCode === 404) { return null; } const content = (await bufferStream(res)).toString('utf-8'); log.error( 'Error fetching tarball for %s@%s (status: %s)', packageName, version, res.statusCode ); log.error(content); return null; }
修改npm.js完毕之后,执行npm run build重新生成server.js文件,然后启动服务:npm run start; 现在私库和公网npm都可以正常预览了
文档地址: https://tnfe.github.io/hel/ 具体不详细说明啦,请参照作者文档使用
接下来说一下Hel-micro + npm私服 + unpkg服务的一个落地实践
假设我有A、B两个业务系统,那么A与B既是模块的使用者又是模块的提供者,既是0又是1??
oh~有点复杂,我们先说0 1的情况吧,明白了0 1,1 0的相互转化也就为所欲为啦~
A系统 => 模块提供者 B系统 => 模块消费者
我们现在把作者提供的远程组件书写方法集成到了A系统,目前是直接放到了src下
远程组件的书写方式可参照上边的文档连接
A系统暴露的远程模块书写成功后,我们执行下如下命令
HEL_APP_HOME_PAGE=http://10.250.4.121:9999/note-comps@0.0.2/hel_dist npm run build
npm publish
注意用nrm检查下是否已经切换到自己私有的npm源了
发布成功后,我们就可以在任意项目里面消费远程组件啦,包括在A项目
假设我们要在B系统消费刚才A系统产生的模块,我们只需要修改一点点地方即可
hel-micro
npm i note-comps
import { preFetchLib } from 'hel-micro'
;(async function() { // await preFetchLib('hel-tpl-remote-vue-comps');
// 自定义前缀 await preFetchLib('note-comps', { apiPrefix: 'http://10.250.4.121:9999' })
// 调试本地开发中的远程组件 // const enableCustom = !!window.location.port; // await preFetchLib('hel-tpl-remote-vue-comps', { // custom: { // host: 'http://localhost:7001', // 基于 web-dev-server 开发中生成产物联调 // // host: 'http://localhost:9001', // 基于 http-server 已构建好的产物联调 // enable: enableCustom, // }, // });
import('./loadApp') })().catch((err) => { console.error('loadApp err: ', err) })
> http://10.250.4.121:9999是我们搭建的unpkg私服的地址 - loadApp.js就是之前main.js里面的内容
import Vue from 'vue'
import App from './App' import store from './store' import router from './router' // import * as Sentry from '@sentry/vue' // import { BrowserTracing } from '@sentry/tracing' import i18n from './lang'
import WujieVue from 'wujie-vue2'
Vue.mixin(mixins) Vue.use(CzUI, { size: 'small', i18n: (key, value) => i18n.t(key, value) })
Vue.use(WujieVue)
// 预加载流程引擎和权限引擎 const { setupApp, preloadApp } = WujieVue
new Vue({ el: '#app', router, store, i18n, render: (h) => h(App) })
- 在组件里面使用
至此,A系统的模块更新后发布后,其他系统的这个模块都会自动更新,那么B系统也可以随意往外暴露各种远程模块给各个系统调用啦。 此时模块联邦就可以在不同系统中随意调度,但是还缺乏一个管控平台 目前我们是微模块 + 微前端配合食用的,具体食用方式,我们还会再出一篇文章详细介绍。
Hel-micro + npm私有仓库 + unpkg私有部署实现模块联邦的最佳实践
所谓工欲善其事,必先利其器(搭建环境)
npm私有仓库
一.原理
我们平时使用npm publish进行发布时,上传的仓库默认地址是npm,通过Verdaccio工具在本地新建一个仓库地址,再把本地的默认上传仓库地址切换到本地仓库地址即可。当npm install时没有找到本地的仓库,则Verdaccio默认配置中会从npm中央仓库下载。
二.常用的仓库地址
npm : https://registry.npmjs.org/
cnpm : http://r.cnpmjs.org/
taobao: https://registry.npm.taobao.org/
三.优势
私密性高,仅团队共享。
安全性高,能够有效的防治恶意代码攻击。
使用局域网,传输速度快。
四.准备环境
兵马未动,粮草先行,既然是搭建私有仓库应用,基础环境得备好。
五.使用verdaccio搭建私有npm服务
unpkg私有化部署
上一章节将npm搭建到服务器了,接下来就是要搭建unpkg cdn服务,并且将上一章节搭建的npm私有仓库连接到unpkg私服上 首先搭建unpkg私服
一.拉取unpkg源码
在package.json的script添加start命令:
执行编译命令:
命令运行完后会在根目录生成server.js文件; 启动服务:
我们自己搭建的unpkg已经可以正常的使用了,但是目前我们私库的npm包还是不能访问,记下来就是添加私库支持了;
二.unpkg添加私库支持
根目录新建npmConfig.js来存放私库包的命名空间:
接下来就是修改修改modules/utils/npm.js文件了,思路大概如下:
修改npm.js完毕之后,执行npm run build重新生成server.js文件,然后启动服务:npm run start; 现在私库和公网npm都可以正常预览了
Hel-micro
文档地址: https://tnfe.github.io/hel/ 具体不详细说明啦,请参照作者文档使用
接下来说一下Hel-micro + npm私服 + unpkg服务的一个落地实践
假设我有A、B两个业务系统,那么A与B既是模块的使用者又是模块的提供者,既是0又是1??
oh~有点复杂,我们先说0 1的情况吧,明白了0 1,1 0的相互转化也就为所欲为啦~
A系统 => 模块提供者 B系统 => 模块消费者
我们现在把作者提供的远程组件书写方法集成到了A系统,目前是直接放到了src下
A系统暴露的远程模块书写成功后,我们执行下如下命令
发布成功后,我们就可以在任意项目里面消费远程组件啦,包括在A项目
消费方式
假设我们要在B系统消费刚才A系统产生的模块,我们只需要修改一点点地方即可
;(async function() { // await preFetchLib('hel-tpl-remote-vue-comps');
// 自定义前缀 await preFetchLib('note-comps', { apiPrefix: 'http://10.250.4.121:9999' })
// 调试本地开发中的远程组件 // const enableCustom = !!window.location.port; // await preFetchLib('hel-tpl-remote-vue-comps', { // custom: { // host: 'http://localhost:7001', // 基于 web-dev-server 开发中生成产物联调 // // host: 'http://localhost:9001', // 基于 http-server 已构建好的产物联调 // enable: enableCustom, // }, // });
import('./loadApp') })().catch((err) => { console.error('loadApp err: ', err) })
import Vue from 'vue'
import App from './App' import store from './store' import router from './router' // import * as Sentry from '@sentry/vue' // import { BrowserTracing } from '@sentry/tracing' import i18n from './lang'
import WujieVue from 'wujie-vue2'
Vue.mixin(mixins) Vue.use(CzUI, { size: 'small', i18n: (key, value) => i18n.t(key, value) })
Vue.use(WujieVue)
// 预加载流程引擎和权限引擎 const { setupApp, preloadApp } = WujieVue
new Vue({ el: '#app', router, store, i18n, render: (h) => h(App) })