Repository: nuxt/nuxt
File: packages/nuxt/src/pages/utils.ts (TypeScript)

Code before:
  for (const script of scriptBlocks) {
    if (!PAGE_META_RE.test(script.code)) {
      continue
    }

    const dynamicProperties = new Set<keyof NuxtPage>()

    let foundMeta = false

    parseAndWalk(script.code, absolutePath.replace(/\.\w+$/, '.' + script.loader), (node) => {
      if (foundMeta) { return }

      if (node.type !== 'ExpressionStatement' || node.expression.type !== 'CallExpression' || node.expression.callee.type !== 'Identifier' || node.expression.callee.name !== 'definePageMeta') { return }

      foundMeta = true
      let code = script.code
      let pageMetaArgument = node.expression.arguments[0]
      if (pageMetaArgument?.type !== 'ObjectExpression') {
        logger.warn(`\`definePageMeta\` must be called with an object literal (reading \`${absolutePath}\`).`)
        return
      }

      // when using ts we slice, transform and parse the `definePageMeta` node to avoid parsing the whole file
      if (/tsx?/.test(script.loader)) {
        const transformed = oxcTransform(absolutePath, script.code.slice(node.start, node.end), { lang: script.loader })
        code = transformed.code
        if (transformed.errors.length) {
          for (const error of transformed.errors) {
            logger.warn('Error while transforming `definePageMeta()`' + error.codeframe)
          }
          return
        }

        // we already know that the first statement is a call expression
        pageMetaArgument = ((parseSync('', transformed.code, { lang: 'js' }).program.body[0]! as ExpressionStatement).expression as CallExpression).arguments[0]! as ObjectExpression
      }

      for (const key of extractionKeys) {
        const property = pageMetaArgument.properties.find((property): property is ObjectProperty => property.type === 'Property' && property.key.type === 'Identifier' && property.key.name === key)
        if (!property) { continue }

        const { value, serializable } = isSerializable(code, property.value)
        if (!serializable) {
          logger.debug(`Skipping extraction of \`${key}\` metadata as it is not JSON-serializable (reading \`${absolutePath}\`).`)
          dynamicProperties.add(extraExtractionKeys.has(key) ? 'meta' : key)
          continue
        }

        if (extraExtractionKeys.has(key)) {
          extractedMeta.meta ??= {}
          extractedMeta.meta[key] = value

Review comment (performance):
@huang-julien now only the `definePageMeta` node is transformed, but will need to be parsed again because of that 🤔

I guess this would be faster on pages with lots of code (less to transform) but slower for smaller pages (parsing twice)

Code after:

  for (const script of scriptBlocks) {
    if (!PAGE_META_RE.test(script.code)) {
      continue
    }

    const dynamicProperties = new Set<keyof NuxtPage>()

    let foundMeta = false

    parseAndWalk(script.code, absolutePath.replace(/\.\w+$/, '.' + script.loader), (node) => {
      if (foundMeta) { return }

      if (node.type !== 'ExpressionStatement' || node.expression.type !== 'CallExpression' || node.expression.callee.type !== 'Identifier' || node.expression.callee.name !== 'definePageMeta') { return }

      foundMeta = true
      let code = script.code
      let pageMetaArgument = node.expression.arguments[0]
      if (pageMetaArgument?.type !== 'ObjectExpression') {
        logger.warn(`\`definePageMeta\` must be called with an object literal (reading \`${absolutePath}\`).`)
        return
      }

      // when using ts we slice, transform and parse the `definePageMeta` node to avoid parsing the whole file
      if (/tsx?/.test(script.loader)) {
        const transformed = oxcTransform(absolutePath, script.code.slice(node.start, node.end), { lang: script.loader })
        code = transformed.code
        if (transformed.errors.length) {
          for (const error of transformed.errors) {
            logger.warn('Error while transforming `definePageMeta()`' + error.codeframe)
          }
          return
        }

        // we already know that the first statement is a call expression
        pageMetaArgument = ((parseSync('', transformed.code, { lang: 'js' }).program.body[0]! as ExpressionStatement).expression as CallExpression).arguments[0]! as ObjectExpression
      }

      for (const key of extractionKeys) {
        const property = pageMetaArgument.properties.find((property): property is ObjectProperty => property.type === 'Property' && property.key.type === 'Identifier' && property.key.name === key)
        if (!property) { continue }

        const { value, serializable } = isSerializable(code, property.value)
        if (!serializable) {
          logger.debug(`Skipping extraction of \`${key}\` metadata as it is not JSON-serializable (reading \`${absolutePath}\`).`)
          dynamicProperties.add(extraExtractionKeys.has(key) ? 'meta' : key)
          continue
        }

        if (extraExtractionKeys.has(key)) {
          extractedMeta.meta ??= {}