使用babel变换将某种类型的所有表达式提升到顶部范围

时间:2016-12-21 12:44:00

标签: javascript babeljs abstract-syntax-tree

在工作中,我们有一个自定义的翻译解决方案。实施如下:

  • smarty模板中,调用get_string('unique_string_identifier', 'Default string')以获取已翻译的字符串。
  • 字符串存储在SQL数据库中。
  • 如果数据库中存在字符串,则对于所选语言(存储在会话中),将返回已翻译的字符串。
  • 否则返回默认字符串。

我目前正在使用React.js重写部分应用程序,并且正在实施javascript get_stringgetString(称之为getString 1}})。

  • translate函数位于名为getString的全局模块中。
我需要一个方法......
  • ...从我的文件中提取所有字符串标识符和默认字符串。
  • ...让反应应用程序知道从服务器请求哪些字符串(通过api)

我认为这是一个完美的解决方案是创建一个babel变换,将所有import React from 'react' import {getString} from 'translate' export default class TestComponent extends React.Component { render() { const translatedString = getString('unique_string_identifier_1', 'Default string 1') return <div> {getString('unique_string_identifier_2', 'Default string 2')} </div> } } 调用移动到顶部作用域,将变量作为参考。这样我就可以相对轻松地解决这两个问题。

import React from 'react'
import {getString} from 'translate'

const _getStringRef0 = getString('unique_string_identifier_1', 'Default string 1')
const _getStringRef1 = getString('unique_string_identifier_2', 'Default string 2')

export default class TestComponent extends React.Component {
  render() {
    const translatedString = _getStringRef0

    return <div>
     {_getStringRef1}
    </div>
  }
}

会变成:

{{1}}

我将如何做到这一点?

1 个答案:

答案 0 :(得分:1)

我已经略微改变了要求,所以......

import React from 'react'
import {getString, makeGetString} from 'translate'

const _ = makeGetString({
  prefix: 'unique_prefix'
})

export default class TestComponent extends React.Component {
  render() {
    const translatedString = getString('unique_string_identifier_1', 'Default string 1 %s', dynamic1, dynamic2)

    return <div>
     {getString('unique_string_identifier_2', 'Default string 2')}
     {_('string_identifier_3')}
    </div>
  }
}

...变为

import React from 'react'
import {getString, makeGetString} from 'translate'

const _getString = getString('unique_string_identifier_1', 'Default string 1 %s');
const _getString2 = getString('unique_string_identifier_2', 'Default string 2');

const _ = makeGetString({
  prefix: 'unique_prefix'
})

const _ref = _('string_identifier_3');

export default class TestComponent extends React.Component {
  render() {
    const translatedString = _getString(dynamic1, dynamic2)

    return <div>
     {_getString2()}
     {_ref()}
    </div>
  }
}

这实际上就是我所拥有的:

module.exports = function(babel) {
  const {types: t} = babel

  const origFnNames = [
    'getString',
    'makeGetString',
  ]

  const getStringVisitor = {
    CallExpression(path) {
      const callee = path.get('callee')
      if(callee && callee.node && this.fnMap[callee.node.name]) {
        this.replacePaths.push(path)
      }
    }
  }

  const makeGetStringVisitor = {
    VariableDeclaration(path) {
      path.node.declarations.forEach((decl) => {
        if(!(decl.init && decl.init.callee && !decl.parent)) {
          return
        }

        const fnInfo = this.fnMap[decl.init.callee.name]

        if(fnInfo && fnInfo.name === 'makeGetString') {
          this.fnMap[decl.id.name] = {
            name: decl.id.name,
            path
          }
        }
      })
    }
  }

  return {
    visitor: {
      ImportDeclaration(path) {
        if(path.node.source.value === 'translate') {
          const fnMap = {}

          path.node.specifiers.forEach((s) => {
            if(origFnNames.indexOf(s.imported.name) !== -1) {
              fnMap[s.local.name] = {
                name: s.imported.name,
                path
              }
            }
          })

          path.parentPath.traverse(makeGetStringVisitor, {fnMap})

          const replacePaths = []

          path.parentPath.traverse(getStringVisitor, {fnMap, replacePaths})

          delete fnMap.makeGetString

          Object.keys(fnMap).map((k) => {
            const fnInfo = fnMap[k]

            const paths = replacePaths.filter((p) => p.get('callee').node.name === fnInfo.name)

            const expressions = paths.map((rPath) => {
              const id = rPath.scope.generateUidIdentifierBasedOnNode(rPath.node)
              const args = rPath.node.arguments

              rPath.replaceWith(t.callExpression(id, args.slice(2)))

              const expr = t.callExpression(t.identifier(fnInfo.name), args.slice(0, 2))

              return t.variableDeclaration('const', [t.variableDeclarator(id, expr)])
            })

            fnInfo.path.insertAfter(expressions)
          })
        }
      }
    }
  }
}