Skip to content

Commit

Permalink
Separate cli from transpiler engine, see #1156
Browse files Browse the repository at this point in the history
  • Loading branch information
samreid committed Nov 27, 2021
1 parent 7b518de commit cd531a4
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 157 deletions.
161 changes: 161 additions & 0 deletions transpile/Transpiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright 2021, University of Colorado Boulder

/**
* Transpiles *.ts and copies all *.js files to chipper/dist. Does not do type checking. Filters based on active-repos
* and subsets of directories within repos (such as js/, images/, and sounds/)
*
* See transpile.js for the CLI usage
*
* @author Sam Reid (PhET Interactive Simulations)
*/

// imports
const fs = require( 'fs' );
const path = require( 'path' );
const crypto = require( 'crypto' );
const core = require( '@babel/core' );

// constants

// TODO: Should the status live with chipper/dist so if you wipe chipper/dist you also wipe the cache?
const statusPath = '../chipper/transpile/cache/status.json';
const args = process.argv.slice( 2 );
const root = '../';

// Directories in a sim repo that may contain things for transpilation
// This is used for a top-down seach in the initial transpilation and for filtering relevant files in the watch process
const subdirs = [ 'js', 'images', 'sounds' ];

class Transpiler {
constructor( options ) {

options = options || {
clean: false
};

// Track the status of each repo. Key= repo, value=md5 hash of contents
this.status = {};

// Make sure a directory exists for the cached status file
fs.mkdirSync( path.dirname( statusPath ), { recursive: true } );

if ( options.clean ) {
fs.writeFileSync( statusPath, JSON.stringify( {}, null, 2 ) );
}

// Load cached status
try {
this.status = JSON.parse( fs.readFileSync( statusPath, 'utf-8' ) );
}
catch( e ) {
this.status = {};
fs.writeFileSync( statusPath, JSON.stringify( this.status, null, 2 ) );
}

this.activeRepos = fs.readFileSync( '../perennial/data/active-repos', 'utf-8' ).trim().split( '\n' );
}

/**
* Transpile the file using babel, and write it to the corresponding location in chipper/dist
* @param {string} filename
* @param {string} text - file text
* @private
*/
static transpileFunction( filename, text ) {
const x = core.transformSync( text, {
filename: filename,
presets: [ '@babel/preset-typescript' ],
sourceMaps: 'inline'
} );
const relativePath = path.relative( root, filename );
const targetPath = path.join( root, 'chipper', 'dist', ...relativePath.split( path.sep ) ).split( '.ts' ).join( '.js' );
fs.mkdirSync( path.dirname( targetPath ), { recursive: true } );
fs.writeFileSync( targetPath, x.code );
}

/**
* For *.ts and *.js files, checks if they have changed file contents since last transpile. If so, the
* file is transpiled.
* @param {string} path
* @private
*/
visitFile( path ) {
if ( path.endsWith( '.js' ) || path.endsWith( '.ts' ) ) {
const changeDetectedTime = Date.now();
const text = fs.readFileSync( path, 'utf-8' );
const hash = crypto.createHash( 'md5' ).update( text ).digest( 'hex' );

// If the file has changed, transpile and update the cache
if ( !this.status[ path ] || this.status[ path ].md5 !== hash ) {
Transpiler.transpileFunction( path, text );
this.status[ path ] = { md5: hash };
fs.writeFileSync( statusPath, JSON.stringify( this.status, null, 2 ) );
console.log( ( Date.now() - changeDetectedTime ) + 'ms: ' + path );
}
}
}

// @private - Recursively visit a directory for files to transpile
visitDirectory( dir ) {
if ( fs.existsSync( dir ) ) {
const files = fs.readdirSync( dir );
files.forEach( file => {
const child = dir + path.sep + file;
if ( fs.lstatSync( child ).isDirectory() ) {
this.visitDirectory( child );
}
else {
this.visitFile( child );
}
} );
}
}

// @private - Visit all the subdirectories in a repo that need transpilation
visitRepo( repo ) {
subdirs.forEach( subdir => this.visitDirectory( `../${repo}/${subdir}` ) );
}

// @public
transpileAll() {
this.activeRepos.forEach( repo => this.visitRepo( repo ) );
}

// @public
watch() {
fs.watch( '../', { recursive: true }, ( eventType, filename ) => {

if ( filename.includes( '/node_modules/' ) ||
filename.includes( '.git/' ) ||
filename.includes( 'chipper/dist/' ) ||
filename.includes( 'transpile/cache/status.json' ) ||

// Temporary files sometimes saved by the IDE
filename.endsWith( '~' ) ) {

// ignore
}
else if ( filename === 'perennial/data/active-repos' ) {
const newActiveRepos = fs.readFileSync( '../perennial/data/active-repos', 'utf-8' ).trim().split( '\n' );
console.log( 'reloaded active repos' );
const newRepos = newActiveRepos.filter( repo => !this.activeRepos.includes( repo ) );

//Scan anything that was added
newRepos.forEach( repo => {
console.log( 'New repo detected in active-repos, transpiling: ' + repo );
this.visitRepo( repo );
} );
this.activeRepos = newActiveRepos;
}
else {
args.includes( '--verbose' ) && console.log( eventType, filename );
const terms = filename.split( path.sep );
if ( this.activeRepos.includes( terms[ 0 ] ) && subdirs.includes( terms[ 1 ] ) ) {
this.visitFile( '../' + filename );
}
}
} );
}
}

module.exports = Transpiler;
42 changes: 42 additions & 0 deletions transpile/transpile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2021, University of Colorado Boulder

/**
* Transpiles *.ts and copies all *.js files to chipper/dist. Does not do type checking. Filters based on active-repos
* and subsets of directories within repos (such as js/, images/, and sounds/)
*
* Usage:
* cd chipper
* node transpile/transpile-all.js --watch
*
* OPTIONS:
* --watch continue watching all directories and transpile on detected changes
* --clean dispose of the cache that tracks file status on startup, can be combined with other commands
* you would need to run --clean if the transpiled file status ever gets out of sync with the
* cache file status, for example if you deleted/modified a transpiled file in chipper/dist.
* --skipInitial skip the initial transpilation
*
* @author Sam Reid (PhET Interactive Simulations)
*/

// constants
const start = Date.now();
const args = process.argv.slice( 2 );

// imports
const Transpiler = require( './Transpiler' );

const transpiler = new Transpiler( {
clean: args.includes( '--clean' )
} );

// Initial pass
if ( !args.includes( '--skipInitial' ) ) {
transpiler.transpileAll();
console.log( 'Finished initial transpilation in ' + ( Date.now() - start ) + 'ms' );
}

// Watch process
if ( args.includes( '--watch' ) ) {
transpiler.watch();
console.log( 'Watching...' );
}
157 changes: 0 additions & 157 deletions transpile/transpiler.js

This file was deleted.

0 comments on commit cd531a4

Please sign in to comment.