@figma/code-connect
Version:
A tool for connecting your design system components in code with your design system in Figma
179 lines • 9.94 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.migrateV1TemplateToV2 = void 0;
exports.writeTemplateFile = writeTemplateFile;
exports.migrateTemplateToUseServerSideHelpers = migrateTemplateToUseServerSideHelpers;
exports.addId = addId;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const prettier = __importStar(require("prettier"));
function writeTemplateFile(doc, outputDir, baseDir, localSourcePath) {
const suffix = '.figma.template.js';
// Determine output filename
let outputPath;
if (outputDir) {
// Use specified output directory
const filename = `${doc.component || 'template'}${suffix}`;
outputPath = path_1.default.join(outputDir, filename);
}
else if (localSourcePath) {
// Use same directory as local source file
const sourceDir = path_1.default.dirname(localSourcePath);
const sourceBasename = path_1.default.basename(localSourcePath, path_1.default.extname(localSourcePath));
const filename = `${sourceBasename}${suffix}`;
outputPath = path_1.default.join(sourceDir, filename);
}
else {
// No source info, use current directory
const filename = `${doc.component || 'template'}${suffix}`;
outputPath = path_1.default.join(baseDir, filename);
}
// Check if file exists and skip (always skip existing files)
if (fs_1.default.existsSync(outputPath)) {
return { outputPath, skipped: true };
}
let template = doc.template;
// Helpers have not been injected - replace reference with server-side versions
template = migrateTemplateToUseServerSideHelpers(template);
// V1 -> V2 codemods
template = (0, exports.migrateV1TemplateToV2)(template);
// Add required id to template (set to TODO if no usable name)
template = addId(template, doc.component || 'TODO');
// Format for ease of use
template = prettier.format(template, {
parser: 'typescript',
semi: false,
trailingComma: 'all',
// pluginSearchDirs: false is needed as otherwise prettier picks up other
// prettier plugins in our monorepo and fails with race condition errors
pluginSearchDirs: false,
});
// Create the template file content
// Format: first line is the URL, rest is the template
const fileContent = `// url=${doc.figmaNode}\n${template}`;
// Ensure output directory exists
const outputDirPath = path_1.default.dirname(outputPath);
if (!fs_1.default.existsSync(outputDirPath)) {
fs_1.default.mkdirSync(outputDirPath, { recursive: true });
}
// Write the file
fs_1.default.writeFileSync(outputPath, fileContent, 'utf-8');
return { outputPath, skipped: false };
}
// Renames must match helpers in code_connect_js_api.raw_source.ts
function migrateTemplateToUseServerSideHelpers(template) {
return (template
// React helpers
.replace(/_fcc_renderReactProp/g, 'figma.helpers.react.renderProp')
.replace(/_fcc_renderReactChildren/g, 'figma.helpers.react.renderChildren')
.replace(/_fcc_jsxElement/g, 'figma.helpers.react.jsxElement')
.replace(/_fcc_function/g, 'figma.helpers.react.function')
.replace(/_fcc_identifier/g, 'figma.helpers.react.identifier')
.replace(/_fcc_object/g, 'figma.helpers.react.object')
.replace(/_fcc_templateString/g, 'figma.helpers.react.templateString')
.replace(/_fcc_renderPropValue/g, 'figma.helpers.react.renderPropValue')
.replace(/_fcc_stringifyObject/g, 'figma.helpers.react.stringifyObject')
.replace(/_fcc_reactComponent/g, 'figma.helpers.react.reactComponent')
.replace(/_fcc_array/g, 'figma.helpers.react.array')
.replace(/isReactComponentArray/g, 'figma.helpers.react.isReactComponentArray')
// Swift helpers
.replace(/__fcc_renderSwiftChildren/g, 'figma.helpers.swift.renderChildren')
// Kotlin/Compose helpers
.replace(/__fcc_renderComposeChildren/g, 'figma.helpers.kotlin.renderChildren'));
}
function addId(template, id) {
return template.replace(/export default \{/, `export default { id: '${id}',`);
}
/**
* Migrates V1 templates to V2 API.
*
* This performs safe, incremental transformations. The following patterns
* are intentionally NOT migrated as they're still supported in V2:
*
* - .__properties__.children() - no direct V2 equivalent, still supported
* - __props metadata building pattern - still valid JavaScript
* - __renderWithFn__() - complex transformation, still supported
*
* These may be addressed in future migrations.
*/
const migrateV1TemplateToV2 = (template) => {
let migrated = template;
// 1. Core object rename
migrated = migrated.replace(/figma\.currentLayer/g, 'figma.selectedInstance');
// 2. Property accessor methods
migrated = migrated.replace(/\.__properties__\.string\(/g, '.getString(');
migrated = migrated.replace(/\.__properties__\.boolean\(/g, '.getBoolean(');
migrated = migrated.replace(/\.__properties__\.enum\(/g, '.getEnum(');
migrated = migrated.replace(/\.__properties__\.__instance__\(/g, '.getInstanceSwap(');
// .__properties__.instance() auto-renders, so we need to add .executeTemplate().example
migrated = migrated.replace(/\.__properties__\.instance\(([^)]+)\)/g, '.getInstanceSwap($1).executeTemplate().example');
// 3. Other method renames
migrated = migrated.replace(/\.__getPropertyValue__\(/g, '.getPropertyValue(');
// 4. __findChildWithCriteria__ - migrate based on type parameter
// For TEXT type with __render__(): __findChildWithCriteria__({ name: 'X', type: "TEXT" }).__render__() → findText('X').textContent
migrated = migrated.replace(/\.__findChildWithCriteria__\(\{\s*name:\s*'([^']+)',\s*type:\s*"TEXT"\s*\}\)\.__render__\(\)/g, ".findText('$1').textContent");
migrated = migrated.replace(/\.__findChildWithCriteria__\(\{\s*type:\s*"TEXT",\s*name:\s*'([^']+)'\s*\}\)\.__render__\(\)/g, ".findText('$1').textContent");
// For INSTANCE type: __findChildWithCriteria__({ type: 'INSTANCE', name: 'X' }) → findInstance('X')
migrated = migrated.replace(/\.__findChildWithCriteria__\(\{\s*name:\s*'([^']+)',\s*type:\s*['"]INSTANCE['"]\s*\}\)/g, ".findInstance('$1')");
migrated = migrated.replace(/\.__findChildWithCriteria__\(\{\s*type:\s*['"]INSTANCE['"],\s*name:\s*'([^']+)'\s*\}\)/g, ".findInstance('$1')");
// For TEXT type without __render__(): __findChildWithCriteria__({ type: 'TEXT', name: 'X' }) → findText('X')
migrated = migrated.replace(/\.__findChildWithCriteria__\(\{\s*name:\s*'([^']+)',\s*type:\s*['"]TEXT['"]\s*\}\)/g, ".findText('$1')");
migrated = migrated.replace(/\.__findChildWithCriteria__\(\{\s*type:\s*['"]TEXT['"],\s*name:\s*'([^']+)'\s*\}\)/g, ".findText('$1')");
// 5. __find__() - migrate to findInstance()
migrated = migrated.replace(/\.__find__\(("([^"]+)"|'([^']+)')\)/g, (match, quote, doubleQuoted, singleQuoted) => {
const name = doubleQuoted || singleQuoted;
return `.findInstance("${name}")`;
});
// 6. __render__() - migrate to executeTemplate().example (but not if part of __findChildWithCriteria__)
migrated = migrated.replace(/\.__render__\(\)/g, '.executeTemplate().example');
// 7. __getProps__() - migrate to executeTemplate().metadata.props
migrated = migrated.replace(/\.__getProps__\(\)/g, '.executeTemplate().metadata.props');
// 8. Export format - simple case
// Match export default figma.code` (or tsx, html, etc) and wrap in { example: ... }
migrated = migrated.replace(/export default figma\.(code|tsx|html|swift|kotlin)`/g, 'export default { example: figma.$1`');
// Close the template literal for simple exports (look for backtick at end, handling multiline)
// Use a more robust approach: find the last backtick that's not followed by more content
migrated = migrated.replace(/(export default \{ example: figma\.\w+`[\s\S]*?)`(?=\s*$)/gm, '$1` }');
// 9. Export format - spread operator case
// { ...figma.code`...`, metadata: ... } → { example: figma.code`...`, metadata: ... }
migrated = migrated.replace(/\{\s*\.\.\.figma\.(code|tsx|html|swift|kotlin)`/g, '{ example: figma.$1`');
return migrated;
};
exports.migrateV1TemplateToV2 = migrateV1TemplateToV2;
//# sourceMappingURL=migration_helpers.js.map