What steps should be taken in a lerna monorepo to ensure that rollup includes a dependency from another package in its transpilation process when using TypeScript?

I've prepared a simple demonstration to showcase my question: Github repository.

Within my lerna monorepo, I have two npm packages housed in the packages directory:

Utils: this package exports a function.

export const add = (a:number, b: number) => a + b

Component-library:: this package contains a basic functional React component.

import React from 'react';
import { add } from '@project/utils';

export const MyComponent = () => <div>{add(2, 2)}</div>;

The root of the monorepo holds a tsconfig.json file with a key called paths, which specifies how imports in the form of @project/* should be mapped to the respective packages.

{
  "compilerOptions": {
    "jsx": "react",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "node",
    "allowJs": true,
    "baseUrl": ".",
    "paths": {
      "@project/*": ["packages/*/src"]
    }
  },
  "exclude": ["**/build/**"]
}

Both packages contain a rollup.config.js file, each being practically identical. For the purpose of discussion here, let's focus on the configuration in the component-library package:

import typescript from '@rollup/plugin-typescript';
import commonjs from '@rollup/plugin-commonjs';

export default {
    input: 'src/index.tsx',
    output: {
        dir: './build',
        format: 'cjs'
    },
    plugins: [
        commonjs(),
        typescript({ tsconfig: '../../tsconfig.json'}),
    ]
};

As a result, both packages utilize the paths set in the root tsconfig.json and employ a plugin for transpiling TypeScript.

The component-library package imports a function named add(a,b) from @project/utils.

My objective is to build this library (using rollup) without having to build the utils package first. Essentially, I aim to construct component-library while resolving imports from utils to its source code rather than the build folder within the symlinked package located in node_modules (in other words, not utilizing the symlinks created by lerna).

I'm close to achieving this goal, but when running the build script in component-library, an error occurs:

src/index.tsx → ./build... [!] Error: Unexpected token (Note that you need plugins to import files that are not JavaScript) ..\utils\src\index.ts (1:21) 1: export const add = (a:number, b: number) => a + b ^

From what I understand, this indicates that the import resolution functions correctly, but rollup doesn't transpile the TS file retrieved from an external dependency.

How can I instruct rollup to include the file from utils in the transpilation process?

Answer №1

Is this truly the root of the problem?

In my interpretation, it appears that while the import resolution is functioning properly, Rollup is failing to transpile the TypeScript file originating from an external dependency.

You have the option to utilize Babel through @rollup/plugin-babel in order to directly transpile TS files with Babel. Babel will analyze files within node_modules.

Answer №2

For my project utilizing (lerna + rollup), I have implemented the following configurations for typing:

  • This is how my rollup configuration appears:
import {defineConfig} from 'rollup';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import {terser} from 'rollup-plugin-terser';
import url from '@rollup/plugin-url';

import * as fs from 'fs';
import path from 'path';

const PACKAGE_NAME = process.cwd();
const packageJson = JSON.parse(fs.readFileSync(path.join(PACKAGE_NAME, 'package.json'), 'utf-8'));

const includePaths = ['**/*.woff', '**/*.woff2', '**/*.svg', '**/*.png'];

export default defineConfig({
  input: 'src/index.ts',
  output: [
    {
      file: packageJson.main,
      format: 'cjs',
      sourcemap: false,
      name: packageJson.name,
    },
    {
      file: packageJson.module,
      format: 'es',
      sourcemap: false,
      name: packageJson.name,
    },
  ],
  plugins: [
    resolve(),
    commonjs(),
    typescript({tsconfig: './tsconfig.json'}),
    terser(),
    url({
      fileName: '[name][extname]',
      include: includePaths,
      limit: 0,
    }),
  ],
  external: [...Object.keys(packageJson.peerDependencies || {})],
});
  • This is what the tsconfig.json looks like:
{
  "compilerOptions": {
    "baseUrl": "src",
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "outDir": "dist",
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "downlevelIteration": true,
    "jsx": "react-jsx",
    "noFallthroughCasesInSwitch": true,
    "declaration": true,
    "declarationDir": ".",
    "emitDeclarationOnly": true
  },
  "include": ["rollup.config.ts"],
  "exclude": ["node_modules", "dist", "src/**/*.test.tsx", "src/**/*.stories.tsx"]
}

In addition, it's crucial to configure settings (module, exports, types) for types in the package.json file, an example being configurations for a particular npm package:

{
  "name": "@portfolio-yues-it-npm/icons",
  "main": "./dist/cjs/index.js",
  "module": "./dist/es/index.js",
  "exports": {
    "types": "./dist/es/index.d.ts",
    "import": "./dist/es/index.js",
    "default": "./dist/cjs/index.js"
  },
  "types": "./dist/es/index.d.ts",
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "rollup --config ../../rollup.config.ts --configPlugin @rollup/plugin-typescript"
  },
  "devDependencies": {
    ...
  }
}

P.S. If you need more guidance, I've authored an article on setting up lerna + rollup

Answer №3

When working with a monorepo, it's important to consider hoisting for most, if not all, packages. To ensure proper resolving, you should explore the nodeResolve option:

{
    rootDir: path.join(process.cwd(), '..')
}

Answer №4

I successfully assembled a functional monorepo similar to the one you detailed by utilizing @rollup/plugin-node-resolve and rollup-plugin-typescript2. The component library distributes React components that can be easily installed via npm. I achieved success by setting esm as the output type and excluding the commonjs() step in my plugins configuration.

My plugins setup resembles the following:

plugins: [
  resolve(),
  typescript({ tsconfig: "./tsconfig.json" }),
],

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

Stop the browser from displaying a customized frameset within the document of an iframe

For some time now, I have been attempting to establish communication between two iframes that belong to the same domain but are embedded on a page from a different domain. The issue arises when the browser imposes a custom frameset layer over the content ...

Assistance with updating an array in MongoDB is required

I've come across a challenge with my document collection: { _id: 1, name: 'xpto', arr: [1, 2, 3] }, { _id: 2, name: 'xyz' } My goal is to add number 4 to the "arr" array using $addToSet and update {multi: 1}, but I e ...

Implementing class toggling in AngularJS with ng-class

Trying to change the class of an element with ng-class <button class="btn"> <i ng-class="{(isAutoScroll()) ? 'icon-autoscroll' : 'icon-autoscroll-disabled'}"></i> </button> isAutoScroll(): $scope.isAutoScrol ...

Exploring ways to run tests on a server REST API using testem

When using Testem, I have a config option called serve_files that handles serving the client-side code for me. However, I also need to run my server because it includes a REST API that the client side relies on. Is there a way to configure Testem to launc ...

AngularJS Login Popup with SpringSecurity

I have successfully integrated spring security with my AngularJS webpage utilizing Rest API. However, I am facing an issue where every time I attempt to log in using the rest api from my customized login page, it prompts me for the login credentials in a p ...

Utilize the atan2() function to rotate a div so that it follows the movement of the mouse

I am trying to create an effect where the mouse aligns with the top of a div and the div rotates as the mouse moves. The rotation should happen when the top part of the div is in line with the mouse cursor. My goal is to achieve this using the atan2 functi ...

Display data only when the user interacts with the input field - AngularJs

I am currently working on a program that requires the input data to only show if the input field is touched once. Unfortunately, I am not getting the expected result as nothing is displayed in the span tag (and there are no errors in the console). Can some ...

Ensuring the functionality of WebAPI endpoints through JavaScript

Is there a foolproof way to ensure that your WebAPI controller routes stay aligned with the client-side requirements? Let's say you have a BooksController for your WebAPI. On the client side, a method is invoked by calling the endpoint like this: $. ...

Why ngModel Behaves Oddly in Angular 2

myList: any; ngOnInit(){ this.myList = [ {id: 1, name: 'a'}, {id: 2, name: 'b'}, {id: 3, name: 'c'}, {id: 4, name: 'd'}, ]; } In my HTML file: <input *ngFor="let l of myLi ...

Updating a React event as it changes with each onChange event

Let's address a disclaimer before diving into the issue - for a quick look, visit this pen and type something there. The Scenario This is the JSX code snippet used in my render method: <input value={this.state.value} onChange={this.handleCh ...

Troubleshooting the problem of divs overlapping when scrolling in JavaScript

I am experiencing some issues with full screen divs that overlay each other on scroll and a background image. When scrolling back to the top in Chrome, the background shifts down slightly, and in Safari, the same issue occurs when scrolling down. I have cr ...

The delete button in the "Chip" component of React Material-UI is not functioning as expected

I'm having trouble with the "Chip" control and its "X" button functionality. Unlike the examples shown here: http://www.material-ui.com/#/components/chip Adding the "onRequestDelete" property does include the "X" button, but it doesn't respond t ...

No values are returned in the AJAX response when I attempt to retrieve any of its attributes

Attempting to utilize AJAX to retrieve JSON data for a Greasemonkey script. The current setup is as follows: var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { v ...

jQuery: Track mouse movement with a delay

I'm looking to create a div that follows cursor movement with a slight delay, similar to the effect seen on this website: In the example link provided, you can observe that the 'follower' has a brief delay in its animation. I attempted to ...

TypeScript's exhaustiveness check seems to be malfunctioning

Imagine we are developing a DB model for the entity Post. Since the database stores data as strings, we need to create a parse function that can take a raw database object and convert it into the correct Post interface. To replicate this, enable the noImp ...

I encountered an Angular error that is preventing me from updating and uploading images to my Firebase Storage because it is unable to locate the storage bucket

Hey there fellow developers! I'm currently working on a simple Angular app that allows users to upload images to a gallery. However, I've encountered an issue while trying to upload the images to Firebase Storage. I keep getting an error mentioni ...

The TypeScript error "Issue with Type Assertion: 'This expression is not callable Type'...' has no call signatures" occurs when there is a missing semicolon

Here's a simplified version of our original code: const start: number = 10 const end: number = 20 (someElement as HTMLInputElement).setSelectionRange(start, end) We encountered an error with the 20, where a red squiggly line appeared indicating ...

Jasmine has detected an undefined dependency

Testing out the following code: constructor(drawingService: DrawingService) { super(drawingService); //... } private writeOnCanvas(): void { this.drawingService.clearCanvas(this.drawingService.previewCtx); this.drawing ...

Encountering ERR_CERT_AUTHORITY_INVALID when trying to attach a YouTube video URL in Angular

I'm currently working on a project using Angular 8. One of the requirements is to display 6 YouTube videos in a modal popup. To achieve this, I have implemented the following code: This is my HTML file code: <iframe [src]="safeSrc1" widt ...

In the case that the prop is empty or undefined, display an error message before rendering the full component

I am working on an ImageBlock component, which has several props like url, alt, caption, and optionally quality with a default value of 75. The essential prop here is the url. I need a quick way to immediately display an AlertError if the url is not provi ...