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: generatecode support import element style #817

Merged
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
1 change: 1 addition & 0 deletions packages/vue-generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
},
"peerDependencies": {
"@babel/parser": "^7.18.13",
"@babel/generator": "^7.18.13",
"@babel/traverse": "^7.18.13"
}
}
8 changes: 7 additions & 1 deletion packages/vue-generator/src/generator/generateApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
genUtilsPlugin,
formatCodePlugin,
parseSchemaPlugin,
genGlobalState
genGlobalState,
appendElePlusStylePlugin
} from '../plugins'
import CodeGenerator from './codeGenerator'

Expand Down Expand Up @@ -63,6 +64,11 @@ export function generateApp(config = {}) {
globalState: globalState || defaultPlugins.globalState
}

// 默认支持 element-plus 注入样式
if (config?.customContext?.injectElementPlusStyle !== false) {
transformEnd.push(appendElePlusStylePlugin(config?.customContext?.injectElementPlusStyle || {}))
}

const codeGenInstance = new CodeGenerator({
plugins: {
transformStart: [parseSchema || defaultPlugins.parseSchema, ...transformStart],
Expand Down
89 changes: 89 additions & 0 deletions packages/vue-generator/src/plugins/appendElePlusStylePlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import prettier from 'prettier'
import { parse } from '@babel/parser'
import traverse from '@babel/traverse'
import generate from '@babel/generator'
import parserBabel from 'prettier/parser-babel'
import { mergeOptions } from '../utils/mergeOptions'

const defaultOption = {
fileName: 'package.json',
path: '.',
prettierOption: {
singleQuote: true,
printWidth: 120,
semi: false,
trailingComma: 'none'
}
}

function genElementPlusStyleDeps(options = {}) {
const realOptions = mergeOptions(defaultOption, options)

const { prettierOption, fileName, path } = realOptions

return {
name: 'tinyEngine-generateCode-plugin-element-plus-style',
description: 'import element-plus style',
/**
* 注入 element-plus 全局样式依赖
* @param {tinyEngineDslVue.IAppSchema} schema
* @returns
*/
run() {
const originPackageItem = this.getFile(path, fileName)

if (!originPackageItem) {
return
}

let originPackageJSON = JSON.parse(originPackageItem.fileContent)
const hasElementPlusDeps = Object.keys(originPackageJSON.dependencies).includes('element-plus')

if (!hasElementPlusDeps) {
return
}

const mainJsFile = this.getFile('./src', 'main.js') || {}

if (!mainJsFile.fileContent) {
return
}

const ast = parse(mainJsFile.fileContent, { sourceType: 'module' })
let lastImport = null
let hasElementPlusStyleImport = false

traverse(ast, {
ImportDeclaration(path) {
lastImport = path

if (path.node.source.value === 'element-plus/dist/index.css') {
hasElementPlusStyleImport = true
}
}
})

// 已经存在 element-plus 的 import,不再插入
if (hasElementPlusStyleImport) {
return
}

// 引入 element-plus 样式依赖
if (lastImport) {
lastImport.insertAfter(parse("import 'element-plus/dist/index.css'", { sourceType: 'module' }).program.body[0])
}

const newFileContent = generate(ast).code

const formattedContent = prettier.format(newFileContent, {
parser: 'babel',
plugins: [parserBabel],
...prettierOption
})

this.replaceFile({ ...mainJsFile, fileContent: formattedContent })
}
}
}

export default genElementPlusStyleDeps
1 change: 1 addition & 0 deletions packages/vue-generator/src/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { default as genTemplatePlugin } from './genTemplatePlugin'
export { default as formatCodePlugin } from './formatCodePlugin'
export { default as genGlobalState } from './genGlobalState'
export { default as parseSchemaPlugin } from './parseSchemaPlugin'
export { default as appendElePlusStylePlugin } from './appendElePlusStylePlugin'
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { expect, test, describe } from 'vitest'
import path from 'path'
import fs from 'fs'
import dirCompare from 'dir-compare'
import { generateApp } from '@/generator/generateApp'
import { appSchemaDemo01 } from './mockData'
import { logDiffResult } from '../../utils/logDiffResult'

describe('generate element-plus material project correctly', () => {
test('should generate element-plus css import statement 预期生成 element-plus 样式依赖引入', async () => {
const instance = generateApp()

const res = await instance.generate(appSchemaDemo01)
const { genResult } = res

// 写入文件
genResult.forEach(({ fileName, path: filePath, fileContent }) => {
fs.mkdirSync(path.resolve(__dirname, `./result/appdemo01/${filePath}`), { recursive: true })
fs.writeFileSync(
path.resolve(__dirname, `./result/appdemo01/${filePath}/${fileName}`),
// 这里需要将换行符替换成 CRLF 格式的
fileContent.replace(/\r?\n/g, '\r\n')
)
})

const compareOptions = {
compareContent: true,
ignoreLineEnding: true,
ignoreAllWhiteSpaces: true,
ignoreEmptyLines: true
}

const path1 = path.resolve(__dirname, './expected/appdemo01')
const path2 = path.resolve(__dirname, './result/appdemo01')

// 对比文件差异
const diffResult = dirCompare.compareSync(path1, path2, compareOptions)

logDiffResult(diffResult)

expect(diffResult.same).toBe(true)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
node_modules
dist/

# local env files
.env.local
.env.*.local

# Editor directories and files
.vscode
.idea

yarn.lock
package-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## portal-app

本工程是使用 TinyEngine 低代码引擎搭建之后得到的出码工程。

## 使用

安装依赖:

```bash
npm install
```

本地启动项目:

```bash
npm run dev
```


Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>portal-app</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "portal-app",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"main": "dist/index.js",
"module": "dist/index.js",
"dependencies": {
"@opentiny/tiny-engine-i18n-host": "^1.0.0",
"@opentiny/vue": "^3.10.0",
"@opentiny/vue-icon": "^3.10.0",
"axios": "^0.21.1",
"axios-mock-adapter": "^1.19.0",
"vue": "^3.3.9",
"vue-i18n": "^9.2.0-beta.3",
"vue-router": "^4.2.5",
"pinia": "^2.1.7",
"element-plus": "^2.4.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.5.1",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"vite": "^4.3.7"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<template>
<router-view></router-view>
</template>

<script setup>
import { I18nInjectionKey } from 'vue-i18n'
import { provide } from 'vue'
import i18n from './i18n'

provide(I18nInjectionKey, i18n)
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**
* Copyright (c) 2023 - present TinyEngine Authors.
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT 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 APPLICABLE LICENSES FOR MORE DETAILS.
*
*/

import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'

export default (config) => {
const instance = axios.create(config)
const defaults = {}
let mock

if (typeof MockAdapter.prototype.proxy === 'undefined') {
MockAdapter.prototype.proxy = function ({ url, config = {}, proxy, response, handleData } = {}) {
let stream = this
const request = (proxy, any) => {
return (setting) => {
return new Promise((resolve) => {
config.responseType = 'json'
axios
.get(any ? proxy + setting.url + '.json' : proxy, config)
.then(({ data }) => {
/* eslint-disable no-useless-call */
typeof handleData === 'function' && (data = handleData.call(null, data, setting))
resolve([200, data])
})
.catch((error) => {
resolve([error.response.status, error.response.data])
})
})
}
}

if (url === '*' && proxy && typeof proxy === 'string') {
stream = proxy === '*' ? this.onAny().passThrough() : this.onAny().reply(request(proxy, true))
} else {
if (proxy && typeof proxy === 'string') {
stream = this.onAny(url).reply(request(proxy))
} else if (typeof response === 'function') {
stream = this.onAny(url).reply(response)
}
}

return stream
}
}

return {
request(config) {
return instance(config)
},
get(url, config) {
return instance.get(url, config)
},
delete(url, config) {
return instance.delete(url, config)
},
head(url, config) {
return instance.head(url, config)
},
post(url, data, config) {
return instance.post(url, data, config)
},
put(url, data, config) {
return instance.put(url, data, config)
},
patch(url, data, config) {
return instance.patch(url, data, config)
},
all(iterable) {
return axios.all(iterable)
},
spread(callback) {
return axios.spread(callback)
},
defaults(key, value) {
if (key && typeof key === 'string') {
if (typeof value === 'undefined') {
return instance.defaults[key]
}
instance.defaults[key] = value
defaults[key] = value
} else {
return instance.defaults
}
},
defaultSettings() {
return defaults
},
interceptors: {
request: {
use(fnHandle, fnError) {
return instance.interceptors.request.use(fnHandle, fnError)
},
eject(id) {
return instance.interceptors.request.eject(id)
}
},
response: {
use(fnHandle, fnError) {
return instance.interceptors.response.use(fnHandle, fnError)
},
eject(id) {
return instance.interceptors.response.eject(id)
}
}
},
mock(config) {
if (!mock) {
mock = new MockAdapter(instance)
}

if (Array.isArray(config)) {
config.forEach((item) => {
mock.proxy(item)
})
}

return mock
},
disableMock() {
mock && mock.restore()
mock = undefined
},
isMock() {
return typeof mock !== 'undefined'
},
CancelToken: axios.CancelToken,
isCancel: axios.isCancel
}
}
Loading
Loading