JSXElement()

in packages/babel-plugin-transform-loadable-component/src/index.js [68:184]


    JSXElement(p) {
      let hasReferencedComponentProp = false
      let hasAsyncProp = false
      let hasLoadingProp = false
      let loadable = false
      const removeImports = []

      const COMPONENT = Symbol('__COMPONENT__')
      const ASYNC = Symbol('__ASYNC__')
      const LOADING = Symbol('__LOADING__')

      const attrs = p.get('openingElement').get('attributes')

      attrs.forEach((attrPath) => {
        // 如果不是一般的 attr (譬如 JSXSpreadAttribute), 则直接跳过检查
        if (!attrPath.isJSXAttribute()) {
          return
        }

        const { name, value, type } = getJSXAttr(attrPath)

        // 检查是否匹配 props.component: ReferencedIdentifier
        if (
          name === 'component' &&
          type === 'expression' &&
          value.isReferencedIdentifier() &&
          this.defaultImportsMap.has(value.node.name)
        ) {
          hasReferencedComponentProp = true
          // eslint-disable-next-line no-param-reassign
          attrPath[COMPONENT] = true
        }

        // 检查是否匹配 props.__async: true
        if (name === '__async') {
          hasAsyncProp = true
          // eslint-disable-next-line no-param-reassign
          attrPath[ASYNC] = true

          if (value === true) {
            loadable = true
          } else if (type === 'expression' && value.isBooleanLiteral()) {
            loadable = value.node.value
          }
        }

        if (name === '__loading' && type === 'expression') {
          hasLoadingProp = true
          // eslint-disable-next-line no-param-reassign
          attrPath[LOADING] = true
        }
      })

      if (hasReferencedComponentProp || hasAsyncProp || hasLoadingProp) {
        let newAttrs = attrs
        const existingReactLoadableIdentifier = this.reactLoadableImport.get(
          'identifier'
        )

        const loadingAttrPath = newAttrs.filter(
          attrPath => attrPath[LOADING]
        )[0]

        if (loadable && hasReferencedComponentProp) {
          newAttrs = newAttrs.map((attrPath) => {
            if (!attrPath[COMPONENT]) {
              return attrPath
            }

            const { value: originComponentValue } = getJSXAttr(attrPath)
            const {
              source,
              path: importDeclarationPath,
            } = this.defaultImportsMap.get(
              originComponentValue.node.name
            )

            this.matchedJSXSet.add(p)
            removeImports.push(importDeclarationPath)

            return {
              node: types.JSXAttribute(
                types.JSXIdentifier('component'),
                types.JSXExpressionContainer(
                  createCalleeCallExpression(
                    // eslint-disable-next-line max-len
                    existingReactLoadableIdentifier || defaultReactLoadableSpecifier,
                    [
                      createObjectExpression({
                        loader: types.arrowFunctionExpression(
                          [],
                          createCalleeCallExpression('import', [
                            types.stringLiteral(source),
                          ])
                        ),
                        loading: loadingAttrPath ?
                          loadingAttrPath.get('value').get('expression').node :
                          undefined,
                      }),
                    ]
                  )
                )
              ),
            }
          })
        }

        // eslint-disable-next-line no-param-reassign
        p.node.openingElement.attributes = newAttrs
          .filter(attrPath => (!attrPath[ASYNC] && !attrPath[LOADING]))
          .map(attrPath => attrPath.node)
      }

      removeImports.forEach((importPath) => {
        importPath && importPath.remove()
      })
    },