
DyFactor هو منصة للكتابة وتنفيذ codemods الموجهة إلى الملف الشخصي. من خلال الجمع بين معلومات وقت التشغيل والتحليل الثابت ، يمكن للتطبيقات ترحيل كودات إلى واجهات برمجة التطبيقات الجديدة بوتيرة أسرع بكثير.
انظر Dyfactor في العمل
$ yarn global add dyfactor$ yarn dyfactor --help
Usage: dyfactor [options] [command]
Options:
-h, --help output usage information
Commands:
init Initializes dyfactor in your project
run Runs a plugin against code
list-plugins Lists the available plugins
help [cmd] display help for [cmd]أدوات مثل JScodeShift و Babel هي أدوات ممتازة لترحيل التعليمات البرمجية من API إلى آخر بسبب حقيقة أنهما يعتمدون على التحليل الثابت. ومع ذلك ، من الممكن تمامًا أن تعتمد أجزاء من التطبيق على معلومات وقت التشغيل لتحديد التنفيذ. في حالات أخرى ، قد يكون لديك DSLs مخصصة مثل لغات القالب التي لا تحتوي على نفس ضمانات التحليل الثابت كما تفعل JavaScript. يتطلب ترحيل هذا النوع من التعليمات البرمجية عادة قدرًا كبيرًا من تدخل المطور لفرز ما يحدث بالفعل. هدف DyFactor هو تقصير هذا الفهم.
في قالب Ember ، يمكن لمطوري الطبقة استدعاء المكونات بالحجج. يمكنهم أيضًا النظر إلى القيم المحلية في فئة الدعم. قد يبدو قالب مكون ما يلي.
على الرغم من أن هذا القالب التصريح ، فليس من الواضح أن أي من MustacheExpressions (التجعيد) كانت وسيطات للمكون أو إذا كانت قيمًا محلية في فئة المكون. الطريقة الوحيدة لمعرفة هي إلقاء نظرة على الاحتجاج بالمكون. في القيام بذلك ، نجد أن firstName و lastName يتم تمريرهما كحجيلات.
لقد أدرك Ember Core أن هذا يمثل مشكلة كبيرة مع نمو التطبيق ، لذلك سمح للوسائط بتثبيت مسبقًا مع @ وسكان محليين this. . المشكلة هي أن ترحيل جميع القوالب في المشروع سيستغرق وقتًا طويلاً لأنه يتطلب من المطورين فصل هذه الأشياء.
هذا هو المكان الذي يأتي فيه DyFactor. من خلال كتابة مكون إضافي ديناميكي ، يمكننا توصيل التطبيق بطريقة تتيح لنا معرفة كيفية حل هذه الرموز في وقت التشغيل. من هناك ، يمكننا استخدام هذه المعلومات للذهاب يدويًا ترحيل الكود أو السماح لـ DyFactor بمحاولة القيام بالترحيل بالنسبة لنا.
على مستوى عال ، DyFactor هو نظام عداء و Plugin. يدعم حاليًا نوعين من الإضافات: المكونات الإضافية الثابتة والمكونات الإضافية الديناميكية.
من المفترض أن يتم استخدام المكون الإضافي الثابت لأداء codemods التي تتطلب تحليلًا ثابتًا فقط وهو واحد فقط على مراحل. يجب أن يُعتبر هذه غلافًا خفيفًا حول codemod التي ستكتبها مع JScodeShift أو Babel.
interface StaticPlugin {
/**
* An array containing all recursive files and directories
* under a given directory
**/
inputs : string [ ] ;
/**
* Hook called by the runner where codemod is implimented
*/
modify ( ) : void ;
} import { StaticPlugin } from 'dyfactor' ;
import * as fs from 'fs' ;
import * as recast from 'recast' ;
function filesOnly ( path ) {
return path . charAt ( path . length - 1 ) !== '/' ;
}
export default class extends StaticPlugin {
modify ( ) {
this . inputs . filter ( filesOnly ) . forEach ( input => {
let content = fs . readFileSync ( input , 'utf8' ) ;
let ast = recast . parse ( content ) ;
let add = ast . program . body [ 0 ] ;
let { builders : b } = recast . types ;
ast . program . body [ 0 ] = b . variableDeclaration ( 'var' , [
b . variableDeclarator (
add . id ,
b . functionExpression (
null , // Anonymize the function expression.
add . params ,
add . body
)
)
] ) ;
add . params . push ( add . params . shift ( ) ) ;
let output = recast . prettyPrint ( ast , { tabWidth : 2 } ) . code ;
fs . writeFileSync ( input , output ) ;
} ) ;
}
} المكون الإضافي الديناميكي ذو مرحلتين. تتيح لك المرحلة الأولى أن تقوم بأمان تطبيق لجمع بيانات القياس عن بُعد وقت التشغيل. ثم يتم تمهيد التطبيق المعني باستخدام Puppeteer لجمع البيانات. المرحلة الثانية هي المسؤولة عن إدخال بيانات القياس عن بُعد وقت التشغيل وتطبيق Codemods بناءً على تلك البيانات. من المهم أن نلاحظ أنه يمكن تشغيل المكونات الإضافية الديناميكية كمكونات إضافية للتدريبات فقط لإنتاج القياس عن بُعد وقت التشغيل وكتابته على القرص.
interface DynamicPlugin {
/**
* An array containing all recursive files and directories
* under a given directory
**/
inputs : string [ ] ;
/**
* Hook called by the runner to instrument the application
*/
instrument ( ) : void ;
/**
* Hook called by the runner to apply codemods based on the telemtry
*/
modify ( telemetry : Telemetry ) : void ;
}
interface Telemetry {
data : any ;
} import { DynamicPlugin , TelemetryBuilder } from 'dyfactor' ;
import * as fs from 'fs' ;
import { preprocess , print } from '@glimmer/syntax' ;
import { transform } from 'babel-core' ;
function filesOnly ( path ) {
return path . charAt ( path . length - 1 ) !== '/' ;
}
function instrumentCreate ( babel ) {
const { types : t } = babel ;
let ident ;
let t = new TelemetryBuilder ( ) ;
let template = babel . template ( `
IDENT.reopenClass({
create(injections) {
let instance = this._super(injections);
${ t . preamble ( ) }
${ t . conditionallyAdd (
'instance._debugContainerKey' ,
( ) => {
return `Object.keys(injections.attrs).forEach((arg) => {
if (! ${ t . path ( 'instance._debugContainerKey' ) } .contains(arg)) {
${ t . path ( 'instance._debugContainerKey' ) } .push(arg);
}
});` ;
} ,
( ) => {
return ` ${ t . path ( 'instance._debugContainerKey' ) } = Object.keys(injections.attrs);` ;
}
) }
return instance;
}
});
` ) ;
return {
name : 'instrument-create' ,
visitor : {
Program : {
enter ( p ) {
ident = p . scope . generateUidIdentifier ( 'refactor' ) ;
} ,
exit ( p ) {
let body = p . node . body ;
let ast = template ( { IDENT : ident } ) ;
body . push ( ast , t . exportDefaultDeclaration ( ident ) ) ;
}
} ,
ExportDefaultDeclaration ( p ) {
let declaration = p . node . declaration ;
let declarator = t . variableDeclarator ( ident , declaration ) ;
let varDecl = t . variableDeclaration ( 'const' , [ declarator ] ) ;
p . replaceWith ( varDecl ) ;
}
}
} ;
}
export default class extends DynamicPlugin {
instrument ( ) {
this . inputs . filter ( filesOnly ) . forEach ( input => {
let code = fs . readFileSync ( input , 'utf8' ) ;
let content = transform ( code , {
plugins : [ instrumentCreate ]
} ) ;
fs . writeFileSync ( input , content . code ) ;
} ) ;
}
modify ( telemetry ) {
telemetry . data . forEach ( components => {
Object . keys ( components ) . forEach ( component => {
let templatePath = this . templateFor ( component ) ;
let template = fs . readFileSync ( templatePath , 'utf8' ) ;
let ast = preprocess ( template , {
plugins : {
ast : [ toArgs ( components [ component ] ) ]
}
} ) ;
fs . writeFileSync ( templatePath , print ( ast ) ) ;
} ) ;
} ) ;
}
templateFor ( fullName : string ) {
let [ , name ] = fullName . split ( ':' ) ;
return this . inputs . find ( input => input . includes ( `templates/components/ ${ name } ` ) ) ;
}
}