# Developing a React Package

- [Developing a React Package](#developing-a-react-package)
    - [Making Sure You're Using the Correct Version of TypeScript](#making-sure-youre-using-the-correct-version-of-typescript)
    - [FAQs](#faqs)
    - [Remaining Mysteries](#remaining-mysteries)
    - [Explanation of package.json scripts](#explanation-of-packagejson-scripts)
    - [Testing your built package in a real project](#testing-your-built-package-in-a-real-project)
    - [Why?](#why)

## FAQs
- How does building work?

  This project has migrated from `create-react-library` (CRL). The CRL script
  used `microbundle` to bundle packages, which in turn used `rollup`.
  Unfortunately microbundle had some quirks and was hard to configure, so we
  switched to using `rollup` directly.

  The `build` command does two things. Emit type declaration (`*.d.ts`) files,
  then bundles the actual code into a single javascript file `index.js` (maybe a
  css file as well). This way, when importing from the package, type information
  and doc comments are included in the bundled package, but all actual
  functionality lives in the `index.js` file.

  So how does bundling work? Rollup is fairly declarative, so the `plugins`
  field of `rollup.config.js` lays out the steps.
  - node-resolve: My understanding of this is that, if you use any external node
    modules at all, you need this plugin. So basically any project.

  - commonjs: Allows you to use any modules which use the `commonjs` style of
    require/modules.exports as es6 import/exports. Again, almost certainly
    required if you have a `node_modules` folder, since most packages on npm are
    written in the commonjs style.

  - babel: This plugin does most of the heavy lifting. It takes code written in
    JSX and TypeScript syntax to plain javascript. The specific presets and
    plugins are based of the `babel-preset-react` preset in create react app.
    Specifically
    [the "create.js" file](https://github.com/facebook/create-react-app/blob/master/packages/babel-preset-react-app/create.js).
    Most of the names are relatively self-explanatory, and just concern
    translation of syntax. But the least clearly named is
    [preset-env](https://babeljs.io/docs/en/babel-preset-env). This specifies
    what the target environment is. In our case, these will be used inside of
    a react application, and thus will be in a browser **env**ironment. Which
    browsers to use is specified by the `"browserslist"` field of `package.json`
    and matches create react app production defaults.

  - postcss: This plugin handles any css files. The default behavior is to
    inject css as inline styles. The `extract` option takes the css and puts it
    into a separate file instead. It automatically takes files with names like
    `*.module.css` and adds hashes to css class names. The default format string
    is `[name]_[local]_[hash:base64:5]`, which says the new class name is

    - The file name (e.g. `styles.module.css --> styles_modules`)
    - The local css class name (e.g. `.fancy-button`)
    - the first 5 characters of the base64 hash of

    But I opted for `[local]__[hash:base64:8]`.

  - terser: Standard Javascript minifier.

- Why are there multiple tsconfig files?

  While developing, we want VSCode and other tooling to run type checks. For
  example a unit test won't tell us much if at runtime we pass bad types.

  But, when we build the project, we don't want to emit type declarations for
  unit tests and storybook files (see first Q).

## Remaining Mysteries

I am still a bit confused by the use of babel and TypeScript in many of the
examples I have found from big projects. The TypeScript compiler is capable of
handling JSX and, of course, TypeScript syntax. My guess is that this is because
these projects usually have TypeScript as an optional feature, they default to
using babel, but a TypeScript-only project could potentially get rid of babel
entirely, and just use a single `tsc` call. However, I'm not sure how this would
interact with other aspects of packages, like tests and storybook. So for now,
I mostly tried to follow existing examples.

The good thing about trying different build settings is that, as long as they
build properly, the user of the package will not see any major differences in
behavior.

## Explanation of package.json scripts

- Building
    - build:types

      Emit type declaration (`*.d.ts`) files. This is so types and doc comments
      are accessible in projects using this package.

    - build

      Run build:types, the use rollup to bundle the package. See FAQ section to
      read about what rollup is doing.

- Publishing
    - prepare

      Used for publishing a package. See the
      [npm docs](https://docs.npmjs.com/cli/v7/using-npm/scripts).

- Testing
    - test:build

      Just runs the build command

    - test:lint

      run eslint against all files, including test and story files.

    - test:unit
      Run unit tests using `react-scripts`. Uses the `cross-env` package to
      ensure the `CI` env variable is set across all platforms. This variable is
      set so that `react-scripts` runs the tests without entering "watch" mode.

    - test:types

      In principle, VSCode should be checking types for you, but this will run
      the TypeScript compiler against the project tsconfig just to be sure.
      Note: this uses the default tsconfig file, meaning it will type check test
      and story files as well.

    - test:watch

      Just like test:unit, but watches for file changes and reruns tests.
      This is what you should primarily use while writing tests.

    - test

      Runs test:unit, test:lint, and test:build in that order.

- Documentation
    - doc

      Create documentation using typedoc, then serve locally.

    - storybook

      Create a storybook page, then serve locally. The `--ci` flag is to prevent
      storybook from opening a browser page automatically.

    - build-storybook

      Instead of generating locally, actually build a static site. Presumably
      for generating/hosting documentation.


## Testing your built package in a real project

If you're doing this for the first time, follow all steps. Otherwise skip to
step 4.

1. Create a React project, (e.g. using `create-react-app`). I'll assume it's
   called `MyProject`. This will be where we can test the `hex-core-js` package we
   want to test. I'll assume it's `@hex-insights/core`.

2. Install any packages you'll need for `MyProject` by adding them to your the
   `package.json` file or npm installing from your terminal.
   - This does _not_ include `@hex-insights/core`, the package you are going to
     test.
   - But this _does_ include any peer dependencies for `@hex-insights/core`,
     e.g. `react@^17.0.1`.

3. Now, build `@hex-insights/core` to `hex-core/js/packages/core/dist/` by
   running `npm run build` in the `hex-core/js/packages/core` folder, or
   `npm run build` from `hex-core/js/` if you plan on testing some or
   all packages.

4. Now go to the `node_modules` folder in `MyProject`, and create or find the
   `@hex-insights` folder. Paste the `dist/` folder in here, and rename to the
   package name (`core`).

    Now your `node_modules` should look like
    ```
    MyProject/
    └── node_modules/
        ├── react/             <- installed in step 2.
        └── @hex-insights/
            └── core/          <- renamed from dist/
                ├── index.js
                ├── index.d.ts
                ├── index.css
                ├── Modules/
                │   └── utils.d.ts
                └── etc...
    ```

5. Now add the name and version of the package to test in `package.json` with a
   version number compatible with the `package.json` for `core`. Your
   `package.json` file for `MyProject` should now look like this.
    ```jsonc
    {
        "name": "MyProject",
        // ...
        "dependencies": {
            "react": "^17.0.2",            // <- peer dep of @hex-insights/core
            "@hex-insights/core": "0.1.0"  // <- added this line
        }
    }
    ```
    Note, this may only be required for VSCode.


## Why?

When working on an javascript/node package, npm offers a few nice features.
Unfortunately, none of these features will be useful for us.

In particular, the `npm link [pkg]` command is used for locally testing packages
without having to rebuild each time. It does this by allowing your package to be
sym-linked. Unfortunately, react **really** doesn't like having multiple
versions of React interacting, and will crash if you try to do this. Even worse,
the project will build fine, but crashes at runtime.

However, while the above method is a bit tedious, it is better in some ways,
since it more realistically tests how the package would actually behave if
installed via npm.

It may be possible to avoid these steps by creating your own symlink to the
`dist/` folder, but I have not tried this.
