Baterka
Baterka

Reputation: 3714

How to cache yarn packages in GitHub Actions

I am using GitHub Actions to build my TypeScript project. Everytime I run action I am waiting 3 minutes for all dependencies to get installed.

Is there way to cache yarn dependencies, so build time will be faster?

I tried this:

     - name: Get yarn cache directory path
       id: yarn-cache-dir-path
       run: echo "::set-output name=dir::$(yarn cache dir)"

     - uses: actions/cache@v1
       id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
       with:
         path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
         key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
         restore-keys: |
           ${{ runner.os }}-yarn-

    - name: Install yarn
      run: npm install -g yarn

    - name: Install project dependencies
      run: yarn

but build time is still same.

Upvotes: 65

Views: 50155

Answers (4)

Quang Lam
Quang Lam

Reputation: 1209

  1. Using actions/setup-node@v2 or newer:

    - name: Set up Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '16'
        cache: 'yarn'
    
    - name: Install project dependencies
      run: yarn
    

    actions/setup-node@v2 or newer has caching built in so you no longer need to set up actions/cache.

  2. Using actions/setup-node@v1 & caching Yarn global cache with actions/cache:

    - name: Set up Node.js
      uses: actions/setup-node@v1
      with:
        node-version: '16'
    
    - name: Get yarn cache directory path
      id: yarn-cache-dir-path
      run: echo "::set-output name=dir::$(yarn cache dir)"
    
    - uses: actions/cache@v3
      id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
      with:
        path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
        key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
        restore-keys: |
        ${{ runner.os }}-yarn-
    
    - name: Install project dependencies
      run: yarn --prefer-offline
    

The caching code above only caches and restores the Yarn global cache directory, it doesn't cache the node_modules directory itself.

To improve the installation speed, you need to tell Yarn to use cached downloads (in the cache directory mentioned above) during installation whenever possible instead of downloading from the server:

- name: Install project dependencies
  run: yarn --prefer-offline
  1. Caching node_modules with actions/cache (not recommended)

    You can also cache the node_modules directory directly and skip the running yarn when the cache is available.

    But it is not recommended because:

    • yarn is good at utilizing global cache. If the dependencies are already available in global cache, yarn can finish running in less than 1 second. (see comment from @mvlabat).
    • node_modules could be corrupted. It is safer to re-run yarn every time and let yarn decides whether to get the files from cache or not (as yarn will try to validate the cache before using it).
    - name: Set up Node.js
      uses: actions/setup-node@v1
      with:
        node-version: '16'
    
    - name: Get yarn cache directory path
        id: yarn-cache-dir-path
        run: echo "::set-output name=dir::$(yarn cache dir)"
    
    - name: Cache yarn cache
        uses: actions/cache@v3
        id: cache-yarn-cache
        with:
        path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
        key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
        restore-keys: |
            ${{ runner.os }}-yarn-
    
    - name: Cache node_modules
        id: cache-node-modules
        uses: actions/cache@v3
        with:
        path: node_modules
        key: ${{ runner.os }}-${{ matrix.node-version }}-nodemodules-${{ hashFiles('**/yarn.lock') }}
        restore-keys: |
            ${{ runner.os }}-${{ matrix.node-version }}-nodemodules-
    
    - run: yarn
        if: |
        steps.cache-yarn-cache.outputs.cache-hit != 'true' ||
        steps.cache-node-modules.outputs.cache-hit != 'true'
    

Upvotes: 110

thisismydesign
thisismydesign

Reputation: 25072

actions/setup-node supports caching since v2 with several custom options.

- uses: actions/checkout@v3

- name: Setup Node.js
  uses: actions/setup-node@v3
  with:
    node-version: '16'
    cache: 'yarn'

- name: Install JS dependencies
  run: yarn install

Caching is done as recommended, only caching yarn cache dir and not node_modules. Caching node_modules is not recommended because it can lead to issues e.g. when node version changes.


Old answer:

This is a 1-liner cache specifically for Yarn: https://github.com/c-hive/gha-yarn-cache

It does caching as recommended by GitHub. Supports Yarn v1 and v2.

Same for NPM: https://github.com/c-hive/gha-npm-cache

Upvotes: 13

Vencovsky
Vencovsky

Reputation: 31625

As the readme of the github package says:

steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
  with:
    node-version: '14'
    cache: 'npm' # or yarn
- run: npm install
- run: npm test

Edit:

Turns out that the way the docs were written was very misleading and they updated to make it clear that it doesn't cache the node_modules folder, but only the global cache directory, as stated in this issue.

Also as stated by Mrchief in the comments:

... you'll still incur the npm i time, just save on download time from internet (if the module is in npm cache)

So you should still use this save time from downloading packages from internet, but if you want to cache node_modules folder, check the other answers where it uses actions/cache.

You should also check Quang Lam answer and it's comments on why you shouldn't cache node_modules folder.

Upvotes: 42

Edric
Edric

Reputation: 26740

As mentioned in the comment next to the id field for the caching step:

Use this to check for cache-hit (steps.yarn-cache.outputs.cache-hit != 'true')

You're missing a conditional if property that determines whether the step should be run:

- name: Install yarn
  run: npm install -g yarn

- name: Install project dependencies
  if: steps.yarn-cache.outputs.cache-hit != 'true' # Over here!
  run: yarn

P.S. You should probably use the Setup NodeJS GitHub Action that additionally sets up Yarn for you:

- uses: actions/setup-node@v1
  with:
    node-version: '10.x' # The version spec of the version to use.

See the action.yml file for a full list of valid inputs.


EDIT: As it turns out, Yarn is included in the list of software installed on the GitHub-hosted Ubuntu 18.04.4 LTS (ubuntu-latest/ubuntu-18.04) runner, so there's no need to include a step to globally install Yarn.

Upvotes: 15

Related Questions