抽离并发布 elpis npm包
本地调试 npm 包
- elpis 中执行
npm link, 将elpis软链接到node_modules目录下(全局) - 业务项目中执行
npm link 包名, 将elpis软链到业务项目中 - 在业务项目中执行
npm i, 会导致链接丢失,需要重新执行npm link 包名
对外提供入口
javascript
// 引入 elpis-core
const ElpisCore = require("./elpis-core");
module.exports = {
/**
* 启动 elpis
* @params options 项目配置,透传到 elpis-core
*/
serverStart(options = {}) {
const app = ElpisCore.start(options);
return app;
}
}elpis-core 模块抽离
- 不仅要加载 elpis 下的文件,还要加载业务项目文件,例如
middleware中间件的加载
javascript
const middlewares = {}
// 读取 elpis/app/middleware/**/**.js 下所有的文件
const elpisMiddlewarePath = path.resolve(__dirname, `..${sep}..${sep}app${sep}middleware`);
const elpisFileList = glob.sync(path.resolve(elpisMiddlewarePath, `.${sep}**${sep}**.js`));
elpisFileList.forEach(handleFile);
// 读取 业务根目录/app/middleware/**/**.js 下所有的文件
const businessMiddlewarePath = path.resolve(app.businessPath, `.${sep}middleware`);
const businessFileList = glob.sync(path.resolve(businessMiddlewarePath, `.${sep}**${sep}**.js`));
businessFileList.forEach(handleFile);
// 把内容加载到 app.middlewares 下
function handleFile(file) {
//提取文件名称
let name = path.resolve(file);
//截取路径 把 app/middleware/custom-module/custom-middleware.js 改为custom-module/custom-middleware
name = name.substring(name.lastIndexOf(`middleware${sep}`) + `middleware${sep}`.length,name.lastIndexOf('.'))
//把'-'改为驼峰式
name = name.replace(/[_-][a-z]/ig,(s)=>s.substring(1).toUpperCase())
//挂载 middleware 到内存app对象中
let tempMiddleware = middlewares;
const names = name.split(sep);
for(let i = 0 ,len = names.length; i<len;++i){
if(i === len -1){
tempMiddleware[names[i]] = require(path.resolve(file))(app);
}else{
if(!tempMiddleware[names[i]]){
tempMiddleware[names[i]] = {}
}
tempMiddleware = tempMiddleware[names[i]]
}
}
}
app.middlewares = middlewares- 其它的,例如
routerSchema、controller、service、extend也是做类似的操作 - config修改: 业务项目配置覆盖 elpis
javascript
// elpis config 目录及相关文件
const elpisConfigPath = path.resolve(__dirname, `..${sep}..${sep}config`);
let defaultConfig = require(path.resolve(elpisConfigPath, `.${sep}config.default.js`));
// 业务 config 目录及相关文件
const businessConfigPath = path.resolve(process.cwd(), `.${sep}config`);
try {
defaultConfig = {
...defaultConfig,
...require(path.resolve(businessConfigPath, `.${sep}config.default.js`))
}
} catch (e) {
console.log('[exception] there is no default.config file')
}
// 获取env.config配置
let envConfig = {}
try {
if (app.env.isLocal()) { //本地环境
envConfig = require(path.resolve(businessConfigPath, `.${sep}config.local.js`))
} else if (app.env.isBeta()) { //测试环境
envConfig = require(path.resolve(businessConfigPath, `.${sep}config.beta.js`))
} else if (app.env.isProduction()) { //生产环境
envConfig = require(path.resolve(businessConfigPath, `.${sep}config.prod.js`))
}
} catch (e) {
console.log('[exception] there is no default.config file')
}
// 覆盖并加载 config 配置
app.config = Object.assign({}, defaultConfig, envConfig)工程化改造
/app/webpack/dev.js,/app/webpack/prod.js的导出改成函数对外暴露工程化构建方法
javascript
// 引入 前端工程化构建方法
const FEBuildDev = require('./app/webpack/dev.js');
const FEBuildProd = require('./app/webpack/prod.js');
module.exports = {
/**
* 编译构建前端工程
* @param env 环境变量 local/production
*/
frontendBuild(env) {
if (env === 'local') {
FEBuildDev();
} else if (env === 'production') {
FEBuildProd();
}
}
}- 路径别名修改
javascript
resolve: {
extensions: ['.js', '.vue', '.less', '.css'],
alias: {
$pages: path.resolve(process.cwd(), './app/pages'),
$common: path.resolve(process.cwd(), './app/pages/common'),
$widgets: path.resolve(process.cwd(), './app/pages/widgets'),
$store: path.resolve(process.cwd(), './app/pages/store'),
},
},
// 上面的代码会加载业务项目中的文件,需要修改成加载elpis中的文件
alias: {
$pages: path.resolve(__dirname, '../../pages'),
$common: path.resolve(__dirname, '../../pages/common'),
$widgets: path.resolve(__dirname, '../../pages/widgets'),
$store: path.resolve(__dirname, '../../pages/store'),
},
// 统一改成前缀 $elpis- 业务项目中使用 webpack 打包,loader 会从业务目录的
node_modules加载,需要从改成 elpis 的node_modules目录加载, 使用require.resolve(loaderName)获取loader路径
javascript
{
test: /\.vue$/,
use: [
{
loader: require.resolve('vue-loader'),
}
]
}- 加载业务
webpack配置
javascript
let businessWebpackConfig = {};
try {
businessWebpackConfig = require(`${process.cwd()}/app/webpack.config.js`);
} catch (e) {
console.log('[exception] there is no webpack.config.js file')
}
module.exports = merge.smart({}, businessWebpackConfig)自定义SSR页面,抽离业务页面
- 除了 elpis 的入口文件需要解析外,还需要解析业务项目中的入口文件
javascript
// 动态构造 businessPageEntries businessHtmlWebpackPluginList
const businessPageEntries = {};
const businessHtmlWebpackPluginList = [];
// 获取 business/app/pages 目录下所有入口文件(entry.xx.js)
const businessEntryList = glob.sync(path.resolve(process.cwd(), './app/pages/**/entry.*.js'))
businessEntryList.forEach(file => {
handleFile(file, businessPageEntries, businessHtmlWebpackPluginList)
})
// 动态构造 elpisPageEntries elpisHtmlWebpackPluginList
const elpisPageEntries = {};
const elpisHtmlWebpackPluginList = [];
// 获取 elpis/app/pages 目录下所有入口文件(entry.xx.js)
const elpisEntryList = glob.sync(path.resolve(__dirname, '../../pages/**/entry.*.js'))
elpisEntryList.forEach(file => {
handleFile(file, elpisPageEntries, elpisHtmlWebpackPluginList)
})
// 构造相关 webpack 处理的数据结构
function handleFile(file, entries = {}, htmlWebpackPluginList = []) {
const entryName = path.basename(file, '.js')
// 构造 entry
entries[entryName] = file
// 构造最终渲染的页面文件
htmlWebpackPluginList.push(
// html-webpack-plugin 辅助注入打包后的 bundle 文件到 tpl 文件中
new HtmlWebpackPlugin({
// 产物(最终模板)输出路径
filename: path.resolve(process.cwd(), "./app/public/dist/", `${entryName}.tpl`),
// 指定要使用的模板文件
template: path.resolve(__dirname, '../../view/entry.tpl'),
// 要注入的代码块
chunks: [entryName]
})
)
}
{
// 入口配置
entry: Object.assign({}, elpisPageEntries, businessPageEntries),
}
{
test: /\.js$/,
include: [
// 处理 elpis 目录
path.resolve(__dirname, '../../pages'),
// 处理 业务 目录
path.resolve(process.cwd(), './app/pages')
],
use: {
loader: require.resolve('babel-loader'),
},
}
alias: {
'vue': require.resolve('vue'),
'@babel/runtime/helpers/toConsumableArray': require.resolve('@babel/runtime/helpers/toConsumableArray'),
'@babel/runtime/helpers/asyncToGenerator': require.resolve('@babel/runtime/helpers/asyncToGenerator'),
'@babel/runtime/regenerator': require.resolve('@babel/runtime/regenerator'),
$elpisPages: path.resolve(__dirname, '../../pages'),
$elpisCommon: path.resolve(__dirname, '../../pages/common'),
$elpisCurl: path.resolve(__dirname, '../../pages/common/curl.js'),
$elpisUtils: path.resolve(__dirname, '../../pages/common/utils.js'),
$elpisWidgets: path.resolve(__dirname, '../../pages/widgets'),
$elpisHeaderContainer: path.resolve(__dirname, '../../pages/widgets/header-container/header-container.vue'),
$elpisSiderContainer: path.resolve(__dirname, '../../pages/widgets/sider-container/sider-container.vue'),
$elpisSchemaTable: path.resolve(__dirname, '../../pages/widgets/schema-table/schema-table.vue'),
$elpisSchemaForm: path.resolve(__dirname, '../../pages/widgets/schema-form/schema-form.vue'),
$elpisSchemaSearchBar: path.resolve(__dirname, '../../pages/widgets/schema-search-bar/schema-search-bar.vue'),
$elpisStore: path.resolve(__dirname, '../../pages/store'),
$elpisBoot: path.resolve(__dirname, '../../pages/boot.js'),
}各种定制化钩子能力支持
elpis 提供的自定义动态组件需要对外提供 业务拓展,支持定制化页面
例如
component组件 config 配置文件
javascript
import createForm from './create-form/create-form.vue';
// 业务扩展 component 配置
import BusinessComponentConfig from '$businessComponentConfig';
const ComponentConfig = {
createForm: {
component: createForm
},
// ...
}
export default {
...ComponentConfig,
...BusinessComponentConfig
};javascript
resolve: {
extensions: ['.js', '.vue', '.less', '.css'],
alias: (() => {
const aliasMap = {};
const blankModulePath = path.resolve(__dirname, '../libs/blank.js');
// dashboard 路由扩展配置
const businessDashboardRouterConfig = path.resolve(process.cwd(), './app/pages/dashboard/router.js');
aliasMap['$businessDashboardRouterConfig'] = fs.existsSync(businessDashboardRouterConfig) ? businessDashboardRouterConfig : blankModulePath;
// schema-view component 扩展配置
const businessComponentConfig = path.resolve(process.cwd(), './app/pages/dashboard/complex-view/schema-view/components/component.config.js');
aliasMap['$businessComponentConfig'] = fs.existsSync(businessComponentConfig) ? businessComponentConfig : blankModulePath;
// schema-form 扩展配置
const businessFormItemConfig = path.resolve(process.cwd(), './app/pages/widgets/schema-form/form-item-config.js');
aliasMap['$businessFormItemConfig'] = fs.existsSync(businessFormItemConfig) ? businessFormItemConfig : blankModulePath;
// schema-search-bar 扩展配置
const $businessSearchItemConfig = path.resolve(process.cwd(), './app/pages/widgets/schema-search-bar/search-item-config.js');
aliasMap['$businessSearchItemConfig'] = fs.existsSync($businessSearchItemConfig) ? $businessSearchItemConfig : blankModulePath;
return {
// ...
...aliasMap,
}
})(),
}如果用户没有配置对应的 config 文件,则使用默认的 blank.js 防止 webpack 打包时报错
- 业务拓展路由
javascript
import boot from '$elpisPages/boot.js'
import dashboard from './dashboard.vue'
import businessDashboardRouterConfig from '$businessDashboardRouterConfig';
const routes = [];
// 头部菜单路由
routes.push({
path: '/view/dashboard/iframe',
component: () => import('./complex-view/iframe-view/iframe-view.vue')
});
const siderRoutes = [
{
path: 'iframe',
component: () => import('./complex-view/iframe-view/iframe-view.vue')
},
{
path: 'schema',
component: () => import('./complex-view/schema-view/schema-view.vue')
},
]
// 侧边栏菜单路由
routes.push({
path: '/view/dashboard/sider',
component: () => import('./complex-view/sider-view/sider-view.vue'),
children: siderRoutes
})
// 业务拓展路由
if (typeof businessDashboardRouterConfig === 'function') {
businessDashboardRouterConfig({ routes, siderRoutes });
}
boot(dashboard, { routes });npm 包发布
package.json中修改name: '@wangzhengmincoder/elpis'- 暴露服务端基础配置
javascript
{
/**
* 服务端基础
*/
Controller: {
Base: require('./app/controller/base.js'),
},
Service: {
Base: require('./app/service/base.js'),
},
}npm login登录npm publish --access public发布- 遇到报错
text
npm notice Publishing to https://registry.npmjs.org/ with tag latest and public access npm error code E403 npm error 403
403 Forbidden - PUT https://registry.npmjs.org/@wangzhengmincoder%2felpis
- Two-factor authentication or granular access token with bypass 2fa enabled is required to publish packages.
npm error 403 In most cases, you or one of your dependencies are requesting npm error 403 a package version that
is forbidden by your security policy, or npm error 403 on a server you do not have access to.
npm error A complete log of this run can be found in: C:\Users\dhswz\AppData\Local\npm-cache\_logs\2026-01-27T08_56_57_750Z-debug-0.log只需要配置一下 npm 的 token
javascript
npm config set //registry.npmjs.org/:_authToken=新生成的Token