前言
话接上文,在上一个学习阶段中,elpis已经基本开发完成了,具备了动态生成页面和组件的能力,那么,在这一章节中,我们要做的就是把项目进行改造,并发布到npm上去,供大家进行使用
附上我的npm包:npm i @yxcheng/augustus
抽离过程
一:本地连接
在发布之前,我们需要先在本地进行调试,这个命令是npm link,所以我们需要在本地新建一个demo文件,然后通过npm link xxx(此处为npm用户的名称+包名) 进行连接
二:相关文件处理
以controller为例,当我们把elpis当做一个公共包来使用时,我们需要注意的是不仅要挂载业务下的controller到app,还需兼容自身的controller,所以对如下代码进行改造
module.exports = (app) => {
const controller = {};
// 读取augusts/controller/**/**.js目录下的文件
const augustsBusinessControllPath = path.resolve(
__dirname,
`..${sep}..${sep}app${sep}controller`,
);
const augustsFileList = glob.sync(
path.resolve(augustsBusinessControllPath, `.${sep}**${sep}**.js`),
);
augustsFileList.forEach((file) => {
handleFile(file);
});
// 读取业务/controller/**/**.js目录下的文件
const businessControllPath = path.resolve(
app.businessPath,
`.${sep}controller`,
);
const businessFileList = glob.sync(
path.resolve(businessControllPath, `.${sep}**${sep}**.js`),
);
businessFileList.forEach((file) => {
handleFile(file);
});
// 把内容加载到app.controller下
function handleFile(file) {
// 提取文件名
let name = path.resolve(file);
// 截取路径 app/controller/custom-module/custom-controller.js => custom-module/custom-controller
name = name.substring(
name.lastIndexOf(`controller${sep}`) + `controller${sep}`.length,
name.lastIndexOf("."),
);
// 把’-‘统一改为驼峰式
name = name.replace(/[_-][a-z]/gi, (s) => s.substring(1).toUpperCase());
// 挂在controller到内存app对象中
let tempController = controller;
const names = name.split(sep);
for (let i = 0, len = names.length; i < len; i++) {
if (i === len - 1) {
const ControllerMoule = require(path.resolve(file))(app);
tempController[names[i]] = new ControllerMoule();
} else {
if (!tempController[names[i]]) {
tempController[names[i]] = {};
}
tempController = tempController[names[i]];
}
}
}
app.controller = controller;
};
对于自身和即将使用的业务文件进行同样的处理,包括extend.js,config.js,middleware.js,router-schema.js,router.js,service.js文件同样依次处理
webpack部分的处理
1.将 dev 和 prod 两个打包方式暴露出去,然后项目就可以通过该函数去执行相应的打包方式
const FEBuildDev = require("./app/webpack/dev.js");
const FEBuildProd = require("./app/webpack/prod.js");
module.exports = {
/**
* 服务端基础
*/
Controller: {
Base: require("./app/controller/base.js"),
},
Service: {
Base: require("./app/service/base.js"),
},
/**
* 编译构建前端工程
* @params env 环境变量 local/prod
*/
frontendBuild(env) {
if (env === "local") {
FEBuildDev();
} else if (env === "production") {
FEBuildProd();
}
},
};
2.webpack.base的修改
resolve: {
extensions: [".js", ".vue", ".less", ".css"],
alias: (() => {
const alaisMap = {};
const blankModulePath = path.resolve(__dirname, "../libs/blank.js");
// dashboard路由拓展配置
const businessDashboardRouterConfig = path.resolve(
process.cwd(),
"./app/pages/dashboard/router.js",
);
alaisMap["$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",
);
alaisMap["$businessComponentConfig"] = fs.existsSync(
businessComponentConfig,
)
? businessComponentConfig
: blankModulePath;
// schema-form 扩展配置
const businessFormItemConfig = path.resolve(
process.cwd(),
"./app/pages/widgets/schema-form/form-item-config.js",
);
alaisMap["$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",
);
alaisMap["$businessSearchItemConfig"] = fs.existsSync(
businessSearchItemConfig,
)
? businessSearchItemConfig
: blankModulePath;
return {
vue: require.resolve("vue"),
"@babel/runtime/regenerator":
require.resolve("@babel/runtime/regenerator"),
"@babel/runtime/helpers/asyncToGenerator":
require.resolve("@babel/runtime/helpers/asyncToGenerator"),
$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"),
...alaisMap,
};
})(),
},
配置公共组件部分
import createForm from "./create-form/create-form.vue";
import editForm from "./edit-form/edit-form.vue";
import detailPanel from "./detail-panel/detail-panel.vue";
// 业务拓展component配置
import BusinessComponentConfig from "$businessComponentConfig";
const componentConfig = {
createForm: {
component: createForm,
},
editForm: {
component: editForm,
},
detailPanel: {
component: detailPanel,
},
};
export default {
...componentConfig,
...BusinessComponentConfig,
};
这里面改动的部分在于
- 路径别名修改,使用之前的路径别名会定位到我们的业务目录下,所以需要进行修改
- 由于我们的包中提供了自己的 loader ,所以需要使用
require.resolve('相关loader')包裹起来 - 配置外部公共组件,由于我们需要提供给使用者一个扩展公共组件的方法,因此除了原先 elpis 自带的公共组件路径外,还需要配置一个能够读取项目(业务)公共组件路径的方法
npm包发布
- 注册npm账号
- 查看是否具有镜像源,如果存在则需要清空
- 登录npm并确认npm名
- 发布npm包
// 查看镜像源
npm config get
// 清空镜像源
npm config set registry
// 登录 npm
npm login //会给邮箱发送验证邮件
// 确定当前登录的 npm 账号和package.json里面的name是否对应
npm whoami
// 发布 npm
npm publish --access public // 公有化提交,第一次提交需要进行公有化提交
npm publish
一开始npm publish --access public一直403提交不上去,查看是因为two-factor authentication
查了一下
自2025年底起,npm 为了安全,采取了两项关键措施:
- 废弃经典令牌:传统的
npm login方式或 Classic Token 已无法满足发布要求。 - 强制2FA或等效方案:所有粒度令牌(Granular Token)默认需要2FA验证。然而,CLI工具只能接受来自验证器应用(如 Google Authenticator)的 TOTP 码,无法使用 Security Key 或恢复码。这导致了一个矛盾:即使您在网页端启用了2FA,如果只配置了 Security Key 而未启用 TOTP,CLI 发布依然会失败 解决方法如下,亲测有效 生成正确的令牌:
- 登录 npmjs.com,点击右上角头像进入 “Access Tokens”。
- 点击 “Generate New Token” ,对permission需要允许读写,然后日期尽量长一点。
- 配置权限:为您要发布的包
@yxcheng/augustus选择 “Read and Write” 或 “Publish” 权限。 - 核心步骤:务必勾选 “Bypass two-factor authentication” 选项。这是解决403错误的关键。
- 生成并复制以
npm_开头的长字符串令牌(只显示一次,请妥善保存) 写入令牌 npm config set //registry.npmjs.org/:_authToken=你的token
执行完之后可以通过npm whoami验证是否生效,能输出用户名说明 OK,之后再npm publish --access public应该问题就没有了