Skip to content

Commit

Permalink
feat(zhi-core): smallest runnable kernel
Browse files Browse the repository at this point in the history
  • Loading branch information
terwer committed May 5, 2023
1 parent 4250a9f commit 639e79c
Show file tree
Hide file tree
Showing 9 changed files with 15,252 additions and 1 deletion.
46 changes: 46 additions & 0 deletions packages/zhi-core/src/lib/core/Bootstrap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2023, Terwer . All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Terwer designates this
* particular file as subject to the "Classpath" exception as provided
* by Terwer in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Terwer, Shenzhen, Guangdong, China, youweics@163.com
* or visit www.terwer.space if you need additional information or have any
* questions.
*/

import Lifecycle from "./lifecycle"
import DependencyItem from "../models/DependencyItem"

/**
* zhi主题唯一激活入口
*
* @author terwer
* @since 0.1.0
*/
class Bootstrap {
private static lifecycle = new Lifecycle()

/**
* 主题激活
*/
public static async start(): Promise<DependencyItem[]> {
return await this.lifecycle.load()
}
}

export default Bootstrap
164 changes: 164 additions & 0 deletions packages/zhi-core/src/lib/core/lifecycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* Copyright (c) 2023, Terwer . All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Terwer designates this
* particular file as subject to the "Classpath" exception as provided
* by Terwer in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Terwer, Shenzhen, Guangdong, China, youweics@163.com
* or visit www.terwer.space if you need additional information or have any
* questions.
*/

import DependencyItem from "../models/DependencyItem"
import { SiyuanDevice } from "@siyuan-community/zhi-device"
import ZhiCoreUtil from "../util/ZhiCoreUtil"

/**
* zhi主题统一生命周期管理
*
* @author terwer
* @version 0.1.0
* @since 0.1.0
*/
class Lifecycle {
private readonly logger
private readonly common
private ZHI_JSON_SCHEMA = "config/zhi-schema.js"
private ZHI_JSON = "config/zhi.js"

constructor() {
this.logger = ZhiCoreUtil.zhiLog("lifecycle")
this.common = ZhiCoreUtil.zhiCommon()
}

/**
* 加载依赖,核心加载顺序按照下面描述的顺序加载,内部的加载顺序由 order 字段决定,
* 所有依赖定义在主题根目录的 `zhi.json`
*
* ```
* 加载顺序如下:
* 1 核心模块-require-hacker、infra、browser-window、插件系统、内部插件
* 2 后端模块
* 3 前端模块
* 4 第三方库
* ```
*/
public async load() {
const allImports = <DependencyItem[]>[]

// json-schema 校验
// json读取
// const { default: data } = await import("/appearance/themes/zhi/zhi.js");data
// const { default: data } = await import("/appearance/themes/zhi/zhi.json", { assert: { type: "json" } });data
const zhiSchemaPath = SiyuanDevice.getZhiThemeImportJsPath(this.ZHI_JSON_SCHEMA)
const { default: zhiSchema } = await import(zhiSchemaPath)
const zhiJsonPath = SiyuanDevice.getZhiThemeImportJsPath(this.ZHI_JSON)
const { default: zhiJson } = await import(zhiJsonPath)
this.logger.debug("zhiSchema=>", zhiSchema)
this.logger.debug("zhiJson=>", zhiJson)
const valiResult = this.common.jsonUtil.validateObjectSchema(zhiSchema, zhiJson)
if (!valiResult.valid) {
throw new Error(
`${this.ZHI_JSON} is not valid, error msg: ${valiResult.error ?? "None"}, please check ${this.ZHI_JSON_SCHEMA}`
)
} else {
this.logger.info(`Success, ${this.ZHI_JSON} is ok`)
}

// 解析json
// 核心模块
const cores = zhiJson.dependencies.core
const coreModuleImports = await this.loadCoreModules(cores)
// 后端模块
const servers = zhiJson.dependencies.server
const backendImports = await this.loadBackendModules(servers)
// 前端模块
const webs = zhiJson.dependencies.web
const frontendImports = await this.loadFrontendModules(webs)
// 第三方组件
const vendors = zhiJson.dependencies.vendor
const vendorImports = await this.loadVendors(vendors)

return allImports.concat(coreModuleImports).concat(backendImports).concat(frontendImports).concat(vendorImports)
}

/**
* 加载核心模块
*
* @private
*/
private async loadCoreModules(deps: object[]): Promise<DependencyItem[]> {
const coreModulesImports: DependencyItem[] = deps.map((dep: object) => {
const dependency = new DependencyItem()
dependency.fromJson(dep)
return dependency
})

this.logger.info(`Registered ${coreModulesImports.length} Core modules`)
return coreModulesImports
}

/**
* 加载后端模块
*
* @private
*/
private async loadBackendModules(deps: object[]): Promise<DependencyItem[]> {
const backendModulesImports: DependencyItem[] = deps.map((dep: object) => {
const dependency = new DependencyItem()
dependency.fromJson(dep)
return dependency
})

this.logger.info(`Registered ${backendModulesImports.length} Backend modules`)
return backendModulesImports
}

/**
* 加载前端模块
*
* @private
*/
private async loadFrontendModules(deps: object[]): Promise<DependencyItem[]> {
const frontendModulesImports: DependencyItem[] = deps.map((dep: object) => {
const dependency = new DependencyItem()
dependency.fromJson(dep)
return dependency
})

this.logger.info(`Registered ${frontendModulesImports.length} Frontend modules`)
return frontendModulesImports
}

/**
* 加载第三方库
*
* @private
*/
private async loadVendors(deps: object[]): Promise<DependencyItem[]> {
const vendorImports: DependencyItem[] = deps.map((dep: object) => {
const dependency = new DependencyItem()
dependency.fromJson(dep)
return dependency
})

this.logger.info(`Registered ${vendorImports.length} Vendors`)
return vendorImports
}
}

export default Lifecycle
138 changes: 137 additions & 1 deletion packages/zhi-core/src/lib/zhi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@
* questions.
*/

import { DeviceTypeEnum } from "@siyuan-community/zhi-device"
import { BasePathTypeEnum, DeviceTypeEnum, SiyuanDevice } from "@siyuan-community/zhi-device"
import ZhiCoreUtil from "./util/ZhiCoreUtil"
import DependencyItem from "./models/DependencyItem"
import Bootstrap from "./core/Bootstrap"

class Zhi {
private readonly logger
private readonly common

private readonly runAs

Expand All @@ -50,17 +53,150 @@ class Zhi {
*/
constructor(runAs: DeviceTypeEnum) {
this.logger = ZhiCoreUtil.zhiLog("zhi-core")
this.common = ZhiCoreUtil.zhiCommon()

this.runAs = runAs ?? DeviceTypeEnum.DeviceType_Node
}

private async main(args: string[]): Promise<DependencyItem[]> {
this.logger.debug("Parsing args...", args)
return await Bootstrap.start()
}

/**
* 主流程加载
*/
public async init(): Promise<void> {
try {
this.logger.info(`Zhi Theme runAs ${this.runAs}`)

// 平台检测
if (
this.runAs !== DeviceTypeEnum.DeviceType_Siyuan_MainWin &&
this.runAs !== DeviceTypeEnum.DeviceType_Siyuan_Browser
) {
this.logger.warn(
`Zhi Theme can only run as ${DeviceTypeEnum.DeviceType_Siyuan_MainWin} or ${DeviceTypeEnum.DeviceType_Siyuan_Browser}`
)
return
}

// 检测内核版本
const kernelVersion = SiyuanDevice.siyuanWindow().siyuan.config.system.kernelVersion
if (this.common.versionUtil.lesser(kernelVersion, this.SUPPORTED_THEME_VERSION)) {
const errMsg = this.common.strUtil.f(
"Your siyuan-note kernel version {0} is not supported by zhi theme, style will look weird, you must install siyuan-note {1}+ to use zhi-theme",
kernelVersion,
this.SUPPORTED_THEME_VERSION
)
this.logger.error(errMsg)
// this.kernelApi.pushErrMsg({
// msg: errMsg,
// })
return
}

if (this.common.versionUtil.lesser(kernelVersion, this.SUPPORTED_KERNEL_VERSION)) {
const warnMsg = this.common.strUtil.f(
"Your siyuan-note kernel version {0} is too low, plugin system will not work, you must install siyuan-note {1}+ to use plugin feature",
kernelVersion,
this.SUPPORTED_KERNEL_VERSION
)
this.logger.warn(warnMsg)
// this.kernelApi.pushMsg({
// msg: warnMsg,
// })
return
}

// 挂载一个日志对象,方便后续动态使用
if (typeof window !== "undefined") {
;(window as any).zhiLog = ZhiCoreUtil.zhiLog("zhi-core")
this.logger.info("ZhiLog mounted")
}
// 挂载一个require对象
if (typeof window !== "undefined") {
;(window as any).zhiRequire = function (libpath: string) {
return SiyuanDevice.requireLib(libpath, false, BasePathTypeEnum.BasePathType_ZhiTheme)
}
this.logger.info("zhiRequire mounted")
}
// 挂载一个import对象
if (typeof window !== "undefined") {
;(window as any).zhiImportPath = async function (libpath: string) {
return SiyuanDevice.getImportJsPath(libpath, BasePathTypeEnum.BasePathType_ZhiTheme)
}
this.logger.info("zhiImportPath mounted")
}

// 初始化第三方依赖
// import
// browser esm path: "/[libpath]"
// electron esm path: "/[libpath]"
// custom-path X
//
// require
// browser X
// electron cjs path: "[abspath][libpath]"
// custom-path require-hacker
const dynamicImports = await this.main([])
for (const item of dynamicImports) {
// 类型校验
if (item.format !== "esm" && item.format !== "cjs" && item.format !== "js") {
this.logger.warn("Only esm, cjs supported, skip this lib!", item.libpath)
continue
}

// 运行环境校验
if (!item.runAs.includes(this.runAs)) {
this.logger.debug(
`I'm sorry because current runtime is ${this.runAs}, while lib's define runtime is ${item.runAs}`
)
this.logger.warn(`This lib can only run at ${item.runAs}, will skip!Lib is=>${item.libpath}`)
continue
}

this.logger.info(`Loading modules form zhi => ${item.libpath}`)
let lib
if (item.importType == "import") {
const libPath = SiyuanDevice.getImportJsPath(item.libpath, item.baseType)
lib = await import(libPath)
this.logger.debug(`Importing lib ${item.libpath} with basePath of ${item.baseType} ...`)
} else {
lib = SiyuanDevice.requireLib(item.libpath, false, item.baseType)
this.logger.debug(`Requiring lib ${item.libpath} with basePath of ${item.baseType} ...`)
}
// 如果有初始化方法,进行初始化
if (lib) {
const libObj = lib
this.logger.debug(`Current ${item.importType} lib ${item.libpath} Obj=>`, typeof libObj)
if (typeof libObj == "function") {
await libObj()
this.logger.info(`Inited ${item.libpath} with default function`)
} else {
if (libObj.init) {
const res = await libObj.init()
if (res) {
this.logger.info(`Detected output from ${item.importType} lib ${item.libpath}=>`, res)
}
this.logger.info(`Inited ${item.libpath} with init function`)
} else {
if (libObj.default) {
const res = await libObj.default()
if (res) {
this.logger.info(`Detected output from ${item.importType} lib ${item.libpath}=>`, res)
}
this.logger.info(`Inited ${item.libpath} with default function`)
}
this.logger.info(`No init method for ${item.importType} ${item.libpath}`)
}
}
} else {
this.logger.debug(`Lib entry is not a function => ${item.importType} ${item.libpath}`, lib)
}
this.logger.info(`Success ${item.importType} ${item.libpath}`)
}

this.logger.info("Zhi Theme inited")
} catch (e) {
this.logger.error("Zhi Theme load error=>", e)
Expand Down
Loading

0 comments on commit 639e79c

Please sign in to comment.