
Dyfactorは、プロファイルガイド付きのCodeModsを作成および実行するためのプラットフォームです。ランタイム情報と静的分析を組み合わせることにより、アプリケーションはコードベースをより速いペースで新しいAPIに移行できます。
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から別のAPIに移行するための優れたツールです。ただし、アプリケーションの一部が実行時間情報に依存して実行を決定することは完全に可能です。それ以外の場合は、JavaScriptと同じ静的分析保証がないテンプレート言語のようなカスタムDSLがあります。このタイプのコードを移行するには、通常、実際に何が起こっているかを整理するために、多くの開発者介入が必要です。 Dyfactorの目標は、この理解のクレバスを短縮することです。
Emberのテンプレートレイヤーでは、開発者は引数でコンポーネントを呼び出すことができます。また、バッキングクラスのローカル値を見ることもできます。コンポーネントのテンプレートは、次のように見える場合があります。
このテンプレートは宣言的ですが、 MustacheExpressions (Curlies)のいずれかがコンポーネントの議論であるか、コンポーネントクラスのローカル値であるかどうかは明らかではありません。知る唯一の方法は、コンポーネントの呼び出しを見に行くことです。そうすることで、 firstNameとlastName引数として渡されることがわかります。
Ember Coreは、アプリケーションが成長するにつれてこれが非常に問題であることを認識しているため、 @および地元の人々がthis. 。問題は、プロジェクト内のすべてのテンプレートを移行すると、開発者がこれらのことを分離する必要があるため、時間がかかりすぎることです。
これがDyfactorが登場する場所です。ダイナミックプラグインを作成することで、これらのシンボルが実行時にどのように解決されているかを知ることができるようにアプリケーションを計測できます。そこから、その情報を使用してコードを手動で移行したり、Dyfactorに移行を試みたりすることができます。
高レベルでは、dyFactorはランナーとプラグインシステムです。現在、静的プラグインと動的プラグインの2種類のプラグインをサポートしています。
静的プラグインは、静的解析のみが必要であり、単一の段階的である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 ) ;
} ) ;
}
} 動的プラグインは2段階です。最初のフェーズでは、アプリケーションを安全に機器に機器にして、そのランタイムテレメトリデータを収集することができます。計装されたアプリケーションは、操り人形師で起動してデータを収集します。第2フェーズは、ランタイムテレメトリデータを内省し、そのデータに基づいてCodeModを適用する責任があります。ランタイムテレメトリを生成してディスクに書き込むために、動的プラグインを単一のフェーズドプラグインとして実行できることに注意することが重要です。
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 } ` ) ) ;
}
}