Tips for caching yarn packages in GitHub Actions

I am currently utilizing GitHub Actions to compile my TypeScript project. Each time I execute the action, I have to wait for approximately 3 minutes for all dependencies to be installed.

Is there a method to cache yarn dependencies in order to reduce build time?

I attempted the following approach:

     - name: Obtain the directory path for yarn cache
       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

However, despite implementing this strategy, the build time remains unchanged.

Answer №1

  1. When using actions/setup-node@v2 or a newer version:

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

    With the newer versions of actions/setup-node@v2, caching is already included, eliminating the need to set up actions/cache.

  2. If you are using actions/setup-node@v1 and want to cache 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 code above only caches and restores the Yarn global cache directory, not the node_modules directory itself.

To speed up installation, specify that Yarn should use cached downloads 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 running yarn if the cache is available.

    However, this approach is not recommended for the following reasons:

    • yarn makes good use of the global cache. If the dependencies are already in the global cache, yarn can finish quickly. (see comment from @mvlabat).
    • node_modules could be corrupted. It's safer to let yarn decide whether to get files from the cache by re-running it each time (as yarn validates 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'
    

Answer №2

According to the guidelines in the documentation of the GitHub package:

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:

It turns out that the instructions were misleading and have now been updated to clarify that it does not cache the node_modules folder, but only the global cache directory, as explained in this issue.

In addition, as mentioned by Mrchief in the comments:

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

Therefore, it is recommended to continue using this to save time on downloading packages from the internet, but if you want to cache the node_modules folder, refer to other solutions that utilize actions/cache.

Be sure to also read Quang Lam's response and its comments on why you should not cache the node_modules folder.

Answer №3

Noted in the comment next to the id field for the caching step:

Verify cache-hit by checking if

steps.yarn-cache.outputs.cache-hit != 'true'

An essential conditional if property is missing to determine when the step should be executed:

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

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

Additionally, consider using the Setup NodeJS GitHub Action that includes Yarn setup:

- uses: actions/setup-node@v1
  with:
    node-version: '10.x' # Specify the version of NodeJS.

Refer to the action.yml file for a complete list of valid inputs.


UPDATE: Yarn is actually pre-installed on the GitHub-hosted Ubuntu 18.04.4 LTS (ubuntu-latest/ubuntu-18.04) runner, eliminating the need for a global installation step.

Answer №4

actions/setup-node now offers caching options starting from version 2, providing customizable features.

- 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

The recommended approach for caching, as outlined in the documentation, involves caching only the yarn cache dir and not node_modules. Caching node_modules is discouraged due to potential issues when changing node versions.


This information has been updated:

A one-liner cache solution designed specifically for Yarn can be found here: https://github.com/c-hive/gha-yarn-cache

This tool aligns with GitHub's recommendations and supports both Yarn v1 and v2.

Similarly, a cache solution tailored for NPM is available at: https://github.com/c-hive/gha-npm-cache

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

There is an error appearing in my .ts code: [ts] The property 'name' is not found in type 'any[]'

While my coding is working fine and data is showing on the page, there seems to be an error occurring in the VSE editor. It is showing something like this: [ts] Property 'name' does not exist on type 'any[]'. This is a snippet of my ...

Inferencing repeated generics in Typescript

Why does Typescript's inference seemingly ignore the mismatch in types in this code snippet? type MyType<TRecord extends Record<string,any>> = { rec: TRecord rec2: TRecord } const myRec = { idFoo: 3 } function createMyType<T ...

typescript throwing an unexpected import/export token error

I'm currently exploring TypeScript for the first time and I find myself puzzled by the import/export mechanisms that differ from what I'm used to with ES6. Here is an interface I'm attempting to export in a file named transformedRowInterfac ...

Creating a global property access expression using the Typescript Compiler API

Currently, I'm grappling with the challenge of creating TypeScript code using the compiler API. Regrettably, official documentation on this subject is scarce, leaving me stranded on a seemingly straightforward task: All I want to do is generate a bas ...

Exploring the world of Unicode in Angular

I need to search for pokemon and retrieve all Pokémon results (with the accent included) This is my array: let games = ['The Legend of Zelda', 'Pokémon', 'Chrono Trigger'] This is how I am attempting to do it: Using HTML ...

Creating Class Names Dynamically in Angular 2 Using ngFor Index

Encountering an issue while trying to generate a dynamic class name based on the ngFor loop index in Angular 2. Due to restrictions, I had to use a specific syntax as Angular 2 does not support ngFor and ngIf together on the same element. Given this setup ...

The error message "Can't resolve all parameters for CustomerService" is preventing Angular from injecting HttpClient

I have a customerService where I am attempting to inject httpClient. The error occurs on the line where I commented //error happens on this line. Everything works fine until I try to inject httpClient into my service. The error message is: `compiler.js: ...

Purge React Query Data By ID

Identify the Issue: I'm facing a challenge with invalidating my GET query to fetch a single user. I have two query keys in my request, fetch-user and id. This poses an issue when updating the user's information using a PATCH request, as the cach ...

Ran into a situation where Nextjs13 had two children sharing the same key

Currently, I am in the process of creating a booking form using shadcn/ui within nextjs13. As part of this, I am mapping over different hairstyles listed in my postgres database to generate selectable options for users. However, during this process, I enco ...

An easy guide on utilizing ngSwitch for datatype selection in Angular

While working in angular2, I came across a question regarding the usage of ngSwitch to load <div> tag based on the datatype of a variable. Is it possible to achieve something like this: <div [ng-switch]="value"> <p *ng-switch-when="isObj ...

Can you explain the distinction between Reflect.getMetadata and Reflect.getOwnMetadata?

Just like the title says, the reflect-metadata API comes with a method called getMetadata and another called getOwnMetadata. Can you explain the distinction between them? The same question applies to hasOwnMetadata, and so on. ...

Guide for referencing brackets with Joi validation

{ "visibleFields": { "design.content.buttons.action.type": { "SHOW_CLOSE": true, "URL": true, "CALL_PHONE": true }, "design.content.formFields": false, "success": fal ...

Issue with Angular 11: Unable to bind to 'ngForOf' as it is not recognized as a valid property of 'tr' element

My issue lies with a particular page that is not functioning correctly, even though it uses the same service as another working page. The error seems to occur before the array is populated. Why is this happening? I appreciate any help in resolving this p ...

What is the best way to implement global error handling for NextJS API requests?

Is there a method to set up a Global error handler that captures the stack trace of errors and sends it to an external system like NewRelic without needing to modify each individual API? This would follow the DRY principle by avoiding changes to multiple ...

Creating and incorporating a generator function within an interface and class: A step-by-step guide

In vanilla JavaScript, the code would look something like this: class Powers { *[Symbol.iterator]() { for(let i = 0; i < 10; i++) yield { i, pow: Math.pow(i, i) } return null; } } This can then be utilized in the following manner: co ...

When using React Hook Form with TypeScript, the error message "Property 'inputRef' does not exist on type DetailedHTMLProps" may arise

This is my custom react-hook controller component written with TypeScript <Controller control={control} name={name} defaultValue={defaultValue} rules={validate} ref={register({ validate })} ...

When using TypeORM's save() method with an array of objects, the @PrimaryColumn() annotations are ignored, resulting

My current situation involves an entity called Point: @Entity() export class Point { @PrimaryGeneratedColumn('uuid') id: string; @IsUUID() @PrimaryColumn({ type: 'uuid', ...

The Angular 5 keyup event is being triggered twice

My app is incredibly simple, just a basic hello world. To enhance its appearance, I incorporated bootstrap for the design and ng-bootstrap for the components. Within one of my TS files, you will find the following code: showMeTheKey(event: KeyboardEvent) ...

Even when using module.exports, NodeJS and MongoDB are still experiencing issues with variable definitions slipping away

Hello there, I'm currently facing an issue where I am trying to retrieve partner names from my MongoDB database and assign them to variables within a list. However, when I attempt to export this information, it seems to lose its definition. Can anyone ...

Exploring the various methods of setting up a Create React App project with TypeScript

One of my old books from a year ago mentioned... npx create-react-app XXX --typescript But looking at the current Create React App documentation, it says... npx create-react-app XXX --template typescript They both seem to yield the same result. Is ther ...