
Dyfactor est une plate-forme pour l'écriture et l'exécution de codes codées guidés par profil. En combinant des informations d'exécution et une analyse statique, les applications sont capables de migrer les bases de code vers de nouvelles API à un rythme beaucoup plus rapide.
Voir Dyfactor en action
$ 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]Des outils comme JScodehift et Babel sont d'excellents outils pour migrer le code d'une API à une autre en raison du fait qu'ils comptent sur une analyse statique. Cependant, il est tout à fait possible que des parties d'une application s'appuient sur les informations d'exécution pour déterminer l'exécution. Dans d'autres cas, vous pouvez avoir des DSL personnalisés comme les langages de modèle qui n'ont pas les mêmes garanties d'analyse statique que JavaScript. La migration de ce type de code nécessite généralement une grande intervention des développeurs pour trier ce qui se passe réellement. L'objectif de Dyfactor est de raccourcir cette crevasse de compréhension.
Dans le modèle d'Ember, les développeurs de couche peuvent invoquer des composants avec des arguments. Ils peuvent également consulter les valeurs locales sur la classe de soutien. Un modèle pour un composant peut ressembler à ce qui suit.
Bien que ce modèle soit déclaratif, il n'est pas évident si l'une des MustacheExpressions (curlies) était des arguments au composant ou s'ils sont des valeurs locales sur la classe de composants. La seule façon de savoir est d'aller regarder l'invocation du composant. Ce faisant, nous constatons que firstName et lastName sont passés en arguments.
Le noyau de braise a reconnu que cela est extrêmement problématique à mesure qu'une application augmente, donc ils ont permis aux arguments d'être pré-fixés avec @ et les habitants sont préfixés avec this. . Le problème est que la migration de tous les modèles d'un projet prendrait trop de temps car elle oblige les développeurs à séparer ces choses.
C'est là que Dyfactor entre en jeu. À partir de là, nous pouvons utiliser ces informations pour migrer manuellement le code ou laisser Dyfactor tenter de faire la migration pour nous.
À un niveau élevé, Dyfactor est un système de coureur et de plugin. Il prend actuellement en charge deux types de plugins: les plugins statiques et les plugins dynamiques.
Un plugin statique est destiné à être utilisé pour effectuer des codemods qui ne nécessitent qu'une analyse statique et ne sont que parasites. Ceux-ci doivent être considérés comme un emballage léger autour d'un codemod que vous écririez avec jscodehift ou 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 plugin dynamique est à deux phases. La première phase vous permet d'instrumenter en toute sécurité une application pour collecter ces données de télémétrie d'exécution. L'application instrumentée est ensuite démarrée avec Puppeteer pour collecter les données. La deuxième phase est responsable de l'introspection des données de télémétrie d'exécution et de l'application de codemods en fonction de ces données. Il est important de noter que les plugins dynamiques peuvent être exécutés sous forme de plugins à phases uniques juste pour produire la télémétrie d'exécution et l'écrire sur le disque.
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 } ` ) ) ;
}
}