This document is a guide for VFS engineers who are tasked with adding Yarn Workspaces to vets-website src/platform/* and src/applications/*

Setting up the worktree and apps' workspaces

If an application is on the allowlist, it can be converted to a Yarn workspace. To do so:

  1. add an entry for your app in the packages array of the workspaces field in the vets-website root level package.json. For example, to add the vaos app you would do the following:

    "workspaces": {
        "packages": [
          "src/applications/vaos"
        ]
      }
    CODE
  2. Create a list of your application’s dependencies. Separate this list into two categories: dependencies that are shared by other applications (shared dependencies) and dependencies that only your application uses (isolated dependencies).

  3. Add a package.json to the root level of your application. To this package.json:

    1. set the "name" property to @department-of-veterans-affairs/applications-{my-app-name}

    2. add all shared dependencies to peerDependencies . For each dependency here, set its version to "*", e.g.

      "peerDependencies": {
        "react": "*"
      }
      CODE

The point here is that if the version of the dependency is changed at the vets-website root level package.json, then your app will use that version.

b. add any isolated dependencies to the dependencies or devDependencies in the app-level package.json, as appropriate.

c. for each isolated dependency add an entry to the no-hoist array in the vets-website root level package.json. For example, if your application were called my-app and you had an isolated dependency of cowsay then you would do the following:

"workspaces": {
  "packages": [
    "src/applications/my-app"
  ],
  "nohoist": [
    "**/applications-my-app/cowsay"
  ]
}
CODE

After this step, when you do a yarn install you will see a node_modules folder inside applications/my-app and it will contain cowsay.

  1. Add any app-level scripts you wish to the

scripts section of the app-level package.json

src/platform code workspaces:

  1. In the workspaces field of the root level package.json, add src/platform/<the directory> to the packages array

  2. In the same file and field, append any unique dependencies to the "nohoist" array. For example, for polyfills directory:

      "workspaces": {
        "packages":[
        ...
        "src/platform/<the directory>",
        ...
        ],
        "nohoist": [
          "**/platform-polyfills/classlist-polyfill" 
        ]
      }
    JSON
  3. Create src/platform/<the directory>/exportsFile.js

    1. in this file import all modules that we want to be made available to export from among all the modules in the directory

    2. re-export them; doing this allows us to have two ways to import a module from a src/platform directory:

      1. directly (which requires knowing the “alias” to a module). For example, with the above package.json a user would do: import polyfillModule from @department-of-veterans-affairs/platform-polyfill/some-module-that-is-ok-to-export

      2. indirectly (through the exportFiles intermediary). import { polyfillModule } from @department-of-veterans-affairs/platform-polyfill

  4. In src/platform/<the directory>, create a package.json. Here is an example for polyfills:

    {
      "name": "@department-of-veterans-affairs/platform-<the directory>",
      "version": "1.0.0",
      "exports": {
        "." :"./exportsFile.js",
        "./some-module-that-is-ok-to-export" :"./path-to-some-module-that-is-ok-to-export"
      },
      peerDependencies:{
       ...
      },
      "license": "MIT",
      "private": true
     }
    CODE
    1. export keys should resemble the directory and file name of the export. Common names can be excluded from the name if there are no conflicts ie: ./components/ExampleModule can be shortened to ./ExampleModule

    2. add all shared dependencies as peerDependencies with version as "*"This way, if a dependency at the root level changes there will not be a mis-match with the version in the workspace.

  5. List dependencies and devDependecies in the src/platform/<the directory>/package.json file. List any unique dependencies in the nohoist field in the root package.json. See tip 1.

  6. Add all modules that are ok to be imported into a module outside of the current src/platform directory as aliases in the export field of the package.json in the current src/platform directory, e.g. "./some-module-that-is-ok-export"

  7. We are going to be encouraging application teams to import modules into their apps via yarn workspace syntax

    1. e.g. import FEATURE_FLAG_NAMES from '@department-of-veterans-affairs/platform-utilities/featureFlagNames' instead of import FEATURE_FLAG_NAMES from 'platform/utilities/feature-toggles/featureFlagNames'.

    2. When importing nested modules from a yarn workspace (i.e. modules inside a folder inside the workspace) ESlint will not be able to resolve the module specified by the new yarn workspace syntax unless an alias for the module is added to the root level babel.config.json.

    3. for the above import the alias added is "@department-of-veterans-affairs/platform-utilities/featureFlagNames": "./src/platform/utilities/feature-toggles/featureFlagNames.js" (note the extension is included).

Testing

  1. Delete all pertinent node_modules folders

  2. Run yarn install

  3. Check the root node_modules/@department-of-veterans-affairs/platform-<the directory>  exist

  4. Check <the directory>  root node_modules to make sure non hoisted dependencies are present and correct

  5. Run yarn watch to test for compilation and runtime errors; the yarn workspaces installation should NOT affect the import/export running of current code (only provide a new option)

  6. Run src/platform/* unit tests with yarn test:unit src/platform/**/*.unit.spec.js

  7. [Test imports from new @department-of-veterans-affairs/... ] (note we are still having issues with Babel)

Trouble shooting notes

Tips

  1. You can get list of imported dependencies ( with repo level install information) by running node script/list-imports.js --app-folder [<the directory]

  2. inside a workspace, ESlint will not be able to resolve an import from another workspace if of the following form:

    // in a file in platform/forms
    import FormApp from 'platform/forms-system/src/js/containers/FormApp';
    CODE

The solution to this problem was to add the following to the root level .eslintrc.js in vets-website:

settings: {
    'import/resolver': {
      node: {
        moduleDirectory: ['node_modules', 'src/'],
      },
      'babel-module': {},
    },
  },
CODE

Debugging

  • If you are running into a LARGE number of compiler errors, first check that the root directory contains a babel.config.json file (as opposed to .babelrc)

  • If you run into importing  errors, check that your package.json file does NOT have the “type” field.

  • if you add an alias to babel.config.json and are still getting an ESlint error that the module can’t be resolved try restarting VS Code.

  • if you are working on the worktree set up, and when running (or when CI runs) yarn install --frozen-lockfile you encounter this error: error Your lockfile needs to be updated, but yarn was run with '--frozen-lockfile'. Remove the following resolutions in the root package.json: "**/yargs-parser": "13.1.2", "punycode": "1.4.1", "**/moment": "^2.29.2"

    Note that, alternatively you can run yarn install --pure-lockfile.