Skip to content

Commit

Permalink
feat(plugin-compiler): 新增多端组件构建模式支持 (#75)
Browse files Browse the repository at this point in the history
* feat(plugin-compiler): 添加 compileType: components 组件构建模式

* feat(utils): 添加组件构建模式的 CompileTypes 配置项

* feat(website): 添加 compileType 为 components 的配置说明

* feat(plugin-compiler): 添加组件 main 入口文件编译

* feat(utils): 修改 CompileTypes 的配置名 component

* feat(website): 添加 customEntries 对于组件配置说明

* feat(plugin-compiler): 修改组件编译的 publicComponents 配置模式,支持数组和对象两种方式

* feat(plugin-compiler): 修复 GitHub 上提出的 commented

* feat(plugin-compiler): 删除类型声明,由于前置已经判断

* feat(plugin-compiler): 跳过组件编译时对 app.x 的处理逻辑
  • Loading branch information
BboyZaki committed Jul 13, 2023
1 parent 9cf6693 commit 3307838
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Mor 是一套基于小程序 DSL (支付宝或微信) 的框架。他的易用

## 部分使用案例

<img src="https://img.alicdn.com/imgextra/i3/O1CN015mLk6V1d6EkhIyNL0_!!6000000003686-0-tps-1520-3499.jpg" />
<img src="https://img.alicdn.com/imgextra/i2/O1CN01nT9RLK1wJ2WjD09Zc_!!6000000006286-0-tps-1520-3500.jpg" />

## 贡献

Expand Down
25 changes: 20 additions & 5 deletions packages/plugin-compiler/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,11 @@ export function applyDefaults(
userConfig.compileType === 'plugin'
) {
userConfig.processPlaceholderComponents = true
} else if (userConfig.compileType === 'subpackage') {
userConfig.processPlaceholderComponents = true
} else if (
userConfig.compileType === 'subpackage' ||
userConfig.compileType === 'component'
) {
userConfig.processPlaceholderComponents = false
}
}

Expand Down Expand Up @@ -520,10 +523,13 @@ export function generateChunkLoadingGlobal(
const globalNameSuffix = userConfig.globalNameSuffix

if (userConfig.compileType !== CompileTypes.miniprogram) {
// s 代表分包
// p 代表插件
// s: 代表分包; p: 代表插件; c: 代表组件
const appType =
userConfig.compileType === CompileTypes.subpackage ? 's' : 'p'
userConfig.compileType === CompileTypes.subpackage
? 's'
: userConfig.compileType === CompileTypes.plugin
? 'p'
: 'c'
segments.push(appType)

// 未定义 globalNameSuffix 时尝试以 package.json 的 name 作为区分,避免冲突
Expand All @@ -540,6 +546,15 @@ export function generateChunkLoadingGlobal(
// 追加全局文件名称后缀,用于避免 chunk loading global 重复
if (globalNameSuffix) segments.push(globalNameSuffix)

// 针对组件添加其版本号,用于避免不同版本的组件冲突
if (userConfig.compileType === CompileTypes.component) {
const pkgVersion = ((runner.config.pkg?.version || '') as string).replace(
/[.-]/g,
'_'
)
segments.push(pkgVersion)
}

return segments.join('_')
}

Expand Down
6 changes: 4 additions & 2 deletions packages/plugin-compiler/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ export const RootEntryConfigFiles = objectEnum([
export const CompileTypeDescriptions = {
miniprogram: '小程序',
plugin: '插件',
subpackage: '分包'
subpackage: '分包',
component: '组件'
} as const

/**
Expand Down Expand Up @@ -913,6 +914,7 @@ export const CompilerUserConfigSchema = z.object({
'app.json': z.string().optional(),
'subpackage.json': z.string().optional(),
'plugin.json': z.string().optional(),
'component.json': z.string().optional(),
pages: z.array(z.string()).optional(),
components: z.array(z.string()).optional()
})
Expand Down Expand Up @@ -1009,7 +1011,7 @@ export const CompilerUserConfigSchema = z.object({
/**
* 是否处理 componentPlaceholder 中的组件
* compileType 为 miniprogram 或 plugin 时默认为 true
* compileType 为 subpackage 时 默认为 false
* compileType 为 subpackage 或 component 时默认为 false
*/
processPlaceholderComponents: z.boolean().optional(),

Expand Down
123 changes: 117 additions & 6 deletions packages/plugin-compiler/src/entries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { scriptTransformer } from '../transformers/scriptTransformer'
import { templateTransformer } from '../transformers/templateTransformer'
import {
IAppConfig,
IComponentsConfig,
IPluginConfig,
ISubPackageConfig,
IUsingComponentConfig
Expand Down Expand Up @@ -827,7 +828,7 @@ export class EntryBuilder implements SupportExts, EntryBuilderHelpers {
/**
* 构建 entry, 同时处理文件维度的条件编译
* bundle 和 transform 编译模式是通过
* app.json / subpackage.json / plugin.json 等入口来构建 entry
* app.json / subpackage.json / plugin.json / component.json 等入口来构建 entry
* 区别是 bundle 会将 npm 组件文件拷贝至 outputPath/npm_components 中
* 而 transform 不强制 入口文件存在,入口文件存在时会从入口文件分析依赖树
* 入口文件不存在时,则通过 glob 的方式来获取所有文件
Expand Down Expand Up @@ -1798,6 +1799,7 @@ export class EntryBuilder implements SupportExts, EntryBuilderHelpers {
* - miniprogram: app.json
* - subpackage: subpackage.json
* - plugin: plugin.json
* - component: component.json
*
* 注意: compileType 为 subpackage 时, 会优先从 context 中获取 subpackageJson
*
Expand Down Expand Up @@ -1847,7 +1849,7 @@ export class EntryBuilder implements SupportExts, EntryBuilderHelpers {
// 尝试载入全局文件
await this.tryAddGlobalFiles()

// 全局 entry 通常指向 app.json 或 subpackage.json 或 plugin.json
// 全局 entry 通常指向 app.json 或 subpackage.json 或 plugin.json 或 component.json
let globalEntry: EntryItem

// 先载入 app.json
Expand Down Expand Up @@ -1971,6 +1973,37 @@ export class EntryBuilder implements SupportExts, EntryBuilderHelpers {
}
}
}
}

// 组件构建
else if (compileType === CompileTypes.component) {
const componentEntry = customEntries['component.json'] || 'component'
const searchPaths = generateSearchPaths(componentEntry)
const componentJsonPath = await this.tryReachFileByExts(
componentEntry,
this.configWithConditionalExts,
searchPaths,
null,
searchPaths
)

if (componentJsonPath) {
const componentJson = await this.readAndPreprocessJsonLikeFile(
componentJsonPath
)
globalEntry = await this.buildByComponent(
componentJson,
componentJsonPath
)
} else {
if (isEntryFileRequired) {
logger.error(
`未找到 component.json 文件, 请检查是否在 ${this.srcPaths.join(
', '
)} 目录中`
)
}
}
} else {
throw new Error(`不支持的编译类型: ${compileType}, 请检查配置`)
}
Expand All @@ -1985,8 +2018,12 @@ export class EntryBuilder implements SupportExts, EntryBuilderHelpers {
private async tryAddProjectConfigFile() {
const { target, compileType } = this.userConfig

// 分包无需添加项目配置
if (compileType === CompileTypes.subpackage) return
// 分包和组件无需添加项目配置
if (
compileType === CompileTypes.subpackage ||
compileType === CompileTypes.component
)
return

const composedPlugins = getComposedCompilerPlugins()

Expand Down Expand Up @@ -2077,6 +2114,9 @@ export class EntryBuilder implements SupportExts, EntryBuilderHelpers {
private async tryAddGlobalFiles() {
const { compileType, mockAppEntry, globalNameSuffix } = this.userConfig

// 组件已提供 mian 定义的 exports 给外部调用的方法,暂不提供 app.x 相关逻辑
if (compileType === CompileTypes.component) return

const globalAppName = mockAppEntry || 'app'
let globalAppPrefix = ''

Expand Down Expand Up @@ -2442,6 +2482,75 @@ export class EntryBuilder implements SupportExts, EntryBuilderHelpers {
return entry
}

/**
* 解析 component.json 并构建 entry
* @param componentJson - 组件配置内容
* @param componentJsonPath - 组件配置路径
* @param parentEntry - 父级 entry
*/
async buildByComponent(
componentJson: IComponentsConfig,
componentJsonPath?: string,
parentEntry?: EntryItem
) {
let entry = parentEntry

// 添加组件 component.json 配置文件
if (componentJsonPath) {
const shouldAnalyze = await this.shouldAnalyzeFileDepenencies(
componentJsonPath
)

entry = this.addToEntry(
componentJsonPath,
EntryType.component,
'direct',
undefined,
null,
'component'
)

// 判断是否需要继续分析依赖
if (shouldAnalyze === false) return entry
}

// 添加组件的 main 文件
if (componentJson.main) {
const componentMainFile = await this.tryReachFileByExts(
componentJson.main,
this.scriptWithConditionalExts,
this.srcPaths
)

if (componentMainFile) {
this.addToEntry(componentMainFile, EntryType.component, 'direct', entry)
}
}

let publicComponentsMap: Record<string, string> = {}
let treatComponentNameAsCustomEntryName = false
if (Array.isArray(componentJson.publicComponents)) {
// publicComponents 写成数组时
componentJson.publicComponents.forEach((item) => {
publicComponentsMap[item] = item
})
} else if (_.isPlainObject(componentJson.publicComponents)) {
// publicComponents 写成对象时
publicComponentsMap = componentJson.publicComponents
treatComponentNameAsCustomEntryName = true
}
await this.addComponentEntries(
publicComponentsMap,
entry,
undefined,
true,
{},
treatComponentNameAsCustomEntryName
)

return entry
}

/**
* 添加页面 entries
* @param pages - 页面数组, 支持数组或对象, 对象可用于指定自定义 entry 名称
Expand Down Expand Up @@ -2497,13 +2606,15 @@ export class EntryBuilder implements SupportExts, EntryBuilderHelpers {
* @param rootDirs - 文件检索根目录, 可选, 默认为 srcPaths
* @param preferRelative - 是否倾向于按照相对目录解析组件
* @param componentPlaceholder - 占位组件配置
* @param treatComponentNameAsCustomEntryName - 是否将组件名称作为作为自定义 entry 名称,默认为 false
*/
async addComponentEntries(
components: Record<string, string> = {},
parentEntry: EntryItem,
rootDirs?: string[],
preferRelative?: boolean,
componentPlaceholder: Record<string, string> = {}
componentPlaceholder: Record<string, string> = {},
treatComponentNameAsCustomEntryName: boolean = false
) {
// 保存 entry 使用 自定义组件的映射
const usingComponentNames = Object.keys(components)
Expand Down Expand Up @@ -2556,7 +2667,7 @@ export class EntryBuilder implements SupportExts, EntryBuilderHelpers {
undefined,
undefined,
undefined,
undefined,
treatComponentNameAsCustomEntryName ? name : undefined,
rootDirs,
preferRelative
)
Expand Down
8 changes: 8 additions & 0 deletions packages/plugin-compiler/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ export interface IPluginConfig {
main?: string
}

// 小程序组件的 component.json 配置
export interface IComponentsConfig {
// 所有自定义组件
publicComponents?: string[] | Record<string, string>
// 组件面向第三方小程序的 js 接口
main?: string
}

export interface IUsingComponentConfig {
usingComponents?: Record<string, string>
componentPlaceholder?: Record<string, string>
Expand Down
8 changes: 7 additions & 1 deletion packages/utils/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,14 @@ export type CompileScriptTargetType = ObjectValues<typeof CompileScriptTarget>
* - miniprogram: 以小程序的方式编译或集成
* - plugin: 以插件的方式编译或集成
* - subpackage: 以插件的方式编译或集成
* - component: 以组件的方式编译
*/
export const CompileTypes = objectEnum(['miniprogram', 'plugin', 'subpackage'])
export const CompileTypes = objectEnum([
'miniprogram',
'plugin',
'subpackage',
'component'
])

/**
* Entry 文件类型
Expand Down
28 changes: 26 additions & 2 deletions website/docs/guides/basic/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,12 +327,14 @@ ts 编译配置, 大部分和 tsconfig 中的含义一致, 优先级高于 tscon
- `miniprogram`: 以小程序的方式编译,入口配置文件为 `app.json`
- `plugin`: 以插件的方式编译,入口配置文件为 `plugin.json`
- `subpackage`: 以分包的方式编译,入口配置文件为 `subpackage.json`
- `component`: 以组件的方式编译,入口配置文件为 `component.json`

编译类型,用于配置当前项目的产物形态,支持类型如下:

- `miniprogram`: 小程序形态,以 `app.json` 作为入口配置文件
- `plugin`: 小程序插件形态,以 `plugin.json` 作为入口配置文件
- `subpackage`: 小程序分包形态,以 `subpackage.json` 作为入口配置文件
- `component`: 小程序组件形态,以 `component.json` 作为入口配置文件

同一个项目可通过不同的 `compileType` 配合不同的入口配置文件输出不同的产物形态,有关多形态相互转换的进一步解释,可参见文档:[小程序形态一体化](/guides/advance/unity-of-forms.md)

Expand Down Expand Up @@ -390,6 +392,26 @@ ts 编译配置, 大部分和 tsconfig 中的含义一致, 优先级高于 tscon
"pages/profile/profile"
]
}

// 小程序组件 component.json 配置示例
// publicComponents 和 main 字段为 MorJS 自定义字段
{
// publicComponents 记录组件列表,标识 bundle 模式下哪些组件需要被编译
// publicComponents 有两种配置写法,写成数组时标识组件列表
"publicComponents": [
"components/banner/index",
"components/image/index",
"components/popup/index"
],
// publicComponents 写成 { key: value } 对象时,将 value 的组件编译到 key 对应的产物目录下
"publicComponents": {
"morjs-banner/index": "components/banner/index",
"morjs-image/index": "components/image/index",
"morjs-popup/index": "components/popup/index"
},
// main 用于配置组件初始化文件
"main": "index.js"
}
```

默认情况下不同 `compileType` 对应的入口配置文件会直接从 `srcPath``srcPaths` 所指定的源码目录中直接载入。
Expand Down Expand Up @@ -526,7 +548,7 @@ css 压缩器自定义配置, 使用时请结合 `cssMinimizer` 所指定的压

用于配置自定义入口文件,包含三种用途:

- 可用于指定入口配置文件的自定义文件路径,如 `app.json` / `plugin.json` / `subpackage.json`,参见 [compileType 配置](/guides/basic/config#compiletype---编译类型)
- 可用于指定入口配置文件的自定义文件路径,如 `app.json` / `plugin.json` / `subpackage.json` / `component.json`,参见 [compileType 配置](/guides/basic/config#compiletype---编译类型)
- 可用于指定一些在 `bundle` 模式下额外需要参与编译且需要定制输出名称的文件,如对外输出某个 `js` 文件
- `bundle` 模式下,无引用关系,但需要额外需要编译的 页面(`pages`) 或 组件(`components`

Expand All @@ -544,6 +566,8 @@ css 压缩器自定义配置, 使用时请结合 `cssMinimizer` 所指定的压
'plugin.json': './src/my-custom-plugin.json',
// 手动指定 subpackage.json 文件路径
'subpackage.json': './src/my-custom-subpackage.json',
// 手动指定 component.json 文件路径
'component.json': './src/my-custom-component.json',
}
}

Expand Down Expand Up @@ -962,7 +986,7 @@ class YourCustomMorJSPlugin {
默认情况下:

-`compileType``miniprogram``plugin` 时默认为 `true`,即处理占位组件
-`compileType``subpackage` 时 默认为 `false`,即不处理占位组件
-`compileType``subpackage` `component` 时默认为 `false`,即不处理占位组件

有关占位组件的用途可参考以下文档:

Expand Down

0 comments on commit 3307838

Please sign in to comment.