UNPKG

react-imported-component

Version:
129 lines (128 loc) 5.44 kB
// @ts-ignore import { existsSync } from 'fs'; import { dirname, join, relative, resolve } from 'path'; import * as crc32 from 'crc-32'; import { processComment } from './magic-comments'; export const encipherImport = (str) => { return crc32.str(str).toString(32); }; // Babel v7 compat let syntax; try { syntax = require('babel-plugin-syntax-dynamic-import'); } catch (err) { try { syntax = require('@babel/plugin-syntax-dynamic-import'); } catch (e) { throw new Error('react-imported-component babel plugin is requiring `babel-plugin-syntax-dynamic-import` or `@babel/plugin-syntax-dynamic-import` to work. Please add this dependency.'); } } syntax = syntax.default || syntax; const resolveImport = (importName, file = '') => { if (importName.charAt(0) === '.') { return relative(process.cwd(), resolve(dirname(file), importName)); } return importName; }; const templateOptions = { placeholderPattern: /^([A-Z0-9]+)([A-Z0-9_]+)$/, }; function getImportArg(callPath) { return callPath.get('arguments.0'); } function getComments(callPath) { return callPath.has('leadingComments') ? callPath.get('leadingComments') : []; } // load configuration const configurationFile = join(process.cwd(), '.imported.js'); const defaultConfiguration = (existsSync(configurationFile) ? require(configurationFile) : {}); export const createTransformer = ({ types: t, template }, excludeMacro = false, configuration = defaultConfiguration) => { const headerTemplate = template(`var importedWrapper = require('react-imported-component/wrapper');`, templateOptions); const importRegistration = template('importedWrapper(MARK, IMPORT)', templateOptions); const hasImports = new Set(); const visitedNodes = new Map(); return { traverse(programPath, fileName) { let isBootstrapFile = false; programPath.traverse({ ImportDeclaration(path) { if (excludeMacro) { return; } const source = path.node.source.value; if (source === 'react-imported-component/macro') { const { specifiers } = path.node; path.remove(); const assignName = 'assignImportedComponents'; if (specifiers.length === 1 && specifiers[0].imported.name === assignName) { isBootstrapFile = true; programPath.node.body.unshift(t.importDeclaration([t.importSpecifier(t.identifier(assignName), t.identifier(assignName))], t.stringLiteral('react-imported-component/boot'))); } else { programPath.node.body.unshift(t.importDeclaration(specifiers.map((spec) => t.importSpecifier(t.identifier(spec.imported.name), t.identifier(spec.imported.name))), t.stringLiteral('react-imported-component'))); } } }, Import({ parentPath }) { if (visitedNodes.has(parentPath.node)) { return; } const newImport = parentPath.node; const rawImport = getImportArg(parentPath); const importName = rawImport.node.value; const rawComments = getComments(rawImport); const comments = rawComments.map((parent) => parent.node.value); const newComments = processComment(configuration, comments, importName, fileName, { isBootstrapFile, }); if (newComments !== comments) { rawComments.forEach((comment) => comment.remove()); newComments.forEach((comment) => { rawImport.addComment('leading', ` ${comment} `); }); } if (!importName) { return; } const requiredFileHash = encipherImport(resolveImport(importName, fileName)); let replace = null; replace = importRegistration({ MARK: t.stringLiteral(`imported_${requiredFileHash}_component`), IMPORT: newImport, }); hasImports.add(fileName); visitedNodes.set(newImport, true); parentPath.replaceWith(replace); }, }); }, finish(node, filename) { if (!hasImports.has(filename)) { return; } node.body.unshift(headerTemplate()); }, hasImports, }; }; export const babelPlugin = (babel, options = {}) => { const transformer = createTransformer(babel, false, { ...defaultConfiguration, ...options, }); return { inherits: syntax, visitor: { Program: { enter(programPath, { file }) { transformer.traverse(programPath, file.opts.filename); }, exit({ node }, { file }) { transformer.finish(node, file.opts.filename); }, }, }, }; };