
Dyfactor es una plataforma para escribir y ejecutar CodeMods guiados por perfil. Al combinar información de tiempo de ejecución y análisis estático, las aplicaciones pueden migrar las bases de código a nuevas API a un ritmo mucho más rápido.
Ver DIFACTOR EN ACCIÓN
$ 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]Herramientas como JScodeshift y Babel son excelentes herramientas para migrar el código de una API a otra debido al hecho de que dependen del análisis estático. Sin embargo, es completamente posible que partes de una aplicación confíen en la información de tiempo de ejecución para determinar la ejecución. En otros casos, puede tener DSL personalizados como lenguajes de plantilla que no tienen las mismas garantías de análisis estático que JavaScript. La migración de este tipo de código generalmente requiere una gran intervención de desarrolladores para resolver lo que realmente está sucediendo. El objetivo de Dyfactor es acortar esta grieta de comprensión.
En la plantilla de Ember, los desarrolladores de la capa pueden invocar componentes con argumentos. También pueden mirar los valores locales en la clase de respaldo. Una plantilla para un componente puede parecerse a la siguiente.
Si bien esta plantilla es declarativa, no es obvio si alguna de las MustacheExpressions (rizos) eran argumentos al componente o si son valores locales en la clase de componentes. La única forma de saber es ver la invocación del componente. Al hacerlo, encontramos que firstName y lastName se pasan como argumentos.
El núcleo de Ember ha reconocido que esto es extremadamente problemático a medida que crece una aplicación, por lo que han permitido que los argumentos se fijan previamente con @ y los locales se prefijan con this. . El problema es que migrar todas las plantillas en un proyecto tomaría demasiado tiempo porque requiere que los desarrolladores separen estas cosas.
Aquí es donde entra Dyfactor. Al escribir un complemento dinámico, podemos instrumentar la aplicación de tal manera que nos permita saber cómo se resuelven estos símbolos en tiempo de ejecución. A partir de ahí, podemos usar esa información para migrar manualmente el código o dejar que el disfactor intente hacer la migración por nosotros.
En un alto nivel, Dactor es un sistema de corredores y complementos. Actualmente admite dos tipos de complementos: complementos estáticos y complementos dinámicos.
Se debe utilizar un complemento estático para realizar CodeMods que solo requieren un análisis estático y solo es en fase única. Estos deben considerarse como un envoltorio de luz alrededor de un CodeMod que escribiría con JScodeshift o 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 ) ;
} ) ;
}
} Un complemento dinámico es de dos fases. La primera fase le permite instrumentar de manera segura una aplicación para recopilar esos datos de telemetría de tiempo de ejecución. La aplicación instrumentada se inicia con titiritero para recopilar los datos. La segunda fase es responsable de introspectar los datos de telemetría de tiempo de ejecución y aplicar CODEMOD en función de esos datos. Es importante tener en cuenta que los complementos dinámicos se pueden ejecutar como complementos en fases únicas solo para producir la telemetría de tiempo de ejecución y escribirlo en el disco.
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 } ` ) ) ;
}
}