
Dyfactor는 프로파일 유도 코드 모드를 작성하고 실행하는 플랫폼입니다. 런타임 정보와 정적 분석을 결합함으로써 응용 프로그램은 훨씬 빠른 속도로 코드베이스를 새로운 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는 러너 및 플러그인 시스템입니다. 현재 정적 플러그인과 동적 플러그인의 두 가지 유형의 플러그인을 지원합니다.
정적 플러그인은 정적 분석 만 필요하며 단일 단계 만있는 코드 모드를 수행하는 데 사용됩니다. 이것들은 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 단계입니다. 첫 번째 단계를 사용하면 해당 런타임 원격 측정 데이터를 수집 할 수있는 응용 프로그램을 안전하게 도구로 만들 수 있습니다. 그런 다음 계측 응용 프로그램을 인형극과 함께 부팅하여 데이터를 수집합니다. 두 번째 단계는 런타임 원격 측정 데이터를 내성하고 해당 데이터를 기반으로 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 } ` ) ) ;
}
}