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

Hoist all expressions of a certain type to top scope using babel transform

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

我目前正在使用 React.js 重写部分应用程序,我正在实施 get_stringjavascript 版本(称之为 getString).

我需要一个方法...

我认为一个完美的解决方案是创建一个 babel 转换,将所有 getString 调用移动到顶级范围,留下一个变量作为参考。这将使我能够相对轻松地解决这两个问题。

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>
  }
}

我该怎么做?

我稍微更改了要求,所以...

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)
          })
        }
      }
    }
  }
}