feat(preview): auto format generated code in preview mode (#1794)

This commit is contained in:
chilingling 2026-04-29 18:14:37 +08:00 committed by GitHub
parent e80fb49346
commit 7f3c45168d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 99 additions and 33 deletions

View File

@ -1,6 +1,7 @@
import { generateApp, type IAppSchema } from '@opentiny/tiny-engine-dsl-vue'
import * as dslVue from '@opentiny/tiny-engine-dsl-vue'
import { getMergeMeta } from '@opentiny/tiny-engine-meta-register'
import { formatString } from '../../js/ast'
import defaultPrettierConfig from '../../js/config-files/prettierrc'
// 应用出码默认配置
@ -42,7 +43,7 @@ const generateAppCode = async (appSchema: IAppSchema, options = {}) => {
const { parseRequiredBlocks, genSFCWithDefaultPlugin } = dslVue as any
const generatePageCode = (...args: any[]) => {
return genSFCWithDefaultPlugin(...args)
return formatString(genSFCWithDefaultPlugin(...args), 'vue')
}
/**

View File

@ -38,12 +38,23 @@ export const string2Ast = (string = ''): BabelAst =>
export const ast2String = (ast: GeneratorInput): string => generate(ast, { retainLines: true }).code // 将 AST 再转回字符串
type FormatterFn = (input: string) => string
type SupportedLanguage = 'json' | 'typescript' | 'javascript' | 'html' | 'css'
type SupportedLanguage = 'json' | 'typescript' | 'javascript' | 'html' | 'css' | 'vue' | 'less'
type SupportedPrettierParser = 'json' | 'babel' | 'babel-ts' | 'html' | 'css' | 'less' | 'vue'
const formatScript: FormatterFn = (string) => {
const PRETTIER_PLUGINS = [parserBabel, parseCss, parserHtml]
const formatWithParser = (string: string, parser: SupportedPrettierParser, options: PrettierOptions = {}): string =>
prettier.format(string, {
parser,
plugins: PRETTIER_PLUGINS,
...basePrettierConfig,
...options
})
const formatScript = (string: string, parser: 'babel' | 'babel-ts' = 'babel'): string => {
let newStr = string
const options: PrettierOptions = {
parser: 'babel',
parser,
plugins: [parserBabel],
...basePrettierConfig
}
@ -72,34 +83,49 @@ const formatScript: FormatterFn = (string) => {
return newStr
}
const formatJson: FormatterFn = (string) =>
prettier.format(string, {
parser: 'json',
plugins: [parserBabel],
trailingComma: 'es5',
...basePrettierConfig
})
const formatJson: FormatterFn = (string) => formatWithParser(string, 'json')
const formatHtml: FormatterFn = (string) =>
prettier.format(string, {
parser: 'html',
plugins: [parserBabel, parserHtml],
...basePrettierConfig
})
const formatHtml: FormatterFn = (string) => formatWithParser(string, 'html')
const formatCss: FormatterFn = (string) =>
prettier.format(string, {
parser: 'css',
plugins: [parseCss],
...basePrettierConfig
})
const formatCss: FormatterFn = (string) => formatWithParser(string, 'css')
const formatVue: FormatterFn = (string) => formatWithParser(string, 'vue')
const formatLess: FormatterFn = (string) => formatWithParser(string, 'less')
const formatterMap: Record<SupportedLanguage, FormatterFn> = {
json: formatJson,
typescript: formatScript,
typescript: (str) => formatScript(str, 'babel-ts'),
javascript: formatScript,
html: formatHtml,
css: formatCss
css: formatCss,
vue: formatVue,
less: formatLess
}
const parserMap: Record<string, SupportedPrettierParser> = {
json: 'json',
js: 'babel',
jsx: 'babel',
mjs: 'babel',
cjs: 'babel',
ts: 'babel-ts',
tsx: 'babel-ts',
css: 'css',
less: 'less',
html: 'html',
vue: 'vue'
}
export const getPrettierParserByFileName = (fileName = ''): SupportedPrettierParser | undefined => {
const pureFileName = fileName.split('?')[0].split('#')[0].toLowerCase()
const extension = pureFileName.split('.').at(-1)
if (!extension || extension === pureFileName) {
return undefined
}
return parserMap[extension]
}
export const formatString = (str: string, language: string): string => {
@ -115,6 +141,27 @@ export const formatString = (str: string, language: string): string => {
return result
}
export const formatStringByFileName = (str: string, fileName: string): string => {
const parser = getPrettierParserByFileName(fileName)
if (!parser) {
return str
}
try {
if (parser === 'babel' || parser === 'babel-ts') {
return formatScript(str, parser)
}
return formatWithParser(str, parser)
} catch (error) {
const printer: Console = console
printer.log(error)
return str
}
}
export { parse, parseExpression, traverse, generate }
export const includedExpression = (code: string, expression: string): boolean => {

View File

@ -1,4 +1,5 @@
import { reactive } from 'vue'
import { formatStringByFileName } from '@opentiny/tiny-engine-common/js/ast'
import { constants } from '@opentiny/tiny-engine-utils'
import { getImportMap as getInitImportMap } from './importMap'
import { getMetaApi, getMergeMeta, META_SERVICE, useEnv } from '@opentiny/tiny-engine-meta-register'
@ -342,6 +343,21 @@ interface IUsePreviewData {
setImportMap: (importMap: Record<string, string>) => void
}
const formatGeneratedFile = (fileContent: unknown, fileName: string): string => {
if (typeof fileContent !== 'string') {
return String(fileContent ?? '')
}
return formatStringByFileName(fileContent, fileName)
}
const formatGeneratedFiles = (files: Record<string, string>) =>
Object.entries(files).reduce((acc, [fileName, fileContent]) => {
acc[fileName] = formatGeneratedFile(fileContent, fileName)
return acc
}, {} as Record<string, string>)
export const usePreviewData = ({ setFiles, store, setImportMap }: IUsePreviewData) => {
const basicFiles = setFiles(srcFiles, 'src/Main.vue')
@ -407,10 +423,10 @@ export const usePreviewData = ({ setFiles, store, setImportMap }: IUsePreviewDat
const enableTailwindCSS = getMergeMeta('engine.config')?.enableTailwindCSS
const appJsCode = processAppJsCode(newFiles['app.js'] || '', params.styles, enableTailwindCSS)
newFiles['app.js'] = appJsCode
newFiles['app.js'] = formatGeneratedFile(appJsCode, 'app.js')
pageCode.forEach((item) => assignFiles(item, newFiles))
const metaFiles = generateMetaFiles(metaData)
const metaFiles = formatGeneratedFiles(generateMetaFiles(metaData))
Object.assign(newFiles, metaFiles)
setFiles(newFiles, 'App.vue')
} else if (previewType === 'app') {
@ -581,7 +597,7 @@ export const usePreviewData = ({ setFiles, store, setImportMap }: IUsePreviewDat
return `${importSnippet}\n ${routeSnippets} \n ${exportSnippet}`
}
const formatCode = (fileContent, fileName) => {
const formatPreviewFile = (fileContent, fileName) => {
if (fileName === 'src/router/index.js') {
fileContent = getRouterFile(appSchema)
} else {
@ -601,23 +617,25 @@ export const usePreviewData = ({ setFiles, store, setImportMap }: IUsePreviewDat
`const route = useRoute()\nconst router = useRouter()\nconst currentRoute = ref()\n\nwatchEffect(() => {\n\tcurrentRoute.value = route.path\n})\n\nconst routeChange = () => {\n\trouter.push(currentRoute.value)\n}\nprovide(I18nInjectionKey, i18n)`
)
}
return fileContent
return formatGeneratedFile(fileContent, fileName)
}
const fileRes = await getPreGenerateInfo()
const newFileRes = fileRes.filter((item) => item.filePath.includes('src/'))
const srcFiles = newFileRes.reduce((prev, item) => {
const fileName = item.filePath
prev[fileName] = formatCode(item.fileContent, fileName)
prev[fileName] = formatPreviewFile(item.fileContent, fileName)
return prev
}, {})
srcFiles['import-map.json'] = JSON.stringify(importMapData)
}, {} as Record<string, string>)
srcFiles['import-map.json'] = formatGeneratedFile(JSON.stringify(importMapData), 'import-map.json')
const newFiles = store.getFiles()
const enableTailwindCSS = getMergeMeta('engine.config')?.enableTailwindCSS
const appJsCode = processAppJsCode(newFiles['app.js'] || '', params.styles, enableTailwindCSS)
srcFiles['app.js'] = appJsCode
srcFiles['app.js'] = formatGeneratedFile(appJsCode, 'app.js')
srcFiles['main.js'] = `import app from './app.js' \n ${srcFiles['src/main.js']}`
srcFiles['main.js'] = srcFiles['main.js'].replace("import 'element-plus/dist/index.css'", '')
srcFiles['main.js'] = formatGeneratedFile(srcFiles['main.js'], 'main.js')
setFiles(srcFiles, 'src/main.js')
}
}