/pretext-react

A ReactJS version of the Pretext UI

Primary LanguageTypeScriptGNU General Public License v3.0GPL-3.0

pretext-react

A ReactJS version of the Pretext UI

This interface is a replacement for the current jquery-based UI for Pretext books when rendered on the web.

Using the React interface in your PreTeXt project

To enable the react interface for your PreTeXt project, you need to set the react.debug.global (or react.debug.local) to yes. You can do this by adding the following inside a your html <target> element.

<stringparam key="debug.react.global" value="yes"/>

and recompile your book. The recompiled version will be using the React css and Javascript served directly from github.

Development

To get started, make sure you have nodejs (>= v16) and npm (Node Package Manager; should be installed by default with Node) installed. Then, in the pretext-react directory, run

git submodule init
git submodule update
npm install
npm run start

to make a live dev-server. After this, when you update code, node will tell your web browser to automatically reload with the new code. However, this may cause issues with internally-cached data, so you may have to refresh the web browser.

If you want to run your book to use the local version of your react build, set the debug.react.local to yes instead of of debug.react.global.

Notes for Windows users

Windows file systems do not have symbolic links, and so the public/ directory, which should be a symbolic link, will show as a file rather than a link. The easiest workaround is to use WSL2 (Windows Subsystem for Linux 2), which will emulate a Linux environment and file system, and to run nodejs inside of that environment. Alternatively, you can explicitly copy a PreTeXt output folder to public/ (thereby avoiding the symbolic link issue).

Testing Different Pretext Content

By default, pretext-react has a copy of precompiled versions of the pretext sample article and book, symbolically linked from the pretext-react-compiled-article subdirectory. By default public/ is refers to the pretext sample article.

To view the sample book, navigate to /pretext-react-compiled-article/html-book-dev/index.html. To view the testing document (used for unit tests), navigate to /pretext-react-compiled-article/html-testing/index.html.

To test out your own content, create a folder or symbolic link in the root project directory with your compiled pretext (make sure you have debug.react.local set to yes). Then, run npm run start and navigate to /your-folder/index.html. (By default, you will be sent to /public/index.html, which points to the pre-compiled sample article.)

Build

To build run

npm run build

The resulting build will be placed in a build/ folder. The Javascript output will be located at build/static/js/main.js and the css will be located at build/static/css/main.css. These scripts assume that all of your Pretext html files have

<script type="module" defer="defer" src="/static/js/main.js"></script>
<link href="./static/css/main.css" rel="stylesheet" />

in their <header>.

Building Pretext Source for Debugging

If you want to use the React frontend with a specific Pretext source, you need to compile it with the following stringparams

* `debug.react.local yes`
* `html-css-shellfile shell_min.css`
* `html-css-bannerfile banner_min.css`
* `html-css-tocfile toc_min.css`
* `html-css-navbarfile navbar_min.css`

Using xsltproc this might look something like

xsltproc --stringparam debug.react.local yes --stringparam html-css-shellfile shell_min.css --stringparam html-css-bannerfile banner_min.css --stringparam html-css-tocfile toc_min.css --stringparam html-css-navbarfile navbar_min.css -stringparam debug.datedfiles no -xinclude ../tmp/pretext/xsl/pretext-html.xsl ../src/html-testing/source/main.ptx

Code Overview

pretext-react is written in TypeScript, specifically the TSX variant of typescript. TSX allows a mixture of TypeScript code and what appears to be HTML. It is a TypeScript variant of the popular JSX. See here for a short introduction to JSX.

pretext-react replaces the "shell" around the pretext content. Additionally, it is responsible for the interactive knowl elements and navigation components.

When the page is loaded, pretext-react does the following.

  1. Extracts necessary information from the current page (mainly the body content) and fetches doc-manifest.xml which contains table of contents information.

  2. Renders the "shell" (the TOC and nav buttons).

  3. Processes and display the page contents. React likes to "own" the page that it renders. For this reason, we process the source with the unifiedjs framework and call the code in hast-react.ts to convert the HAST (HTML Abstract Syntax Tree) into a tree of React components. Along the way, certain components are replaced with their full-featured equivalents. For example, links are replaced with <InternalLink /> elements (provided they are internal links!). These replaces are located in src/components/replaces.

Of note, when the user clicks on a knowl, it is handled by React components (the ones coming from replacers/knowls.tsx).

State Management

Redux via Redux Toolkit is used to manage global state. It allows several components to access the same data while staying in sync. It also minimizes re-renders by asking components to re-render only when their data changes.

If you install Redux Devtools for Firefox or Chrome you can inspect the contents of the page's global store.

In the Redux global store, table of contents information/navigation information is stored. In addition, the HTML source of each cached page is stored. This means the page doesn't need to be re-downloaded when the user returns to it!

Code Philosophy/Files

The code is written using React Function-based components (not class-based components). Files aim to be less than 200 lines of code with each file containing one component (or a couple closely-related components). The TypeScript compiler will combine code that is imported via import statements into a single file in the end, so there is no concern that the end-user will have to load hundreds of files.

Here's a brief overview of the important files and folders:

  • index.tsx - This is the entry point for the App. It sets up only what is required to get the app started (e.g., stuff that needs to be done before the app starts).

  • components-for-page/shell.tsx - This is what renders the app. Every component that you see is a child of a component from here.

  • state-management/ - Setup for Redux/global state management.

  • state-management/redux-slices/ - Global state management. Each "piece" of state is broken off into its own features. For instance, state related to caching vs. state related to the TOC, etc.

  • components-for-shell/ - This is where the React components go which are used for the Shell, that is the non-text part of the page (e.g., the table of contents, the nav buttons, etc.)

  • components-for-page/ - This is where the components used in the page's contents go. E.g., the components which render knowls, etc. are located here.

  • components-common/ - This is where the components that are used in both the shell and the page content are. These are generally more abstract components.

  • replacers/ - Replacers manipulate the HTML tree and insert React components where needed. See the README in the replacers/ folder for more details.

Testing

Automated testing is done with Jest and the Puppeteer library, which runs a headless version of Chrome that can be interacted with via Javascript. This allows for full UI tests that measure actual browser behavior. These tests are called End to End (e2e) tests and are located in tests/*e2e.test.js.

To run the tests, you first must build the react interface with

npm run build

To run the tests one time (like is done on the CI server), run

npm run test:e2e-with-setup-and-teardown

In development it is useful to have tests re-run every time you modify a test file. This can be done via

npm run test:serve
npm run test:e2e-watch

This will rerun the tests every time a file is changed (though if you change a React file, you will need to rerun npm run build).

It is often helpful to run the test server (npm run test:serve) and the tests (npm run test:e2e-watch) in separate terminals so their output is not intermixed.

The Test Server

The test server run with npm run test:serve will serve files by default from pretext-react-compiled-article/html-testing and will fall back to build/ if the files cannot be found.

Testing Tips

Headless testing is hard because you don't really know what the page "looks like" at a particular time. However, adding

await page.screenshot({ path: `test-screenshot.png` });

will save a snapshot (image) of the current page state for extra debugging ease!