import traverse from '@babel/traverse'; import generate from '@babel/generator'; import * as t from '@babel/types'; import assert from 'assert'; import prettier from 'prettier'; import { findLastIndex } from 'lodash'; import uppercamelcase from 'uppercamelcase'; import { readFileSync, readdirSync } from 'fs'; import { dirname } from 'path'; import rimraf from 'rimraf'; import { findExportDefaultDeclaration, findImportNodes, getIdentifierDeclaration, getReturnNode, haveChildren, isJSXElement, findIndex, parseContent, combineImportNodes, getValidStylesName, } from '../util'; import { BLOCK_LAYOUT_PREFIX, INSERT_BLOCK_PLACEHOLDER, UMI_UI_FLAG_PLACEHOLDER, } from '../constants'; export default (content, opts) => { const { // 绝对路径,提取插入时用 absolutePath, // 是否提取区块到当前文件,完成后删除具体的区块及其目录 isExtractBlock, // 测试时不删除 dontRemoveExtractedBlock, relativePath, identifier, index = 0, latest, } = opts; function addImport(node, id) { const { body } = node; const lastImportSit = findLastIndex(body, (item: object) => t.isImportDeclaration(item)); const newImport = t.importDeclaration( [t.importDefaultSpecifier(t.identifier(id))], t.stringLiteral(relativePath), ); body.splice(lastImportSit + 1, 0, newImport); } function getExtractBlockNode(stylesName) { const code = readFileSync(absolutePath, 'utf-8'); const ast = parseContent(code); let returnNode = null; let importNodes = []; traverse(ast, { Program(path) { const { node } = path; let d = findExportDefaultDeclaration(node) as any; d = getIdentifierDeclaration(d, path); const ret = getReturnNode(d, path); returnNode = ret.node; importNodes = findImportNodes(node); }, Identifier(path) { const { node } = path; if (node.name === 'styles') { node.name = stylesName; } }, }); return { returnNode, importNodes, }; } function addBlockToJSX({ newNode, node, replace, id }) { assert(isJSXElement(node), 'add block to jsx failed, not valid jsx element'); // Build new node if (!newNode) { newNode = t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier(id), [], true), null, [], true); } if (haveChildren(node)) { // 是否最后插入 if (latest) { node.children.push(newNode); } else { const insertIndex = findIndex(node.children, index, isJSXElement); node.children.splice(insertIndex, 0, newNode); } } else { replace( t.jsxFragment( t.jsxOpeningFragment(), t.jsxClosingFragment(), index === 0 ? [newNode, node] : [node, newNode], ), ); } } const ast = parseContent(content); if (typeof index === 'string' && index.startsWith(BLOCK_LAYOUT_PREFIX)) { const targetIndex = parseInt(index.replace(BLOCK_LAYOUT_PREFIX, ''), 10); let currIndex = 0; traverse(ast, { // TODO: remove import { UmiUIFlag } from 'umi' // TODO: remove import { UmiUIFlag, AAA } from 'umi' => import { AAA } from 'umi' // ImportDeclaration: { // exit(path) { // const { node } = path; // const specifierIndex = node.specifiers.findIndex( // specify => // t.isImportSpecifier(specify) && specify.imported.name === UMI_UI_FLAG_PLACEHOLDER, // ); // if (specifierIndex > -1) { // if (node.specifiers.length === 1) { // // import { UmiUIFlag } from 'umi' // path.remove(); // } else { // path.get(`specifiers.${specifierIndex}`).remove(); // } // } // }, // }, // support JSXElement(path) { const { node } = path; const { openingElement } = node; if ( t.isJSXIdentifier(openingElement.name) && openingElement.name.name === UMI_UI_FLAG_PLACEHOLDER ) { if (targetIndex === currIndex) { // 添加过之后无需提示 const id = uppercamelcase(identifier); addImport(path.findParent(p => p.isProgram()).node, id); const newNode = t.jsxElement( t.jsxOpeningElement(t.jsxIdentifier(id), [], true), null, [], true, ); path.parent.children.push(newNode); // remove path.remove(); } currIndex += 1; } }, JSXText(path) { const { node } = path; const { value } = node; if (value.trim().startsWith(INSERT_BLOCK_PLACEHOLDER)) { if (targetIndex === currIndex) { // 添加过之后无需提示 node.value = INSERT_BLOCK_PLACEHOLDER; const id = uppercamelcase(identifier); addImport(path.findParent(p => p.isProgram()).node, id); const newNode = t.jsxElement( t.jsxOpeningElement(t.jsxIdentifier(id), [], true), null, [], true, ); path.parent.children.push(newNode); } currIndex += 1; } }, }); } else { traverse(ast, { Program(path) { const { node } = path; let d = findExportDefaultDeclaration(node) as any; // support hoc while (t.isCallExpression(d)) { // eslint-disable-next-line d = d.arguments[0]; } d = getIdentifierDeclaration(d, path); // Support hoc again while (t.isCallExpression(d)) { // eslint-disable-next-line d = d.arguments[0]; } const ret = getReturnNode(d, path); assert(ret, 'Can not find return node'); const id = uppercamelcase(identifier); // TODO: check id exists if (isExtractBlock) { const stylesName = getValidStylesName(path); const { returnNode, importNodes } = getExtractBlockNode(stylesName); const originImportNodes = findImportNodes(node); combineImportNodes(node, originImportNodes, importNodes, absolutePath, stylesName); addBlockToJSX({ ...ret, id, newNode: returnNode, }); // 清理目录 if (!dontRemoveExtractedBlock) { rimraf.sync(absolutePath); if (readdirSync(dirname(absolutePath)).length === 0) { rimraf.sync(dirname(absolutePath)); } } } else { addImport(node, id); addBlockToJSX({ ...ret, id, newNode: null, }); } }, }); } // console.log(JSON.stringify(ast, null, 2)); const newCode = generate(ast, {}).code; return prettier.format(newCode, { singleQuote: true, trailingComma: 'es5', printWidth: 100, parser: 'typescript', }); };