Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate using TypeScript in our build tools and Node code. #1272

Closed
kathy-phet opened this issue Jul 5, 2022 · 40 comments
Closed

Investigate using TypeScript in our build tools and Node code. #1272

kathy-phet opened this issue Jul 5, 2022 · 40 comments

Comments

@kathy-phet
Copy link

Liam also noted this other one: https://bun.sh/

@samreid
Copy link
Member

samreid commented Jul 5, 2022

From #1206

Deno https://deno.land/ describes itself as: Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.

It has built-in support for TypeScript, and looks to have a superior package manager system to node. We have numerous other issues with the existing package management system, and will one day want to be able to use TypeScript in our build side. Deno looks like a good place to investigate. A few hours of investigation may help us rule this out, but if we can go forward, this would be an epic-level issue, so I'll label it as such and add it to the project board.

@samreid
Copy link
Member

samreid commented Jul 5, 2022

@liam-mulhall said:

It seems like more JS runtimes are cropping up: https://bun.sh/. Deno might not be the winner despite being worked on by Ryan Dahl. It would probably be a good idea to keep an eye on the various Node alternatives before we pick one.

@samreid
Copy link
Member

samreid commented Jul 6, 2022

I used a star history chart maker to compare node, deno and bun https://star-history.com/#denoland/deno&nodejs/node&Jarred-Sumner/bun&Date

image


UPDATE from MK 4/11/24:
image

@samreid
Copy link
Member

samreid commented Aug 2, 2022

I'm wondering if an easier intermediate step would be to use our existing transpiler interface and convert chipper/perennial code to TypeScript and just invoke it like node ../chipper/dist/ etc. We could potentially have our own alias that forwards calls, like: tsnode ./myscript.ts. But we would probably at least want to get decent sourcemaps. And changing a fundamental part of our toolchain like that will be nontrivial (whether going to node otherPath or tsnode or deno or https://typestrong.org/ts-node/). Importantly, would grunt work with these changes?

In #990, all devs confirmed they are using node 14+, which supports import, which seems a better fit for TypeScript.

https://stackoverflow.com/questions/62419970/grunt-and-es6-modules-in-node-incompatible-without-the-use-of-mjs

See related issue #861 (comment)

UPDATE: grunt is our limiting factor here. It requires require statements from the grunt-cli. We could swap out grunt-cli for an alias-like function like so:

# https://stackoverflow.com/questions/7131670/make-a-bash-alias-that-takes-a-parameter
grunt2() {
    node ../chipper/js/grunt2/grunt2.mjs $1 $2 $3 $4
}

But that is an invasive change that requires everything to be modules.

To step in the right direction toward ES6 modules/typeScript without having to replace 100% of grunt at the outset, we can change Transpiler to transform import statements to require statements (in chipper/perennial only!), then point Gruntfile at chipper/dist.

In doing so, we get advantages:

  • Ability to upgrade incrementally
  • Grunt still works, build process still works
  • Opt-in to ES6 imports as we wish (ok to mix with require statements)
  • Opt-in to TypeScript as we wish (ok to mix with JS)

For these costs:

  • more complexity in transpiler
  • will tooling like WebStorm get confused if we mix and match?
    Questions:
  • What about code like SimVersion which we want to import in Node/grunt as well as in the sim? Would the transpiler output 2 forms? How will our import statements know which to take?

Anyways, here's a working prototype:

Index: main/chipper/js/grunt/chipAway.js
===================================================================
diff --git a/main/chipper/js/grunt/chipAway.js b/main/chipper/js/grunt/chipAway.js
deleted file mode 100644
--- a/main/chipper/js/grunt/chipAway.js	(revision 466b649e1f05d50e7280272a7b62d0dff9b00eba)
+++ /dev/null	(revision 466b649e1f05d50e7280272a7b62d0dff9b00eba)
@@ -1,60 +0,0 @@
-// Copyright 2022, University of Colorado Boulder
-
-/**
- * Produces an assignment list of responsible devs. Run from lint.js
- *
- * The Chip Away option provides a quick and easy method to assign devs to their respective repositories
- * for ease in adopting and applying new typescript linting rules.
- * Chip Away will return a markdown formatted checklist with the repository name, responsible dev,
- * and number of errors.
- * Response  format:
- * - [ ] {{REPO}}: @{{GITHUB_USERNAME}} {{NUMBER}} errors in {{NUMBER}} files.
- *
- * @author Sam Reid (PhET Interactive Simulations)
- * @author Marla Schulz (PhET Interactive Simulations)
- */
-
-const fs = require( 'fs' );
-const _ = require( 'lodash' ); // eslint-disable-line require-statement-match
-const path = require( 'path' );
-
-/**
- * @param {ESLint.LintResult[]} results - the results from eslint.lintFiles( patterns )
- *                                      - { filePath: string, errorCount: number, warningCount: number }
- *                                      - see https://eslint.org/docs/latest/developer-guide/nodejs-api#-lintresult-type
- * @returns {string} - a message with the chip-away checkboxes in GitHub markdown format, or a message describing why it
- *                   - could not be accomplished
- */
-module.exports = results => {
-
-  // NOTE: This should never be run in a maintenance mode since this loads a file from phet-info which
-  // does not have its SHA tracked as a dependency.
-  let responsibleDevs = null;
-  try {
-    responsibleDevs = JSON.parse( fs.readFileSync( '../phet-info/sim-info/responsible_dev.json' ) );
-  }
-  catch( e ) {
-
-    // set responsibleDevs to an empty object if the file cannot be found or is not parseable.
-    // In this scenario, responsibleDev info would not be logged with other repo error info.
-    responsibleDevs = {};
-  }
-
-  const repos = results.map( result => path.relative( '../', result.filePath ).split( path.sep )[ 0 ] );
-  const assignments = _.uniq( repos ).map( repo => {
-
-    const filteredResults = results.filter( result => path.relative( '../', result.filePath ).split( path.sep )[ 0 ] === repo );
-    const fileCount = filteredResults.filter( result => result.errorCount + result.warningCount > 0 ).length;
-    const errorCount = _.sum( filteredResults.map( file => file.errorCount + file.warningCount ) );
-
-    if ( errorCount === 0 || repo === 'perennial-alias' ) {
-      return null;
-    }
-    else {
-      const usernames = responsibleDevs[ repo ] ? responsibleDevs[ repo ].responsibleDevs.join( ', ' ) : '';
-      return ` - [ ] ${repo}: ${usernames} ${errorCount} errors in ${fileCount} files.`;
-    }
-  } );
-
-  return assignments.filter( assignment => assignment !== null ).join( '\n' );
-};
\ No newline at end of file
Index: main/chipper/js/common/Transpiler.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/chipper/js/common/Transpiler.js b/main/chipper/js/common/Transpiler.js
--- a/main/chipper/js/common/Transpiler.js	(revision 466b649e1f05d50e7280272a7b62d0dff9b00eba)
+++ b/main/chipper/js/common/Transpiler.js	(date 1659578639105)
@@ -105,7 +105,11 @@
       presets: [
         '../chipper/node_modules/@babel/preset-typescript',
         '../chipper/node_modules/@babel/preset-react'
-      ], sourceMaps: 'inline'
+      ],
+      sourceMaps: 'inline',
+      plugins: sourceFile.includes( 'chipper/' ) ? [
+        '../chipper/node_modules/@babel/plugin-transform-modules-commonjs'
+      ] : []
     } );
 
     fs.mkdirSync( path.dirname( targetPath ), { recursive: true } );
Index: main/chipper/js/grunt/chipAway.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/chipper/js/grunt/chipAway.ts b/main/chipper/js/grunt/chipAway.ts
new file mode 100644
--- /dev/null	(date 1659578870969)
+++ b/main/chipper/js/grunt/chipAway.ts	(date 1659578870969)
@@ -0,0 +1,65 @@
+// Copyright 2022, University of Colorado Boulder
+
+/**
+ * Produces an assignment list of responsible devs. Run from lint.js
+ *
+ * The Chip Away option provides a quick and easy method to assign devs to their respective repositories
+ * for ease in adopting and applying new typescript linting rules.
+ * Chip Away will return a markdown formatted checklist with the repository name, responsible dev,
+ * and number of errors.
+ * Response  format:
+ * - [ ] {{REPO}}: @{{GITHUB_USERNAME}} {{NUMBER}} errors in {{NUMBER}} files.
+ *
+ * @author Sam Reid (PhET Interactive Simulations)
+ * @author Marla Schulz (PhET Interactive Simulations)
+ */
+
+const fs = require( 'fs' );
+const _ = require( 'lodash' ); // eslint-disable-line require-statement-match
+const path = require( 'path' );
+
+/**
+ * @param {ESLint.LintResult[]} results - the results from eslint.lintFiles( patterns )
+ *                                      - { filePath: string, errorCount: number, warningCount: number }
+ *                                      - see https://eslint.org/docs/latest/developer-guide/nodejs-api#-lintresult-type
+ * @returns {string} - a message with the chip-away checkboxes in GitHub markdown format, or a message describing why it
+ *                   - could not be accomplished
+ */
+export default results => {
+
+  // // NOTE: This should never be run in a maintenance mode since this loads a file from phet-info which
+  // // does not have its SHA tracked as a dependency.
+  // let responsibleDevs = null;
+  // try {
+  //   responsibleDevs = JSON.parse( fs.readFileSync( '../phet-info/sim-info/responsible_dev.json' ) );
+  // }
+  // catch( e ) {
+  //
+  //   // set responsibleDevs to an empty object if the file cannot be found or is not parseable.
+  //   // In this scenario, responsibleDev info would not be logged with other repo error info.
+  //   responsibleDevs = {};
+  // }
+  //
+  // const repos = results.map( result => path.relative( '../', result.filePath ).split( path.sep )[ 0 ] );
+  // const assignments = _.uniq( repos ).map( repo => {
+  //
+  //   const filteredResults = results.filter( result => path.relative( '../', result.filePath ).split( path.sep )[ 0 ] === repo );
+  //   const fileCount = filteredResults.filter( result => result.errorCount + result.warningCount > 0 ).length;
+  //   const errorCount = _.sum( filteredResults.map( file => file.errorCount + file.warningCount ) );
+  //
+  //   if ( errorCount === 0 || repo === 'perennial-alias' ) {
+  //     return null;
+  //   }
+  //   else {
+  //     const usernames = responsibleDevs[ repo ] ? responsibleDevs[ repo ].responsibleDevs.join( ', ' ) : '';
+  //     return ` - [ ] ${repo}: ${usernames} ${errorCount} errors in ${fileCount} files.`;
+  //   }
+  // } );
+  //
+  // return assignments.filter( assignment => assignment !== null ).join( '\n' );
+  console.log( 'chip away' );
+
+  const m: number = 7;
+  console.log( m );
+
+};
\ No newline at end of file
Index: main/chipper/js/grunt/lint.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/chipper/js/grunt/lint.js b/main/chipper/js/grunt/lint.js
--- a/main/chipper/js/grunt/lint.js	(revision 466b649e1f05d50e7280272a7b62d0dff9b00eba)
+++ b/main/chipper/js/grunt/lint.js	(date 1659578850543)
@@ -12,7 +12,7 @@
 const _ = require( 'lodash' ); // eslint-disable-line require-statement-match
 const { ESLint } = require( 'eslint' ); // eslint-disable-line require-statement-match
 const fs = require( 'fs' );
-const chipAway = require( './chipAway' );
+import chipAway from './chipAway.js';
 const showCommandLineProgress = require( '../common/showCommandLineProgress' );
 const CacheLayer = require( '../common/CacheLayer' );
 
@@ -173,6 +173,8 @@
     }
   }
 
+  chipAway();
+
   process.chdir( cwd );
 
   const ok = totalWarnings + totalErrors === 0;
Index: main/mean-share-and-balance/Gruntfile.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/mean-share-and-balance/Gruntfile.js b/main/mean-share-and-balance/Gruntfile.js
--- a/main/mean-share-and-balance/Gruntfile.js	(revision 59784f82064abbdb0e6fa2d0b89a92c13f8bd197)
+++ b/main/mean-share-and-balance/Gruntfile.js	(date 1659578728625)
@@ -3,4 +3,4 @@
 /* eslint-env node */
 
 // use chipper's gruntfile
-module.exports = require( '../chipper/js/grunt/Gruntfile.js' );
+module.exports = require( '../chipper/dist/js/chipper/js/grunt/Gruntfile.js' ); // eslint-disable-line
~/apache-document-root/main/mean-share-and-balance$ grunt lint
Running "lint" task
chip away
7

Done.

@samreid
Copy link
Member

samreid commented Aug 4, 2022

Here's a simpler change set that accomplishes the same thing.

Index: main/chipper/js/common/Transpiler.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/chipper/js/common/Transpiler.js b/main/chipper/js/common/Transpiler.js
--- a/main/chipper/js/common/Transpiler.js	(revision 466b649e1f05d50e7280272a7b62d0dff9b00eba)
+++ b/main/chipper/js/common/Transpiler.js	(date 1659587064640)
@@ -97,6 +97,7 @@
    * @private
    */
   static transpileFunction( sourceFile, targetPath, text ) {
+    console.log( sourceFile );
     const x = core.transformSync( text, {
       filename: sourceFile,
 
@@ -108,8 +109,10 @@
       ], sourceMaps: 'inline'
     } );
 
+    const code = sourceFile.includes( '../chipper/' ) ? x.code.split( 'export default ' ).join( 'module.exports = ' ) : x.code;
+
     fs.mkdirSync( path.dirname( targetPath ), { recursive: true } );
-    fs.writeFileSync( targetPath, x.code );
+    fs.writeFileSync( targetPath, code );
   }
 
   // @public
Index: main/mean-share-and-balance/Gruntfile.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/mean-share-and-balance/Gruntfile.js b/main/mean-share-and-balance/Gruntfile.js
--- a/main/mean-share-and-balance/Gruntfile.js	(revision 59784f82064abbdb0e6fa2d0b89a92c13f8bd197)
+++ b/main/mean-share-and-balance/Gruntfile.js	(date 1659587196834)
@@ -3,4 +3,4 @@
 /* eslint-env node */
 
 // use chipper's gruntfile
-module.exports = require( '../chipper/js/grunt/Gruntfile.js' );
+module.exports = require( '../chipper/dist/js/chipper/js/grunt/Gruntfile.js' ); // eslint-disable-line

Do that, then rename chipper/Gruntfile.js to Gruntfile.ts and run grunt or grunt lint from mean-share-and-balance. The only thing failing for that part is WebStorm can't find a lint configuration (works fine from the command line):

image

@samreid
Copy link
Member

samreid commented Aug 11, 2022

From today's dev meeting:

SR: OK if we start allowing *.ts in chipper? Context is #1272

Grunt would still work but for scripts that use *.ts you would have to launch from node chipper/dist/js/chipper/myscript.js
Stack traces wouldn’t line up in this version
Or should we wait for sprint/quarterly goal?
Transpiler would remain in 100% *.js so there is no chicken-and-egg problem
MK: Have you thought about ts-node?
SR: It would improve consistency if chipper could use TypeScript. But there isn’t a lot of change happening in chipper right now. We could wait to do this until there is larger work to be done in chipper.
SR: Are developers OK using different commands/tools to run TS tasks?
That seems fine.
SR: 2-4 devs could do a 2 week sprint to port as much of chipper to TS as possible. OR we get TS working in chipper and sprinkle in porting over time.
SR: Deno will not work with grunt. Ts-node might not work with grunt. Migrating away from grunt is not trivial.
SR: I have a prototype that has grunt working with TypeScript. But it requires an additional step to work with the transpiler.
MK: I recommend pointing everything to chipper/dist, that will allow us to sprinkle in TypeScript at our discretion.
SR: Using chipper/dist for now would not prevent us from using Deno or TS-Node in the future.
JO: Using chipper/dist for now seems better for now so we can avoid technical debt and get into TS faster. Even though it’s somewhat clunky (chipper/dist) for now.
JG: Using TypeScript in chipper sounds really nice. Not sure of the amount of work to get there.
MS: chipper/dist sounds like a good plan for now.
AV: Yes, I don’t have enough data for a meaningful answer based on this.
JB: chipper/dist sounds fine
SR: The unsolved problem is a salmon banner related to linting.
KP: OK You have green light, go for it!
SR: Ok, thanks

@samreid
Copy link
Member

samreid commented Aug 12, 2022

Would it be OK for chipper typescript code to import files from phet-core? Uh-oh, that will interfere with grunt’s need to transpile imports to require().

In slack, @zepumph responded:

Yes probably. We have always had quite a separation about tooling and sim code. Maybe we could move optionize and IntentionalAny to perennial-alias?

@samreid
Copy link
Member

samreid commented Aug 12, 2022

To me, it would seem reasonable that chipper could use code from phet-core (which doesn't have any external dependencies).

Uh-oh, that will interfere with grunt’s need to transpile imports to require().

The hack above detected code in chipper and automatically converted require statements to import statements (in chipper files). If using files from other repos, this heuristic won't work anymore, and we shouldn't output 2 variants of each files (with imports vs requires).

So it seems that grunt is the main bottleneck preventing us from using es6 modules or typescript. Would we want to use typescript with require()? Maybe, but it is a half measure, and we are trying to get things more consistent across our systems. And that would make it so optionize or other exports couldn't be used by require code.

We also have the constraint that all our tooling relies on grunt-cli and grunt tooling. Checking out an old version of SHAs, you would still expect grunt --brands=phet,phet-io to work perfectly. We could swap out grunt for our own alias or executable, but that is confusing and would require each developer (and server machine) tinkering with their setup to make sure it is right. Instead, we can leave grunt as an adapter layer that invokes our own processes. I tested this with clean and it is working well, using TypeScript and import. This could also be done with deno, but I think our workload will be simplified if that is considered as a potential later step, and may not be necessary if the port to TypeScript is good enough in itself.

Prototype so far:

Index: js/phet-build-script.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/phet-build-script.ts b/js/phet-build-script.ts
new file mode 100644
--- /dev/null	(date 1660281306129)
+++ b/js/phet-build-script.ts	(date 1660281306129)
@@ -0,0 +1,28 @@
+// Copyright 2020-2021, University of Colorado Boulder
+// Entry point for phet build commands forwarded from grunt
+
+import * as fs from 'fs'; // eslint-disable-line bad-sim-text
+
+const args: string[] = process.argv.slice( 2 ); // eslint-disable-line
+
+const assert = ( predicate: unknown, message: string ) => {
+  if ( !predicate ) {
+    throw new Error( message );
+  }
+};
+
+const command = args[ 0 ];
+
+// https://unix.stackexchange.com/questions/573377/do-command-line-options-take-an-equals-sign-between-option-name-and-value
+const repos = args.filter( arg => arg.startsWith( '--repo=' ) ).map( arg => arg.split( '=' )[ 1 ] );
+assert && assert( repos.length === 1, 'should have 1 repo' );
+const repo = repos[ 0 ];
+if ( command === 'clean' ) {
+  const buildDirectory = `../${repo}/build`;
+
+  if ( fs.existsSync( buildDirectory ) ) {
+    fs.rmSync( buildDirectory, { recursive: true, force: true } );
+  }
+
+  fs.mkdirSync( buildDirectory );
+}
\ No newline at end of file
Index: js/grunt/Gruntfile.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/grunt/Gruntfile.js b/js/grunt/Gruntfile.js
--- a/js/grunt/Gruntfile.js	(revision f03f428a3aa6c142d45d184a41381983cb3c2da8)
+++ b/js/grunt/Gruntfile.js	(date 1660281293951)
@@ -93,15 +93,37 @@
     'build'
   ] );
 
+  const wrapPhetBuildScript = string => {
+    const args = string.split( ' ' );
+
+    const child_process = require( 'child_process' );
+
+    return () => {
+      const done = grunt.task.current.async();
+
+      const p = child_process.spawn( 'node', [ '../chipper/dist/js/chipper/js/phet-build-script.js', ...args ], {
+        cwd: process.cwd()
+      } );
+
+      p.on( 'error', error => {
+        grunt.fail.fatal( `Perennial task failed: ${error}` );
+        done();
+      } );
+      p.stderr.on( 'data', data => console.log( String( data ) ) );
+      p.stdout.on( 'data', data => console.log( String( data ) ) );
+      p.on( 'close', code => {
+        if ( code !== 0 ) {
+          grunt.fail.fatal( `Perennial task failed with code: ${code}` );
+        }
+        done();
+      } );
+    };
+  };
+
   grunt.registerTask( 'clean',
     'Erases the build/ directory and all its contents, and recreates the build/ directory',
-    wrapTask( async () => {
-      const buildDirectory = `../${repo}/build`;
-      if ( grunt.file.exists( buildDirectory ) ) {
-        grunt.file.delete( buildDirectory );
-      }
-      grunt.file.mkdir( buildDirectory );
-    } ) );
+    wrapPhetBuildScript( `clean --repo=${repo}` )
+  );
 
   grunt.registerTask( 'build-images',
     'Build images only',

Also, this strategy lets us opt-in one task at a time. Maybe lint would be good to do next, but it has usages in perennial and the lint hooks (as well as chipper). Maybe those sites can just call the new harness so they don't need to be ported all-or-none?

@samreid
Copy link
Member

samreid commented Aug 12, 2022

@marlitas and I reviewed the proposal above, and it seems promising. We would like to test on windows before committing it to master.

@samreid
Copy link
Member

samreid commented Aug 12, 2022

We reviewed the proposed implementation with @zepumph and tested on Windows. It seemed to have reasonable operational behavior on Windows (low overhead for the spawn), but @zepumph expressed concern about maintaining the grunt layer. I don't like the grunt layer at all but argued that it may be low enough cost to maintain until we are ready to break backward compatibility in our maintenance steps.

@samreid
Copy link
Member

samreid commented Aug 19, 2022

I committed the patch above. I also realized this means the transpiler process needs to be running for clean to work, so we will need to abandon and rethink this approach or transpile chipper on startup. I realized this because of

  • clients that clone phet sims and want to develop them. But in the readme file it does say to start the transpiler before it says you can run grunt (though the connection isnt 100% clear)
  • build server does not run a transpile watch process, so it will need to transpile on startup.

Therefore, it seems reasonable to transpile chipper on Gruntfile startup. I'm concerned this could cause hiccups on a build server, so this should be tested.

Also, deno would work around that problem.

@samreid
Copy link
Member

samreid commented Aug 22, 2022

Lots of red in CT from the commits above:

acid-base-solutions : build
Build failed with status code 1:
Running "report-media" task

Running "clean" task
(node:33663) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)

/data/share/phet/continuous-testing/ct-snapshots/1661167440414/chipper/dist/js/chipper/js/phet-build-script/phet-build-script.js:3
import * as fs from 'fs'; // eslint-disable-line bad-sim-text
^^^^^^

SyntaxError: Cannot use import statement outside a module
at wrapSafe (internal/modules/cjs/loader.js:979:16)
at Module._compile (internal/modules/cjs/loader.js:1027:27)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
at Module.load (internal/modules/cjs/loader.js:928:32)
at Function.Module._load (internal/modules/cjs/loader.js:769:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)
at internal/main/run_main_module.js:17:47

Fatal error: Perennial task failed with code: 1�
Snapshot from 8/22/2022, 5:24:00 AM

Bayes node version is: v14.15.0, my local version is: v16.13.1. That may explain the difference?

@samreid
Copy link
Member

samreid commented Aug 22, 2022

@marlitas and I saw that a package.json in a nested directory like chipper/js/phet-build-script did not seem to be respected. We cannot change chipper/package.json since there is a lot of require code. We could output to *.mjs but that will require changes to Transpiler.js in 2 places. First, in the place that looks up the corresponding chipper/dist path, and second in pruneStaleDistFiles. This seems easy enough and maybe we should do that. The other option would be to update everyone to Node 16.

@marlitas and I would like to try the *.mjs strategy first.

@samreid
Copy link
Member

samreid commented Aug 24, 2022

I was interested to hear that deno will support importing from the npm ecosystem within the next few months. That will make it a lot easier to move in that direction. https://deno.com/blog/changes#compatibility-with-node-and-npm

@samreid
Copy link
Member

samreid commented Aug 27, 2022

In experimenting with chipping away at this, I've considered that:

  • Maintaining the overhead of grunt adapters would be unfortunate. (@zepumph described this eloquently)
  • If we know we don't want the overhead (complexity) of grunt adapters in the long run, we may wish to save time in the long run by thinking about the best long term goal, which will involve 2 parts:

the API of how we run build lifecycle commands.

Developer experience about how build lifecycle commands are invoked. Do we want a central point that runs all things? Or should things just be scripts that can be run ad-hoc?

Some examples:

  • grunt --brands=phet,phet-io
  • node ../chipper/js/scripts/build.js --brands=phet,phet-io
  • node ../chipper/dist/js/chipper/scripts/build.js --brands=phet,phet-io
  • phetbuild --brands=phet,phet-io
  • deno run --allow-write --allow-read ../chipper/phetbuild.ts --brands=phet,phet-io

the implementation of the build lifecycle commands

  • if we want a "nickname" command like grunt or phetbuild, it could be implemented as a command line executable or an alias for something like node ../chipper/etc. It would need to work on dev machines and server machines. If we want tasks to be invokable from each other and from command line, there would be some overhead (like if node ../chipper/js/lint needed a command line API as well as an export API).

My recommendation: Wait "3 months" until deno has better npm support, then prototype with it to see if it works with our npm modules. If that seems promising, then write a long-term proposal around using deno as the entry point with the "bare minimum" of lifecycle commands centralized in separate scripts. deno ../chipper/js/phetbuild/build.ts deno ../chipper/js/phetbuild/update.ts etc. Document and formalize this API so it can be kept stable for maintenance builds. Allow arbitrary (unstable) scripts outside of the stable entry points, which do not need to be stable for maintenance builds.

The main advantages of this proposal:

  • Eliminate having to run npm install in every repo every time, just to get grunt adapters. See Centralize grunt? #494
  • Eliminate having to install npm install grunt-cli
  • Eliminate ever having to run npm install again in normal development
  • Use ES6 modules in node Migrate node.js to ES6 modules #861
  • Ability to use TypeScript, including importing our own patterns from phet-core, like optionize etc. And our lint rules.
  • Addresses a security concern

The main costs of this approach:

  • Time to design the desired API and implementation strategy.
  • The large cost of porting our build tools (chipper/js/grunt is 5000+ LOC), and difficulty testing. More in perennial.
  • Developers and server machines will need to install deno. 3rd party developers will need to install deno to run builds. Developers will need to learn and the new command line tools.
  • Perennial will need a 3rd way (chipper 3.0) for maintaining sims.

@samreid samreid removed their assignment Aug 27, 2022
samreid added a commit to phetsims/reactants-products-and-leftovers that referenced this issue Apr 12, 2024
samreid added a commit to phetsims/states-of-matter-basics that referenced this issue Apr 12, 2024
samreid added a commit to phetsims/rutherford-scattering that referenced this issue Apr 12, 2024
samreid added a commit that referenced this issue Apr 12, 2024
@samreid
Copy link
Member

samreid commented Apr 12, 2024

I swamped this issue with commits, so will continue in #1437. Closing.

@samreid samreid closed this as completed Apr 12, 2024
samreid added a commit to phetsims/perennial that referenced this issue Oct 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants