示例#1
0
文件: index.js 项目: lxe/babel
  addImport(source: string, imported: string, name?: string = imported): Object {
    let alias = `${source}:${imported}`;
    let id = this.dynamicImportIds[alias];

    if (!id) {
      source = this.resolveModuleSource(source);
      id = this.dynamicImportIds[alias] = this.scope.generateUidIdentifier(name);

      let specifiers = [];

      if (imported === "*") {
        specifiers.push(t.importNamespaceSpecifier(id));
      } else if (imported === "default") {
        specifiers.push(t.importDefaultSpecifier(id));
      } else {
        specifiers.push(t.importSpecifier(id, t.identifier(imported)));
      }

      let declar = t.importDeclaration(specifiers, t.stringLiteral(source));
      declar._blockHoist = 3;

      this.path.unshiftContainer("body", declar);
    }

    return id;
  }
 const imports = components.map((component, i) => {
     return t.importDeclaration(
         [t.importSpecifier(
             t.identifier(`${component.export.name}_${i}`),
             t.identifier(component.export.name)
         )],
         t.stringLiteral(component.fullPath)
     );
 });
  (node, importWrapper = defaultWrapperName, importFrom = 'cycle-hmr') => {
    const importLiteral = importWrapper
    const importLocalLiteral = getWrapperName(importWrapper)
    let importIdentifier = t.identifier(importLiteral)
    let importLocalIdentifier = t.identifier(importLocalLiteral)

    const importDeclaration = t.importDeclaration([
      t.importSpecifier(importLocalIdentifier, importIdentifier)
    ], t.stringLiteral(importFrom));
    node.body.unshift(importDeclaration);
  }
 return __awaiter(this, void 0, void 0, function* () {
     const bundleSource = babel.program([]);
     const sourceAnalysis = yield bundler.analyzer.analyze([...assignedBundle.bundle.files]);
     for (const resolvedSourceUrl of [...assignedBundle.bundle.files].sort()) {
         const moduleDocument = analyzer_utils_1.getAnalysisDocument(sourceAnalysis, resolvedSourceUrl);
         const moduleExports = es6_module_utils_1.getModuleExportNames(moduleDocument);
         const starExportName = es6_module_utils_1.getOrSetBundleModuleExportName(assignedBundle, resolvedSourceUrl, '*');
         bundleSource.body.push(babel.importDeclaration([babel.importNamespaceSpecifier(babel.identifier(starExportName))], babel.stringLiteral(resolvedSourceUrl)));
         if (moduleExports.size > 0) {
             bundleSource.body.push(babel.exportNamedDeclaration(undefined, [babel.exportSpecifier(babel.identifier(starExportName), babel.identifier(starExportName))]));
             bundleSource.body.push(babel.exportNamedDeclaration(undefined, [...moduleExports].map((e) => babel.exportSpecifier(babel.identifier(e), babel.identifier(es6_module_utils_1.getOrSetBundleModuleExportName(assignedBundle, resolvedSourceUrl, e)))), babel.stringLiteral(resolvedSourceUrl)));
         }
     }
     const { code } = babel_generator_1.default(bundleSource);
     return code;
 });
示例#5
0
function transform(options) {
    if (options.adapter) {
        adapter_1.setAdapter(options.adapter);
    }
    if (adapter_1.Adapter.type === "swan" /* swan */) {
        constant_1.setLoopOriginal('privateOriginal');
    }
    constant_1.THIRD_PARTY_COMPONENTS.clear();
    const code = options.isTyped
        ? ts.transpile(options.code, {
            jsx: ts.JsxEmit.Preserve,
            target: ts.ScriptTarget.ESNext,
            importHelpers: true,
            noEmitHelpers: true
        })
        : options.code;
    options.env = Object.assign({ 'process.env.TARO_ENV': options.adapter || 'weapp' }, options.env || {});
    options_1.setTransformOptions(options);
    utils_1.setting.sourceCode = code;
    // babel-traverse 无法生成 Hub
    // 导致 Path#getSource|buildCodeFrameError 都无法直接使用
    // 原因大概是 babylon.parse 没有生成 File 实例导致 scope 和 path 原型上都没有 `file`
    // 将来升级到 babel@7 可以直接用 parse 而不是 transform
    const ast = babel_core_1.transform(code, options_1.buildBabelTransformOptions()).ast;
    if (options.isNormal) {
        return { ast };
    }
    // transformFromAst(ast, code)
    let result;
    const componentSourceMap = new Map();
    const imageSource = new Set();
    const importSources = new Set();
    let componentProperies = [];
    let mainClass;
    let storeName;
    let renderMethod;
    let isImportTaro = false;
    babel_traverse_1.default(ast, {
        TemplateLiteral(path) {
            const nodes = [];
            const { quasis, expressions } = path.node;
            let index = 0;
            if (path.parentPath.isTaggedTemplateExpression()) {
                return;
            }
            for (const elem of quasis) {
                if (elem.value.cooked) {
                    nodes.push(t.stringLiteral(elem.value.cooked));
                }
                if (index < expressions.length) {
                    const expr = expressions[index++];
                    if (!t.isStringLiteral(expr, { value: '' })) {
                        nodes.push(expr);
                    }
                }
            }
            // + 号连接符必须保证第一和第二个 node 都是字符串
            if (!t.isStringLiteral(nodes[0]) && !t.isStringLiteral(nodes[1])) {
                nodes.unshift(t.stringLiteral(''));
            }
            let root = nodes[0];
            for (let i = 1; i < nodes.length; i++) {
                root = t.binaryExpression('+', root, nodes[i]);
            }
            path.replaceWith(root);
        },
        ClassDeclaration(path) {
            mainClass = path;
            const superClass = utils_1.getSuperClassCode(path);
            if (superClass) {
                try {
                    componentProperies = transform({
                        isRoot: false,
                        isApp: false,
                        code: superClass.code,
                        isTyped: true,
                        sourcePath: superClass.sourcePath,
                        outputPath: superClass.sourcePath
                    }).componentProperies;
                }
                catch (error) {
                    //
                }
            }
        },
        ClassExpression(path) {
            mainClass = path;
        },
        ClassMethod(path) {
            if (t.isIdentifier(path.node.key) && path.node.key.name === 'render') {
                renderMethod = path;
            }
        },
        IfStatement(path) {
            const consequent = path.get('consequent');
            if (!consequent.isBlockStatement()) {
                consequent.replaceWith(t.blockStatement([
                    consequent.node
                ]));
            }
        },
        CallExpression(path) {
            const callee = path.get('callee');
            if (utils_1.isContainJSXElement(path)) {
                return;
            }
            if (callee.isReferencedMemberExpression()) {
                const id = utils_1.findFirstIdentifierFromMemberExpression(callee.node);
                const property = callee.node.property;
                if (t.isIdentifier(property) && property.name.startsWith('on')) {
                    const funcExpr = path.findParent(p => p.isFunctionExpression());
                    if (funcExpr && funcExpr.isFunctionExpression()) {
                        const taroAPI = funcExpr.findParent(p => p.isCallExpression() && t.isMemberExpression(p.node.callee) && t.isIdentifier(p.node.callee.object, { name: 'Taro' }));
                        if (taroAPI && taroAPI.isCallExpression()) {
                            throw utils_1.codeFrameError(funcExpr.node, '在回调函数使用从 props 传递的函数时,请把回调函数改造为箭头函数并一直使用 `this` 取值');
                        }
                    }
                }
                const calleeIds = getIdsFromMemberProps(callee.node);
                if (t.isIdentifier(id) && id.name.startsWith('on') && "alipay" /* alipay */ !== adapter_1.Adapter.type) {
                    const fullPath = buildFullPathThisPropsRef(id, calleeIds, path);
                    if (fullPath) {
                        path.replaceWith(t.callExpression(fullPath, path.node.arguments));
                    }
                }
            }
            if (callee.isReferencedIdentifier()) {
                const id = callee.node;
                const ids = [id.name];
                if (t.isIdentifier(id) && id.name.startsWith('on')) {
                    const funcExpr = path.findParent(p => p.isFunctionExpression());
                    if (funcExpr && funcExpr.isFunctionExpression()) {
                        const taroAPI = funcExpr.findParent(p => p.isCallExpression() && t.isMemberExpression(p.node.callee) && t.isIdentifier(p.node.callee.object, { name: 'Taro' }));
                        if (taroAPI && taroAPI.isCallExpression()) {
                            throw utils_1.codeFrameError(funcExpr.node, '在回调函数使用从 props 传递的函数时,请把回调函数改造为箭头函数并一直使用 `this` 取值');
                        }
                    }
                    const fullPath = buildFullPathThisPropsRef(id, ids, path);
                    if (fullPath) {
                        path.replaceWith(t.callExpression(fullPath, path.node.arguments));
                    }
                }
            }
        },
        // JSXIdentifier (path) {
        //   const parentPath = path.parentPath
        //   if (!parentPath.isJSXAttribute()) {
        //     return
        //   }
        //   const element = parentPath.parentPath
        //   if (!element.isJSXOpeningElement()) {
        //     return
        //   }
        //   const elementName = element.get('name')
        //   if (!elementName.isJSXIdentifier()) {
        //     return
        //   }
        //   if (DEFAULT_Component_SET.has(elementName.node.name)) {
        //     return
        //   }
        //   const expr = parentPath.get('value.expression')
        // },
        JSXElement(path) {
            const assignment = path.findParent(p => p.isAssignmentExpression());
            if (assignment && assignment.isAssignmentExpression() && !options.isTyped) {
                const left = assignment.node.left;
                if (t.isIdentifier(left)) {
                    const binding = assignment.scope.getBinding(left.name);
                    if (binding && binding.scope === assignment.scope) {
                        if (binding.path.isVariableDeclarator()) {
                            binding.path.node.init = path.node;
                            assignment.remove();
                        }
                        else {
                            throw utils_1.codeFrameError(path.node, '同一个作用域的JSX 变量延时赋值没有意义。详见:https://github.com/NervJS/taro/issues/550');
                        }
                    }
                }
            }
            const switchStatement = path.findParent(p => p.isSwitchStatement());
            if (switchStatement && switchStatement.isSwitchStatement()) {
                const { discriminant, cases } = switchStatement.node;
                const ifStatement = cases.map((Case, index) => {
                    const [consequent] = Case.consequent;
                    if (!t.isBlockStatement(consequent)) {
                        throw utils_1.codeFrameError(switchStatement.node, '含有 JSX 的 switch case 语句必须每种情况都用花括号 `{}` 包裹结果');
                    }
                    const block = t.blockStatement(consequent.body.filter(b => !t.isBreakStatement(b)));
                    if (index !== cases.length - 1 && t.isNullLiteral(Case.test)) {
                        throw utils_1.codeFrameError(Case, '含有 JSX 的 switch case 语句只有最后一个 case 才能是 default');
                    }
                    const test = Case.test === null ? t.nullLiteral() : t.binaryExpression('===', discriminant, Case.test);
                    return { block, test };
                }).reduceRight((ifStatement, item) => {
                    if (t.isNullLiteral(item.test)) {
                        ifStatement.alternate = item.block;
                        return ifStatement;
                    }
                    const newStatement = t.ifStatement(item.test, item.block, t.isBooleanLiteral(ifStatement.test, { value: false })
                        ? ifStatement.alternate
                        : ifStatement);
                    return newStatement;
                }, t.ifStatement(t.booleanLiteral(false), t.blockStatement([])));
                switchStatement.insertAfter(ifStatement);
                switchStatement.remove();
            }
            const isForStatement = (p) => p && (p.isForStatement() || p.isForInStatement() || p.isForOfStatement());
            const forStatement = path.findParent(isForStatement);
            if (isForStatement(forStatement)) {
                throw utils_1.codeFrameError(forStatement.node, '不行使用 for 循环操作 JSX 元素,详情:https://github.com/NervJS/taro/blob/master/packages/eslint-plugin-taro/docs/manipulate-jsx-as-array.md');
            }
            const loopCallExpr = path.findParent(p => utils_1.isArrayMapCallExpression(p));
            if (loopCallExpr && loopCallExpr.isCallExpression()) {
                const [func] = loopCallExpr.node.arguments;
                if (t.isArrowFunctionExpression(func) && !t.isBlockStatement(func.body)) {
                    func.body = t.blockStatement([
                        t.returnStatement(func.body)
                    ]);
                }
            }
        },
        JSXOpeningElement(path) {
            const { name } = path.node.name;
            const binding = path.scope.getBinding(name);
            if (process.env.NODE_ENV !== 'test' && constant_1.DEFAULT_Component_SET.has(name) && binding && binding.kind === 'module') {
                const bindingPath = binding.path;
                if (bindingPath.parentPath.isImportDeclaration()) {
                    const source = bindingPath.parentPath.node.source;
                    if (source.value !== constant_1.COMPONENTS_PACKAGE_NAME) {
                        throw utils_1.codeFrameError(bindingPath.parentPath.node, `内置组件名: '${name}' 只能从 ${constant_1.COMPONENTS_PACKAGE_NAME} 引入。`);
                    }
                }
            }
            if (name === 'Provider') {
                const modules = path.scope.getAllBindings('module');
                const providerBinding = Object.values(modules).some((m) => m.identifier.name === 'Provider');
                if (providerBinding) {
                    path.node.name = t.jSXIdentifier('view');
                    const store = path.node.attributes.find(attr => attr.name.name === 'store');
                    if (store && t.isJSXExpressionContainer(store.value) && t.isIdentifier(store.value.expression)) {
                        storeName = store.value.expression.name;
                    }
                    path.node.attributes = [];
                }
            }
            if (constant_1.IMAGE_COMPONENTS.has(name)) {
                for (const attr of path.node.attributes) {
                    if (attr.name.name === 'src') {
                        if (t.isStringLiteral(attr.value)) {
                            imageSource.add(attr.value.value);
                        }
                        else if (t.isJSXExpressionContainer(attr.value)) {
                            if (t.isStringLiteral(attr.value.expression)) {
                                imageSource.add(attr.value.expression.value);
                            }
                        }
                    }
                }
            }
        },
        JSXAttribute(path) {
            const { name, value } = path.node;
            if (options.jsxAttributeNameReplace) {
                for (const r in options.jsxAttributeNameReplace) {
                    if (options.jsxAttributeNameReplace.hasOwnProperty(r)) {
                        const element = options.jsxAttributeNameReplace[r];
                        if (t.isJSXIdentifier(name, { name: r })) {
                            path.node.name = t.jSXIdentifier(element);
                        }
                    }
                }
            }
            if (!t.isJSXIdentifier(name) || value === null || t.isStringLiteral(value) || t.isJSXElement(value)) {
                return;
            }
            const expr = value.expression;
            const exprPath = path.get('value.expression');
            const classDecl = path.findParent(p => p.isClassDeclaration());
            const classDeclName = classDecl && classDecl.isClassDeclaration() && lodash_1.get(classDecl, 'node.id.name', '');
            let isConverted = false;
            if (classDeclName) {
                isConverted = classDeclName === '_C' || classDeclName.endsWith('Tmpl');
            }
            if (!t.isBinaryExpression(expr, { operator: '+' }) && !t.isLiteral(expr) && name.name === 'style' && !isConverted) {
                const jsxID = path.findParent(p => p.isJSXOpeningElement()).get('name');
                if (jsxID && jsxID.isJSXIdentifier() && constant_1.DEFAULT_Component_SET.has(jsxID.node.name)) {
                    exprPath.replaceWith(t.callExpression(t.identifier(constant_1.INTERNAL_INLINE_STYLE), [expr]));
                }
            }
            if (name.name.startsWith('on')) {
                if (exprPath.isReferencedIdentifier()) {
                    const ids = [expr.name];
                    const fullPath = buildFullPathThisPropsRef(expr, ids, path);
                    if (fullPath) {
                        exprPath.replaceWith(fullPath);
                    }
                }
                if (exprPath.isReferencedMemberExpression()) {
                    const id = utils_1.findFirstIdentifierFromMemberExpression(expr);
                    const ids = getIdsFromMemberProps(expr);
                    if (t.isIdentifier(id)) {
                        const fullPath = buildFullPathThisPropsRef(id, ids, path);
                        if (fullPath) {
                            exprPath.replaceWith(fullPath);
                        }
                    }
                }
                // @TODO: bind 的处理待定
            }
        },
        ImportDeclaration(path) {
            const source = path.node.source.value;
            if (importSources.has(source)) {
                throw utils_1.codeFrameError(path.node, '无法在同一文件重复 import 相同的包。');
            }
            else {
                importSources.add(source);
            }
            const names = [];
            if (source === constant_1.TARO_PACKAGE_NAME) {
                isImportTaro = true;
                path.node.specifiers.push(t.importSpecifier(t.identifier(constant_1.INTERNAL_SAFE_GET), t.identifier(constant_1.INTERNAL_SAFE_GET)), t.importSpecifier(t.identifier(constant_1.INTERNAL_GET_ORIGNAL), t.identifier(constant_1.INTERNAL_GET_ORIGNAL)), t.importSpecifier(t.identifier(constant_1.INTERNAL_INLINE_STYLE), t.identifier(constant_1.INTERNAL_INLINE_STYLE)), t.importSpecifier(t.identifier(constant_1.GEL_ELEMENT_BY_ID), t.identifier(constant_1.GEL_ELEMENT_BY_ID)));
            }
            if (source === constant_1.REDUX_PACKAGE_NAME || source === constant_1.MOBX_PACKAGE_NAME) {
                path.node.specifiers.forEach((s, index, specs) => {
                    if (s.local.name === 'Provider') {
                        specs.splice(index, 1);
                        specs.push(t.importSpecifier(t.identifier('setStore'), t.identifier('setStore')));
                    }
                });
            }
            path.traverse({
                ImportDefaultSpecifier(path) {
                    const name = path.node.local.name;
                    names.push(name);
                },
                ImportSpecifier(path) {
                    const name = path.node.imported.name;
                    names.push(name);
                    if (source === constant_1.TARO_PACKAGE_NAME && name === 'Component') {
                        path.node.local = t.identifier('__BaseComponent');
                    }
                }
            });
            componentSourceMap.set(source, names);
        }
    });
    if (!isImportTaro) {
        ast.program.body.unshift(t.importDeclaration([
            t.importDefaultSpecifier(t.identifier('Taro')),
            t.importSpecifier(t.identifier(constant_1.INTERNAL_SAFE_GET), t.identifier(constant_1.INTERNAL_SAFE_GET)),
            t.importSpecifier(t.identifier(constant_1.INTERNAL_GET_ORIGNAL), t.identifier(constant_1.INTERNAL_GET_ORIGNAL)),
            t.importSpecifier(t.identifier(constant_1.INTERNAL_INLINE_STYLE), t.identifier(constant_1.INTERNAL_INLINE_STYLE))
        ], t.stringLiteral('@tarojs/taro')));
    }
    if (!mainClass) {
        throw new Error('未找到 Taro.Component 的类定义');
    }
    mainClass.node.body.body.forEach(handleThirdPartyComponent);
    const storeBinding = mainClass.scope.getBinding(storeName);
    mainClass.scope.rename('Component', '__BaseComponent');
    if (storeBinding) {
        const statementPath = storeBinding.path.getStatementParent();
        if (statementPath) {
            ast.program.body.forEach((node, index, body) => {
                if (node === statementPath.node) {
                    body.splice(index + 1, 0, t.expressionStatement(t.callExpression(t.identifier('setStore'), [
                        t.identifier(storeName)
                    ])));
                }
            });
        }
    }
    resetTSClassProperty(mainClass.node.body.body);
    if (options.isApp) {
        renderMethod.replaceWith(t.classMethod('method', t.identifier('_createData'), [], t.blockStatement([])));
        return { ast };
    }
    result = new class_1.Transformer(mainClass, options.sourcePath, componentProperies).result;
    result.code = babel_generator_1.default(ast).code;
    result.ast = ast;
    const lessThanSignReg = new RegExp(constant_1.lessThanSignPlacehold, 'g');
    result.compressedTemplate = result.template;
    result.template = html_1.prettyPrint(result.template, {
        max_char: 0,
        unformatted: process.env.NODE_ENV === 'test' ? [] : ['text']
    });
    result.template = result.template.replace(lessThanSignReg, '<');
    result.imageSrcs = Array.from(imageSource);
    return result;
}
示例#6
0
文件: h5.js 项目: AlloyTeam/Nuclear
function processOthers (code, filePath, fileType) {
  let ast = wxTransformer({
    code,
    sourcePath: filePath,
    isNormal: true,
    isTyped: Util.REG_TYPESCRIPT.test(filePath),
    adapter: 'h5'
  }).ast
  let taroImportDefaultName
  let hasAddNervJsImportDefaultName = false
  let hasJSX = false
  let isPage = fileType === FILE_TYPE.PAGE
  let hasComponentDidMount = false
  let hasComponentDidShow = false

  ast = babel.transformFromAst(ast, '', {
    plugins: [
      [require('babel-plugin-danger-remove-unused-import'), { ignore: ['@tarojs/taro', 'react', 'nervjs'] }]
    ]
  }).ast

  const ClassDeclarationOrExpression = {
    enter (astPath) {
      const node = astPath.node
      if (!node.superClass) return
      if (
        node.superClass.type === 'MemberExpression' &&
        node.superClass.object.name === taroImportDefaultName
      ) {
        node.superClass.object.name = taroImportDefaultName
        if (node.id === null) {
          const renameComponentClassName = '_TaroComponentClass'
          astPath.replaceWith(
            t.classExpression(
              t.identifier(renameComponentClassName),
              node.superClass,
              node.body,
              node.decorators || []
            )
          )
        }
        //@fix
      } else if (node.superClass.name === 'Component' || node.superClass.name === 'WeElement') {
        resetTSClassProperty(node.body.body)
        if (node.id === null) {
          const renameComponentClassName = '_TaroComponentClass'
          astPath.replaceWith(
            t.classExpression(
              t.identifier(renameComponentClassName),
              node.superClass,
              node.body,
              node.decorators || []
            )
          )
        }
      }
    }
  }

  const programExitVisitor = {
    ClassBody: {
      exit (astPath) {
        if (!hasComponentDidMount) {
          astPath.pushContainer('body', t.classMethod(
            'method', t.identifier('componentDidMount'), [],
            t.blockStatement([]), false, false))
        }
        if (!hasComponentDidShow) {
          astPath.pushContainer('body', t.classMethod(
            'method', t.identifier('componentDidShow'), [],
            t.blockStatement([]), false, false))
        }
        //@fix
        astPath.unshiftContainer('body', t.classProperty(
          t.identifier('static css'),
          t.identifier('___css')
        ))
      }
    }
  }

  traverse(ast, {
    ClassExpression: ClassDeclarationOrExpression,
    ClassDeclaration: ClassDeclarationOrExpression,
    ClassMethod: isPage ? {
      exit (astPath) {
        const node = astPath.node
        const key = node.key
        const keyName = getObjKey(key)
        if (keyName === 'componentDidMount') {
          hasComponentDidMount = true
        } else if (keyName === 'componentDidShow') {
          hasComponentDidShow = true
        }
      }
    } : {},
    //@fix
    ClassBody:{
      enter (astPath) {
      astPath.unshiftContainer('body', t.classProperty(
        t.identifier('static css'),
        t.identifier('___css')
      ))
      }
    },
    ImportDeclaration: {
      enter (astPath) {
        const node = astPath.node
        const source = node.source
        let value = source.value
        const specifiers = node.specifiers
        //@fix
        if(value.endsWith('.css')){
          let css = fs.readFileSync(filePath.replace('.tsx','.css').replace('.js','.css'), 'utf-8').replace(/\/\*[^*]*\*+([^/][^*]*\*+)*\//g, '')
          //page里需要注入 appcss
          if(filePath.indexOf('/src/pages/')!==-1||filePath.indexOf('\\src\\pages\\')!==-1){
            css = appCSS + css
          }
          astPath.replaceWith(t.variableDeclaration('const',[t.variableDeclarator(t.identifier(`___css`),t.callExpression(t.identifier('Omi.rpx'),[t.stringLiteral(compileWxss(css))]),)]))
          return
        }
        if (Util.isAliasPath(value, pathAlias)) {
          source.value = value = Util.replaceAliasPath(filePath, value, pathAlias)
        }
        if (!Util.isNpmPkg(value)) {
          if (Util.REG_SCRIPTS.test(value)) {
            const realPath = path.resolve(filePath, '..', value)
            const dirname = path.dirname(realPath)
            const extname = path.extname(realPath)
            const removeExtPath = path.join(dirname, path.basename(realPath, extname))
            node.source = t.stringLiteral(Util.promoteRelativePath(path.relative(filePath, removeExtPath)).replace(/\\/g, '/'))
          }
        } else if (value === PACKAGES['@tarojs/taro']) {
          let specifier = specifiers.find(item => item.type === 'ImportDefaultSpecifier')
          if (specifier) {
            hasAddNervJsImportDefaultName = true
            taroImportDefaultName = specifier.local.name
            specifier.local.name = nervJsImportDefaultName
          } else if (!hasAddNervJsImportDefaultName) {
            //@fix
            //hasAddNervJsImportDefaultName = true
            // node.specifiers.unshift(
            //   t.importDefaultSpecifier(t.identifier(nervJsImportDefaultName))
            // )
          }
          const taroApisSpecifiers = []
          const deletedIdx = []
          specifiers.forEach((item, index) => {
            if (item.imported && taroApis.indexOf(item.imported.name) >= 0) {
              taroApisSpecifiers.push(t.importSpecifier(t.identifier(item.local.name), t.identifier(item.imported.name)))
              deletedIdx.push(index)
            }
          })
          _.pullAt(specifiers, deletedIdx)
          source.value = PACKAGES['nervjs']

          if (taroApisSpecifiers.length) {
            astPath.insertBefore(t.importDeclaration(taroApisSpecifiers, t.stringLiteral(PACKAGES['@tarojs/taro-h5'])))
          }
          if (!specifiers.length) {
            astPath.remove()
          }
        } else if (value === PACKAGES['@tarojs/redux']) {
          source.value = PACKAGES['@tarojs/redux-h5']
        } else if (value === PACKAGES['@tarojs/mobx']) {
          source.value = PACKAGES['@tarojs/mobx-h5']
        }
      }
    },
    JSXElement: {
      enter (astPath) {
        hasJSX = true
      }
    },
    Program: {
      exit (astPath) {
        if (isPage) {
          astPath.traverse(programExitVisitor)
        }
        const node = astPath.node
        if (hasJSX && !hasAddNervJsImportDefaultName) {
          //@fix
          // node.body.unshift(
          //   t.importDeclaration([
          //     t.importDefaultSpecifier(t.identifier(nervJsImportDefaultName))
          //   ], t.stringLiteral(PACKAGES['nervjs']))
          // )
        }
        if (taroImportDefaultName) {
          const importTaro = toAst(`import ${taroImportDefaultName} from '${PACKAGES['@tarojs/taro-h5']}'`)
          node.body.unshift(importTaro)
        }
      }
    }
  })
  const generateCode = generate(ast, {
    jsescOption: {
      minimal: true
    }
  }).code
  return {
    code: generateCode,
    ast
  }
}
示例#7
0
文件: h5.js 项目: AlloyTeam/Nuclear
function processEntry (code, filePath) {
  let ast = wxTransformer({
    code,
    sourcePath: filePath,
    isNormal: true,
    isTyped: Util.REG_TYPESCRIPT.test(filePath),
    adapter: 'h5'
  }).ast
  let taroImportDefaultName
  let providorImportName
  let storeName
  let renderCallCode

  let hasAddNervJsImportDefaultName = false
  let hasConstructor = false
  let hasComponentWillMount = false
  let hasComponentDidMount = false
  let hasComponentDidShow = false
  let hasComponentDidHide = false
  let hasComponentWillUnmount = false
  let hasJSX = false
  let hasState = false

  const initPxTransformNode = toAst(`Taro.initPxTransform(${JSON.stringify(pxTransformConfig)})`)
  const additionalConstructorNode = toAst(`Taro._set$app(this)`)

  ast = babel.transformFromAst(ast, '', {
    plugins: [
      [require('babel-plugin-danger-remove-unused-import'), { ignore: ['@tarojs/taro', 'react', 'nervjs'] }]
    ]
  }).ast

  const ClassDeclarationOrExpression = {
    enter (astPath) {
      const node = astPath.node
      if (!node.superClass) return
      if (
        node.superClass.type === 'MemberExpression' &&
        node.superClass.object.name === taroImportDefaultName
      ) {
        node.superClass.object.name = taroImportDefaultName
        if (node.id === null) {
          const renameComponentClassName = '_TaroComponentClass'
          astPath.replaceWith(
            t.classExpression(
              t.identifier(renameComponentClassName),
              node.superClass,
              node.body,
              node.decorators || []
            )
          )
        }
        //@fix
      } else if (node.superClass.name === 'Component' || node.superClass.name === 'WeElement') {
        resetTSClassProperty(node.body.body)
        if (node.id === null) {
          const renameComponentClassName = '_TaroComponentClass'
          astPath.replaceWith(
            t.classExpression(
              t.identifier(renameComponentClassName),
              node.superClass,
              node.body,
              node.decorators || []
            )
          )
        }
      }
    }
  }

  /**
   * ProgramExit使用的visitor
   * 负责修改render函数的内容,在componentDidMount中增加componentDidShow调用,在componentWillUnmount中增加componentDidHide调用。
   */
  const programExitVisitor = {
    ClassMethod: {
      exit (astPath) {
        const node = astPath.node
        const key = node.key
        const keyName = getObjKey(key)
        let funcBody

        const isRender = keyName === 'render'
        const isComponentWillMount = keyName === 'componentWillMount'
        const isComponentDidMount = keyName === 'componentDidMount'
        const isComponentWillUnmount = keyName === 'componentWillUnmount'
        const isConstructor = keyName === 'constructor'
        const basename = JSON.stringify(addLeadingSlash(stripTrailingSlash(routerBasename)))

        if (isRender) {
          const routes = pages.map((v, k) => {
            const absPagename = addLeadingSlash(v)
            const relPagename = `.${absPagename}`
            const chunkName = relPagename.split('/').filter(v => !/^(pages|\.)$/i.test(v)).join('_')
            return createRoute({
              absPagename,
              relPagename,
              chunkName,
              isIndex: k === 0
            })
          })
          //@fix
          funcBody = `<o-router
            mode={${JSON.stringify(routerMode)}}
            publicPath={${JSON.stringify(routerMode === 'hash' ? '/' : publicPath)}}
            routes={[${routes.join(',')}]}
            customRoutes={${JSON.stringify(customRoutes)}}
            basename={${basename}}
          />`

          /* 插入Tabbar */
          if (tabBar) {
            const homePage = pages[0] || ''
            if (tabbarPos === 'top') {
              funcBody = `
                <${tabBarContainerComponentName}>
                  <${tabBarComponentName} conf={${tabBarConfigName}} homePage="${homePage}" router={${taroImportDefaultName}}/>
                  <${tabBarPanelComponentName}>
                    ${funcBody}
                  </${tabBarPanelComponentName}>
                </${tabBarContainerComponentName}>`
            } else {
              funcBody = `
                <${tabBarContainerComponentName}>

                  <${tabBarPanelComponentName}>
                    ${funcBody}
                  </${tabBarPanelComponentName}>

                  <${tabBarComponentName}
                    mode={${JSON.stringify(routerMode)}}
                    conf={this.state.${tabBarConfigName}}
                    homePage="${homePage}"
                    router={${taroImportDefaultName}}
                    basename={${basename}} />

                </${tabBarContainerComponentName}>`
            }
          }

          /* 插入<Provider /> */
          if (providerComponentName && storeName) {
            // 使用redux 或 mobx
            funcBody = `
              <${providorImportName} store={${storeName}}>
                ${funcBody}
              </${providorImportName}>`
          }

          /* 插入<Router /> */
          node.body = toAst(`{return (${funcBody});}`, { preserveComments: true })
        }

        if (tabBar && isComponentWillMount) {
          const initTabBarApisCallNode = toAst(`Taro.initTabBarApis(this, Taro)`)
          astPath.get('body').pushContainer('body', initTabBarApisCallNode)
        }

        // if (hasConstructor && isConstructor) {
        //   astPath.get('body').pushContainer('body', additionalConstructorNode)
        // }

        if (hasComponentDidShow && isComponentDidMount) {
          const componentDidShowCallNode = toAst(`this.componentDidShow()`)
          astPath.get('body').pushContainer('body', componentDidShowCallNode)
        }

        if (hasComponentDidHide && isComponentWillUnmount) {
          const componentDidHideCallNode = toAst(`this.componentDidHide()`)
          astPath.get('body').unshiftContainer('body', componentDidHideCallNode)
        }
      }
    },
    ClassProperty: {
      exit (astPath) {
        const node = astPath.node
        const key = node.key
        const value = node.value
        if (key.name !== 'state' || !t.isObjectExpression(value)) return
        astPath.node.value.properties.push(t.objectProperty(
          t.identifier(tabBarConfigName),
          tabBar
        ))
      }
    },
    ClassBody: {
      exit (astPath) {
        if (hasComponentDidShow && !hasComponentDidMount) {
          astPath.pushContainer('body', t.classMethod(
            'method', t.identifier('componentDidMount'), [],
            t.blockStatement([]), false, false))
        }
        if (hasComponentDidHide && !hasComponentWillUnmount) {
          astPath.pushContainer('body', t.classMethod(
            'method', t.identifier('componentWillUnmount'), [],
            t.blockStatement([]), false, false))
        }
        //@fix
        // if (!hasConstructor) {
        //   astPath.pushContainer('body', t.classMethod(
        //     'method', t.identifier('constructor'), [t.identifier('props'), t.identifier('context')],
        //     t.blockStatement([toAst('super(props, context)'), additionalConstructorNode]), false, false))
        // }
        if (tabBar) {
          if (!hasComponentWillMount) {
            astPath.pushContainer('body', t.classMethod(
              'method', t.identifier('componentWillMount'), [],
              t.blockStatement([]), false, false))
          }
          if (!hasState) {
            astPath.unshiftContainer('body', t.classProperty(
              t.identifier('state'),
              t.objectExpression([])
            ))
          }
        }
      }
    }
  }

  /**
   * ClassProperty使用的visitor
   * 负责收集config中的pages,收集tabbar的position,替换icon。
   */
  const classPropertyVisitor = {
    ObjectProperty (astPath) {
      const node = astPath.node
      const key = node.key
      const value = node.value
      const keyName = getObjKey(key)
      if (keyName === 'pages' && t.isArrayExpression(value)) {
        const subPackageParent = astPath.findParent(isUnderSubPackages)
        let root = ''
        if (subPackageParent) {
          /* 在subPackages属性下,说明是分包页面,需要处理root属性 */
          const rootNode = astPath.parent.properties.find(v => {
            return getObjKey(v.key) === 'root'
          })
          root = rootNode ? rootNode.value.value : ''
        }
        value.elements.forEach(v => {
          const pagePath = `${root}/${v.value}`.replace(/\/{2,}/g, '/')
          pages.push(pagePath.replace(/^\//, ''))
        })
      } else if (keyName === 'tabBar' && t.isObjectExpression(value)) {
        // tabBar
        tabBar = value
        value.properties.forEach(node => {
          if (node.keyName === 'position') tabbarPos = node.value.value
        })
      } else if ((keyName === 'iconPath' || keyName === 'selectedIconPath') && t.isStringLiteral(value)) {
        astPath.replaceWith(
          t.objectProperty(t.stringLiteral(keyName), t.callExpression(t.identifier('require'), [t.stringLiteral(`./${value.value}`)]))
        )
      }
    }
  }

  traverse(ast, {
    ClassExpression: ClassDeclarationOrExpression,
    ClassDeclaration: ClassDeclarationOrExpression,
    ClassProperty: {
      enter (astPath) {
        const node = astPath.node
        const key = node.key
        const value = node.value
        const keyName = getObjKey(key)

        if (keyName === 'state') hasState = true
        if (keyName !== 'config' || !t.isObjectExpression(value)) return
        astPath.traverse(classPropertyVisitor)
      }
    },
    ImportDeclaration: {
      enter (astPath) {
        const node = astPath.node
        const source = node.source
        const specifiers = node.specifiers
        let value = source.value
        //@fix
        if(value.endsWith('.css')){
          appCSS = fs.readFileSync(filePath.replace('.tsx','.css').replace('.js','.css'), 'utf-8').replace(/\/\*[^*]*\*+([^/][^*]*\*+)*\//g, '')
          // astPath.replaceWith(t.variableDeclaration('const',[t.variableDeclarator(t.identifier(`___css`),t.stringLiteral(appCSS))]))
          astPath.remove()
          return
        }
        if (Util.isAliasPath(value, pathAlias)) {
          source.value = value = Util.replaceAliasPath(filePath, value, pathAlias)
        }
        if (!Util.isNpmPkg(value)) {
          if (value.indexOf('.') === 0) {
            const pathArr = value.split('/')
            if (pathArr.indexOf('pages') >= 0) {
              astPath.remove()
            } else if (Util.REG_SCRIPTS.test(value)) {
              const realPath = path.resolve(filePath, '..', value)
              const dirname = path.dirname(realPath)
              const extname = path.extname(realPath)
              const removeExtPath = path.join(dirname, path.basename(realPath, extname))
              node.source = t.stringLiteral(Util.promoteRelativePath(path.relative(filePath, removeExtPath)).replace(/\\/g, '/'))
            }
          }
          return
        }
        if (value === PACKAGES['@tarojs/taro']) {
          let specifier = specifiers.find(item => item.type === 'ImportDefaultSpecifier')
          if (specifier) {
            hasAddNervJsImportDefaultName = true
            taroImportDefaultName = specifier.local.name
            specifier.local.name = nervJsImportDefaultName
          } else if (!hasAddNervJsImportDefaultName) {
            hasAddNervJsImportDefaultName = true
            //@fix
            // node.specifiers.unshift(
            //   t.importDefaultSpecifier(t.identifier(nervJsImportDefaultName))
            // )
          }
          const taroApisSpecifiers = []
          const deletedIdx = []
          specifiers.forEach((item, index) => {
            if (item.imported && taroApis.indexOf(item.imported.name) >= 0) {
              taroApisSpecifiers.push(t.importSpecifier(t.identifier(item.local.name), t.identifier(item.imported.name)))
              deletedIdx.push(index)
            }
          })
          _.pullAt(specifiers, deletedIdx)
          source.value = PACKAGES['nervjs']

          if (taroApisSpecifiers.length) {
            astPath.insertBefore(t.importDeclaration(taroApisSpecifiers, t.stringLiteral(PACKAGES['@tarojs/taro-h5'])))
          }
          if (!specifiers.length) {
            astPath.remove()
          }
        } else if (value === PACKAGES['@tarojs/redux']) {
          const specifier = specifiers.find(item => {
            return t.isImportSpecifier(item) && item.imported.name === providerComponentName
          })
          if (specifier) {
            providorImportName = specifier.local.name
          } else {
            providorImportName = providerComponentName
            specifiers.push(t.importSpecifier(t.identifier(providerComponentName), t.identifier(providerComponentName)))
          }
          source.value = PACKAGES['@tarojs/redux-h5']
        } else if (value === PACKAGES['@tarojs/mobx']) {
          const specifier = specifiers.find(item => {
            return t.isImportSpecifier(item) && item.imported.name === providerComponentName
          })
          if (specifier) {
            providorImportName = specifier.local.name
          } else {
            providorImportName = providerComponentName
            specifiers.push(t.importSpecifier(t.identifier(providerComponentName), t.identifier(providerComponentName)))
          }
          source.value = PACKAGES['@tarojs/mobx-h5']
        }
      }
    },
    CallExpression: {
      enter (astPath) {
        const node = astPath.node
        const callee = node.callee
        const calleeName = callee.name
        const parentPath = astPath.parentPath

        if (t.isMemberExpression(callee)) {
          if (callee.object.name === taroImportDefaultName && callee.property.name === 'render') {
            callee.object.name = nervJsImportDefaultName
            renderCallCode = generate(astPath.node).code
            astPath.remove()
          }
        } else {
          if (calleeName === setStoreFuncName) {
            if (parentPath.isAssignmentExpression() ||
              parentPath.isExpressionStatement() ||
              parentPath.isVariableDeclarator()) {
              parentPath.remove()
            }
          }
        }
      }
    },
    ClassMethod: {
      exit (astPath) {
        const node = astPath.node
        const key = node.key
        const keyName = getObjKey(key)
        if (keyName === 'constructor') {
          hasConstructor = true
        } else if (keyName === 'componentWillMount') {
          hasComponentWillMount = true
        } else if (keyName === 'componentDidMount') {
          hasComponentDidMount = true
        } else if (keyName === 'componentDidShow') {
          hasComponentDidShow = true
        } else if (keyName === 'componentDidHide') {
          hasComponentDidHide = true
        } else if (keyName === 'componentWillUnmount') {
          hasComponentWillUnmount = true
        }
      }
    },
    JSXElement: {
      enter (astPath) {
        hasJSX = true
      }
    },
    JSXOpeningElement: {
      enter (astPath) {
        if (astPath.node.name.name === 'Provider') {
          for (let v of astPath.node.attributes) {
            if (v.name.name !== 'store') continue
            storeName = v.value.expression.name
            break
          }
        }
      }
    },
    Program: {
      exit (astPath) {
        const importNervjsNode = t.importDefaultSpecifier(t.identifier(nervJsImportDefaultName))
        const importRouterNode = toAst(`import '${PACKAGES['@tarojs/router']}'`)
        //@fix
        const importMpNode = toAst(`import './libs/mp'`)
        const importTaroH5Node = toAst(`import ${taroImportDefaultName} from '${PACKAGES['@tarojs/taro-h5']}'`)
        const importComponentNode = toAst(`import { View, ${tabBarComponentName}, ${tabBarContainerComponentName}, ${tabBarPanelComponentName}} from '${PACKAGES['@tarojs/components']}'`)
        const lastImportIndex = _.findLastIndex(astPath.node.body, t.isImportDeclaration)
        const lastImportNode = astPath.get(`body.${lastImportIndex > -1 ? lastImportIndex : 0}`)
        const extraNodes = [
          //@fix
          //importTaroH5Node,
          importMpNode,
          importRouterNode,
          //@fix
          //initPxTransformNode
        ]

        astPath.traverse(programExitVisitor)

        if (hasJSX && !hasAddNervJsImportDefaultName) {
          //@fix
          //extraNodes.unshift(importNervjsNode)
        }
        if (tabBar) {
          extraNodes.unshift(importComponentNode)
        }

        lastImportNode.insertAfter(extraNodes)
        if (renderCallCode) {
          const renderCallNode = toAst(renderCallCode)
          astPath.pushContainer('body', renderCallNode)
        }
      }
    }
  })
  const generateCode = generate(ast, {
    jsescOption: {
      minimal: true
    }
  }).code
  return {
    code: generateCode,
    ast
  }
}
示例#8
0
文件: weapp.js 项目: ApolloRobot/taro
function parseAst (type, ast, sourceFilePath, filePath) {
  const styleFiles = []
  const scriptFiles = []
  const jsonFiles = []
  const mediaFiles = []
  let configObj = {}
  let componentClassName = null
  let taroJsReduxConnect = null
  function traverseObjectNode (node, obj) {
    if (node.type === 'ClassProperty' || node.type === 'ObjectProperty') {
      const properties = node.value.properties
      obj = {}
      properties.forEach((p, index) => {
        obj[p.key.name] = traverseObjectNode(p.value)
      })
      return obj
    }
    if (node.type === 'ObjectExpression') {
      const properties = node.properties
      obj = {}
      properties.forEach((p, index) => {
        const key = t.isIdentifier(p.key) ? p.key.name : p.key.value
        obj[key] = traverseObjectNode(p.value)
      })
      return obj
    }
    if (node.type === 'ArrayExpression') {
      return node.elements.map(item => traverseObjectNode(item))
    }
    if (node.type === 'NullLiteral') {
      return null
    }
    return node.value
  }
  let taroImportDefaultName
  let needExportDefault = false
  let exportTaroReduxConnected = null
  traverse(ast, {
    ClassDeclaration (astPath) {
      const node = astPath.node
      if (node.superClass) {
        if (node.superClass.name === 'Component' ||
        (node.superClass.type === 'MemberExpression' &&
        node.superClass.object.name === taroImportDefaultName)) {
          needExportDefault = true
          if (node.id === null) {
            componentClassName = '_TaroComponentClass'
            astPath.replaceWith(t.classDeclaration(t.identifier(componentClassName), node.superClass, node.body, node.decorators || []))
          } else if (node.id.name === 'App') {
            componentClassName = '_App'
            astPath.replaceWith(t.classDeclaration(t.identifier(componentClassName), node.superClass, node.body, node.decorators || []))
          } else {
            componentClassName = node.id.name
          }
        }
      }
    },

    ClassProperty (astPath) {
      const node = astPath.node
      if (node.key.name === 'config') {
        configObj = traverseObjectNode(node)
        if (type === PARSE_AST_TYPE.ENTRY) {
          appConfig = configObj
        }
        astPath.remove()
      }
    },

    ImportDeclaration (astPath) {
      const node = astPath.node
      const source = node.source
      let value = source.value
      const valueExtname = path.extname(value)
      if (Util.isNpmPkg(value) && notExistNpmList.indexOf(value) < 0) {
        if (value === taroJsComponents) {
          astPath.remove()
        } else {
          const specifiers = node.specifiers
          if (value === taroJsFramework) {
            let defaultSpecifier = null
            specifiers.forEach(item => {
              if (item.type === 'ImportDefaultSpecifier') {
                defaultSpecifier = item.local.name
              }
            })
            if (defaultSpecifier) {
              taroImportDefaultName = defaultSpecifier
            }
            value = taroWeappFramework
          } else if (value === taroJsRedux) {
            specifiers.forEach(item => {
              if (item.type === 'ImportSpecifier') {
                const local = item.local
                if (local.type === 'Identifier' && local.name === 'connect') {
                  taroJsReduxConnect = item.imported.name
                }
              }
            })
          }
          source.value = getExactedNpmFilePath(value, filePath)
          astPath.replaceWith(t.importDeclaration(node.specifiers, node.source))
        }
      } else if (path.isAbsolute(value)) {
        Util.printLog(Util.pocessTypeEnum.ERROR, '引用文件', `文件 ${sourceFilePath} 中引用 ${value} 是绝对路径!`)
      } else if (Util.REG_STYLE.test(valueExtname)) {
        const stylePath = path.resolve(path.dirname(sourceFilePath), value)
        if (styleFiles.indexOf(stylePath) < 0) {
          styleFiles.push(stylePath)
        }
        astPath.remove()
      }
    },

    VariableDeclaration (astPath) {
      const node = astPath.node
      if (node.declarations.length === 1 && node.declarations[0].init &&
        node.declarations[0].init.type === 'CallExpression' && node.declarations[0].init.callee &&
        node.declarations[0].init.callee.name === 'require') {
        const init = node.declarations[0].init
        const args = init.arguments
        let value = args[0].value
        const id = node.declarations[0].id
        if (Util.isNpmPkg(value) && notExistNpmList.indexOf(value) < 0) {
          if (value === taroJsComponents) {
            astPath.remove()
          } else {
            if (value === taroJsFramework && id.type === 'Identifier') {
              taroImportDefaultName = id.name
              value = taroWeappFramework
            } else if (value === taroJsRedux) {
              const declarations = node.declarations
              declarations.forEach(item => {
                const id = item.id
                if (id.type === 'ObjectPattern') {
                  const properties = id.properties
                  properties.forEach(p => {
                    if (p.type === 'ObjectProperty') {
                      if (p.value.type === 'Identifier' && p.value.name === 'connect') {
                        taroJsReduxConnect = p.key.name
                      }
                    }
                  })
                }
              })
            }
            args[0].value = getExactedNpmFilePath(value, filePath)
            astPath.replaceWith(t.variableDeclaration(node.kind, [t.variableDeclarator(id, init)]))
          }
        }
      }
    },

    CallExpression (astPath) {
      const node = astPath.node
      const callee = node.callee
      if (t.isMemberExpression(callee)) {
        if (callee.object.name === taroImportDefaultName && callee.property.name === 'render') {
          astPath.remove()
        }
      } else if (callee.name === 'require') {
        const args = node.arguments
        let value = args[0].value
        const valueExtname = path.extname(value)
        if (Util.REG_STYLE.test(valueExtname)) {
          const stylePath = path.resolve(path.dirname(sourceFilePath), value)
          if (styleFiles.indexOf(stylePath) < 0) {
            styleFiles.push(stylePath)
          }
          astPath.remove()
        } else if (path.isAbsolute(value)) {
          Util.printLog(Util.pocessTypeEnum.ERROR, '引用文件', `文件 ${sourceFilePath} 中引用 ${value} 是绝对路径!`)
        }
      }
    },

    ExportDefaultDeclaration (astPath) {
      const node = astPath.node
      const declaration = node.declaration
      needExportDefault = false
      if (declaration && declaration.type === 'ClassDeclaration') {
        const superClass = declaration.superClass
        if (superClass &&
          (superClass.name === 'Component' ||
          (superClass.type === 'MemberExpression' &&
          superClass.object.name === taroImportDefaultName))) {
          needExportDefault = true
          if (declaration.id === null) {
            componentClassName = '_TaroComponentClass'
          } else if (declaration.id.name === 'App') {
            componentClassName = '_App'
          } else {
            componentClassName = declaration.id.name
          }
          astPath.replaceWith(t.classDeclaration(t.identifier(componentClassName), superClass, declaration.body, declaration.decorators || []))
        }
      } else if (declaration.type === 'CallExpression') {
        const callee = declaration.callee
        if (callee && callee.type === 'CallExpression') {
          const subCallee = callee.callee
          if (subCallee.type === 'Identifier' && subCallee.name === taroJsReduxConnect) {
            const args = declaration.arguments
            if (args.length === 1 && args[0].name === componentClassName) {
              needExportDefault = true
              exportTaroReduxConnected = `${componentClassName}__Connected`
              astPath.replaceWith(t.variableDeclaration('const', [t.variableDeclarator(t.identifier(`${componentClassName}__Connected`), t.CallExpression(declaration.callee, declaration.arguments))]))
            }
          }
        }
      }
    },

    Program: {
      exit (astPath) {
        astPath.traverse({
          ImportDeclaration (astPath) {
            const node = astPath.node
            const source = node.source
            let value = source.value
            const valueExtname = path.extname(value)
            if (value.indexOf('.') === 0) {
              let isPage = false
              const pages = appConfig.pages
              let importPath = path.resolve(path.dirname(sourceFilePath), value)
              importPath = Util.resolveScriptPath(importPath)
              pages.forEach(page => {
                if (path.normalize(importPath).indexOf(path.normalize(page)) >= 0) {
                  isPage = true
                }
              })
              if (isPage) {
                astPath.remove()
              } else if (Util.REG_SCRIPT.test(valueExtname) || Util.REG_TYPESCRIPT.test(valueExtname)) {
                const vpath = path.resolve(sourceFilePath, '..', value)
                let fPath = value
                if (fs.existsSync(vpath)) {
                  fPath = vpath
                }
                if (scriptFiles.indexOf(fPath) < 0) {
                  scriptFiles.push(fPath)
                }
              } else if (Util.REG_JSON.test(valueExtname)) {
                const vpath = path.resolve(sourceFilePath, '..', value)
                if (jsonFiles.indexOf(vpath) < 0) {
                  jsonFiles.push(vpath)
                }
                if (fs.existsSync(vpath)) {
                  const obj = JSON.parse(fs.readFileSync(vpath).toString())
                  const specifiers = node.specifiers
                  let defaultSpecifier = null
                  specifiers.forEach(item => {
                    if (item.type === 'ImportDefaultSpecifier') {
                      defaultSpecifier = item.local.name
                    }
                  })
                  if (defaultSpecifier) {
                    let objArr = [t.nullLiteral()]
                    if (Array.isArray(obj)) {
                      objArr = convertArrayToAstExpression(obj)
                    } else {
                      objArr = convertObjectToAstExpression(obj)
                    }
                    astPath.replaceWith(t.variableDeclaration('const', [t.variableDeclarator(t.identifier(defaultSpecifier), t.objectExpression(objArr))]))
                  } else {
                    astPath.remove()
                  }
                }
              } else if (Util.REG_FONT.test(valueExtname) || Util.REG_IMAGE.test(valueExtname) || Util.REG_MEDIA.test(valueExtname)) {
                const vpath = path.resolve(sourceFilePath, '..', value)
                if (!fs.existsSync(vpath)) {
                  Util.printLog(Util.pocessTypeEnum.ERROR, '引用文件', `文件 ${sourceFilePath} 中引用 ${value} 不存在!`)
                  return
                }
                if (mediaFiles.indexOf(vpath) < 0) {
                  mediaFiles.push(vpath)
                }
                const specifiers = node.specifiers
                let defaultSpecifier = null
                specifiers.forEach(item => {
                  if (item.type === 'ImportDefaultSpecifier') {
                    defaultSpecifier = item.local.name
                  }
                })
                if (defaultSpecifier) {
                  astPath.replaceWith(t.variableDeclaration('const', [t.variableDeclarator(t.identifier(defaultSpecifier), t.stringLiteral(vpath.replace(sourceDir, '').replace(/\\/g, '/')))]))
                } else {
                  astPath.remove()
                }
              } else if (!valueExtname) {
                let vpath = Util.resolveScriptPath(path.resolve(sourceFilePath, '..', value))
                const outputVpath = vpath.replace(sourceDir, outputDir)
                let relativePath = path.relative(filePath, outputVpath)
                if (vpath && vpath !== sourceFilePath) {
                  if (!fs.existsSync(vpath)) {
                    Util.printLog(Util.pocessTypeEnum.ERROR, '引用文件', `文件 ${sourceFilePath} 中引用 ${value} 不存在!`)
                  } else {
                    if (fs.lstatSync(vpath).isDirectory()) {
                      if (fs.existsSync(path.join(vpath, 'index.js'))) {
                        vpath = path.join(vpath, 'index.js')
                        relativePath = path.join(relativePath, 'index.js')
                      } else {
                        Util.printLog(Util.pocessTypeEnum.ERROR, '引用目录', `文件 ${sourceFilePath} 中引用了目录 ${value}!`)
                        return
                      }
                    }
                    if (scriptFiles.indexOf(vpath) < 0) {
                      scriptFiles.push(vpath)
                    }
                    relativePath = Util.promoteRelativePath(relativePath)
                    relativePath = relativePath.replace(path.extname(relativePath), '.js')
                    source.value = relativePath
                    astPath.replaceWith(t.importDeclaration(node.specifiers, node.source))
                  }
                }
              }
            }
          },
          CallExpression (astPath) {
            const node = astPath.node
            const callee = node.callee
            if (callee.name === 'require') {
              const args = node.arguments
              let value = args[0].value
              const valueExtname = path.extname(value)
              if (value.indexOf('.') === 0) {
                let isPage = false
                const pages = appConfig.pages
                let importPath = path.resolve(path.dirname(sourceFilePath), value)
                importPath = Util.resolveScriptPath(importPath)
                pages.forEach(page => {
                  if (path.normalize(importPath).indexOf(path.normalize(page)) >= 0) {
                    isPage = true
                  }
                })
                if (isPage) {
                  astPath.remove()
                } else if (Util.REG_JSON.test(valueExtname)) {
                  const vpath = path.resolve(sourceFilePath, '..', value)
                  if (jsonFiles.indexOf(vpath) < 0) {
                    jsonFiles.push(vpath)
                  }
                  if (fs.existsSync(vpath)) {
                    const obj = JSON.parse(fs.readFileSync(vpath).toString())
                    let objArr = [t.nullLiteral()]
                    if (Array.isArray(obj)) {
                      objArr = convertArrayToAstExpression(obj)
                    } else {
                      objArr = convertObjectToAstExpression(obj)
                    }
                    astPath.replaceWith(t.objectExpression(objArr))
                  }
                } else if (Util.REG_SCRIPT.test(valueExtname) || Util.REG_TYPESCRIPT.test(valueExtname)) {
                  const vpath = path.resolve(sourceFilePath, '..', value)
                  let fPath = value
                  if (fs.existsSync(vpath)) {
                    fPath = vpath
                  }
                  if (scriptFiles.indexOf(fPath) < 0) {
                    scriptFiles.push(fPath)
                  }
                } else if (Util.REG_FONT.test(valueExtname) || Util.REG_IMAGE.test(valueExtname) || Util.REG_MEDIA.test(valueExtname)) {
                  const vpath = path.resolve(sourceFilePath, '..', value)
                  if (mediaFiles.indexOf(vpath) < 0) {
                    mediaFiles.push(vpath)
                  }
                  astPath.replaceWith(t.stringLiteral(vpath.replace(sourceDir, '').replace(/\\/g, '/')))
                } else if (!valueExtname) {
                  let vpath = Util.resolveScriptPath(path.resolve(sourceFilePath, '..', value))
                  const outputVpath = vpath.replace(sourceDir, outputDir)
                  let relativePath = path.relative(filePath, outputVpath)
                  if (vpath) {
                    if (!fs.existsSync(vpath)) {
                      Util.printLog(Util.pocessTypeEnum.ERROR, '引用文件', `文件 ${sourceFilePath} 中引用 ${value} 不存在!`)
                    } else {
                      if (fs.lstatSync(vpath).isDirectory()) {
                        if (fs.existsSync(path.join(vpath, 'index.js'))) {
                          vpath = path.join(vpath, 'index.js')
                          relativePath = path.join(relativePath, 'index.js')
                        } else {
                          Util.printLog(Util.pocessTypeEnum.ERROR, '引用目录', `文件 ${sourceFilePath} 中引用了目录 ${value}!`)
                          return
                        }
                      }
                      if (scriptFiles.indexOf(vpath) < 0) {
                        scriptFiles.push(vpath)
                      }
                      relativePath = Util.promoteRelativePath(relativePath)
                      relativePath = relativePath.replace(path.extname(relativePath), '.js')
                      args[0].value = relativePath
                    }
                  }
                }
              }
            }
          }
        })
        const node = astPath.node
        const exportVariableName = exportTaroReduxConnected || componentClassName
        if (needExportDefault) {
          const exportDefault = template(`export default ${exportVariableName}`, babylonConfig)()
          node.body.push(exportDefault)
        }
        const taroWeappFrameworkPath = getExactedNpmFilePath(taroWeappFramework, filePath)
        let insert
        switch (type) {
          case PARSE_AST_TYPE.ENTRY:
            insert = template(`App(require('${taroWeappFrameworkPath}').default.createApp(${exportVariableName}))`, babylonConfig)()
            node.body.push(insert)
            break
          case PARSE_AST_TYPE.PAGE:
            insert = template(`Page(require('${taroWeappFrameworkPath}').default.createPage(${exportVariableName}, { path: '${sourceFilePath.replace(appPath + path.sep, '').replace(/\\/g, '/')}' }))`, babylonConfig)()
            node.body.push(insert)
            break
          default:
            break
        }
      }
    }
  })
  return {
    code: generate(ast).code,
    styleFiles,
    scriptFiles,
    jsonFiles,
    configObj,
    mediaFiles,
    componentClassName
  }
}
示例#9
0
export const importReact = () => {
  return t.importDeclaration([
    t.importDefaultSpecifier(t.identifier('React')),
  ], t.stringLiteral('react'))
}
示例#10
0
 return modules.map(opts => {
     return t.importDeclaration(
         [t.importDefaultSpecifier(t.identifier(opts.identifier))],
         t.stringLiteral(opts.module)
     );
 });
示例#11
-6
function extractStyles({
  src,
  styleGroups,
  namedStyleGroups,
  sourceFileName,
  whitelistedModules,
  cacheObject,
  parserPlugins,
  addCSSRequire,
  errorCallback,
  extremelyLiteMode,
}) {
  invariant(typeof src === 'string', '`src` must be a string of javascript');

  invariant(
    typeof sourceFileName === 'string' && path.isAbsolute(sourceFileName),
    '`sourceFileName` must be an absolute path to a .js file'
  );

  invariant(
    typeof cacheObject === 'object' && cacheObject !== null,
    '`cacheObject` must be an object'
  );

  if (typeof styleGroups !== 'undefined') {
    invariant(
      Array.isArray(styleGroups),
      '`styleGroups` must be an array of style prop objects'
    );
  }

  if (typeof namedStyleGroups !== 'undefined') {
    invariant(
      typeof namedStyleGroups === 'object' && namedStyleGroups !== null,
      '`namedStyleGroups` must be an object of style prop objects keyed by className'
    );
  }

  if (typeof whitelistedModules !== 'undefined') {
    invariant(
      Array.isArray(whitelistedModules),
      '`whitelistedModules` must be an array of paths to modules that are OK to require'
    );
  }

  if (typeof errorCallback !== 'undefined') {
    invariant(
      typeof errorCallback === 'function',
      '`errorCallback` is expected to be a function'
    );
  } else {
    errorCallback = console.warn;
  }

  if (typeof addCSSRequire === 'undefined') {
    addCSSRequire = true;
  }

  const sourceDir = path.dirname(sourceFileName);

  // Using a map for (officially supported) guaranteed insertion order
  const cssMap = new Map();

  const ast = parse(src, parserPlugins);

  let jsxstyleSrc;
  const validComponents = {};
  let useImportSyntax = false;
  let hasValidComponents = false;
  let needsRuntimeJsxstyle = false;

  if (typeof extremelyLiteMode === 'string') {
    jsxstyleSrc =
      extremelyLiteMode === 'react'
        ? 'jsxstyle'
        : `jsxstyle-${extremelyLiteMode}`;
    Object.assign(validComponents, liteComponents);
    hasValidComponents = true;
  }

  // Find jsxstyle require in program root
  ast.program.body = ast.program.body.filter(item => {
    if (t.isVariableDeclaration(item)) {
      for (let idx = -1, len = item.declarations.length; ++idx < len; ) {
        const dec = item.declarations[idx];

        if (
          // var ...
          !t.isVariableDeclarator(dec) ||
          // var {...}
          !t.isObjectPattern(dec.id) ||
          // var {x} = require(...)
          !t.isCallExpression(dec.init) ||
          !t.isIdentifier(dec.init.callee) ||
          dec.init.callee.name !== 'require' ||
          // var {x} = require('one-thing')
          dec.init.arguments.length !== 1 ||
          !t.isStringLiteral(dec.init.arguments[0])
        ) {
          continue;
        }

        // var {x} = require('jsxstyle')
        if (!JSXSTYLE_SOURCES.hasOwnProperty(dec.init.arguments[0].value)) {
          continue;
        }

        if (jsxstyleSrc) {
          invariant(
            jsxstyleSrc === dec.init.arguments[0].value,
            'Expected duplicate `require` to be from "%s", received "%s"',
            jsxstyleSrc,
            dec.init.arguments[0].value
          );
        }

        for (let idx = -1, len = dec.id.properties.length; ++idx < len; ) {
          const prop = dec.id.properties[idx];
          if (
            !t.isObjectProperty(prop) ||
            !t.isIdentifier(prop.key) ||
            !t.isIdentifier(prop.value)
          ) {
            continue;
          }

          // only add uppercase identifiers to validComponents
          if (
            prop.key.name[0] !== prop.key.name[0].toUpperCase() ||
            prop.value.name[0] !== prop.value.name[0].toUpperCase()
          ) {
            continue;
          }

          // map imported name to source component name
          validComponents[prop.value.name] = prop.key.name;
          hasValidComponents = true;
        }

        jsxstyleSrc = dec.init.arguments[0].value;

        // if this is the only variable declaration, remove it
        // TODO: handle weird const a = 1, b = 2; maybe
        if (len === 1) return false;
      }
    } else if (t.isImportDeclaration(item)) {
      // omfg everyone please just use import syntax

      // not imported from jsxstyle? byeeee
      if (
        !t.isStringLiteral(item.source) ||
        !JSXSTYLE_SOURCES.hasOwnProperty(item.source.value)
      ) {
        return true;
      }

      if (jsxstyleSrc) {
        invariant(
          jsxstyleSrc === item.source.value,
          'Expected duplicate `import` to be from "%s", received "%s"',
          jsxstyleSrc,
          item.source.value
        );
      }

      jsxstyleSrc = item.source.value;
      useImportSyntax = true;

      for (let idx = -1, len = item.specifiers.length; ++idx < len; ) {
        const specifier = item.specifiers[idx];
        if (
          !t.isImportSpecifier(specifier) ||
          !t.isIdentifier(specifier.imported) ||
          !t.isIdentifier(specifier.local)
        ) {
          continue;
        }

        if (
          specifier.imported.name[0] !==
            specifier.imported.name[0].toUpperCase() ||
          specifier.local.name[0] !== specifier.local.name[0].toUpperCase()
        ) {
          continue;
        }

        validComponents[specifier.local.name] = specifier.imported.name;
        hasValidComponents = true;
      }

      // remove import
      return false;
    }
    return true;
  });

  // jsxstyle isn't included anywhere, so let's bail
  if (!jsxstyleSrc || !hasValidComponents) {
    return {
      js: src,
      css: '',
      cssFileName: null,
      ast,
      map: null,
    };
  }

  // class or className?
  const classPropName =
    jsxstyleSrc === 'jsxstyle-preact' ? 'class' : 'className';

  // Generate a UID that's unique in the program scope
  let boxComponentName;
  traverse(ast, {
    Program(path) {
      boxComponentName = path.scope.generateUid('Box');
    },
  });

  traverse(ast, {
    JSXElement(path) {
      const node = path.node.openingElement;

      if (
        // skip non-identifier opening elements (member expressions, etc.)
        !t.isJSXIdentifier(node.name) ||
        // skip non-jsxstyle components
        !validComponents.hasOwnProperty(node.name.name)
      ) {
        return;
      }

      // Remember the source component
      const originalNodeName = node.name.name;
      const src = validComponents[originalNodeName];

      const removeAllTrace = liteComponents.hasOwnProperty(originalNodeName);

      if (!removeAllTrace) {
        node.name.name = boxComponentName;
      }

      const defaultProps = componentStyles[src];
      invariant(defaultProps, `jsxstyle component <${src} /> does not exist!`);

      const propKeys = Object.keys(defaultProps);
      // looping backwards because of unshift
      for (let idx = propKeys.length; --idx >= 0; ) {
        const prop = propKeys[idx];
        const value = defaultProps[prop];
        if (value == null || value === '') {
          continue;
        }

        let valueEx;
        if (typeof value === 'number') {
          valueEx = t.jSXExpressionContainer(t.numericLiteral(value));
        } else if (typeof value === 'string') {
          valueEx = t.stringLiteral(value);
        } else {
          continue;
        }

        node.attributes.unshift(t.jSXAttribute(t.jSXIdentifier(prop), valueEx));
      }

      // Generate scope object at this level
      const staticNamespace = getStaticBindingsForScope(
        path.scope,
        whitelistedModules,
        sourceFileName
      );
      const evalContext = vm.createContext(staticNamespace);

      let lastSpreadIndex = null;
      const flattenedAttributes = [];
      node.attributes.forEach(attr => {
        if (t.isJSXSpreadAttribute(attr)) {
          if (canEvaluate(staticNamespace, attr.argument)) {
            const spreadValue = vm.runInContext(
              generate(attr.argument).code,
              evalContext
            );

            if (typeof spreadValue !== 'object' || spreadValue === null) {
              lastSpreadIndex = flattenedAttributes.push(attr) - 1;
            } else {
              for (const k in spreadValue) {
                const value = spreadValue[k];

                if (typeof value === 'number') {
                  flattenedAttributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(k),
                      t.jSXExpressionContainer(t.numericLiteral(value))
                    )
                  );
                } else if (value === null) {
                  // why would you ever do this
                  flattenedAttributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(k),
                      t.jSXExpressionContainer(t.nullLiteral())
                    )
                  );
                } else {
                  // toString anything else
                  // TODO: is this a bad idea
                  flattenedAttributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(k),
                      t.jSXExpressionContainer(t.stringLiteral('' + value))
                    )
                  );
                }
              }
            }
          } else {
            lastSpreadIndex = flattenedAttributes.push(attr) - 1;
          }
        } else {
          flattenedAttributes.push(attr);
        }
      });

      node.attributes = flattenedAttributes;

      let propsAttributes;
      const staticAttributes = {};
      let inlinePropCount = 0;

      const staticTernaries = [];

      node.attributes = node.attributes.filter((attribute, idx) => {
        if (
          // keep the weirdos
          !attribute.name ||
          !attribute.name.name ||
          // haven't hit the last spread operator
          idx < lastSpreadIndex
        ) {
          inlinePropCount++;
          return true;
        }

        const name = attribute.name.name;
        const value = t.isJSXExpressionContainer(attribute.value)
          ? attribute.value.expression
          : attribute.value;

        // if one or more spread operators are present and we haven't hit the last one yet, the prop stays inline
        if (lastSpreadIndex !== null && idx <= lastSpreadIndex) {
          inlinePropCount++;
          return true;
        }

        // pass ref, key, and style props through untouched
        if (UNTOUCHED_PROPS.hasOwnProperty(name)) {
          return true;
        }

        // className prop will be handled below
        if (name === classPropName) {
          return true;
        }

        // validate component prop
        if (name === 'component') {
          if (t.isLiteral(value) && typeof value.value === 'string') {
            const char1 = value.value[0];
            // component="article"
            if (char1 === char1.toUpperCase()) {
              // an uppercase string with be turned into a component, which is not what we want.
              // TODO: look into transforming to React.createElement.
              // main downside is that further transformations that rely on JSX won't work.
              inlinePropCount++;
            }
          } else if (t.isIdentifier(value)) {
            const char1 = value.name[0];
            // component={Avatar}
            if (char1 === char1.toLowerCase()) {
              // a lowercase identifier will be transformed to a DOM element. that's not good.
              inlinePropCount++;
            }
          } else if (t.isMemberExpression(value)) {
            // component={variable.prop}
          } else {
            // TODO: extract more complex expressions out as separate var
            errorCallback(
              '`component` prop value was not handled by extractStyles: %s',
              generate(value).code
            );
            inlinePropCount++;
          }
          return true;
        }

        // pass key and style props through untouched
        if (UNTOUCHED_PROPS.hasOwnProperty(name)) {
          return true;
        }

        if (name === 'props') {
          if (!value) {
            errorCallback('`props` prop does not have a value');
            inlinePropCount++;
            return true;
          }

          if (t.isObjectExpression(value)) {
            let errorCount = 0;
            const attributes = [];

            for (const k in value.properties) {
              const propObj = value.properties[k];

              if (t.isObjectProperty(propObj)) {
                let key;

                if (t.isIdentifier(propObj.key)) {
                  key = propObj.key.name;
                } else if (t.isStringLiteral(propObj.key)) {
                  // starts with a-z or _ followed by a-z, -, or _
                  if (/^\w[\w-]+$/.test(propObj.key.value)) {
                    key = propObj.key.value;
                  } else {
                    errorCallback(
                      '`props` prop contains an invalid key: `%s`',
                      propObj.key.value
                    );
                    errorCount++;
                    continue;
                  }
                } else {
                  errorCallback(
                    'unhandled object property key type: `%s`',
                    propObj.type
                  );
                  errorCount++;
                }

                if (ALL_SPECIAL_PROPS.hasOwnProperty(key)) {
                  errorCallback(
                    '`props` prop cannot contain `%s` as it is used by jsxstyle and will be overwritten.',
                    key
                  );
                  errorCount++;
                  continue;
                }

                if (t.isStringLiteral(propObj.value)) {
                  // convert literal value back to literal to ensure it has double quotes (siiiigh)
                  attributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(key),
                      t.stringLiteral(propObj.value.value)
                    )
                  );
                } else {
                  // wrap everything else in a JSXExpressionContainer
                  attributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(key),
                      t.jSXExpressionContainer(propObj.value)
                    )
                  );
                }
              } else if (t.isSpreadProperty(propObj)) {
                attributes.push(t.jSXSpreadAttribute(propObj.argument));
              } else {
                errorCallback(
                  'unhandled object property value type: `%s`',
                  propObj.type
                );
                errorCount++;
              }
            }

            if (errorCount > 0) {
              inlinePropCount++;
            } else {
              propsAttributes = attributes;
            }

            return true;
          }

          if (
            // if it's not an object, spread it
            // props={wow()}
            t.isCallExpression(value) ||
            // props={wow.cool}
            t.isMemberExpression(value) ||
            // props={wow}
            t.isIdentifier(value)
          ) {
            propsAttributes = [t.jSXSpreadAttribute(value)];
            return true;
          }

          // if props prop is weird-looking, leave it and warn
          errorCallback('props prop is an unhandled type: `%s`', value.type);
          inlinePropCount++;
          return true;
        }

        if (name === 'mediaQueries') {
          if (canEvaluateObject(staticNamespace, value)) {
            staticAttributes[name] = vm.runInContext(
              // parens so V8 doesn't parse the object like a block
              '(' + generate(value).code + ')',
              evalContext
            );
          } else if (canEvaluate(staticNamespace, value)) {
            staticAttributes[name] = vm.runInContext(
              generate(value).code,
              evalContext
            );
          } else {
            inlinePropCount++;
            return true;
          }
          return false;
        }

        // if value can be evaluated, extract it and filter it out
        if (canEvaluate(staticNamespace, value)) {
          staticAttributes[name] = vm.runInContext(
            generate(value).code,
            evalContext
          );
          return false;
        }

        if (t.isConditionalExpression(value)) {
          // if both sides of the ternary can be evaluated, extract them
          if (
            canEvaluate(staticNamespace, value.consequent) &&
            canEvaluate(staticNamespace, value.alternate)
          ) {
            staticTernaries.push({ name, ternary: value });
            // mark the prop as extracted
            staticAttributes[name] = null;
            return false;
          }
        } else if (t.isLogicalExpression(value)) {
          // convert a simple logical expression to a ternary with a null alternate
          if (
            value.operator === '&&' &&
            canEvaluate(staticNamespace, value.right)
          ) {
            staticTernaries.push({
              name,
              ternary: {
                test: value.left,
                consequent: value.right,
                alternate: t.nullLiteral(),
              },
            });
            staticAttributes[name] = null;
            return false;
          }
        }

        if (removeAllTrace) {
          errorCallback(
            'jsxstyle-loader encountered a dynamic prop (`%s`) on a lite ' +
              'jsxstyle component (`%s`). If you would like to pass dynamic ' +
              'styles to this component, specify them in the `style` prop.',
            generate(attribute).code,
            originalNodeName
          );
          return false;
        }

        // if we've made it this far, the prop stays inline
        inlinePropCount++;
        return true;
      });

      let classNamePropValue;
      const classNamePropIndex = node.attributes.findIndex(
        attr => attr.name && attr.name.name === classPropName
      );
      if (classNamePropIndex > -1 && Object.keys(staticAttributes).length > 0) {
        classNamePropValue = getPropValueFromAttributes(
          classPropName,
          node.attributes
        );
        node.attributes.splice(classNamePropIndex, 1);
      }

      // if all style props have been extracted, jsxstyle component can be
      // converted to a div or the specified component
      if (inlinePropCount === 0) {
        const propsPropIndex = node.attributes.findIndex(
          attr => attr.name && attr.name.name === 'props'
        );
        // deal with props prop
        if (propsPropIndex > -1) {
          if (propsAttributes) {
            propsAttributes.forEach(a => node.attributes.push(a));
          }
          // delete props prop
          node.attributes.splice(propsPropIndex, 1);
        }

        const componentPropIndex = node.attributes.findIndex(
          attr => attr.name && attr.name.name === 'component'
        );
        if (componentPropIndex > -1) {
          const attribute = node.attributes[componentPropIndex];
          const componentPropValue = t.isJSXExpressionContainer(attribute.value)
            ? attribute.value.expression
            : attribute.value;

          if (
            t.isLiteral(componentPropValue) &&
            typeof componentPropValue.value === 'string'
          ) {
            node.name.name = componentPropValue.value;
          } else if (t.isIdentifier(componentPropValue)) {
            node.name.name = componentPropValue.name;
          } else if (t.isMemberExpression(componentPropValue)) {
            node.name.name = generate(componentPropValue).code;
          }

          // remove component prop from attributes
          node.attributes.splice(componentPropIndex, 1);
        } else {
          node.name.name = 'div';
        }
      } else {
        needsRuntimeJsxstyle = true;
        if (lastSpreadIndex !== null) {
          // if only some style props were extracted AND additional props are spread onto the component,
          // add the props back with null values to prevent spread props from incorrectly overwriting the extracted prop value
          Object.keys(staticAttributes).forEach(attr => {
            node.attributes.push(
              t.jSXAttribute(
                t.jSXIdentifier(attr),
                t.jSXExpressionContainer(t.nullLiteral())
              )
            );
          });
        }
      }

      if (path.node.closingElement) {
        path.node.closingElement.name.name = node.name.name;
      }

      const stylesByClassName = getStylesByClassName(
        styleGroups,
        namedStyleGroups,
        staticAttributes,
        cacheObject
      );

      const extractedStyleClassNames = Object.keys(stylesByClassName).join(' ');

      const classNameObjects = [];

      if (classNamePropValue) {
        if (canEvaluate(null, classNamePropValue)) {
          // TODO: don't use canEvaluate here, need to be more specific
          classNameObjects.push(
            t.stringLiteral(
              vm.runInNewContext(generate(classNamePropValue).code)
            )
          );
        } else {
          classNameObjects.push(classNamePropValue);
        }
      }

      if (staticTernaries.length > 0) {
        const ternaryObj = extractStaticTernaries(
          staticTernaries,
          evalContext,
          cacheObject
        );

        // ternaryObj is null if all of the extracted ternaries have falsey consequents and alternates
        if (ternaryObj !== null) {
          // add extracted styles by className to existing object
          Object.assign(stylesByClassName, ternaryObj.stylesByClassName);
          classNameObjects.push(ternaryObj.ternaryExpression);
        }
      }

      if (extractedStyleClassNames) {
        classNameObjects.push(t.stringLiteral(extractedStyleClassNames));
      }

      const classNamePropValueForReals = classNameObjects.reduce((acc, val) => {
        if (!acc) {
          if (
            // pass conditional expressions through
            t.isConditionalExpression(val) ||
            // pass non-null literals through
            (t.isLiteral(val) && val.value !== null)
          ) {
            return val;
          }
          return t.logicalExpression('||', val, t.stringLiteral(''));
        }

        const accIsString = t.isLiteral(acc) && typeof acc.value === 'string';

        let inner;
        if (t.isLiteral(val)) {
          if (typeof val.value === 'string') {
            if (accIsString) {
              // join adjacent string literals
              return t.stringLiteral(`${acc.value} ${val.value}`);
            }
            inner = t.stringLiteral(` ${val.value}`);
          } else {
            inner = t.binaryExpression('+', t.stringLiteral(' '), val);
          }
        } else if (
          t.isConditionalExpression(val) ||
          t.isBinaryExpression(val)
        ) {
          if (accIsString) {
            return t.binaryExpression(
              '+',
              t.stringLiteral(`${acc.value} `),
              val
            );
          }
          inner = t.binaryExpression('+', t.stringLiteral(' '), val);
        } else if (t.isIdentifier(val) || t.isMemberExpression(val)) {
          // identifiers and member expressions make for reasonable ternaries
          inner = t.conditionalExpression(
            val,
            t.binaryExpression('+', t.stringLiteral(' '), val),
            t.stringLiteral('')
          );
        } else {
          if (accIsString) {
            return t.binaryExpression(
              '+',
              t.stringLiteral(`${acc.value} `),
              t.logicalExpression('||', val, t.stringLiteral(''))
            );
          }
          // use a logical expression for more complex prop values
          inner = t.binaryExpression(
            '+',
            t.stringLiteral(' '),
            t.logicalExpression('||', val, t.stringLiteral(''))
          );
        }
        return t.binaryExpression('+', acc, inner);
      }, null);

      if (classNamePropValueForReals) {
        if (
          t.isLiteral(classNamePropValueForReals) &&
          typeof classNamePropValueForReals.value === 'string'
        ) {
          node.attributes.push(
            t.jSXAttribute(
              t.jSXIdentifier(classPropName),
              t.stringLiteral(classNamePropValueForReals.value)
            )
          );
        } else {
          node.attributes.push(
            t.jSXAttribute(
              t.jSXIdentifier(classPropName),
              t.jSXExpressionContainer(classNamePropValueForReals)
            )
          );
        }
      }

      const lineNumbers =
        node.loc.start.line +
        (node.loc.start.line !== node.loc.end.line
          ? `-${node.loc.end.line}`
          : '');

      const comment = util.format(
        '/* %s:%s (%s) */',
        sourceFileName.replace(process.cwd(), '.'),
        lineNumbers,
        originalNodeName
      );

      for (const className in stylesByClassName) {
        if (cssMap.has(className)) {
          if (comment) {
            const val = cssMap.get(className);
            val.commentTexts.push(comment);
            cssMap.set(className, val);
          }
        } else {
          let css = '';
          const styleProps = stylesByClassName[className];

          // get object of style objects
          const styleObjects = getStyleKeysForProps(styleProps, true);
          delete styleObjects.classNameKey;
          const styleObjectKeys = Object.keys(styleObjects).sort();

          for (let idx = -1, len = styleObjectKeys.length; ++idx < len; ) {
            const k = styleObjectKeys[idx];
            const item = styleObjects[k];
            let itemCSS =
              `.${className}` +
              (item.pseudoclass ? ':' + item.pseudoclass : '') +
              (item.pseudoelement ? '::' + item.pseudoelement : '') +
              ` {${item.styles}}`;

            if (item.mediaQuery) {
              itemCSS = `@media ${item.mediaQuery} { ${itemCSS} }`;
            }
            css += itemCSS + '\n';
          }

          cssMap.set(className, { css, commentTexts: [comment] });
        }
      }
    },
  });

  const css = Array.from(cssMap.values())
    .map(n => n.commentTexts.map(t => `${t}\n`).join('') + n.css)
    .join('');
  // path.parse doesn't exist in the webpack'd bundle but path.dirname and path.basename do.
  const baseName = path.basename(sourceFileName, '.js');
  const cssRelativeFileName = `./${baseName}.jsxstyle.css`;
  const cssFileName = path.join(sourceDir, cssRelativeFileName);

  // Conditionally add Box import/require to the top of the document
  if (needsRuntimeJsxstyle) {
    if (useImportSyntax) {
      ast.program.body.unshift(
        t.importDeclaration(
          [
            t.importSpecifier(
              t.identifier(boxComponentName),
              t.identifier('Box')
            ),
          ],
          t.stringLiteral(jsxstyleSrc)
        )
      );
    } else {
      ast.program.body.unshift(
        t.variableDeclaration('var', [
          t.variableDeclarator(
            t.identifier(boxComponentName),
            t.memberExpression(
              t.callExpression(t.identifier('require'), [
                t.stringLiteral(jsxstyleSrc),
              ]),
              t.identifier('Box')
            )
          ),
        ])
      );
    }
  }

  // append require/import statement to the document
  if (css !== '') {
    if (useImportSyntax) {
      ast.program.body.unshift(
        t.importDeclaration([], t.stringLiteral(cssRelativeFileName))
      );
    } else {
      ast.program.body.unshift(
        t.expressionStatement(
          t.callExpression(t.identifier('require'), [
            t.stringLiteral(cssRelativeFileName),
          ])
        )
      );
    }
  }

  const result = generate(
    ast,
    {
      fileName: sourceFileName,
      retainLines: false,
      compact: 'auto',
      concise: false,
      sourceMaps: true,
      sourceFileName,
      quotes: 'single',
    },
    src
  );

  return {
    js: result.code,
    css,
    cssFileName,
    ast: result.ast,
    map: result.map,
  };
}