
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 в другой из -за того, что они полагаются на статический анализ. Тем не менее, вполне возможно, что части приложения полагаются на информацию времени выполнения для определения выполнения. В других случаях у вас могут быть пользовательские DSL, такие как языки шаблонов, которые не имеют таких же гарантий статического анализа, как и JavaScript. Миграция этого типа кода обычно требует большого вмешательства разработчиков, чтобы разобраться в том, что на самом деле происходит. Цель Dyfactor - сократить эту щель понимания.
В шаблонах Ember разработчики могут вызывать компоненты с аргументами. Они также могут смотреть на локальные значения на классе поддержки. Шаблон для компонента может выглядеть как следующее.
Хотя этот шаблон является декларативным, не очевидно, если какие -либо из MustacheExpressions (крили) были аргументами для компонента или если они являются локальными значениями в классе компонентов. Единственный способ узнать - посмотреть на вызов компонента. При этом мы обнаруживаем, что firstName и lastName передается в качестве аргументов.
Ember Core признала, что это чрезвычайно проблематично по мере роста приложения, поэтому они позволили предварительно фиксировать аргументы с помощью @ и местных жителей, чтобы быть префиксированными с this. Полем Проблема заключается в том, что мигрирование всех шаблонов в проекте займет слишком много времени, потому что это требует, чтобы разработчики разделили эти вещи.
Именно здесь появляется Dyfactor. Записав динамический плагин, мы можем придать приложение таким образом, что позволяет нам узнать, как эти символы разрешаются во время выполнения. Оттуда мы можем использовать эту информацию, чтобы перейти вручную мигрировать код или позволить 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 ) ;
} ) ;
}
} Динамический плагин двухэтапно. Первый этап позволяет вам безопасно придать приложение для сбора данных телеметрии времени выполнения. Затем инструментальное приложение загружается с кукловодом для сбора данных. Второй этап отвечает за нарушение данных телеметрии во время выполнения и применение кодовых демодов на основе этих данных. Важно отметить, что динамические плагины можно запустить в виде одноэтажных плагинов, чтобы создать телеметрию времени выполнения и записать ее на диск.
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 } ` ) ) ;
}
}