Exemple #1
0
function buildRefTemplate(name, refName, loop, key) {
    const attrs = [
        t.jSXAttribute(t.jSXIdentifier('is'), t.stringLiteral(name)),
        t.jSXAttribute(t.jSXIdentifier('data'), t.stringLiteral(`{{...${refName ? `${loop ? '' : '$$'}${refName}` : '__data'}}}`))
    ];
    if (key) {
        attrs.push(key);
    }
    return t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('template'), attrs), t.jSXClosingElement(t.jSXIdentifier('template')), []);
}
      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);
        }
      });
 Object.keys(staticAttributes).forEach(attr => {
   node.attributes.push(
     t.jSXAttribute(
       t.jSXIdentifier(attr),
       t.jSXExpressionContainer(t.nullLiteral())
     )
   );
 });
Exemple #4
0
function setJSXAttr(jsx, name, value, path) {
    const element = jsx.openingElement;
    if (!t.isJSXIdentifier(element.name)) {
        return;
    }
    if (element.name.name === 'Block' || element.name.name === 'block' || !path) {
        jsx.openingElement.attributes.push(t.jSXAttribute(t.jSXIdentifier(name), value));
    }
    else {
        const block = buildBlockElement();
        setJSXAttr(block, name, value);
        block.children = [jsx];
        path.node = block;
    }
}
Exemple #5
0
function buildJSXAttr(name, value) {
    return t.jSXAttribute(t.jSXIdentifier(name), t.jSXExpressionContainer(value));
}
      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;
      });
Exemple #7
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;
}
Exemple #8
0
function buildBlockElement() {
    return t.jSXElement(t.jSXOpeningElement(t.jSXIdentifier('block'), []), t.jSXClosingElement(t.jSXIdentifier('block')), []);
}
Exemple #9
-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,
  };
}