UNPKG

@figma/code-connect

Version:

A tool for connecting your design system components in code with your design system in Figma

149 lines 6.68 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isRawTemplate = isRawTemplate; exports.parseRawFile = parseRawFile; const fs_1 = __importDefault(require("fs")); const typescript_1 = __importDefault(require("typescript")); const label_language_mapping_1 = require("./label_language_mapping"); const helpers_1 = require("./helpers"); /** * Returns true if the file content looks like a raw template file, i.e. its * leading comment block contains a `// url=`, `// component=`, or `// source=` * directive. Used to distinguish raw `.figma.ts` templates from React/HTML * Code Connect files that share the same extension. */ function isRawTemplate(content) { for (const line of content.split('\n')) { const trimmed = line.trim(); if (trimmed === '' || trimmed.startsWith('//')) { if (/^\/\/\s*(url|component|source)=/.test(trimmed)) { return true; } continue; } // First non-comment, non-blank line reached without finding a known directive — not a raw template break; } return false; } function transpileTypeScriptTemplate(filePath, fileContent) { // Convert ESM import of 'figma' to require syntax // Supports: import figma from 'figma' const figmaImportRegex = /^import\s+figma\s+from\s+['"]figma['"]\s*;?\s*$/m; if (figmaImportRegex.test(fileContent)) { fileContent = fileContent.replace(figmaImportRegex, "const figma = require('figma')"); } // Detect any remaining non-type import statements. Type-only imports (`import type`) // are erased by the TS compiler and are fine. Regular imports (except the figma // import we just converted) are not supported in Phase 1 (no bundling), so we show // a helpful error. const importRegex = /^import\s+(?!type\s)/m; if (importRegex.test(fileContent)) { const match = fileContent.match(/^(import\s+.+)$/m); const importLine = match ? match[1] : 'import ...'; throw new Error(`TypeScript template files only support importing from 'figma'.\n` + `Found in ${filePath}:\n` + ` ${importLine}\n\n` + `Use "const figma = require('figma')" or "import figma from 'figma'" to access the Figma API.\n` + `Other module imports will be supported in a future version.`); } const result = typescript_1.default.transpileModule(fileContent, { compilerOptions: { module: typescript_1.default.ModuleKind.ESNext, target: typescript_1.default.ScriptTarget.ES2021, removeComments: false, }, }); return result.outputText; } /** * Extracts metadata fields (url, component, source) from the leading comments * of a raw template file. This must be done before transpilation because * TypeScript can remove comments that appear before type-only imports. */ function extractMetadataFields(fileContent) { const lines = fileContent.split('\n'); const fields = {}; let templateStartLine = 0; // Parse consecutive comment lines at the start of the file for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); // Match pattern: // fieldName=value const match = line.match(/^\/\/\s*(\w+)=(.+)$/); if (match) { const [, fieldName, fieldValue] = match; const normalizedFieldName = fieldName.toLowerCase(); if (normalizedFieldName === 'url' || normalizedFieldName === 'component' || normalizedFieldName === 'source') { fields[normalizedFieldName] = fieldValue.trim(); } templateStartLine = i + 1; } else if (line === '' || line.startsWith('//')) { // Allow blank lines or other comments, but don't increment template start continue; } else { // First non-comment line found break; } } return { fields, templateStartLine }; } function parseRawFile(filePath, label, config, dir) { let fileContent = fs_1.default.readFileSync(filePath, 'utf-8'); // Extract metadata fields BEFORE transpilation to avoid losing comments // that appear before type-only imports (which TypeScript erases) const { fields, templateStartLine } = extractMetadataFields(fileContent); if (filePath.endsWith('.ts')) { fileContent = transpileTypeScriptTemplate(filePath, fileContent); } if (!fields.url) { throw new Error(`Missing required url field in ${filePath}. Please add a // url=... comment to the top of the file.`); } let figmaNodeUrl = fields.url; // Extract template from the transpiled content, starting at the line // where the metadata comments ended in the original source const transpiledLines = fileContent.split('\n'); let templateStartIndex = 0; // Skip lines until we've passed the original metadata comment section for (let i = 0; i < transpiledLines.length; i++) { const line = transpiledLines[i].trim(); // Skip blank lines and comments at the start if (line === '' || line.startsWith('//')) { continue; } // First non-comment line in transpiled output templateStartIndex = i; break; } const template = transpiledLines.slice(templateStartIndex).join('\n'); // Apply documentUrlSubstitutions if provided if (config?.documentUrlSubstitutions) { figmaNodeUrl = (0, helpers_1.applyDocumentUrlSubstitutions)(figmaNodeUrl, config.documentUrlSubstitutions); } // Determine effective label from parameter, config, or default const effectiveLabel = label || config?.label || label_language_mapping_1.CodeConnectLabel.Code; const language = config?.language || (0, label_language_mapping_1.getInferredLanguageForRaw)(effectiveLabel); return { figmaNode: figmaNodeUrl, component: fields.component, template, // nestable by default unless user specifies in template // (templateData.nestable AND template.metadata.nestable need to // be true for instance to be nested) templateData: { nestable: true, isParserless: true }, language, label: effectiveLabel, source: fields.source || '', sourceLocation: { line: -1 }, metadata: { cliVersion: require('../../package.json').version, }, }; } //# sourceMappingURL=raw_templates.js.map