phetsims / chipper

Tools for developing and building PhET interactive simulations.
MIT License
11 stars 14 forks source link

Convert codebase to use ES6 import/export modules instead of requirejs #820

Closed samreid closed 4 years ago

samreid commented 4 years ago

In https://github.com/phetsims/chipper/issues/551 we started moving simulation code to ES6, and that has been working nicely. One remaining step that we stand to benefit from is converting from requirejs to ES6 module import/export. When we began HTML5 development around 2013, ES6 was not finalized and there was no support for ES6 module import/export. Now it is the standard. It is supported on many platforms directly, and we can compile to ES5 compatibility for IE11:

image

When we converted from Java to HTML by way of AMD/requirejs, we lost most of our tooling support. For instance, in this code, WebStorm is unable to identify the parameters for the Emitter type, and code navigation only takes you to line 13, not to the definition of Emitter.

image

Similarly, in this case, WebStorm cannot find modelViewTransform.modelToViewX, or container.leftWallDoesWork, and marks them as unresolved

image

This is in part because WebStorm does not know how to parse AMD modules which we are using for requirejs. ES6 module import/export has much better support--it can show parameters:

image

and it does a much better job highlighting and navigating:

image

I want to stress that this is not a minor concern--better support for highlighting, navigation, autocomplete would be a significant help in day-to-day development. I don't know what benefits might be attained in Sublime or other environments.

Furthermore, moving to the established standard pattern will be beneficial for the long term, for communicating with other developers, bringing new team members up-to-speed, interoperability with other software, future-proofing, etc.

Some questions as we proceed:

I know we are deeply entrenched in our existing pattern and got it to work for us pretty well, but my hope is that with some effort we can build a stronger foundation to help streamline future development.

Let's discuss at an upcoming developer meeting.

pixelzoom commented 4 years ago

I think that a quick (15 min?), high-level discussion of this in dev meeting is fine. But there a lot of questions, and I don't think it makes sense to involve the entire development team in a deep dive. If this is a priority for PhET, then I propose that a sub-team be formed to investigate.

pixelzoom commented 4 years ago

12/19/19 dev meeting:

Pros:

Cons:

Feasibility test 1 - what are the problems?

Feasibility test 2 - investigate production needs

Adoption:

The development team agrees that this must happen, and should be scheduled into the project management plan. The question is when.

@samreid and @jonathanolson will each put in 10 hours (sprint) on "Feasibility test 1" by mid January. Then we'll revisit and discuss with @kathy-phet if proceeding looks feasible.

samreid commented 4 years ago

I added a script that migrates repos to use import/export. It is very prototype-y, but here's how it can be used for example-sim.

  1. check out example-sim webpack branch
  2. install webpack and url-loader via
    npm install --save-dev webpack
    npm install --save-dev webpack-cli
    npm install --save-dev url-loader 
  3. run grunt migrate
  4. run npx webpack

The result of running those steps is that it ports example-sim and all its dependencies to use import/export and builds it. It uses image loader and json loader. i18n plugin is ignored and mipmaps just use image loader at the moment. It runs in the browser for me, and works nicely. The mipmap dimensions are wrong, so the layout is incorrect. Brief investigation of highlighting/navigation/parameter popup works great for ES6 classes, not so much for places where we use inherit.

Do not run the following step without understanding that revert-migrate does a hard reset on selected repos. That being said, iteration can be done via:

revert-migrate.sh && grunt migrate && npx webpack

@jonathanolson and I were surprised to see that a build (without uglify) takes only a few seconds, at least for example-sim:

real 0m2.342s user 0m3.948s sys 0m0.301s

Earlier today @pixelzoom and I wondered if using ES6 module import/export would help us with automatic imports, and it appears that it does (again, only for es6 classes, not for inherit types), and it's great that it uses aliases.

image

UPDATE: A reminder to myself that I had to tell Webstorm about the config file like so: https://stackoverflow.com/questions/34943631/path-aliases-for-imports-in-webstorm even though the top answer says it should be automatic.

UPDATE: I also tested by switching soundEnabled to true in a few places, and disabling assertions (due to a sizing error in the navbar), and sound is playing when I press the reset all button.

samreid commented 4 years ago

Notes from collaboration with @jonathanolson:

Compilation-free Mode proposal:

  1. Use absolute paths like import example from '/example-sim/js/example';
  2. Everyone must use same local url localhost
  3. Requires a micro-compliation step every time string/json/image/mipmap/audio is changed to turn them into JS (but this may speed up iteration by distributing that work). So everything would be a JS module, and perfectly compatible with compilation free or compiled mode (so this would speed up builds as well).

Compilation-only Mode proposal:

  1. Everything is done through build, can use watch mode or equivalent
  2. No need to support two mechanisms
  3. Can use aliases, directly reference assets like import packageJSON from 'package.json'
  4. We got webpack-dev-server working, which provides live-reload. Code automatically compiles in memory (200ms) and browser automatically reloads. @jonathanolson and I agree this is promising enough that "compilation-only" mode may be sufficient.
samreid commented 4 years ago

While looking into fast or incremental recompilation, we found that webpack supports a concept called hot module replacement, which allows you to edit code without reloading the entire application.

With node_modules/.bin/webpack-dev-server --hotOnly and

  if ( module.hot ) {
    module.hot.accept( './BarMagnetNode.js', () =>{
      self.removeChild( barMagnetNode );
      barMagnetNode = new BarMagnetNode( model.barMagnet, modelViewTransform );
      self.addChild( barMagnetNode );
    } );
  }

I am able to edit the code with a live, running, stateful simulation, like so:

Kapture 2019-12-20 at 13 44 58-800

Please note, I am editing from my IDE, not from the chrome devtools, and a rebuild is taking place before swapping the code in the browser.

Note the large logo size is because we haven't got mipmaps working yet.

pixelzoom commented 4 years ago
  1. Everyone must use same local url localhost

Can you clarify what this means? Does that mean that everyone must use an identical URL for running sims out of their working copy? If so, that's going to be tricky for sharing my working copy with VMware. I currently need to use the IP address of the host machine, not localhost. So my URL looks like http://192.168.1.2/phet/, not http://localhost/phet/.

samreid commented 4 years ago

Does that mean that everyone must use an identical URL for running sims out of their working copy?

You would be able to use different hostname and port, but across developers the path would have to be the same. For instance, I serve ~/github so my path is like /example-sim but I've seen other developers serve something akin to ~/ so their path would be something like /github/example-sim. If we want "compilation-free" mode to work, it seems we would need to standardize on the Path part so that modules could be loaded in the browser without any preprocessing. However, @jonathanolson and I were stunned by the responsiveness of the incremental builds + live reload and we suspect that may be sufficiently fast for development and we may not need to pursue the "compilation-free" mode.

pixelzoom commented 4 years ago

Good to hear that incremental build + live reload is fast.

If we do need to go with "compilation-free" mode, please consider 2 things when making the decision on URL path. (1) On macOS, it's very easy (well-documented) and conventional to configure Apache to serve local websites out of ${HOME}/Sites/. (2) Some of us (me included) may have non-PhET websites that we need to serve locally. Therefore, the path to my PhET working directory on macOS is http://localhost/~cmalley/GitHub/. The path to the local copy of my company website is http://localhost/~cmalley/pixelzoom/. Etc. Trying to flatten things out to something like http://localhost/phetmarks/ would be a big pain for me, across multiple machines and VMs.

samreid commented 4 years ago

Notes from discussion with @jonathanolson.

Question: How can we support jumping around to different simulations?

  1. We could use compilation-free mode, as described above
  2. We could build all of the simulations into a single *.js bundle. Would that take a long time to rebuild or load in the browser? To answer this, we tried to bring more sims into the fold. Example sim alone weighs 3.9MB. example-sim and acid-base-solutions combined weighs 4.6MB. We haven't experimented with load times. To investigate this question better, we may need to port more simulations to modules. This would mean generalizing and improving the migration rules.
  3. We could look into support for webpack building multiple apps (one webpack instance building multiple apps)
  4. Would it be feasible to run multiple copies of webpack watch mode? (multiple webpack instances, each building one app)

a patch

```diff Index: perennial/bin/revert-migrate.sh IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- perennial/bin/revert-migrate.sh (revision 754be48a6d0fa438c07d51859a6baa478ba6bd57) +++ perennial/bin/revert-migrate.sh (date 1577125467447) @@ -1,56 +1,376 @@ -#!/bin/bash +cd ../acid-base-solutions +git reset --hard + +cd ../area-builder +git reset --hard + +cd ../area-model-algebra +git reset --hard + +cd ../area-model-decimals +git reset --hard + +cd ../area-model-introduction +git reset --hard + +cd ../area-model-multiplication +git reset --hard + +cd ../arithmetic +git reset --hard + +cd ../atomic-interactions +git reset --hard + +cd ../balancing-act +git reset --hard + +cd ../balancing-chemical-equations +git reset --hard + +cd ../balloons-and-static-electricity +git reset --hard + +cd ../beers-law-lab +git reset --hard + +cd ../bending-light +git reset --hard + +cd ../blackbody-spectrum +git reset --hard + +cd ../blast +git reset --hard + +cd ../build-a-fraction +git reset --hard + +cd ../build-a-molecule +git reset --hard + +cd ../build-an-atom +git reset --hard + +cd ../bumper +git reset --hard + +cd ../buoyancy +git reset --hard + +cd ../calculus-grapher +git reset --hard + +cd ../capacitor-lab-basics +git reset --hard + +cd ../chains +git reset --hard + +cd ../charges-and-fields +git reset --hard + +cd ../circuit-construction-kit-ac +git reset --hard + +cd ../circuit-construction-kit-black-box-study +git reset --hard + +cd ../circuit-construction-kit-dc +git reset --hard + +cd ../circuit-construction-kit-dc-virtual-lab +git reset --hard + +cd ../color-vision +git reset --hard + +cd ../collision-lab +git reset --hard + +cd ../concentration +git reset --hard + +cd ../coulombs-law +git reset --hard + +cd ../curve-fitting +git reset --hard + +cd ../density +git reset --hard + +cd ../diffusion +git reset --hard + +cd ../energy-forms-and-changes +git reset --hard + +cd ../energy-skate-park +git reset --hard + +cd ../energy-skate-park-basics +git reset --hard + +cd ../equality-explorer +git reset --hard + +cd ../equality-explorer-basics +git reset --hard + +cd ../equality-explorer-two-variables +git reset --hard + +cd ../estimation +git reset --hard + +cd ../example-sim +git reset --hard + +cd ../expression-exchange +git reset --hard + +cd ../faradays-law +git reset --hard + +cd ../fluid-pressure-and-flow +git reset --hard + +cd ../forces-and-motion-basics +git reset --hard -#======================================================================================= -# -# Resets the repos associated with the migrate feasibility prototype, see https://github.com/phetsims/chipper/issues/820 -# -# Author: Sam Reid -# -#======================================================================================= +cd ../fraction-comparison +git reset --hard -cd ../axon +cd ../fraction-matcher +git reset --hard + +cd ../fractions-equality +git reset --hard + +cd ../fractions-intro +git reset --hard + +cd ../fractions-mixed-numbers +git reset --hard + +cd ../friction +git reset --hard + +cd ../function-builder +git reset --hard + +cd ../function-builder-basics +git reset --hard + +cd ../gas-properties +git reset --hard + +cd ../gases-intro +git reset --hard + +cd ../gene-expression-essentials +git reset --hard + +cd ../graphing-lines +git reset --hard + +cd ../graphing-quadratics +git reset --hard + +cd ../graphing-slope-intercept +git reset --hard + +cd ../gravity-and-orbits +git reset --hard + +cd ../gravity-force-lab +git reset --hard + +cd ../gravity-force-lab-basics +git reset --hard + +cd ../griddle git reset --hard -cd ../brand +cd ../hookes-law git reset --hard -cd ../dot +cd ../interaction-dashboard +git reset --hard + +cd ../isotopes-and-atomic-mass +git reset --hard + +cd ../john-travoltage git reset --hard cd ../joist git reset --hard -cd ../kite +cd ../least-squares-regression git reset --hard -cd ../phetcommon +cd ../make-a-ten git reset --hard -cd ../phet-core +cd ../masses-and-springs git reset --hard -cd ../phet-io +cd ../masses-and-springs-basics git reset --hard -cd ../example-sim +cd ../models-of-the-hydrogen-atom git reset --hard -cd ../scenery +cd ../molarity +git reset --hard + +cd ../molecules-and-light +git reset --hard + +cd ../molecule-polarity +git reset --hard + +cd ../molecule-shapes +git reset --hard + +cd ../molecule-shapes-basics +git reset --hard + +cd ../natural-selection +git reset --hard + +cd ../neuron +git reset --hard + +cd ../number-line-integers +git reset --hard + +cd ../number-play +git reset --hard + +cd ../ohms-law +git reset --hard + +cd ../optics-lab +git reset --hard + +cd ../pendulum-lab +git reset --hard + +cd ../ph-scale +git reset --hard + +cd ../ph-scale-basics +git reset --hard + +cd ../phet-io-test-sim +git reset --hard + +cd ../plinko-probability +git reset --hard + +cd ../projectile-motion +git reset --hard + +cd ../proportion-playground +git reset --hard + +cd ../reactants-products-and-leftovers +git reset --hard + +cd ../resistance-in-a-wire +git reset --hard + +cd ../rutherford-scattering git reset --hard cd ../scenery-phet git reset --hard + +cd ../simula-rasa +git reset --hard + +cd ../states-of-matter +git reset --hard + +cd ../states-of-matter-basics +git reset --hard cd ../sun git reset --hard cd ../tambo git reset --hard + +cd ../tappi +git reset --hard + +cd ../trig-tour +git reset --hard + +cd ../twixt +git reset --hard + +cd ../under-pressure +git reset --hard + +cd ../unit-rates +git reset --hard + +cd ../vector-addition +git reset --hard + +cd ../vector-addition-equations +git reset --hard + +cd ../vegas +git reset --hard + +cd ../vibe +git reset --hard + +cd ../wave-interference +git reset --hard + +cd ../wave-on-a-string +git reset --hard + +cd ../waves-intro +git reset --hard + +cd ../wilder +git reset --hard + +cd ../axon +git reset --hard + +cd ../scenery +git reset --hard + +cd ../brand +git reset --hard cd ../tandem git reset --hard + +cd ../phet-core +git reset --hard + +cd ../dot +git reset --hard + +cd ../kite +git reset --hard cd ../utterance-queue git reset --hard -cd ../example-sim \ No newline at end of file +cd ../phetcommon +git reset --hard + +cd ../phet-io +git reset --hard + +cd ../example-sim Index: chipper/js/grunt/migrate.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- chipper/js/grunt/migrate.js (revision 6479b33911a10f0b1651e7a405bb5a74f3129f6d) +++ chipper/js/grunt/migrate.js (date 1577128388899) @@ -18,6 +18,39 @@ return str.split( search ).join( replacement ); }; +/** + * + * const lightBulbImage = require( 'image!ACID_BASE_SOLUTIONS/light-bulb-icon.png' ); + * import lightBulbImage from 'ACID_BASE_SOLUTIONS/../images/light-bulb-icon.png'; + * + * @param {string} contents + */ +const replaceImageStatements = contents => { + const prefix = 'require( \'image!'; + let index = 0; + while ( contents.indexOf( prefix, index ) >= 0 ) { + const innerIndex = contents.indexOf( prefix ); + let nextIndex = contents.indexOf( '/', innerIndex + 1 ); + const start = contents.substring( 0, nextIndex ); + contents = start + '/../images/' + contents.substring( nextIndex + 1 ); + index += prefix.length + 1; + } + return contents; +}; + +const replaceMipmapStatements = contents => { + const prefix = 'require( \'mipmap!'; + let index = 0; + while ( contents.indexOf( prefix, index ) >= 0 ) { + const innerIndex = contents.indexOf( prefix ); + let nextIndex = contents.indexOf( '/', innerIndex + 1 ); + const start = contents.substring( 0, nextIndex ); + contents = start + '/../images/' + contents.substring( nextIndex + 1 ); + index += prefix.length + 1; + } + return contents; +}; + const migrateFile = async ( repo, relativeFile ) => { if ( relativeFile.endsWith( '/PhetioIDUtils.js' ) ) { return; @@ -32,7 +65,11 @@ contents = replace( contents, '= require( \'ifphetio!', '= function(){return function(){ return function(){}; };}; // ' ); contents = replace( contents, 'require( \'mipmap!BRAND/logo.png\' )', 'require( \'BRAND/../images/logo.png\' ).default' ); contents = replace( contents, 'require( \'mipmap!BRAND/logo-on-white.png\' )', 'require( \'BRAND/../images/logo-on-white.png\' ).default' ); - contents = replace( contents, 'require( \'image!EXAMPLE_SIM/barMagnet.png\' )', 'require( \'EXAMPLE_SIM/../images/barMagnet.png\' ).default' ); + + contents = replaceImageStatements( contents ); + contents = replaceMipmapStatements( contents ); + + // contents = replace( contents, 'require( \'image!EXAMPLE_SIM/barMagnet.png\' )', 'require( \'EXAMPLE_SIM/../images/barMagnet.png\' ).default' ); contents = replace( contents, 'require( \'mipmap!JOIST/keyboard-icon-on-white.png\' )', 'require( \'JOIST/../images/keyboard-icon-on-white.png\' ).default' ); contents = replace( contents, 'require( \'mipmap!JOIST/keyboard-icon.png\' )', 'require( \'JOIST/../images/keyboard-icon.png\' ).default' ); contents = replace( contents, 'require( \'sound!TAMBO/empty_apartment_bedroom_06_resampled.mp3\' )', 'require( \'TAMBO/../sounds/empty_apartment_bedroom_06_resampled.mp3\' ).default' ); @@ -100,6 +137,10 @@ contents = replace( contents, `return inherit;`, `export default inherit;` ); contents = replace( contents, `' ).default;`, `';` ); + contents = replace( contents, ` from 'image!`, ` from '` ); + contents = replace( contents, ` from 'mipmap!`, ` from '` ); + + // import '/axon/js/myFile' // contents = replace(contents,`from 'AXON/`,`from '/axon/js/`); // contents = replace(contents,`from 'BRAND/`,`from '/brand/phet/js/`); @@ -159,20 +200,130 @@ // const repos = fs.readFileSync( '../perennial/data/migrate-repos', 'utf-8' ).trim().split( /\r?\n/ ).map( sim => sim.trim() ); const repos = `axon +scenery brand +tandem +phet-core dot -joist kite +utterance-queue phetcommon -phet-core phet-io +acid-base-solutions +area-builder +area-model-algebra +area-model-decimals +area-model-introduction +area-model-multiplication +arithmetic +atomic-interactions +balancing-act +balancing-chemical-equations +balloons-and-static-electricity +beers-law-lab +bending-light +blackbody-spectrum +blast +build-a-fraction +build-a-molecule +build-an-atom +bumper +buoyancy +calculus-grapher +capacitor-lab-basics +chains +charges-and-fields +circuit-construction-kit-ac +circuit-construction-kit-black-box-study +circuit-construction-kit-dc +circuit-construction-kit-dc-virtual-lab +color-vision +collision-lab +concentration +coulombs-law +curve-fitting +density +diffusion +energy-forms-and-changes +energy-skate-park +energy-skate-park-basics +equality-explorer +equality-explorer-basics +equality-explorer-two-variables +estimation example-sim -scenery +expression-exchange +faradays-law +fluid-pressure-and-flow +forces-and-motion-basics +fraction-comparison +fraction-matcher +fractions-equality +fractions-intro +fractions-mixed-numbers +friction +function-builder +function-builder-basics +gas-properties +gases-intro +gene-expression-essentials +graphing-lines +graphing-quadratics +graphing-slope-intercept +gravity-and-orbits +gravity-force-lab +gravity-force-lab-basics +griddle +hookes-law +interaction-dashboard +isotopes-and-atomic-mass +john-travoltage +joist +least-squares-regression +make-a-ten +masses-and-springs +masses-and-springs-basics +models-of-the-hydrogen-atom +molarity +molecules-and-light +molecule-polarity +molecule-shapes +molecule-shapes-basics +natural-selection +neuron +number-line-integers +number-play +ohms-law +optics-lab +pendulum-lab +ph-scale +ph-scale-basics +phet-io-test-sim +plinko-probability +projectile-motion +proportion-playground +reactants-products-and-leftovers +resistance-in-a-wire +rutherford-scattering scenery-phet +simula-rasa +states-of-matter +states-of-matter-basics sun tambo -tandem -utterance-queue`.trim().split( /\r?\n/ ).map( sim => sim.trim() ); +tappi +trig-tour +twixt +under-pressure +unit-rates +vector-addition +vector-addition-equations +vegas +vibe +wave-interference +wave-on-a-string +waves-intro +wilder`.trim().split( /\r?\n/ ).map( sim => sim.trim() ); repos.forEach( ( repo, index ) => { console.log( index + '/' + repos.length ); Index: chipper/js/all.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- chipper/js/all.js (date 1577125892624) +++ chipper/js/all.js (date 1577125892624) @@ -0,0 +1,2 @@ +import exampleSimMain from 'EXAMPLE_SIM/example-sim-main'; +import hello from '../../acid-base-solutions/js/acid-base-solutions-main'; \ No newline at end of file ```
samreid commented 4 years ago

Status so far:

What's working well:

Caveats for IDE support

Likewise, we would no longer return inherit(...), and since we benefit from ES6 classes more, we would probably convert more inherit to class where possible.

Unanswered questions

Next steps

jonathanolson commented 4 years ago

I've added the mipmap plugin support and a moderate placeholder for the string plugin (not using other locales besides English yet). I've also transferred things over to chipper to make things non-sim-specific, and so multiple sims can be included in the watch-rebuild cycle.

This can be done with the following:

cd chipper
git checkout webpack
git pull
npm update
grunt migrate
npx webpack-dev-server

Then the following three URLs should work, and will reload on changes that affect the respective sims:

Next technical steps would be:

jonathanolson commented 4 years ago

I've updated npm dependencies (npm update required again).

Additionally to enable split chunks and fast updates with LOTS of sims, I'm using a plugin to generate the sim dev HTML, so URLs have changed to e.g. http://localhost:9000/dist/molecule-shapes_phet.html?brand=phet&ea

jonathanolson commented 4 years ago

Investigated scalability (mixed results, may be able to improve), and got the integration with chipper proof of concept working nicely.

samreid commented 4 years ago

Notes from today's discussion:

MK: We could do compliation mode now, then save "compilation-free" for a potential later step

DB: for the precompilation step, would you have to keep track of both source PNG and output PNG.js?
JO: This would be done as part of 'grunt update', for instance.
SR: We would check in the micro-compiled steps

CM: What about clients outside of PhET? What if a client is using scenery, for instance? JO: We would still be able to build scenery and its dependencies, using webpack. Using aliases or certain plugins would "lock us in" to a certain builder, such as webpack. Compilation free "frees us up" so that we aren't locked into any particular builder. SR: Maybe ES6 modules will be easier for clients to leverage CM: We should do this at some point, but we will need to be aware that this will impact clients. AP: I'm not aware of any clients using scenery that a change like this would break.

MK: Is there something else on the horizon that we will want to move to after this?
JB: This is the module system for JS that was missing when we began. That's why we chose requirejs at the time, but this fills the hole that was missing when we began. MK: This is built into the language, so it's not just a library that will be easily replaced.

MK: I'm excited and interested in taking further steps, thanks!

AP: How far are you? SR: 10 hours plus discussions and documentation JO: 12 hours was main development part

AP: What's next? JO: We have to decide "compilation free" or "compilation required". Updating aqua, CT, etc. Builds do work with chipper. We need string plugin. We need to make sure migrate is working nicely. Sourcemaps. Lint things, like lint rules for require statements. Be able to build scenery independently. Clean up since we have been in prototyping mode.

MK: What about maintenance releases? JO: Now maintenance releases, some sims will be in requirejs, some in ES6 modules. You will probably need to make a separate patch for each. SR: Would we rebuild and redeploy everything JO: Not necessary, this is an isolated chipper change which would be compatible with our maintenance release process, just as long as we supply appropriate patches.

MK: Is there a way to strip out assertions? JO: Uglification would still work as a postprocessing step. SR: Does the current build use webpack uglify plugin? JO: It's an independent step, but if we want sourcemaps, we would need to integrate them better.

SR: What about compilation-free vs compilation required? JO: I think it should be investigated. Maybe OK to lose aliases. We could still use webpack or webpack-dev-server to get fast loading times when iterating on a single sim. The main disadvantage to me is that we would need to 'grunt update' after changing a string/image/sound. JB: I don't feel like I have enough information to comment on this aspect. JO: I'd want to investigate compilation free mode before deciding. MK: It seems like moving toward "compilation free" makes onboarding easier, and things more idiomatic. JO: That sounds like a good next step.

AP: It seems everyone is in agreement with this general direction. "We should do this, but what's the timeline"
JB: I agree, this seems like the standard we should gravitate towards.

AP: Anyone else interested and have time to work on next steps here? JB: I have competing priorities. MK: I'm happy to help. Probably busy until January 24th SR: I'm interested in continuing development on this, though it's difficult to hit my 40/40/20 split. AP: Go ahead and JO/SR spend another 10 hours on the compilation-free part. Then maybe at the end of January, we can bring it to Kathy and have a better sense of estimate for bringing it to the whole team.

MK: I'm trying to get Gravity Force Lab into RC in the next few weeks. We should coordinate this transformation with this and other publications.

SR: Absolute vs relative? JO: I'm not completely opposed to relative, but we would need great tool support. Absolute is slightly better for moving things.
SR: I think IDEA got relative paths wrong on a "move" MK: I'm voting absolute CK: I think so SR: How will the IDE know about the root? It seems to somehow.

SR: What about sublime? DB: Chat with Jesse

samreid commented 4 years ago

I reviewed the following websites about whether we should use absolute or relative paths:

https://github.com/meteor/todos/issues/205 https://medium.com/beqode/absolute-vs-relative-import-paths-nodejs-1e4efa65a7bb https://webpack.js.org/configuration/resolve/#resolvealias

https://intellij-support.jetbrains.com/hc/en-us/community/posts/360000142844-How-to-avoid-relative-paths-in-Javascript-import-autocompletion-

It seems conventional wisdom is that if you have a small project with low depth level or files don't move around much, then relative is best. Otherwise, absolute is best. Also reasoning that writing the script to generate absolute is easier than detecting relative paths, I decided to start with that. We can always change our minds later.

samreid commented 4 years ago

After the commit,

revert-migrate.sh && grunt migrate && grunt modulify

Prepares acid-base-solutions into compilation-free form which can load in the browser without any build step (aside from the modulify which already ran). I need help on the mipmap and the string plugins. I should also note that I created a joist branch "webpack" which is necessary to overcome some shortcomings.

UPDATE: I also learned that checkNamespaces will no longer work.

samreid commented 4 years ago

I'm struggling to get webpack to deal with absolute paths. The only thing I've had any success with is this hack which pretends absolute paths are aliases:

Index: webpack.config.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- webpack.config.js   (revision 0da1030e6818ce7f0dd64f630ee9ec93273f936b)
+++ webpack.config.js   (date 1578031583805)
@@ -155,7 +155,7 @@

 // NOTE: Load dependencies more specifically from a sim list in the future, so we don't have such a direct dependency.
 // Repos could be deleted in the future and then prior checkouts with this wouldn't work.
-const activeRepos = fs.readFileSync( path.resolve( __dirname, '../perennial/data/active-repos' ), 'utf-8' ).trim().split( /\r?\n/ ).map( s => s.trim () );
+const activeRepos = fs.readFileSync( path.resolve( __dirname, '../perennial/data/active-repos' ), 'utf-8' ).trim().split( /\r?\n/ ).map( s => s.trim() );
 const reposByNamespace = {};
 const aliases = {};
 const entries = {};
@@ -166,7 +166,7 @@
     const packageObject = JSON.parse( fs.readFileSync( packageFile, 'utf-8' ) );
     if ( packageObject.phet && packageObject.phet.requirejsNamespace ) {
       reposByNamespace[ packageObject.phet.requirejsNamespace ] = repo;
-      aliases[ packageObject.phet.requirejsNamespace ] = path.resolve( __dirname, `../${repo}${repo === 'brand' ? '/phet' : ''}/js` );
+      aliases[ '/'+repo ] = path.resolve( __dirname, `../${repo}` );
     }
   }
 }

This seems fragile and like it may break in a future webpack. Also, there are many scattered require(SOMETHING/something) calls which were written to avoid circular loading problems, which we run into after this.

samreid commented 4 years ago

I also tried pointing entry point to "../startup.js" so it would have the correct absolute path, then importing main, but it had the same failure mode.

samreid commented 4 years ago

Maybe we should experiment using relative paths? I tested and WebStorm "Move" updates import paths correctly if and only if "search for references" is selected. That way relative paths will be easy to read, like:

// absolute
import InductorNode from '/circuit-construction-kit-common/js/view/InductorNode';
import Node from '/scenery/js/nodes/Node';

// relative
import InductorNode from './InductorNode';
import Node from '../../../scenery/js/nodes/Node';

We won't need to configure any servers, including developer servers or phettest, CT, etc. Or configure our IDE to know what the "root" is. Or provide instructions to clients about how to configure their servers. I'm not too excited about ../../../scenery/ but maybe we will get used to it.

Also, now that we are using import WebStorm knows how to collapse them:

image

samreid commented 4 years ago

After the commits, acid-base-solutions is running compilation-free using relative paths, and building with webpack. Next steps: strings, mipmaps, entire codebase.

Some notes for when I return to strings:

// strings
import getString from '../../chipper/js/webpack/getString';  //handles locale decision and value access
import ABSStrings from '../acid-base-solutions-strings';

const acidBaseSolutionsTitleString = getString( ABSStrings, 'acid-base-solutions.title' );

Or we could make the ABSStrings a rich object like:

const acidBaseSolutionsTitleString = ABSStrings.getString('acid-base-solutions.title' );

Or looking up the locale

const acidBaseSolutionsTitleString = ABSStrings.values['acid-base-solutions.title']; // values is a ES5 get that uses the locale to determine the values.
samreid commented 4 years ago

After the commit, English strings are loading and working nicely for acid base solutions in compilation-free mode. The iterative rebuild command is:

revert-migrate.sh && grunt migrate && grunt modulify

If we need to prune the locales, that would be done with grunt modulify and the rest of the build would remain unchanged. Right now it just includes English.

samreid commented 4 years ago

Here's my working list of sims that aren't passing aqua in compilation-free mode:

&testSims=curve-fitting,energy-forms-and-changes,expression-exchange,graphing-lines,graphing-slope-intercept,griddle,joist,least-squares-regression,masses-and-springs,number-play,projectile-motion,rutherford-scattering,scenery-phet,sun,tambo,tappi,trig-tour,twixt,vegas,vibe
samreid commented 4 years ago

Current progress:

Next steps and questions:

EDIT FROM MK:

EDIT FROM @samreid:

UPDATE FROM @jonathanolson and @samreid discussion Jan 21

UPDATES from @samreid

I'll discuss current status and next steps with @jonathanolson before we bring this back to the team.

jessegreenberg commented 4 years ago

Dev meeting 1/9/19:

To proceed with this @ariel-phet suggested we could sprint through steps to integrate this. It might happen in mid February as we could be through some publications by then and align with other timelines. Team agreed this is most efficient way to do this.

@samreid and @jonathanolson will continue with some of the above questions until then.

samreid commented 4 years ago

More notes from today's discussion:

AP: would a TypeScript investigation truly be obviated by making this switch to es6 modules? Aren’t there some other potential advantages? If not, no problem at all, I just have it on my list to potentially have someone investigate TypeScript in the future. JO: Most of the benefits of TypeScript are not given by using es6 modules SR: It’s a spectrum. RequireJS has less IDE support than ES6 modules, which again have less IDE support than typescript/flow. Typescript requires a compilation step, which ES6 modules do not require. Flow (another static type checker for JS) supports a mode where the types are indicates via comments https://flow.org/en/docs/types/comments/ and hence compilation is not necessary. After we get experience with ES6 build toolchains we may discover a way to streamline compilations across many sims, which is currently one of the hurdles to adoption for a typechecker. I think we should pursue ES6 modules and investigate both Typescript and Flow as a follow-up step.

AP: OK for SR and JO to spend more time on this, focusing on coming up with milestones and timeline for the process. We can also pick a time for “sprint”, deciding when it is the right time, and go for it. Maybe end of feb or early march? JB: A sprint for this sounds great. Feb 15 or so, when I start a new sim, it would be great to be full es6 stack when we start. AP: Are developers OK joining forces on this, or do we want something mostly working before it is hoisted on the team? Consensus: We are happy to join on this, it doesn’t need to be fully working before we cut over.

samreid commented 4 years ago

After the commits, acid-base-solutions is nearly linting.

samreid commented 4 years ago

I created a planning document here: https://docs.google.com/document/d/1_b8_DBP2Ier4fHkU_osOIjnQ6wdUtyjiifSznEzV4JM/edit

jonathanolson commented 4 years ago

Named exports/imports

For files like DOT/Utils or constants files, I think switching to also support named exports would be ideal. It's possible to have both default and named exports (since the default export literally exports something with the name default). Testing shows this works nicely:

// Utils.js
export function clamp( value, min, max ) {
  // stuff
}

const Utils = {
  clamp: clamp, // place it in the Utils object (extra verbose since we disallow the shortcut still)

  // etc.
};

export default Utils;

This allows a few different import styles, for example the default still works:

import Utils from '../dot/js/Utils.js';

Utils.clamp( ... );

but also allows direct usage:

import { clamp } from '../dot/js/Utils.js';

clamp( ... );

Notably either can be renamed (import Utils as DotUtilsfrom or import { clamp as someClamp }).

Additionally, this would work well with constants files:

// ABSConstants.js
const WEAK_STRENGTH_MAX = 1E2;
export const CONCENTRATION_RANGE = new RangeWithValue( 1E-3, 1, 1E-2 );
export const PH_RANGE = new Range( 0, 14 );
export const WATER_EQUILIBRIUM_CONSTANT = 1E-14;
export const WATER_CONCENTRATION = 55.6; // water concentration when it's used as a solvent, mol/L
export const WEAK_STRENGTH_RANGE = new RangeWithValue( 1E-10, WEAK_STRENGTH_MAX, 1E-7 );
export const STRONG_STRENGTH = WEAK_STRENGTH_MAX + 1; // arbitrary, but needs to be greater than weak max

and we can keep the code for compatibility with the defaults, which could be removed when we don't need the default:

const ABSConstants = {
  CONCENTRATION_RANGE: CONCENTRATION_RANGE,
  PH_RANGE: PH_RANGE,
  WATER_EQUILIBRIUM_CONSTANT: WATER_EQUILIBRIUM_CONSTANT,
  WATER_CONCENTRATION: WATER_CONCENTRATION,
  WEAK_STRENGTH_RANGE: WEAK_STRENGTH_RANGE,
  STRONG_STRENGTH: STRONG_STRENGTH
};

acidBaseSolutions.register( 'ABSConstants', ABSConstants );

export default ABSConstants;

This allows:

import { PH_RANGE } from '../../../../acid-base-solutions/js/common/ABSConstants.js';

PH_RANGE.min

Additionally, if we prefer to NOT get rid of the ABSConstants.PH_RANGE, it is fine to import with a star:

import * as ABSConstants from '../../../../acid-base-solutions/js/common/ABSConstants.js';

ABSConstants.PH_RANGE.min

So it's my feeling that this is better long-term than our current approach. I'm unclear on whether the "getting old things to that style" is worth the effort, but I'd like to use those new features for new simulations or code.

Cyclic dependencies

For those cases where we need to ensure a module loads (but we don't need to assign it to a value, potentially because we use the namespace system as a workaround), we should use an empty import, e.g. in Node.js, we need to ensure Trail.js is loaded:

import '../../../scenery/js/util/Trail.js';

Additionally, cyclic dependencies should work well with our system in general (at least with my testing on the cases where I've run into things), so we should be safe to do this (loading in browser and built):

// Vector2.js
import Vector3 from './Vector3.js';
// In Vector2's definition:
toVector3: function() {
  return new Vector3( this.x, this.y, 0 );
},

// Vector3.js
import Vector2 from './Vector2.js';
// In Vector3's definition:
toVector2: function() {
  return new Vector2( this.x, this.y );
},

So empty requires should turn into empty imports, and we should have steps after to move to cyclic imports where this helps for clarity (so we can avoid using our namespace system as a workaround).

samreid commented 4 years ago

All work tracked in other issues, closing.