
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 หนึ่งไปยังอีกอันหนึ่งเนื่องจากพวกเขาพึ่งพาการวิเคราะห์แบบคงที่ อย่างไรก็ตามมันเป็นไปได้อย่างสมบูรณ์สำหรับชิ้นส่วนของแอปพลิเคชันที่จะพึ่งพาข้อมูลรันไทม์เพื่อพิจารณาการดำเนินการ ในกรณีอื่น ๆ คุณอาจมี DSL ที่กำหนดเองเช่นภาษาแม่แบบที่ไม่มีการวิเคราะห์แบบคงที่เช่นเดียวกับ JavaScript การโยกย้ายรหัสประเภทนี้มักจะต้องมีการแทรกแซงของนักพัฒนาซอฟต์แวร์อย่างมากเพื่อแยกแยะสิ่งที่เกิดขึ้นจริง เป้าหมายของ Dyfactor คือการทำให้รอยแยกของความเข้าใจสั้นลง
ในนักพัฒนาเลเยอร์เทมเพลตของ Ember สามารถเรียกใช้ส่วนประกอบด้วยอาร์กิวเมนต์ พวกเขายังสามารถดูค่าท้องถิ่นในคลาสสำรอง เทมเพลตสำหรับส่วนประกอบอาจมีลักษณะดังต่อไปนี้
ในขณะที่เทมเพลตนี้มีการประกาศ แต่ก็ไม่ชัดเจนว่า MustacheExpressions (curlies) ใด ๆ เป็นข้อโต้แย้งกับส่วนประกอบหรือถ้าพวกเขาเป็นค่าท้องถิ่นในคลาสส่วนประกอบ วิธีเดียวที่จะรู้คือไปดูการเรียกใช้ส่วนประกอบ ในการทำเช่นนั้นเราพบว่า firstName และ lastName จะถูกส่งผ่านเป็นอาร์กิวเมนต์
Ember Core ได้รับการยอมรับว่านี่เป็นปัญหาอย่างมากเมื่อแอปพลิเคชันเติบโตขึ้นดังนั้นพวกเขาจึงอนุญาตให้อาร์กิวเมนต์ได้รับการแก้ไขล่วงหน้าด้วย @ และคนในท้องถิ่นที่จะนำหน้าด้วย this. - ปัญหาคือการโยกย้ายเทมเพลตทั้งหมดในโครงการจะใช้เวลานานเกินไปเพราะต้องมีนักพัฒนาที่จะแยกสิ่งเหล่านี้ออก
นี่คือที่ Dyfactor เข้ามาโดยการเขียนปลั๊กอินแบบไดนามิกเราสามารถเครื่องมือแอปพลิเคชันในลักษณะที่ช่วยให้เรารู้ว่าสัญลักษณ์เหล่านี้ได้รับการแก้ไขอย่างไรในรันไทม์ จากนั้นเราสามารถใช้ข้อมูลนั้นเพื่อย้ายรหัสด้วยตนเองหรือปล่อยให้ Dyfactor พยายามทำการย้ายถิ่นให้เรา
ในระดับสูง Dyfactor เป็นนักวิ่งและระบบปลั๊กอิน ปัจจุบันรองรับปลั๊กอินสองประเภท: ปลั๊กอินคงที่และปลั๊กอินแบบไดนามิก
ปลั๊กอินแบบคงที่มีไว้เพื่อใช้ในการดำเนินการ codemods ที่ต้องการการวิเคราะห์แบบคงที่เท่านั้น สิ่งเหล่านี้ควรคิดว่าเป็นเสื้อคลุมเบารอบ 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 ) ;
} ) ;
}
} ปลั๊กอินแบบไดนามิกเป็นสองเฟส ระยะแรกช่วยให้คุณสามารถใช้แอปพลิเคชั่นในการรวบรวมข้อมูล telemetry รันไทม์ได้อย่างปลอดภัย แอปพลิเคชันเครื่องมือจะถูกบูทกับ Puppeteer เพื่อรวบรวมข้อมูล ระยะที่สองมีหน้าที่ในการตรวจสอบข้อมูล telemetry รันไทม์และใช้ codemods ตามข้อมูลนั้น เป็นสิ่งสำคัญที่จะต้องทราบว่าปลั๊กอินแบบไดนามิกสามารถเรียกใช้เป็นปลั๊กอินแบบเฟสเดี่ยวเพื่อสร้าง telemetry รันไทม์และเขียนลงในดิสก์
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 } ` ) ) ;
}
}