前言
npm 是一个沉淀了 80% 中后台系统的重复劳动且提供 20% 拓展能力的一个框架。前面的章节已经介绍了核心框架elpis-core,前端工程化,页面搭建等相关内容。本文将介绍如何将 Elpis 基础框架功能抽离封装成 npm 包,方便在客制化项目中直接引用,实现业务代码与基础架构的分离。
elpis-core 抽离
入口改造
将相关方法在入口处暴露,提供给接入方,方便使用。此处暴露了以下几个方法:
serverStart: 启动 elpis;frontedBuild: 入参env,根据环境变量走不同的 webpack 配置;不仅仅使用 elpis 中的 webpack 配置,同时也会结合接入方对应目录下的配置。后文会有说明;Service和Controller: 服务端基础方法;
const ElpisCore = require('./elpis-core')
// 引入前端工程化构建方法
const FEBuildDev = require('./app/webpack/dev')
const FEBuildProd = require('./app/webpack/prod')
module.exports = {
/**
* 启动 elpis
* @param {Object} options - 项目配置,透传到ElpisCore
*/
serverStart(options = {}) {
const app = ElpisCore.start(options)
return app
},
/**
* 编译构建前端工程
* @param {String} env 环境变量 dev/prod
*/
frontedBuild(env) {
if (env === 'dev') {
FEBuildDev()
} else if (env === 'prod') {
FEBuildProd()
}
},
/**
* 服务端基础
*/
Controller: {
Base: require('./app/controller/base')
},
Service: {
Base: require('./app/service/base')
}
}
loader 改造
elpis-core 是一个约定大于配置的服务框架,在使用时,只需要在对应的目录下写下相对应的文件,elpis-core 会帮你配置好其他的细节,并将内容挂载到 app 实例下。为此,不仅要处理 elpis 本身的loader(常用__dirname标识文件位置),也要加载接入方业务的 loader(常用process.cwd()标识文件位置)。遂做了以下改造,以 controller 为例。
// 读取 elpis/app/controller/**/**.js 下所有的文件
const elpisControllerPath = path.resolve(
__dirname,
`..${sep}..${sep}app${sep}controller`
);
const elpisFileList = glob.sync(
path.resolve(elpisControllerPath, `.${sep}**${sep}**.js`)
);
elpisFileList.forEach((file) => {
handleFile(file);
});
// 读取 业务/app/controller/**/**.js 下所有的文件
const businessControllerPath = path.resolve(
app.businessPath,
`.${sep}controller`
);
const businessFileList = glob.sync(
path.resolve(businessControllerPath, `.${sep}**${sep}**.js`)
);
businessFileList.forEach((file) => {
handleFile(file);
});
webpack 工程化抽离
别名配置
将 process.cwd() 改为 __dirname。两者区别
使用require.resolve() 提升可移植性
在开发 elpis 时使用了第三方依赖。当讲 elpis 作为一个 npm 包发布时,不能要求接入方再去安装 elpis 需要的第三方依赖。因此改用require.resolve()解析 elpis 中使用到的第三方依赖的路径并使用他们。在碰到 can not resolve module 或 第三方那个依赖加载报错时,尤其要注意这点。
使用 webpack
如前文所述,会暴露出 elpis 本地开发和生产打包的配置,供接入方使用。同时支持结合使用接入方在约定目录下自定义的 webpack 配置
// 动态构造 elpis entry入口elpisPageEntries 和 elpisHtmlWebpackPluginList
const elpisPageEntries = {};
const elpisHtmlWebpackPluginList = [];
// path.resolve 构建绝对路径,entryStr:/pages/**/entry.*.js。 *用于给glob做匹配
const elpisEntryStr = path.resolve(__dirname, "../../pages/**/entry.*.js");
// 获取elpis/app/pages 目录下所有入口文件(entry.**.js)
glob.sync(elpisEntryStr).forEach((file) => {
handleFile(file, elpisPageEntries, elpisHtmlWebpackPluginList);
});
// 动态构造业务 business entry 入口 businessPageEntries 和 businessHtmlWebpackPluginList
const businessPageEntries = {};
const businessHtmlWebpackPluginList = [];
// path.resolve 构建绝对路径,entryStr:/pages/**/entry.*.js。 *用于给glob做匹配
const businessEntryStr = path.resolve(
process.cwd(),
"./app/pages/**/entry.*.js"
);
// 获取elpis/app/pages 目录下所有入口文件(entry.**.js)
glob.sync(businessEntryStr).forEach((file) => {
handleFile(file, businessPageEntries, businessHtmlWebpackPluginList);
});
// 加载业务webpack 配置
let businessWebpackConfig = {}
try {
businessWebpackConfig = require(path.resolve(process.cwd(), './app/webpack.config.js'))
} catch (error) {
console.error('加载业务webpack配置失败:', error)
}
// const elpisWebpackConfig = ...
module.exports = merge.smart(elpisWebpackConfig, businessWebpackConfig)
依赖更改
除部分 eslint、git、单测相关的开发依赖外,其他的开发依赖 devDependencies 都转为生产依赖dependencies
terser错误
记录一下自己踩的坑:在使用 npm link 时能正常打包。但发布成 npm 包以后再执行 npm run build 就会报错 Error in xxx.js from terser 和 xxx undefined in terser。这是因为低版本的 terser-webpack-plugin 和在压缩代码时对于 es6 语法支持不完全导致的。升级 terser-webpack-plugin 版本并开启 ecma 即可
optimization: {
// 使用 TerserWebpackPlugin 的并发和缓存,提升压缩阶段的性能
minimize: true,
minimizer: [
new TerserWebpackPlugin({
parallel: true, // 利用多核CPU的优势濑加快压缩速度
terserOptions: {
ecma: 2020,
compress: {
drop_console: true, // 移除所有的console语句
},
},
}),
// 优化并压缩 css 资源
new CssMinimizerPlugin(),
],
}
页面与组件的抽离与拓展
如图,elpis 支持全新页面,dashboard页面的拓展,也支持自定义组件(scheme-view 组件、form 组件、search-bar 组件)的拓展,只需在约定目录下创建文件即可。具体配置可点我查看。这儿再一次体现 elpis 的强大拓展性和约定大于配置的思想。
支持页面拓展
- 在
webpack.base.js中,定义了接入方业务路由的别名$businessDashboardRouteConfig,指向path.resolve(process.cwd(), "./app/pages/dashboard/router.js")该目录, 同时为了防止未按规范建立目录结构而报错,做了相关错误处理。
// 业务 dashboard 路由扩展配置
const businessDashboardRouteConfig = path.resolve(
process.cwd(), "./app/pages/dashboard/router.js"
);
aliasMap["$businessDashboardRouteConfig"] = fs.existsSync(businessDashboardRouteConfig)
? businessDashboardRouteConfig
: blankModulePath;
- 在 elpis 中,将
routes和siderRoutes传给接入方
import businessDashboardRouteConfig from "$businessDashboardRouteConfig";
import boot from "$elpisPages/boot";
import dashboard from "./dashboard.vue";
const routes = [ ... ]
const siderRoutes = [ ... ]
if (
businessDashboardRouteConfig &&
typeof businessDashboardRouteConfig === "function"
) {
businessDashboardRouteConfig({ routes, siderRoutes });
}
boot(dashboard, { routes });
- 在接入方业务路由中,接收
routes和siderRoutes参数,并添加业务路由。
module.exports = ({ routes, siderRoutes }) => {
// 头部路由
routes.push({
path: "/view/dashboard/todo",
component: () => import("./todo/todo.vue"),
});
// 侧边栏路由
siderRoutes.push({
path: "todo",
component: () => import("./todo/todo.vue"),
});
};
这样在 elpis 中就能拿到接入方业务路由,也就支持了接入方拓展页面。
支持组件拓展
- 以 scheme-view 组件为例,与页面拓展类似, 在
webpack.base.js中,定义了接入方业务路由的别名$businessComponentConfig,指向path.resolve(process.cwd(), "./app/pages/dashboard/complex-view/schema-view/components/component-config.js")该目录, 同时为了防止未按规范建立目录结构而报错,做了相关错误处理。 - 在 elpis
app/pages/dashboard/complex-view/schema-view/components/component-config.js中引入$businessComponentConfig,就可以拿到接入方自定义的业务组件
import createForm from "./create-form/create-form.vue";
import detailPanel from "./detail-panel/detail-panel.vue";
import editForm from "./edit-form/edit-form.vue";
// 业务拓展 components 配置
import BusinessComponentConfig from "$businessComponentConfig";
const ComponentConfig = {
createForm: {
component: createForm,
},
editForm: {
component: editForm,
},
detailPanel: {
component: detailPanel,
},
};
export default {
...ComponentConfig,
...BusinessComponentConfig,
};
页面抽离
由于 elpis 是一个普适的框架,原在开发中留有相关业务,测试的页面如 project-list、model配置都应该抽离。elpis 中只保留基础代码。
npm 发布
npm 开发环境调试
以上这些步骤,是如何开发调试通过的呢?毕竟需要确保正确才能发布到npm,这就涉及到开发环境使用本地 npm 包:
-
创建一个工程当做需要使用 elpis 的接入方,如 elpis-demo;
-
在 elpis 目录下使用
npm link创建 elpis 的全局软链; -
在 elpis-demo 目录下使用
npm link elpis进行连接,此时会在 elpis-demo 的 node_modules 目录下找到 elpis;npm ls <package-name>: 验证链接是否成功npm ls --link: 查看当前项目中通过 npm link 链接的包- 开发完成后,记得解除链接并清理缓存:
npm unlink <package-name>和npm cache clean --force
npm 发布步骤
- 首次发布需到 npm 官网注册账号;
- 将当前镜像源设置为官方镜像源
npm config set registry;不传参则默认官方镜像 - 登录 npm:
npm login; - 查看 npm 账号是否正确
npm whoami; - 确认 package.json 中版本号正确,每次发布都需要使用新版本号;
- 发布
npm publish。首次发布,需要加上--access public参数表示包发布为公共包,允许所有人安装和使用;