UNPKG

react-repl-plus

Version:

React component for editing React components, it is similar to vue repl

151 lines (133 loc) 7.13 kB
import { File, ReplStore } from "../component/repl/storeContext"; import { transform } from "@babel/standalone"; import { type NodePath } from "@babel/traverse"; import type { ExportDefaultDeclaration, Identifier, ImportDeclaration, ExportNamedDeclaration, ExportAllDeclaration } from "@babel/types"; import MagicString from "magic-string"; const modulesKey = '__modules__' const moduleKey = '__module__' const exportKey = '__export__' export function compileModulesForPreview(store: ReplStore) { const processed: string[] = [] const seen = new Set<File>() processFile(store, store.files[store.mainFile], processed, seen) return processed } function processFile(store: ReplStore, file: File, processed: string[], seen: Set<File>) { if (seen.has(file)) { return } seen.add(file) const { code: js, importedFiles } = processModule(store, file.filename) processChildFiles(store, importedFiles, processed, seen) processed.push(js) } function processChildFiles(store: ReplStore, importedFiles: Set<string>, processed: string[], seen: Set<File>) { for (const filename of importedFiles) { processFile(store, store.files[filename], processed, seen) } } /** * todo: 模块解析这里是否能优化,解析了两次 */ function processModule(store: ReplStore, filename: string) { function defineExport(name: string, local = name) { s.append(`\n${exportKey}(${moduleKey}, "${name}", () => ${local})`) } const rawCode = store.files[filename].code const s = new MagicString(rawCode) const importedFiles = new Set<string>() let code = transform(rawCode, { presets: ['react'], plugins: [() => { return { visitor: { ExportAllDeclaration(path: NodePath<ExportAllDeclaration>) { const { node } = path const modulePath = node.source.value.replace(/\.\//, '') importedFiles.add(modulePath) s.overwrite(node.start!, node.end!, `for (const key in ${modulesKey}["${modulePath}"]) { if (key !== 'default') { ${moduleKey}[key] = ${modulesKey}["${modulePath}"][key] } }`) }, ExportDefaultDeclaration(path: NodePath<ExportDefaultDeclaration>) { const { node } = path s.append(`${moduleKey}.default = ${(node.declaration as Identifier).name}\n`) s.remove(node.start!, node.end!) }, ExportNamedDeclaration(path: NodePath<ExportNamedDeclaration>) { const { node } = path if (node.declaration) { // export const age = 18, name = 'xxx' if (node.declaration.type === 'VariableDeclaration') { for (const decl of node.declaration.declarations) { const varName = decl.id.name s.append(`\n ${moduleKey}.${varName} = ${varName}`) } } // export function get(){}, export class Person{} else if (node.declaration.type === 'ClassDeclaration' || node.declaration.type === 'FunctionDeclaration') { const varName = node.declaration.id?.name s.append(`\n ${moduleKey}.${varName} = ${varName}`) } s.remove(node.start!, node.declaration.start!) } // export { foo, bar } from './foo' else if (node.source) { const modulePath = node.source.value.replace(/\.\//, '') const expandNames = node.specifiers?.map(specifier => specifier.local.name) if (expandNames && expandNames.length !== 0) s.overwrite(node.start!, node.end!, `const { ${expandNames.join(',')} } = ${modulesKey}["${modulePath}"]`) for (const specifier of node.specifiers) { const varName = specifier.local.name s.appendRight(node.end!, `\n ${moduleKey}.${varName} = ${varName}`) } s.remove(node.start!, node.end!) } // export { name, age } else { for (const specifier of node.specifiers) { const varName = specifier.local.name s.append(`\n ${moduleKey}.${varName} = ${varName}`) } s.remove(node.start!, node.end!) } }, ImportDeclaration(path: NodePath<ImportDeclaration>) { const { node } = path const importDefault = node.specifiers.find(specifier => specifier.type === 'ImportDefaultSpecifier') if (node.source.value.startsWith('./')) { const modulePath = node.source.value.replace(/^\.\//, '') importedFiles.add(modulePath) if (importDefault) { // import foo from 'xx' const moduleName = importDefault.local.name s.overwrite(node.start!, node.end!, `const ${moduleName} = ${modulesKey}["${modulePath}"].default\n`) }else { const nameSpaceObj = node.specifiers.find(specifier => specifier.type === 'ImportNamespaceSpecifier') if (nameSpaceObj) { // import * as xx from 'xx' s.overwrite(node.start!, node.end!, `const ${nameSpaceObj.local.name} = ${modulesKey}["${modulePath}"]`) } else { // import { foo } from 'xx' const expandNames = node.specifiers?.map(specifier => specifier.local.name) if (expandNames && expandNames.length !== 0) s.overwrite(node.start!, node.end!, `const { ${expandNames.join(',')} } = ${modulesKey}["${modulePath}"]`) } } } } } } }] }).code! code = transform(s.toString(), { presets: ['react'], }).code! code = `const ${moduleKey} = ${modulesKey}[${JSON.stringify( filename, )}] = { [Symbol.toStringTag]: "Module" }\n\n` + code return { code, importedFiles } }