How Should VSCode Be Configured To Support A Lerna Monorepo?
Asked Answered
A

2

20

I have a lerna monorepo containing lots of packages.

I'm trying to achieve the following:

  1. Ensure that VSCode provides the correct import suggestions (based on package names, not on relative paths) from one package to another.
  2. Ensure that I can 'Open Definition' of one of these imports and be taken to the src of that file.

For 1. I mean that if I am navigating code within package-a and I start to type a function exported by package-b, I get a suggestion that will trigger the adding of an import: `import { example } from 'package-b'.

For 2. I mean that if I alt/click on the name of a function exported by 'package-b' while navigating the file from a different package that has imported it, I am taken to '/packages/namespace/package/b/src/file-that-contains-function.js',

My (lerna) monorepo is structured as standard, for example here is a 'components' package that is published as @namespace/components.

- packages
    - components
       - package.json
       - node_modules
       - src
         - index.js
         - components
           - Button
             - index.js
             - Button.js
       - es
         - index.js
         - components
           - Button
             - index.js
             - Button.js

Note that each component is represented by a directory so that it can contain other components if necessary. In this example, packages/components/index exports Button as a named export. Files are transpiled to the package's /es/ directory.

By default, VSCode provides autosuggestions for imports, but it is confused by this structure and, for if a different package in the monorepo needs to use Button for example, will autosuggest all of the following import paths:

  • packages/components/src/index.js
  • packages/components/src/Button/index.js
  • packages/components/src/Button/Button.js
  • packages/components/es/index.js
  • packages/components/es/Button/index.js
  • packages/components/es/Button/Button.js

However none of these are the appropriate, because they will be rendered as relative paths from the importing file to the imported file. In this case, the following import is the correct import:

import { Button } from '@namespace/components'

Adding excludes to the project's jsconfig.json has no effect on the suggested paths, and doesn't even remove the suggestions at /es/*:

{
  "compilerOptions": {
    "target": "es6",
  },
  "exclude": [
    "**/dist/*",
    "**/coverage/*",
    "**/lib/*",
    "**/public/*",
    "**/es/*"
  ]
}

Explicitly adding paths using the "compilerOptions" also fails to set up the correct relationship between the files:

{
  "compilerOptions": {
    "target": "es6",
    "baseUrl": ".",
    "paths": {
      "@namespace/components/*": [
        "./packages/namespace-components/src/*.js"
      ]
    }
  },
}

At present Cmd/Clicking on an import from a different package fails to open anything (no definition is found).

How should I configure VSCode so that:

  1. VSCode autosuggests imports from other packages in the monorepo using the namespaced package as the import value.
  2. Using 'Open Definition' takes me to the src of that file.

As requested, I have a single babel config in the root:

const { extendBabelConfig } = require(`./packages/example/src`)

const config = extendBabelConfig({
  // Allow local .babelrc.js files to be loaded first as overrides
  babelrcRoots: [`packages/*`],
})

module.exports = config

Which extends:

const presets = [
  [
    `@babel/preset-env`,
    {
      loose: true,
      modules: false,
      useBuiltIns: `entry`,
      shippedProposals: true,
      targets: {
        browsers: [`>0.25%`, `not dead`],
      },
    },
  ],
  [
    `@babel/preset-react`,
    {
      useBuiltIns: true,
      modules: false,
      pragma: `React.createElement`,
    },
  ],
]

const plugins = [
  `@babel/plugin-transform-object-assign`,
  [
    `babel-plugin-styled-components`,
    {
      displayName: true,
    },
  ],
  [
    `@babel/plugin-proposal-class-properties`,
    {
      loose: true,
    },
  ],
  `@babel/plugin-syntax-dynamic-import`,
  [
    `@babel/plugin-transform-runtime`,
    {
      helpers: true,
      regenerator: true,
    },
  ],
]

// By default we build without transpiling modules so that Webpack can perform
// tree shaking. However Jest cannot handle ES6 imports becuase it runs on
// babel, so we need to transpile imports when running with jest.
if (process.env.UNDER_TEST === `1`) {
  // eslint-disable-next-line no-console
  console.log(`Running under test, so transpiling imports`)
  plugins.push(`@babel/plugin-transform-modules-commonjs`)
}

const config = {
  presets,
  plugins,
}

module.exports = config
Appliance answered 10/9, 2019 at 12:11 Comment(6)
Do you mind to share how do you build your project?Disrespect
Do you have a global jsconfig.json file or in every packages/ directory a jsconfig.json file?Disrespect
@mathayk each package contains a /src/ directory. Babel transpiles these files into an /es/ directory, so each package contains both a /src/ and /es/ directory. Added this to the question.Appliance
@mathayk I have a single global jsconfig.json file.Appliance
thanks for your answers! Can you add your babel.config.js file to the question. Which babel version are you using? Do you have one or more babel config file?Disrespect
@mathayk added config to the question. "@babel/cli": "^7.1.5", "@babel/core": "^7.1.6", "@babel/plugin-proposal-class-properties": "^7.1.0", "@babel/plugin-syntax-dynamic-import": "^7.0.0-rc.2", "@babel/plugin-transform-modules-commonjs": "^7.1.0", "@babel/plugin-transform-object-assign": "^7.0.0", "@babel/plugin-transform-runtime": "^7.1.0", "@babel/plugin-transform-template-literals": "^7.2.0", "@babel/preset-env": "^7.1.6", "@babel/preset-flow": "^7.0.0-rc.2", "@babel/preset-react": "^7.0.0-beta.56", "@babel/runtime": "^7.1.5",Appliance
A
3

Edit: This is broken with the latest version of VSCode.

I finally managed to get this working reliably. You need to create a separate jsconfig.js for every package in your monorepo, for example:

  • {monorepo root}/packages/some-package/jsconfig.json:
{
  "compilerOptions": {
    "target": "es6",
    "jsx": "preserve",
    "module": "commonjs"
  },
  "include": ["src/**/*.js"],
  "exclude": ["src/index.js"]
}

Note that I've excluded the src/index.js file so it doesn't get offered as an import suggestion from within that package.

This setup appears to achieve:

  • Intellisense import suggestions from packages instead of using relative paths.
  • Go to definition to source of other packages in the monorepo.

VSCode has been pretty flaky of late, but it seems to be working.

Note this is working for a JavaScript-only monorepo (not Typescript).

Appliance answered 10/11, 2020 at 16:40 Comment(1)
OK, so now we need to figure out how to make it work for TypeScript in WSL2 and we're going to be golden.Carlock
A
13

In your case, I would make use of lerna in combination with yarn workspaces. When running yarn install, all your packages are linked under your @namespace in a global node_modules folder. With that, you get IntelliSense.

I've set up an example repository here: https://github.com/flolude/stackoverflow-lerna-monorepo-vscode-intellisense

intellisense preview

You just need to add "useWorkspaces": "true" to your lerna.json

lerna.json

{
  "packages": ["packages/*"],
  "version": "0.0.0",
  "useWorkspaces": "true"
}

And the rest is just propper naming:

global package.json

{
  "name": "namespace",
  // ...
}
package.json of your component package

{
  "name": "@namespace/components",
  "main": "src/index.js",
  // ...
}
package.json of the package that imports the components

{
  "name": "@namespace/components",
  "main": "src/index.js",
  "dependencies": {
     "@namespace/components":"0.0.0"
  }
  // ...
}

Then you can do the following:

import { Component1 } from '@namespace/components';

// your logic

Automatically Importing from @namespace

Unfortunately, I couldn't find a way to make this work in VSCode with a Javascript Monorepo. But there are some things you can do:

  1. Use Typescript (tutorial, other tutorial)
  2. Use module-alias
  3. Add import {} from '@namespace/components' to the top of your file add import to file
  4. Use Auto Import Extension auto import
Areopagus answered 18/9, 2019 at 8:45 Comment(5)
Thanks. You're halfway there. The 'Open Definition' is definitely working. However the import suggestions still use a relative path, for example open /packages/website/src/index.js, remove the import and type 'Comp'. You will see that VSCode offers an import suggestion, however if you select it, the import it adds will be import { Component1 } from '../../components/src/component1', not import { Component1 } from '@namespace/components'.Appliance
Alright, no easy answers for this. But I've edited my answer to provide some optionsAreopagus
I am really sorry. I think in your situation I would try to switch to typescriptAreopagus
No need to apologise. Unfortunately that isn't really feasible. Too much work for our codebase.Appliance
If you set it up once, it isn't really much more work. But I won't judge as I don't have enough insight into your project. I hope I could at least help a bit :)Areopagus
A
3

Edit: This is broken with the latest version of VSCode.

I finally managed to get this working reliably. You need to create a separate jsconfig.js for every package in your monorepo, for example:

  • {monorepo root}/packages/some-package/jsconfig.json:
{
  "compilerOptions": {
    "target": "es6",
    "jsx": "preserve",
    "module": "commonjs"
  },
  "include": ["src/**/*.js"],
  "exclude": ["src/index.js"]
}

Note that I've excluded the src/index.js file so it doesn't get offered as an import suggestion from within that package.

This setup appears to achieve:

  • Intellisense import suggestions from packages instead of using relative paths.
  • Go to definition to source of other packages in the monorepo.

VSCode has been pretty flaky of late, but it seems to be working.

Note this is working for a JavaScript-only monorepo (not Typescript).

Appliance answered 10/11, 2020 at 16:40 Comment(1)
OK, so now we need to figure out how to make it work for TypeScript in WSL2 and we're going to be golden.Carlock

© 2022 - 2024 — McMap. All rights reserved.