Update node-main to work in (webpacked) browser as well as node, and rename node-main to main - #1566
Open
dpvc wants to merge 1 commit into
Open
Update node-main to work in (webpacked) browser as well as node, and rename node-main to main#1566dpvc wants to merge 1 commit into
node-main to work in (webpacked) browser as well as node, and rename node-main to main#1566dpvc wants to merge 1 commit into
Conversation
…rename node-main to main
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #1566 +/- ##
===========================================
- Coverage 87.12% 87.10% -0.03%
===========================================
Files 392 394 +2
Lines 89187 89232 +45
Branches 5063 5074 +11
===========================================
+ Hits 77706 77725 +19
- Misses 11481 11487 +6
- Partials 0 20 +20 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Currently, importing or requiring
@mathjaxor@mathjax/srcgets you aninit()command that can be used to configuration MathJax. With the changes fromfeature/component-types, theinit()function now can return a correctly typedMathJaxvariable with the configuration being type checked as well. Theinit()function, however, is only for node applications, and getting proper type checking for web applications is a bit harder.This PR makes the
init()function usable in both node a web applications, making it easier to manage typing for theMathJaxobject in both settings. In fact, now the exact same file can be used in both settings (when packed for the web).The original
init()implementation always used thestartupcomponent, so one had to load all the needed components explicitly in theloader.loadarray. That's OK for node, but inefficient for web usage, where it is desirable to use a combined component liketex-chtml.jsin order to reduce the number of file transfers that must be performed. This PR extendsinit()to allow a second (optional) argument that gives the component to load, with it defaulting tostartupso that it is backward compatible with current usage. This means that node applications now can also load combined components throughinit().Example usage is
The code here replaces the old
node-maincomponent, which was node-only, with a newmaincomponent that can be used in either setting, and that will detect which setting to use automatically, adjusting the configuration to use the proper adaptor and asynchronous file loading methods. There are also newnodeandbrowsercomponents that can be used to specify which version you want (without the checking code). These each come in two forms, one that used the bundled files, and one that loads the needed components directly from the source. As in the past,@mathjax/src/sourcewill load the source version, while@mathjax/srcwill load the bundled one. Similarly,@mathjax/src/nodeand@mathjax/src/browserload the bundled versions, while@mathjax/src/node/sourceand@mathjax/src/browser/sourceload directly from the source files.There are also a number of improvements to the typing, including checking the
loader.loadarray and thetex.packagesarrays for consistency with the packages that have been specified for theinit()function.Because the usual usage for
init()is to doas illustrated above, the
MathJaxvariable is not available within the configuration itself. That has implications for thestartup.ready()function, which must callMathJax.startup.defaultReady(). In order to handle this, the various functions (startup.ready(),startup.pageReady(),loader.ready(),loader.failed(), etc.) have been modified in two ways: first,thisis set to the calling object (startuporloader), so that you can usethis.defaultReady(), which is nicer anyway; and second, they are passed theMathJaxobject, so that you can useMathJax._or any of the other MathJax properties within theready()function. That is, you could dowithout problems. The
MathJaxvariable is properly typed for theready()andpageReady()functions, based on the components being loaded.Finally, I discovered a timing issue with the loading of combined components while working on this, and there are changes to the configurations for the combined files like
tex-chtml.jsto resolve that. The issue was that those files include not just loading their code, but also running the startup sequence (loading theloader.loadcomponents, creating the startup document and input/output jax, calling the startup ready function, and so on). That sequence is promise based, but the loader did not have access to those promises, and was indicating that the file was loaded and complete before that sequence was finished. That means that when theloader.loadfiles are loaded, they could be interleaved with the startup actions, which is not good.So this PR includes changes to the combined configurations to properly synchronize the loader with the startup sequence. That is, if you
Loader.load()a combined component, its promise will not resolve until the startup sequence completes. This gets the nesting of loads to be correct.##Details
The change in
components/core/core.jsis to make the changes tomathjax.asyncLoadandmathjax.jsonconditional on them not already being specified. This allows you to importts/util/asyncLoad/esm.js, for example, and not have it overwritten when the specified component (e.g.,tex-chtml) is loaded.The change to
components/core/locale.jsis to set the directory where the message for theLocaleclass are taken. This is because they are registered when theLocaleobject is imported, which is before whenLocale.isComponentis set, and the directory to be used is determined by that setting when it is registered. All other locales will use the component paths (e.g.,[mathjax]/input/tex/__locales__), but the messages inLocaleitself would try to use the directory names themselves. That would be OK in node, but not in the browser, which doesn't have access to the file system directly. This patches that one directory that is in place before the setting has been made.The
components/mjs/maindirectory holds the replacement code for the oldernode-maincomponent. This consists of three components: one in themaindirectory itself, and two more inmain/browserandmain/node. This is so they each can have their ownconfig.jsonfiles in order to make the separate components. They each also have a.d.tsfile that is similar to the one that was added tonode-mainin the recentfeature/component-typesbranch. These.d.tsfiles are used to give the type checking for theinit()function's parameters and its return value, and are the key pieces for using this with Typescript. They are almost identical across the three components, but the browser version uses the browser adaptor as its default adaptor, while the others use theliteDOMadaptor.The
init()function takes a generic parameter,T, that gives the components that will be loaded. This can be an intersection of the names, as in'tex-chtml' | '[tex]/action', or an array of such names, such as['tex-chtml' | '[tex]/action'], or aCOMPONENT_DEFobject (so that third-party extensions can be handled), or an intersection of any mixture of these. The component names are type checked against the ones available in thets/types/Components.tsfile, so spelling errors will be identified at compile time. Note that you do need to include in this list the combined component that you will be loading. I could not find a way to have that done automatically based on the second parameter toinit(), unfortunately. The default value forTisstartup.There is also a second generic,
Athat allows you to specify the adaptor that you will be using, which has a default value ofbrowserfor the browser component, andliteDOMfor the others. But you can specifyjsdomorlinkedom(orliteDOMorbrowser) if you are loading a different adaptor. Thenodecomponent (and themainone when used in node) will use include theliteDOMadaptor automatically if another adaptor hasn't already been included in theloader.loadarray. TheAvalue will also add theadaptor/...component to theT` list automatically, so the adaptor doesn't have to be added by hand.The
init()function takes two arguments: the configuration (type-checked against the configuration options available in the components specified byTandA), and an optional component to load (that must be one of the ones you have specified inT, guaranteeing that the configuration is correct for the component you are loading). The default component is thestartupcomponent. This means that using justinit({...})can be used, as in the past, but because it is now type-checked, only theloaderandstartupoptions will be allowed. And since theloader.loadarray it checked against the components in theTgeneric, you would not be able to specify anything to load without adding the needed components to theTfirst. This is potentially a breaking change. The solution, of course, is to add the neededTgeneric. Alternatively, one could useinit({...} as any)as a work-around.Finally, the return value for
init()is the MathJax object with the types available for the components specified inT.The
browser/browser.jsfile contains the main code that is needed to replacenode-main; this is themakeInit()function, which is used to create theinit()function that can be used either in the browser or in node. It accepts two parameters, one to do additional setup (needed fornode) and one to specify the adaptor to use. These are both empty for the browser, since all the components are set up for browser use already, and it is node usage that needs to replace the adaptor and specify how files should be loaded, etc.The
browser.jsfile loads theLoaderobject so that it can load the specified component, and gets the globalMathJaxobject fromglobal.js, which will have moved any original value into theMathJax.configproperty. It sets theloader.failedfunction to throw the error (rather than just report it), so that it can be traded byinit().catch(), though this can be overridden in the configuration pass toinit(), if desired. Then for in-browser use, we set the mathjax path for loading components to be jsDelivr.net (since the code that callsinit()will need to be webpacked, and MathJax won't be able to determine the location of the local MathJax files, which may not even be available where the webpacked file has been placed). Again, that can be overridden in the configuration. Then, if there is extra setup (for node applications), that is done.Now we apply the configuration that was passed to
init(), which is where the settings above can be replaced. If an adaptor was requested, we insert that into theloader.loadarray, but only if there isn't another adaptor already being loaded. Then we set theMathJax.loadervalue to be theLoaderthat we obtained (when a combined component liketex-chtml.jsis loaded, it will already have a copy ofLoader, and we need to be able to reconcile that when the component is loaded; we will describe the details later).Next, if a component was specified, we load it (if the component was set to and empty string or
null, we don't load a component, and are left with aMathJaxthat only has the loader, which might be useful if one wants to load components by hand later). Waiting for this component to load was where I noticed the problem with the timing of the startup sequence described above. ThisLoader.load()call now does not resolve until the startup sequence has actually completed.Finally, we wait on the
MathJax.startup.promise(if present), and then return theMathJaxglobal object.The initial
windows.exports ??= {}is to support CommonJS usage, where some webpacked files end up trying to setexportsvalues rather thanmodule.exports(it has to do with the Typescript compiling of.ctsfiles like the ones used to load the defaulten.jsonlocale files). I could don't find a better work-around.The
browser.jsfile exportsMathJaxand theinit()function, just likenode-maindid.The browser's
config.jsoncopies thebrowser.d.tsfile, and makes the webpacked version inbundle/main.The browser's
json.cjsfile is used in thewebpack.cjsfile to replace thets/components/mjs/json.cjsfile with a simplified one that is browser-specific. The original is a dual-purpose one that works in both node and the browser, but includes animport()call that needs special handling if webpacked. It includes a comment to prevent that from being a problem, but that comment is lost whenbrowser.jsis webpacked, and so if the code that imports@mathjax/srcor@mathjax/src/browseris itself webpacked (as it will need to be), that magic comment is lost, and you would get a warning about importing a file from a variable. This substitution prevents that.The
webpack.cjsfile is used by the MathJax web packing work-flow to modify the standard webpack configuration that it uses. In this case, it sets things up to produce acommonjsmodule (so its exports are available), to make the substitution of thejson.cjsfile, and to make only a single chunk (otherwise it creates several chunk files).The
components/mjs/mainandcomponents/mjs/main/nodecomponents are set up similarly. Themain.jsfile code from both the browser and node implementation and selects which to use based on the presence of thewindowglobal object (via thehasWindowcontext variable) and exports the resultingMathJaxandinit()values.The
nodecomponent has two additional support files:node.cjsandsetup.js. The first separates out some file-loading commands that we don't want to have webpacked into a bundled file that would be used on the web, e.g., when themaincomponent is used for a web application. Sincemainloads both the node and browser code, ifnode.cjswere not separated out, thefs,path, andimportcalls would be included in web applications, which would lead to errors. So these are separated out to a file that is only loaded when the node version is selected bymainor by importing@mathjax/src/node(or@mathjax/node).The
setup.jsfile is the one that provides thenodeSetup()function thatmakeInit()uses to customize theinit()function for use in node. (Thenode.jscomponent file just loadsnodeSetupandadaptorfromsetup.jsand makes theinit()command using them. The reason this is separated out fromnode.jsis that it gets loaded by themain.jscomponent, but we don't want to make theinit()file for node, which would cause thenode.cjsfile to load, in the case that the browser version is being used, in order to prevent thenode.cjscode from being included in browser-based applications.The
nodeSetup()function is used by themakeInit()function customize theinit()function for node. In this case, it loadsnode.cjsfile to get therequirefunction andpathlibrary and checks if thesource.jsfile is available and loads that. When the loader starts up, it tries to determine the location of the MathJax files and saves that inMathJax.config.loader.paths.mathjax. If it can't do it, it sets the path to/. We check for that (orfile:///in the case of windows), and when the path can't be determined (as will be the case if the node application is bundled), we look to see if@mathjax/srcor@mathjaxcan be located, and set the path to that. This means the configuration won't have to set themathjaxpath explicitly if one of those packages can be found.Then we check if the source mapping is available and use that if so. This is how
@mathjax/src/sourcemanages to work.Then if there is no
asyncLoadconfigured yet, we use theREQUIREcommand fromnode.cjs, and mark it as synchronous. We also useREQUIREfor theloader.requirevalue so that the loader knows how to get files. The configuration passed toinit()can override that, if desired.Finally, we use the
liteDOMadaptor, unless the configuration overrides that.The next dozen or so files are modifications to the combined components to handle the synchronization issue described above. A new
readyAfter()function is added to thecomponents/mjs/startup/init.jsfile that handles the details of the synchronization, and the files likemml-chtml.jsthat used to callloadFont(startup))now usewhere
COMPONENTis the name of the component (mml-chtmlin this case). Since the component name appears twice, I put it in a variable so that there is no chance of them getting out of sync. Note thatstartup/startup.jsitself uses the samereadyAfter()as the other combined components.The way
readyAfter()works is as follows. TheLoaderhas a mechanism for allowing the landing of a component to require additional components to load (for example, the output jax can cause a font component to load when an alternative font is specified), and theLoader.load()call is not resolved until those additional actions are taken. That is handled through the component'scheckLoad()function in the loader's configuration for the component. So, for example,MathJax.config.loader['output/chtml'].checkLoad()is the function that is called (if it exists) to decide if more actions need to be taken beforeoutput/chtmlis considered fully loaded.We hook into this feature to make sure the combined components' startup actions are complete before their
Loader.load()calls are satisfied. To do that,readyAfter()creates a promise (calledload) and set's up the component'scheckReady()function to wait for that promise to resolve. ThereadyAfter()function is passed a function,startup, that returns a promise that we said on, and resolve theloadpromise afterstartupresolves. That means thatLoader.load()won't resolve until thecheckLoad()resolves, which happens whenloadresolves, which happens only afterstartupcompletes.Note, however, that
checkReady()is user-configurable, so we save the oldcheckReady()(if any) and call that after theloadpromise resolves, and return its promise (so thatcheckReady()will not resolve until after the user's originalcheckReady()also resolves). Whew!One other change to
startup/init.jsis to add.catch((error) => MathJax.startup.promiseReject(error));to the startup sequence. That way, if theCONFIG.failed()function (which is the Loader'sfailed()function) throws its error, this secondcatchwill pick that up and cause theMathJax.startup.promiseto reject with the error. This is what allowsinit().catch()to catch the error, otherwise we get a warning about a rejected promise not getting caught.Of course, there are also 8 deleted files from the old
node-maindirectory. The completes the changes incomponents/mjs.The
package.jsonfile's exports table is modified to remove thenode-mainreferences and add the newmain,nodeandbrowserpackages. It also swaps the compilation order for the cjs projects so that the magic comments added to thejson.tsfiles (below) are properly preserved.The
testsuites/src/setupTex.jsfile is similarly changed to load themain.jsfile rather thannode-main.jsI made a separate PR for the change to the
a11y/explorer/Region.tsfile, but included it here as well since it causes problems with checking webpacking of browser applications using the new packages.The semantic enrichment in
a11y/semantic-enrich.tsforces thesre.localeto the current locale as set inLocale, which may not be the case, since it is the menu component that initializesdocument.options.sre, and if the menu isn't included, sre's values may not get initialized.The magic comment for webpack is added to the
ts/components/cjs/json.tsfile so that webpack will not complain about therequire()call on a variable file name. Similarly for thets/components/mjs/json.tsfile later on.Some of the types in
ts/components/loader.tsare improved, so that the configuration passed toinit()can be better type-checked. The call to theloader.ready()function is adjusted so thatthisis theLoaderobject, and it now gets theMathJaxobject passed to it, ad described above. Finally, whenMathJax.loaderis already set, we check that it is the same asLoaderitself, and if not, we transfer our variables over to the originalMathJax.loader. This is because the newmainand other components loadts/components/loader.tsdirectly, and then may load a bundled component (rather than its source version). When that is the case, theLoaderinmain/main.jsand the one in the bundled component will be different, and while they may operate the same, their data about the versions of the loaded components, the path filters that are set up by the other components that are loaded, and the data in thePackageobject about the components that have been loaded would not be shared. This makes sure the two versions both have access to the same data. This allows the maininit()function to bootstrap the loader used to load the main component requested by theinit()call.The
ts/components/package.tsfile has similar changes supporting better type checking, and passing the MathJax variable to theready()and other configurable functions. The same forts/components/startup.js, which also includes some formatting changes that were made ugly byprettier. The extra?forStartup.document?.menuis because the document might not be produced (e.g., when no handler gets defined).The change to
FontData.tsis because some errors are just strings, and soerr.messagedoesn't exist.A number of updates are made to the
ts/typefiles to improve the typing of the configuration and globalMathJaxobjects. First, ints/types/Components.ts, a newCOMBINEDtype is used to create the data for the combined component files so that they include thestartupcomponent (as all combined ones do) without having to specify it explicitly in the list of components it includes, and also to add thecomponentproperty that is used to get the list of components loaded that is used to type-check the component argument toinit(), as well as theloader.loadarray. TheCOMBINEDtype then is used for all the combined components defined in theCOMPONENTSlist below.The
TEX_PACKAGEtype is changed ints/types/Types.tsso that it can be used for all the tex packages, even the ones with no configuration options that used to useEMPTY_COMPONENTto define them. This change was made so that thetex.packagesarray can be checked so that it doesn't try to configure packages that were not loaded.The
localeoption that sets the initial locale is added to thestartupandloadercomponents, since one of those is always included, and since those are the components that use it.Finally, a new
ADAPTORSobject type is added that includes the data needed to process the various adaptors. In particular, the entries specify the DOM types used by each adaptor, and the component definition for it. This object lets us use justliteDOMorbrowser, etc., for theAgeneric type forinit(), for example, rather than something more complicated. In particular, it means we can produce the proper DOM typing automatically based on the adaptor selected.Most of the improved typing comes from the
ts/types/Types.jsfile. Here theCOMPONENT_DEFtype is extended to include a newtex_packagefield that lists the package names of the TeX packages that are loaded. This is used to check thetex.packagesconfiguration so that only loaded extensions can be included. (If custom extensions are created in thestartup.ready()function, one would need to add a customCOMPONENT_DEFto theTgeneric forinit(). You could useTEX_PACKAGEto do that.)Several new type processors are added to handle checking for the
loader.loadandtex.packagesarrays, and to add the proper typing to thestartup.ready()andstartup.pageReady()functions so that theMathJaxargument will have the correct types within those functions.As mentioned earlier, the
TEX_PACKAGEtype is modified to work for packages with no configuration options. It also now sets thetex_packagesvalue needed for checking thetex.packagesarray.The
CONFIGtype that produces the types for theMathJax.configobject is modified to include theLOAD,PACKAGESandREADYadjustments for checkingloader.load,tex.packages,startup.ready(), andstartup.pageReady(). An extra generic booleanRis used to decide whether thestartupfunctions are adjusted. This is to avoid a recursion when producing the type for theMathJaxargument ofready()andpageReady(), since theMathJaxobject includes aconfigsub-object. So we useTYPES2MJX_OBJECT<T, D, false>here (the false is passed on toCONFIG) so that we don't get an infinite recursion. TheCONFIGtype now also needs theDgeneric to pass on toTYPES2MJX_OBJECT, so it is added here an in the calls toCONFIG.A new
COMPONENTS_OFselector gets thecomponentsobject from the definition (this is needed later to get the intersection consisting of the component names for checking the second parameter toinit()).The
Rgeneric is added toTYPES2MJX_OBEJCTso that it can be passed on toCONFIGto avoid the infinite loop.The
MATHJAX,MATHJAX_OBJECTandMATHJAX_CONFIGtypes are moved out of thets/types/dom/html.tsandts/types/dom/lite.tsfiles and into thets/types/mjx.tsfile, which is renamed tots/types/mathjax.js, as a common version can now be used. These now have an optional second genericAthat specifies the adaptor to use (defaulting toliteDOM), and we use theADAPTORSobject to get the proper DOM node types instead of having DOM-sepecificMATHJAXtypes. One can useMATHJAX_OBJECT<'tex-chtml'>to get the types forMathJaxwith thetex-chtmlcomponent and theliteDOM, orMATHJAX_OBJECT<'tex-chtml', 'browser'>to get it with browser node typings.The new
ADAPTOR_LISTtype is the list of adaptor names that are available, while theMJX_COMPONENTStype gives the list of components that have been included in a type configuration, whileMATHJAX_COMPONENTSproduces that given a list of components, and an adaptor name. Finally,ADAPTOR_DOM<A>gives the DOM node types for the adaptor specified byA.The last changes are to the
tsconfigconfigurations. These are to include thejson.tsfiles in the compilations that preserve the magic comments for webpack.