Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(material): add material package #537

Open
wants to merge 1 commit into
base: refactor/develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions packages/materials/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# TinyEngine 官方物料

## 使用

### 持续构建

```bash
npm run serve
```

解释:
1. 会持续监听 src 目录下文件变动,持续构建出来物料产物
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a blank line before the list for proper Markdown formatting.

+ 
解释:

Committable suggestion was skipped due to low confidence.

Tools
Markdownlint

12-12: null (MD032, blanks-around-lists)
Lists should be surrounded by blank lines

2. 会启动静态服务器。

### 将组件库分别进行构建,以及将所有组件库构建成一份物料产物

```bash
npm run build

# 构建成功会得到比如 ElementPlus.json、TinyVue.json 等组件库对应的 json,以及 all.json
```

### 将组件库分别进行构建

```bash
npm run build:split

# 构建成功会得到比如 ElementPlus.json、TinyVue.json 等组件库对应的 json
```

## 添加自己的物料

请先大致了解 TinyEngine 物料协议:https://opentiny.design/tiny-engine#/protocol
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace the bare URL with a Markdown link for better readability and to adhere to best practices.

- 请先大致了解 TinyEngine 物料协议:https://opentiny.design/tiny-engine#/protocol
+ 请先大致了解 TinyEngine 物料协议:[TinyEngine 物料协议](https://opentiny.design/tiny-engine#/protocol)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
请先大致了解 TinyEngine 物料协议:https://opentiny.design/tiny-engine#/protocol
请先大致了解 TinyEngine 物料协议:[TinyEngine 物料协议](https://opentiny.design/tiny-engine#/protocol)
Tools
Markdownlint

33-33: null (MD034, no-bare-urls)
Bare URL used


src 目录功能约定结构:

```bash
src/
|__ ElementPlus 组件库名称
|__ Button.json ElementPlus Button组件
|__ Table.json ElementPlus Table 组件
```

所以,我们添加自己的物料可以大致分为两步:

1. 根据目录结构约定添加 xxx.json 组件文件
2. xxx.json 中根据物料协议进行书写。

## TODO

- [ ] 脚本自动生成组件库对应物料。
222 changes: 222 additions & 0 deletions packages/materials/buildMaterials.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import fsExtra from 'fs-extra'
import path from 'node:path'
import chokidar from 'chokidar'
import fg from 'fast-glob'
import { fileURLToPath } from 'node:url'
import httpServer from 'http-server'
import portFinder from 'portfinder'
import Logger from '../../scripts/logger.mjs'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

const logger = new Logger('buildMaterials')

// 物料文件存放文件夹名称
const materialsDir = path.resolve(__dirname, './src')

/**
* 校验组件文件数据
* @param {string} file 组件文件路径
* @param {object} component 组件数据
* @returns
*/
const validateComponent = (file, component) => {
const requiredFields = ['component']
const fields = Object.keys(component)
const requiredList = requiredFields.filter((field) => !fields.includes(field))

if (requiredList.length) {
logger.error(`组件文件 ${file} 缺少必要字段:${requiredList.join('、')}。`)

return false
}

if (!component.npm) {
logger.warn(`组件文件 ${file} 缺少 npm 字段,出码时将不能通过import语句导入组件。`)

return false
}

return true
}

const generateComponents = async (entry) => {
const files = await fg('*.json', { cwd: entry })
if (!files.length) {
// logger.warn('物料文件夹为空,请先执行`pnpm splitMaterials`命令拆分物料资产包')
return
}

const bundle = {
componentsMap: [],
components: [],
snippets: []
}

files.forEach((file) => {
const material = fsExtra.readJsonSync(path.resolve(entry, file), { throws: false })

if (!material) {
const fileFullPath = path.join(process.cwd(), file)

logger.error(`文件格式有误 (${fileFullPath})`)

return
}

const valid = validateComponent(file, material)

if (!valid) return

const { snippets: componentSnippets, category, ...componentInfo } = material

bundle.components.push(componentInfo)

const snippet = bundle.snippets.find((item) => item.group === category)

if (snippet) {
componentSnippets && snippet.children.push(componentSnippets[0])
} else if (category && componentInfo) {
bundle.snippets.push({
group: category,
children: componentSnippets || []
})
}

const npmInfo = componentInfo.npm
const { package: packageName = '', version = '', exportName = '' } = npmInfo

const mapItem = {
componentName: componentInfo.component,
package: packageName,
version,
exportName
}

if (typeof npmInfo.destructuring === 'boolean') {
mapItem.destructuring = componentInfo.npm.destructuring
}

if (npmInfo.package) {
bundle.componentsMap.push(mapItem)
}
})

return bundle
Comment on lines +44 to +106
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimize the generateComponents function to handle empty directories more gracefully.

Consider logging a warning when the materials directory is empty, which could help in debugging.

-    // logger.warn('物料文件夹为空,请先执行`pnpm splitMaterials`命令拆分物料资产包')
+    logger.warn('物料文件夹为空,请先执行`pnpm splitMaterials`命令拆分物料资产包')
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const generateComponents = async (entry) => {
const files = await fg('*.json', { cwd: entry })
if (!files.length) {
// logger.warn('物料文件夹为空,请先执行`pnpm splitMaterials`命令拆分物料资产包')
return
}
const bundle = {
componentsMap: [],
components: [],
snippets: []
}
files.forEach((file) => {
const material = fsExtra.readJsonSync(path.resolve(entry, file), { throws: false })
if (!material) {
const fileFullPath = path.join(process.cwd(), file)
logger.error(`文件格式有误 (${fileFullPath})`)
return
}
const valid = validateComponent(file, material)
if (!valid) return
const { snippets: componentSnippets, category, ...componentInfo } = material
bundle.components.push(componentInfo)
const snippet = bundle.snippets.find((item) => item.group === category)
if (snippet) {
componentSnippets && snippet.children.push(componentSnippets[0])
} else if (category && componentInfo) {
bundle.snippets.push({
group: category,
children: componentSnippets || []
})
}
const npmInfo = componentInfo.npm
const { package: packageName = '', version = '', exportName = '' } = npmInfo
const mapItem = {
componentName: componentInfo.component,
package: packageName,
version,
exportName
}
if (typeof npmInfo.destructuring === 'boolean') {
mapItem.destructuring = componentInfo.npm.destructuring
}
if (npmInfo.package) {
bundle.componentsMap.push(mapItem)
}
})
return bundle
const generateComponents = async (entry) => {
const files = await fg('*.json', { cwd: entry })
if (!files.length) {
logger.warn('物料文件夹为空,请先执行`pnpm splitMaterials`命令拆分物料资产包')
return
}
const bundle = {
componentsMap: [],
components: [],
snippets: []
}
files.forEach((file) => {
const material = fsExtra.readJsonSync(path.resolve(entry, file), { throws: false })
if (!material) {
const fileFullPath = path.join(process.cwd(), file)
logger.error(`文件格式有误 (${fileFullPath})`)
return
}
const valid = validateComponent(file, material)
if (!valid) return
const { snippets: componentSnippets, category, ...componentInfo } = material
bundle.components.push(componentInfo)
const snippet = bundle.snippets.find((item) => item.group === category)
if (snippet) {
componentSnippets && snippet.children.push(componentSnippets[0])
} else if (category && componentInfo) {
bundle.snippets.push({
group: category,
children: componentSnippets || []
})
}
const npmInfo = componentInfo.npm
const { package: packageName = '', version = '', exportName = '' } = npmInfo
const mapItem = {
componentName: componentInfo.component,
package: packageName,
version,
exportName
}
if (typeof npmInfo.destructuring === 'boolean') {
mapItem.destructuring = componentInfo.npm.destructuring
}
if (npmInfo.package) {
bundle.componentsMap.push(mapItem)
}
})
return bundle

}

const getFrameworkWithData = (data) => {
return {
data: {
framework: 'Vue',
materials: data
}
}
}

const buildComponents = async (config = {}) => {
try {
const entries = await fg('*/', {
cwd: materialsDir,
onlyDirectories: true,
deep: 1
})

const { buildCombine = true } = config

const allBundles = {
components: [],
snippets: [],
componentsMap: []
}

for (const entry of entries) {
const res = await generateComponents(path.resolve(materialsDir, `${entry}`))

if (res) {
fsExtra.outputJSONSync(path.resolve(__dirname, `./dist/${entry}.json`), getFrameworkWithData(res), { spaces: 2 })

allBundles.components = allBundles.components.concat(res.components)
allBundles.snippets = allBundles.snippets.concat(res.snippets)
allBundles.componentsMap = allBundles.componentsMap.concat(res.componentsMap)
}
}

if (buildCombine) {
fsExtra.outputJSONSync(path.resolve(__dirname, `./dist/all.json`), getFrameworkWithData(allBundles), { spaces: 2 })
}

logger.success('物料资产包构建成功')
} catch (error) {
logger.error(`物料资产包构建失败:${error}`)
}
}

// 持续构建
async function serve() {
// 监听materials下json文件的变化
const watcher = chokidar.watch(`${materialsDir}/**/*.json`, { ignoreInitial: true })

watcher.on('all', (event, file) => {
const eventMap = {
add: '新增',
change: '更新',
unlink: '删除'
}
const fileFullPath = path.join(process.cwd(), file)

logger.info(`${eventMap[event]}组件文件 (${fileFullPath})`)

// 监听物料文件变化,更新物料资产包
buildComponents()
})

// 第一次需要手动出发构建一遍
await buildComponents()

const staticServerPort = await portFinder.getPortPromise({ port: 4001 })

const server = httpServer.createServer({
caches: 1,
cors: true,
root: path.resolve(__dirname, './dist')
})

server.listen(staticServerPort, () => {
logger.success(`物料服务已启动 http://127.0.0.1:${staticServerPort}`)
})

}

// 单次构建,分组件库
function buildSplit() {
buildComponents({ buildCombine: false })
}

// 单次构建,合并所有组件库
function build() {
buildComponents()
}


function start() {
const commandsMap = {
serve: serve,
build: build,
'build:split': buildSplit
}

const command = process.argv.slice(2)

if (!commandsMap[command]) {
logger.error(`[@opentiny/tiny-engine-materials] 不支持${command}命令`)

return
}

commandsMap[command]()
}
Comment on lines +202 to +219
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

The start function in packages/materials/buildMaterials.mjs does include a basic validation check to ensure the command is in the commandsMap. However, it only checks if the command exists in the map and does not handle other potential issues such as invalid command formats or additional arguments.

  • Location in codebase:
    • packages/materials/buildMaterials.mjs (lines 202-219)

To improve the validation, consider adding more comprehensive checks, such as verifying the format of the command and handling unexpected additional arguments.

Analysis chain

Validate command input in start function.

Ensure that the command validation is comprehensive and covers all expected scenarios.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify command validation in the start function.

# Test: Search for command validation logic. Expect: Comprehensive validation logic.
rg --type javascript $'commandsMap[command]'

Length of output: 80



Script:

#!/bin/bash
# Description: Verify command validation in the start function.

# Test: Search for command validation logic. Expect: Comprehensive validation logic.
rg --type js $'commandsMap[command]'

Length of output: 37



Script:

#!/bin/bash
# Description: Locate the start function and review its implementation.

# Search for the start function definition in JavaScript files.
rg --type js 'function start'

Length of output: 31



Script:

#!/bin/bash
# Description: Locate the start function and review its implementation.

# Search for the start function definition in JavaScript and ES module files.
rg --type-add 'mjs:*.mjs' --type js --type mjs 'function start'

Length of output: 122



Script:

#!/bin/bash
# Description: Extract and display the start function implementation from buildMaterials.mjs.

# Extract the start function implementation.
rg --type-add 'mjs:*.mjs' --type mjs --context 10 'function start' packages/materials/buildMaterials.mjs

Length of output: 495



start()
37 changes: 37 additions & 0 deletions packages/materials/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@opentiny/tiny-engine-materials",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"serve": "node buildMaterials.mjs serve",
"build": "node buildMaterials.mjs build",
"build:split": "node buildMaterials.mjs build:split"
},
"keywords": [],
"publishConfig": {
"access": "public"
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/opentiny/tiny-engine",
"directory": "packages/materials"
},
"bugs": {
"url": "https://github.com/opentiny/tiny-engine/issues"
},
"author": "OpenTiny Team",
"license": "MIT",
"homepage": "https://opentiny.design/tiny-engine",
"devDependencies": {
"chokidar": "^3.5.3",
"fast-glob": "^3.3.2",
"fs-extra": "^11.2.0",
"http-server": "^14.1.1",
"portfinder": "^1.0.32"
}
}
Loading