
Dyfactor adalah platform untuk menulis dan melaksanakan codemods yang dipandu profil. Dengan menggabungkan informasi runtime dan analisis statis, aplikasi dapat memigrasikan basis kode ke API baru dengan kecepatan yang jauh lebih cepat.
Lihat Dyfactor beraksi
$ 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]Alat -alat seperti JScodeshift dan Babel adalah alat yang sangat baik untuk memigrasikan kode dari satu API ke yang lain karena fakta bahwa mereka bergantung pada analisis statis. Namun, sepenuhnya mungkin bagi bagian dari suatu aplikasi untuk mengandalkan informasi runtime untuk menentukan eksekusi. Dalam kasus lain Anda mungkin memiliki DSL khusus seperti bahasa templat yang tidak memiliki jaminan analisis statis yang sama seperti JavaScript. Migrasi jenis kode ini biasanya membutuhkan banyak intervensi pengembang untuk memilah apa yang sebenarnya terjadi. Tujuan Dyfactor adalah untuk mempersingkat celah pemahaman ini.
Di Template Layer Template, pengembang dapat memohon komponen dengan argumen. Mereka juga dapat melihat nilai -nilai lokal di kelas pendukung. Template untuk komponen mungkin terlihat seperti berikut.
Meskipun template ini bersifat deklaratif, tidak jelas jika salah satu dari MustacheExpressions (Curlies) adalah argumen untuk komponen atau jika mereka adalah nilai lokal pada kelas komponen. Satu -satunya cara untuk mengetahui adalah dengan melihat doa komponen. Dengan melakukan itu, kami menemukan firstName dan lastName disahkan sebagai argumen.
Inti Ember telah mengakui ini sangat bermasalah seiring pertumbuhan aplikasi, jadi mereka telah memungkinkan argumen untuk ditetapkan sebelumnya dengan @ dan penduduk setempat diawali dengan this. . Masalahnya adalah bahwa memigrasi semua templat dalam suatu proyek akan memakan waktu terlalu lama karena mengharuskan pengembang untuk memisahkan hal -hal ini.
Di sinilah dyfactor masuk. Dengan menulis plugin dinamis, kita dapat instrumen aplikasi sedemikian rupa sehingga memungkinkan kita untuk mengetahui bagaimana simbol -simbol ini diselesaikan saat runtime. Dari sana, kita dapat menggunakan informasi itu untuk memigrasikan kode secara manual atau membiarkan Dyfactor mencoba melakukan migrasi untuk kita.
Pada level tinggi, dyfactor adalah sistem pelari dan plugin. Saat ini mendukung dua jenis plugin: plugin statis dan plugin dinamis.
Plugin statis dimaksudkan untuk digunakan untuk melakukan codemods yang hanya membutuhkan analisis statis dan hanya bertahap tunggal. Ini harus dianggap sebagai pembungkus cahaya di sekitar codemod yang akan Anda tulis dengan JScodeshift atau 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 ) ;
} ) ;
}
} Plugin dinamis adalah dua fase. Fase pertama memungkinkan Anda untuk dengan aman instrumen aplikasi untuk mengumpulkan data telemetri runtime tersebut. Aplikasi yang diinstrumentasi kemudian di -boot dengan dalang untuk mengumpulkan data. Fase kedua bertanggung jawab untuk introspeksi data telemetri runtime dan menerapkan codemods berdasarkan data itu. Penting untuk dicatat bahwa plugin dinamis dapat dijalankan sebagai plugin bertahap tunggal hanya untuk menghasilkan telemetri runtime dan menulisnya ke disk.
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 } ` ) ) ;
}
}