Skip to content
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
3 changes: 2 additions & 1 deletion packages/canvas/render/src/RenderMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ export default defineComponent({
() => activeSchema.css,
(value) => {
setPageCss(value)
}
},
{ deep: true }
)

const utilsWatchCanceler = window.host.watch(
Expand Down
6 changes: 4 additions & 2 deletions packages/canvas/render/src/material-function/page-getter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { defineComponent, h, ref, onMounted } from 'vue'
import { getController } from '../canvas-function'
import RenderMain from '../RenderMain'
import { handleScopedCss } from './handle-scoped-css'
import { utils } from '@opentiny/tiny-engine-utils'

const { obJectCssToString } = utils
const pageSchema: Record<string, any> = {}

async function fetchPageSchema(pageId: string) {
Expand All @@ -16,7 +18,7 @@ async function fetchPageSchema(pageId: string) {
// tailwindcss function and directive 特性需要使用 <style type="text/tailwindcss"> 标签
// https://tailwindcss.com/docs/functions-and-directives
// 所以原来 new CSSStyleSheet 的方式改成了 document.createElement('style') 的方式
export function initStyle(key: string, content: string) {
export function initStyle(key: string, content: string | object) {
if (!content) {
return
}
Expand All @@ -32,7 +34,7 @@ export function initStyle(key: string, content: string) {
document.head.appendChild(styleSheet)
}

handleScopedCss(key, content).then((scopedCss) => {
handleScopedCss(key, obJectCssToString(content)).then((scopedCss) => {
styleSheet.textContent = scopedCss.css
})
}
Expand Down
33 changes: 21 additions & 12 deletions packages/plugins/page/src/composable/usePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,22 +115,31 @@ export interface MaterialsOptions {
}

const generateCssString = (pageOptions: PageOptions, materialsOptions: MaterialsOptions) => {
if (!pageOptions?.pageBaseStyle?.className || !pageOptions?.pageBaseStyle?.style) {
return ''
let cssObject: any = {}
const parseStyle = (styleString: string) => {
const styleObj: any = {}
const styleItems = styleString.split(';')
styleItems.forEach((item: string) => {
if (item) {
const stylekeyValue = item.split(':')
styleObj[stylekeyValue[0]] = stylekeyValue[1].trim()
}
})
return styleObj
}
if (pageOptions?.pageBaseStyle?.className && pageOptions?.pageBaseStyle?.style) {
cssObject[`.${pageOptions.pageBaseStyle.className}`] = parseStyle(pageOptions.pageBaseStyle.style)
}

const formatCssRule = (className: string, style: string) => `.${className} {\n ${style.trim()}\n}\n`
const baseStyle = `.${pageOptions.pageBaseStyle.className}{\r\n ${pageOptions.pageBaseStyle.style}\r\n}\r\n`

if (!materialsOptions.useBaseStyle) {
return baseStyle
if (materialsOptions.useBaseStyle) {
cssObject = {
...cssObject,
[`.${materialsOptions.blockBaseStyle.className}`]: parseStyle(materialsOptions.blockBaseStyle.style),
[`.${materialsOptions.componentBaseStyle.className}`]: parseStyle(materialsOptions.componentBaseStyle.style)
}
}
Comment on lines +134 to 140
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing null guards for blockBaseStyle and componentBaseStyle.

When materialsOptions.useBaseStyle is truthy, this code assumes blockBaseStyle and componentBaseStyle exist with valid className and style properties. Unlike line 130 which guards pageBaseStyle, these properties are accessed unconditionally and will throw if undefined.

Proposed fix
   if (materialsOptions.useBaseStyle) {
-    cssObject = {
-      ...cssObject,
-      [`.${materialsOptions.blockBaseStyle.className}`]: parseStyle(materialsOptions.blockBaseStyle.style),
-      [`.${materialsOptions.componentBaseStyle.className}`]: parseStyle(materialsOptions.componentBaseStyle.style)
-    }
+    const { blockBaseStyle, componentBaseStyle } = materialsOptions
+    if (blockBaseStyle?.className && blockBaseStyle?.style) {
+      cssObject[`.${blockBaseStyle.className}`] = parseStyle(blockBaseStyle.style)
+    }
+    if (componentBaseStyle?.className && componentBaseStyle?.style) {
+      cssObject[`.${componentBaseStyle.className}`] = parseStyle(componentBaseStyle.style)
+    }
   }
📝 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. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (materialsOptions.useBaseStyle) {
cssObject = {
...cssObject,
[`.${materialsOptions.blockBaseStyle.className}`]: parseStyle(materialsOptions.blockBaseStyle.style),
[`.${materialsOptions.componentBaseStyle.className}`]: parseStyle(materialsOptions.componentBaseStyle.style)
}
}
if (materialsOptions.useBaseStyle) {
const { blockBaseStyle, componentBaseStyle } = materialsOptions
if (blockBaseStyle?.className && blockBaseStyle?.style) {
cssObject[`.${blockBaseStyle.className}`] = parseStyle(blockBaseStyle.style)
}
if (componentBaseStyle?.className && componentBaseStyle?.style) {
cssObject[`.${componentBaseStyle.className}`] = parseStyle(componentBaseStyle.style)
}
}
🤖 Prompt for AI Agents
In `@packages/plugins/page/src/composable/usePage.ts` around lines 134 - 140, When
materialsOptions.useBaseStyle is true the code accesses
materialsOptions.blockBaseStyle and materialsOptions.componentBaseStyle without
null checks; add guards so you only reference .className and .style when
blockBaseStyle/componentBaseStyle are defined. Update the block that builds
cssObject (the parseStyle calls and computed keys) to check existence of
materialsOptions.blockBaseStyle and materialsOptions.componentBaseStyle (and
their .className/.style) before adding those entries to cssObject, falling back
to skipping the entry if missing. Keep parseStyle and cssObject merging logic
unchanged except for these presence checks.


return [
formatCssRule(pageOptions.pageBaseStyle.className, pageOptions.pageBaseStyle.style),
formatCssRule(materialsOptions.blockBaseStyle.className, materialsOptions.blockBaseStyle.style),
formatCssRule(materialsOptions.componentBaseStyle.className, materialsOptions.componentBaseStyle.style)
].join('\n')
return cssObject
}

const getDefaultPage = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ import { Select as TinySelect } from '@opentiny/vue'
import { useProperties, useCanvas, useHistory, useHelp } from '@opentiny/tiny-engine-meta-register'
import { LinkButton } from '@opentiny/tiny-engine-common'
import { CodeConfigurator } from '@opentiny/tiny-engine-configurator'
import { formatString } from '@opentiny/tiny-engine-common/js/ast'
import useStyle, { updateGlobalStyleStr } from '../../js/useStyle'
import { stringify, getSelectorArr } from '../../js/parser'

Expand Down Expand Up @@ -448,15 +447,40 @@ watchEffect(() => {
selectorValidator(classNameState.newSelector)
})

const cssStringToObject = (cssString) => {
cssString = cssString.replace(/\s+/g, ' ').trim()
const result = {}
const regex = /([^{]+)\{([^}]+)\}/g

let match
while ((match = regex.exec(cssString)) !== null) {
const selector = match[1].trim()
const properties = match[2].trim()

const propertiesObj = {}

const propertyRegex = /([^{;]+):([^;]+)/g
let propertyMatch

while ((propertyMatch = propertyRegex.exec(properties)) !== null) {
const key = propertyMatch[1].trim()
const value = propertyMatch[2].trim()
propertiesObj[key] = value
}

result[selector] = propertiesObj
}
return result
}
Comment on lines +450 to +474
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Locate the file and explore its structure
find . -name "index.vue" -path "*/classNamesContainer/*" -type f

Repository: opentiny/tiny-engine

Length of output: 135


🏁 Script executed:

# Find the parser module
find . -name "parser.ts" -o -name "parser.js" | grep -E "js/parser|parser\.ts|parser\.js"

Repository: opentiny/tiny-engine

Length of output: 160


🏁 Script executed:

# Explore the directory structure
ls -la packages/settings/styles/src/

Repository: opentiny/tiny-engine

Length of output: 407


🏁 Script executed:

# Read the index.vue file to see the cssStringToObject function in context
wc -l packages/settings/styles/src/components/classNamesContainer/index.vue

Repository: opentiny/tiny-engine

Length of output: 137


🏁 Script executed:

# Read the parser module
cat -n packages/settings/styles/src/js/parser.ts

Repository: opentiny/tiny-engine

Length of output: 7696


🏁 Script executed:

# Read the relevant section of index.vue around lines 450-474
sed -n '440,490p' packages/settings/styles/src/components/classNamesContainer/index.vue

Repository: opentiny/tiny-engine

Length of output: 1281


🏁 Script executed:

# Check the imports at the top of index.vue
head -50 packages/settings/styles/src/components/classNamesContainer/index.vue

Repository: opentiny/tiny-engine

Length of output: 2272


🏁 Script executed:

# Search for how css is used after being saved via updateSchema
rg -n "updateSchema.*css|css.*parseList|css.*stringify" packages/settings/styles/src/components/classNamesContainer/ -A 3 -B 3

Repository: opentiny/tiny-engine

Length of output: 765


🏁 Script executed:

# Check if there's any usage of parser elsewhere in the codebase for CSS serialization
rg -n "useStyle|parser.*css|stringify" packages/settings/styles/src/components/classNamesContainer/index.vue

Repository: opentiny/tiny-engine

Length of output: 310


🏁 Script executed:

# Look for how css data is processed when loading/editing
rg -n "state\.cssContent|cssContent" packages/settings/styles/src/components/classNamesContainer/index.vue -B 2 -A 2

Repository: opentiny/tiny-engine

Length of output: 222


🏁 Script executed:

# Check the useStyle hook to understand the data structure and how css is used
cat -n packages/settings/styles/src/js/useStyle.ts

Repository: opentiny/tiny-engine

Length of output: 13340


🏁 Script executed:

# Look for updateSchema implementation to see what it expects for css parameter
rg -n "updateSchema" packages/settings/styles/src/ -B 5 -A 10 | head -60

Repository: opentiny/tiny-engine

Length of output: 4823


🏁 Script executed:

# Look at the code-configurator component to understand what "content" parameter contains
rg -n "code-configurator" packages/settings/styles/src/components/classNamesContainer/index.vue -B 5 -A 10

Repository: opentiny/tiny-engine

Length of output: 1153


🏁 Script executed:

# Check how save function is called and what `@save` event provides
rg -n "@save|save.*content" packages/settings/styles/src/components/classNamesContainer/index.vue -B 2 -A 2

Repository: opentiny/tiny-engine

Length of output: 361


🏁 Script executed:

# Check what obJectCssToString does and how it relates to the reverse operation
rg -n "obJectCssToString" packages/ -B 2 -A 5

Repository: opentiny/tiny-engine

Length of output: 4370


🏁 Script executed:

# Get the full implementation of obJectCssToString
sed -n '462,500p' packages/utils/src/utils/index.ts

Repository: opentiny/tiny-engine

Length of output: 482


🏁 Script executed:

# Test cssStringToObject with complex CSS to understand its limitations
python3 << 'EOF'
import re

def cssStringToObject(cssString):
  cssString = cssString.replace(/\s+/g, ' ').strip()
  result = {}
  regex = r'([^{]+)\{([^}]+)\}'
  
  for match in re.finditer(regex, cssString):
    selector = match.group(1).strip()
    properties = match.group(2).strip()
    
    propertiesObj = {}
    propertyRegex = r'([^{;]+):([^;]+)'
    
    for propMatch in re.finditer(propertyRegex, properties):
      key = propMatch.group(1).strip()
      value = propMatch.group(2).strip()
      propertiesObj[key] = value
    
    result[selector] = propertiesObj
  
  return result

# Test cases
test_cases = [
  ('.simple { color: red; }', 'Simple selector'),
  ('.a, .b { color: blue; }', 'Multiple selectors (comma)'),
  ('@media (max-width: 600px) { .box { width: 100%; } }', '@media query'),
  ('@keyframes slide { from { left: 0; } to { left: 100%; } }', '@keyframes'),
  ('.with-pseudo:hover { color: green; }', 'Pseudo-class'),
  ('.content { content: ";"; }', 'Content with semicolon'),
  ('.test { font-family: "Arial, sans-serif"; }', 'Value with comma'),
]

for css, description in test_cases:
  result = cssStringToObject(css)
  print(f"\n{description}:")
  print(f"  Input: {css}")
  print(f"  Output: {result}")
  print(f"  Success: {bool(result)}")
EOF

Repository: opentiny/tiny-engine

Length of output: 150


🏁 Script executed:

# Test the regex parser with specific edge cases using JavaScript/Node
node << 'EOF'
const cssStringToObject = (cssString) => {
  cssString = cssString.replace(/\s+/g, ' ').trim()
  const result = {}
  const regex = /([^{]+)\{([^}]+)\}/g

  let match
  while ((match = regex.exec(cssString)) !== null) {
    const selector = match[1].trim()
    const properties = match[2].trim()

    const propertiesObj = {}

    const propertyRegex = /([^{;]+):([^;]+)/g
    let propertyMatch

    while ((propertyMatch = propertyRegex.exec(properties)) !== null) {
      const key = propertyMatch[1].trim()
      const value = propertyMatch[2].trim()
      propertiesObj[key] = value
    }

    result[selector] = propertiesObj
  }
  return result
}

const testCases = [
  { input: '.simple { color: red; }', desc: 'Simple selector' },
  { input: '.a, .b { color: blue; }', desc: 'Multiple selectors' },
  { input: '@media (max-width: 600px) { .box { width: 100%; } }', desc: '@media query' },
  { input: '.content { content: ";"; }', desc: 'Content with semicolon' },
  { input: '.test { background: url("data:image/svg+xml;..."); }', desc: 'Value with braces' },
]

testCases.forEach(({ input, desc }) => {
  const result = cssStringToObject(input)
  console.log(`\n${desc}:`)
  console.log(`  Input: ${input}`)
  console.log(`  Output: ${JSON.stringify(result)}`)
  console.log(`  Data loss: ${Object.keys(result).length === 0 ? 'YES' : 'PARTIAL'}`)
})
EOF

Repository: opentiny/tiny-engine

Length of output: 728


🏁 Script executed:

# Check what the parser module returns for complex CSS
sed -n '75,144p' packages/settings/styles/src/js/parser.ts | head -60

Repository: opentiny/tiny-engine

Length of output: 1276


🏁 Script executed:

# Verify the exact format updateSchema expects by checking if there are type definitions or usage patterns
rg -n "updateSchema.*css|css.*:" packages/settings/styles/src/ -A 2 -B 2 | head -40

Repository: opentiny/tiny-engine

Length of output: 2311


🏁 Script executed:

# Check if there's documentation or schema definition for what the css property should be
find packages -name "*.ts" -o -name "*.d.ts" | xargs grep -l "schema.*css\|css.*schema" | head -5

Repository: opentiny/tiny-engine

Length of output: 107


🏁 Script executed:

# Let's trace through what happens when updateSchema receives css parameter
# Check the useEditor to see the pattern more clearly
cat -n packages/settings/styles/src/js/useEditor.ts

Repository: opentiny/tiny-engine

Length of output: 2686


🏁 Script executed:

# Verify what format the save function should pass
# Look at the complete flow in index.vue around the save function
sed -n '470,490p' packages/settings/styles/src/components/classNamesContainer/index.vue

Repository: opentiny/tiny-engine

Length of output: 524


🏁 Script executed:

# Check what type of css parameter updateSchema actually expects
rg -n "pageSchema\.css\s*=" packages/settings/styles/src/ -B 2 -A 2

Repository: opentiny/tiny-engine

Length of output: 46


🏁 Script executed:

# Look for where the schema.css is read back
rg -n "getPageSchema.*css|schema\.css" packages/settings/styles/src/js/useStyle.ts -B 2 -A 5

Repository: opentiny/tiny-engine

Length of output: 336


🏁 Script executed:

# Check the watch that loads CSS from schema to understand the expected format
sed -n '176,189p' packages/settings/styles/src/js/useStyle.ts

Repository: opentiny/tiny-engine

Length of output: 424


🏁 Script executed:

# Understand the data flow: what gets stored and retrieved
# Check if css can be string or object in the schema
rg -n "css\s*:" packages/ -t ts -t vue | grep -E "schema.*css|css.*schema" | head -20

Repository: opentiny/tiny-engine

Length of output: 91


🏁 Script executed:

# Trace through a complete example: save -> updateSchema -> load
# Check how updateGlobalStyleStr preserves data
sed -n '268,293p' packages/settings/styles/src/js/useStyle.ts

Repository: opentiny/tiny-engine

Length of output: 692


🏁 Script executed:

# Final verification: trace the exact data type expected by updateSchema
# Look at how useEditor passes css to updateSchema (line 61)
sed -n '59,64p' packages/settings/styles/src/js/useEditor.ts

Repository: opentiny/tiny-engine

Length of output: 216


🏁 Script executed:

# And compare to how updateGlobalStyleStr does it
sed -n '261,266p' packages/settings/styles/src/js/useStyle.ts

Repository: opentiny/tiny-engine

Length of output: 225


🏁 Script executed:

# Verify what format the schema expects when it's loaded back
# Check line 180 in useStyle.ts more carefully
node << 'EOF'
// Simulate the obJectCssToString with different inputs

function obJectCssToString(css) {
  if (typeof css === 'string') {
    return css
  }
  let cssString = ''
  
  for (const selector in css) {
    const properties = css[selector]
    let ruleString = `${selector} {\r\n`
    
    for (const property in properties) {
      ruleString += ` ${property}: ${properties[property]};\r\n`
    }
    
    ruleString += '}\n'
    cssString += ruleString
  }
  return cssString
}

// Test with regex-based parser output
const regexOutput = {
  ".simple": { "color": "red" },
  ".a, .b": { "color": "blue" }  // Multiple selectors stored as-is
}

// Test with proper parser output structure
const parserOutput = {
  ".simple": { "mouseState": "", "pureSelector": ".simple", "rules": { "color": "red" } },
  ".a": { "mouseState": "", "pureSelector": ".a", "rules": { "color": "blue" } }
}

console.log("Regex output converted:")
console.log(obJectCssToString(regexOutput))
console.log("\nParser output with rules only:")
console.log(obJectCssToString(Object.fromEntries(
  Object.entries(parserOutput).map(([sel, val]) => [sel, val.rules])
)))
EOF

Repository: opentiny/tiny-engine

Length of output: 225


🏁 Script executed:

# Final check: what does updateSchema actually store and how is it retrieved?
# Look for the actual updateSchema implementation
rg -n "const updateSchema|export.*updateSchema" packages/ -t ts -A 5 | head -30

Repository: opentiny/tiny-engine

Length of output: 1077


🏁 Script executed:

# Verify the actual type that updateSchema stores for css
rg -n "css:" packages/canvas -t ts | grep -v "cssContent\|cssString\|cssClass" | head -20

Repository: opentiny/tiny-engine

Length of output: 401


Regex CSS parser causes silent data loss for complex CSS rules.

cssStringToObject cannot preserve @media, @keyframes, multiple selectors, or property values containing ; or }. This causes corruption when saving and reloading CSS (e.g., content: ";" becomes content: "", and @media rules lose their wrapper).

Use the existing parser and stringify utilities consistently, following the pattern in updateGlobalStyle():

-const cssStringToObject = (cssString) => {
-  cssString = cssString.replace(/\s+/g, ' ').trim()
-  const result = {}
-  const regex = /([^{]+)\{([^}]+)\}/g
-  ...
-  return result
-}

 const save = ({ content }) => {
-  const cssString = cssStringToObject(content)
+  const { parseList, styleObject } = parser(content)
+  const cssString = stringify(parseList, styleObject)
   const { addHistory } = useHistory()
   const { updateRect } = useCanvas().canvasApi.value
   const { updateSchema } = useCanvas()

This preserves all CSS structures through the round-trip.

🤖 Prompt for AI Agents
In `@packages/settings/styles/src/components/classNamesContainer/index.vue` around
lines 450 - 474, The custom regex parser cssStringToObject is dropping complex
CSS constructs (at-rules like `@media/`@keyframes, multiple selectors, and
property values containing semicolons or braces); replace its use with the
existing CSS parser/stringify utilities used in updateGlobalStyle() so the code
round-trips without loss — locate cssStringToObject and change the logic to
parse the input using the shared parser API and to serialize using the matching
stringify utility (following updateGlobalStyle()’s pattern) so `@rules`, grouped
selectors and literal property values (e.g., content: ";") are preserved.


const save = ({ content }) => {
const cssString = formatString(content.replace(/"/g, "'"), 'css')
const cssString = cssStringToObject(content)
const { addHistory } = useHistory()
const { updateRect } = useCanvas().canvasApi.value
const { updateSchema } = useCanvas()

updateSchema({ css: cssString })
state.schemaUpdateKey++

addHistory()
updateRect()
}
Expand Down
6 changes: 3 additions & 3 deletions packages/settings/styles/src/js/useStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { constants, utils } from '@opentiny/tiny-engine-utils'
import { parser, stringify, getSelectorArr } from './parser'

const { EXPRESSION_TYPE } = constants
const { generateRandomLetters, parseExpression } = utils
const { generateRandomLetters, parseExpression, obJectCssToString } = utils

const state = reactive({
// 当前选中节点的 style,解析成对象返回
Expand Down Expand Up @@ -177,10 +177,10 @@ export const initStylePanelWatch = () => {
watch(
() => useCanvas().getPageSchema?.()?.css,
(value) => {
state.cssContent = value || ''
state.cssContent = obJectCssToString(value) || ''

// 解析css
const { parseList, selectors, styleObject } = parser(value)
const { parseList, selectors, styleObject } = parser(state.cssContent)

state.cssParseList = parseList
state.selectors = selectors
Expand Down
26 changes: 26 additions & 0 deletions packages/utils/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,3 +452,29 @@ export const obj2StyleString = (obj: any) => {
.join('; ')
)
}

/**
* JSON转样式字符串
* @param {*} string
* @returns
*/

export const obJectCssToString = (css) => {
if (typeof css === 'string') {
return css
}
let cssString = ''

for (const selector in css) {
const properties = css[selector]
let ruleString = `${selector} {\r\n`

for (const property in properties) {
ruleString += ` ${property}: ${properties[property]};\r\n`
}

ruleString += '}\n'
cssString += ruleString
}
return cssString
Comment on lines +462 to +479
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against null/undefined to avoid runtime crashes.
for (const selector in css) throws when css is null/undefined, which is a common state for empty schemas.

🐛 Suggested fix
 export const obJectCssToString = (css) => {
+  if (!css) {
+    return ''
+  }
   if (typeof css === 'string') {
     return css
   }
+  if (typeof css !== 'object') {
+    return ''
+  }
   let cssString = ''
 
-  for (const selector in css) {
-    const properties = css[selector]
+  for (const [selector, properties] of Object.entries(css)) {
     let ruleString = `${selector} {\r\n`
 
-    for (const property in properties) {
-      ruleString += ` ${property}: ${properties[property]};\r\n`
+    for (const [property, value] of Object.entries(properties || {})) {
+      ruleString += ` ${property}: ${value};\r\n`
     }
🤖 Prompt for AI Agents
In `@packages/utils/src/utils/index.ts` around lines 462 - 479, The function
obJectCssToString should guard against css being null or undefined (and
non-object) to avoid runtime crashes; at the top of obJectCssToString check if
css is null/undefined and return an empty string (or if typeof css === 'string'
keep returning it), and when iterating ensure properties and
properties[property] are accessed only if properties is truthy (skip
null/undefined property objects) so the for-in loops never run on null; update
obJectCssToString accordingly to early-return for falsy/non-object inputs and to
safely handle missing nested property objects.

}
1 change: 1 addition & 0 deletions packages/vue-generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"homepage": "https://opentiny.design/tiny-engine",
"dependencies": {
"@opentiny/tiny-engine-builtin-component": "workspace:*",
"@opentiny/tiny-engine-utils": "workspace:*",
"@vue/compiler-sfc": "3.2.45",
"@vue/shared": "^3.3.4",
"vue": "^3.4.15",
Expand Down
7 changes: 5 additions & 2 deletions packages/vue-generator/src/generator/vue/sfc/generateStyle.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { utils } from '@opentiny/tiny-engine-utils'

const { obJectCssToString } = utils
export const generateStyleTag = (schema, config = {}) => {
const { css } = schema
const { scoped = true, lang = '' } = config
Expand All @@ -12,6 +15,6 @@ export const generateStyleTag = (schema, config = {}) => {
if (lang) {
langDesc = `lang=${langDesc}`
}

return `<style ${langDesc} ${scopedStr}> ${css || ''} </style>`
const cssString = obJectCssToString(css)
return `<style ${langDesc} ${scopedStr}> ${cssString || ''} </style>`
}