stylelint / vscode-stylelint

The official Visual Studio Code extension for Stylelint
https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint
MIT License
315 stars 29 forks source link

[Bug]: ESM-only stylelint plugins fail to import with Yarn PnP #464

Open kherock opened 1 year ago

kherock commented 1 year ago

How did you encounter this bug?

The stylelint-prettier extension recently refactored its code to use Prettier's ESM entrypoint in https://github.com/prettier/stylelint-prettier/pull/305. Yarn's PnP resolution does support ESM, but this comment from #272 is not completely true as it only pertains to CJS modules:

Just calling our setup function from within the script that will perform the require should be enough - it'll inject the runtime within the process and the rest (like the zip accesses via fs) is transparent.

The only way to hook into Node's ESM resolution is to pass --loader as a CLI arg or in NODE_OPTIONS.

Link to Minimal Reproducible Example

https://github.com/kherock/vscode-stylelint-yarn-esm-bug

Code Snippet

No response

Stylelint Configuration

extends:
  - stylelint-prettier/recommended

Extension Configuration

No response

Actual Behaviour

When Stylelint or a Stylelint plugin attempts to import ESM during server startup, an ERR_MODULE_NOT_FOUND error is thrown.

Expected Behaviour

The Stylint extension should check for a .pnp.loader.mjs file for Yarn projects and configure all spawned child processes to use Yarn's ESM resolution via the --loader option (which is supported on all non-EOL'd Node.js versions).

Logs

[Debug - 12:54:17 p.m.] [language-server] Resolved Stylelint using PnP | path: "/Users/herockk/Workspaces/example-project/.pnp.cjs"
[Debug - 12:54:17 p.m.] [language-server] Running Stylelint | options: {"ignoreDisables":false,"reportDescriptionlessDisables":false,"reportNeedlessDisables":false,"reportInvalidScopeDisables":false,"ignorePath":"/Users/herockk/Workspaces/example-project/.stylelintignore","code":"...","codeFilename":"/Users/herockk/Workspaces/example-project/packages/react/error-page/style.scss"}
node:internal/process/promises:279
            triggerUncaughtException(err, true /* fromPromise */);
            ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'prettier' imported from /Users/herockk/Workspaces/example-project/.yarn/__virtual__/stylelint-prettier-virtual-333ffca36b/0/cache/stylelint-prettier-npm-4.0.2-013484b286-b60112c10b.zip/node_modules/stylelint-prettier/stylelint-prettier.js
Did you mean to import prettier-npm-3.0.0-7ffbcce680-6a832876a1.zip/node_modules/prettier/index.cjs?
    at new NodeError (node:internal/errors:387:5)
    at packageResolve (node:internal/modules/esm/resolve:957:9)
    at moduleResolve (node:internal/modules/esm/resolve:1006:20)
    at defaultResolve (node:internal/modules/esm/resolve:1220:11)
    at nextResolve (node:internal/modules/esm/loader:165:28)
    at ESMLoader.resolve (node:internal/modules/esm/loader:844:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:431:18)
    at ESMLoader.import (node:internal/modules/esm/loader:528:22)
    at importModuleDynamically (node:internal/modules/cjs/loader:1119:29)
    at importModuleDynamicallyWrapper (node:internal/vm/module:438:21) {
  code: 'ERR_MODULE_NOT_FOUND'
}

Stylelint Version

v15.10.1

vscode-stylelint Version

v1.2.4

Node.js Version

v16.20.1

Operating System

macOS Ventura 13.4.1

Windows Subsystem for Linux

No response

Code of Conduct

bryanjtc commented 11 months ago

@kherock Have you found a workaround or an alternative?

guitartsword commented 10 months ago

@kherock Have you found a workaround or an alternative?

I have a workaround:

# inside your yarn PnP project folder
cd ..
yarn init
yarn config set nodeLinker node-modules
yarn add prettier

just make sure it created a node_modules folder and not using PnP

psychobolt commented 8 months ago

I have same issue trying to use Stylelint 16. Seems like their API is now deprecating all common-js plugins and extensions. I don't think using Stylelint modules directly for Yarn ESM PnP is viable. We may need to spawn stylelint as a process. This most likely need a parser similar to SublineLinter-Stylelint's that convert each message to a warning object

psychobolt commented 6 months ago

I managed to create a hybrid PnP + node_modules setup for my project so I can migrate to the latest stylelint version (v16). You can manually link to node_modules using a similar configuration:

First install stylelint and any configurations into a non-PnP workspace. Optionally install the same configurations into your main project:

yarn workspace third-party add -DE stylelint stylelint-config-standard-prettier stylelint-config-standard-scss # ... 
# yarn add -DE stylelint stylelint-config-standard-prettier stylelint-config-standard-scss # ... 

For the configuration, you can use Yarn's INIT_CWD environment variable to detect if stylelint is running with Yarn or VSCode's node to resolve module path:

Update (3/27/24): stylelint now requires /index.js to exists. See: https://github.com/stylelint/stylelint/pull/7578

// stylelint.config.js
import { createRequire } from 'module';
import path from 'path';

const require = createRequire(
  process.env.INIT_CWD ?? // leave out if not mirroring setup for running stylelint CLI with Yarn node
    path.join(process.cwd(), 'packages/third-party/node_modules')
);

/** @type {import('stylelint').Config} */
export default {
  extends: [
    require.resolve("stylelint-config-standard-scss"),
    require.resolve("stylelint-config-prettier-scss"),
  ]
};

Set the library path in .vscode/settings.json

{
  "stylelint.stylelintPath": "packages/third-party/node_modules/stylelint",
}

The above setup should work in VSCode and running stylelint CLI.

InvisibleGit commented 5 months ago

First install stylelint and any configurations into a non-PnP workspace. Optionally install the same configurations into your main project:

That's basically the same hack @guitartsword suggested and isn't a solution.

This issue makes vscode-stylelint unusable for Yarn users for a long time, and stylelint and it's various plugins have moved very far in the past year. Unfortunately I don't know where to start looking for a solution, and is it an issue with this extension, vs-code or yarn itself... :(

psychobolt commented 5 months ago

Yeah that is indeed a workaround. Right now the only way for Yarn to resolve ESM modules is using their node loader. Unfortately, that would require to support runtime as its own process and forwarding , which is how vscode-eslint works.

kherock commented 4 months ago

With the Node 20.6 register utility and #439, it should be possible patch in PnP ESM resolution to vscode-stylelint's Node.js process. I'm trying to use this in my .yarn/sdks/stylelint/lib/index.cjs:

#!/usr/bin/env node

const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);

const relPnpApiPath = "../../../../.pnp.cjs";
const relPnpLoaderPath = "../../../../.pnp.loader.mjs";

const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);

if (existsSync(absPnpApiPath)) {
  if (!process.versions.pnp) {
    // Setup the environment to be able to require stylelint
    require(absPnpApiPath).setup();
    register(relPnpLoaderPath, pathToFileURL(__filename))
  }
}

// Defer to the real stylelint your application uses
module.exports = absRequire(`stylelint`);

The extension tells me that register is not a function, which makes sense as Electron doesn't support these features yet.

Mouvedia commented 4 months ago

@kherock Electron 29 uses Node.js 20.9.0. i.e. the next version of vscode will support it

ref microsoft/vscode#209818

kherock commented 3 months ago

I'm on the latest VSCode now. The language server loads, but now get this error when opening a CSS file:

[Error - 6:58:41 PM] (node:1245) [stylelint:002] DeprecationWarning: The CommonJS Node.js API is deprecated.
See https://stylelint.io/migration-guide/to-16
(Use `Code Helper (Plugin) --trace-deprecation ...` to show where the warning was created)
[Error - 6:58:41 PM] [language-server] Error running lint | uri: "file:///Users/herockk/Workspaces/example/s
hell/src/globals.scss" error: {"url":"file:///Users/herockk/Workspaces/example/.yarn/__virtual__/stylelint-p
rettier-virtual-4b73a1c895/3/.yarn/berry/cache/stylelint-prettier-npm-5.0.0-f95c073012-10c0.zip/node_modules
/stylelint-prettier/recommended.js","code":"ERR_MODULE_NOT_FOUND","filepath":"/Users/herockk/Workspaces/exam
ple/.yarn/__virtual__/stylelint-prettier-virtual-4b73a1c895/3/.yarn/berry/cache/stylelint-prettier-npm-5.0.0
-f95c073012-10c0.zip/node_modules/stylelint-prettier/recommended.js","name":"Error","message":"Cannot find m
odule '/Users/herockk/Workspaces/example/.yarn/__virtual__/stylelint-prettier-virtual-4b73a1c895/3/.yarn/ber
ry/cache/stylelint-prettier-npm-5.0.0-f95c073012-10c0.zip/node_modules/stylelint-prettier/recommended.js' im
ported from /Users/herockk/Workspaces/example/.yarn/__virtual__/cosmiconfig-virtual-a7403f58a3/3/.yarn/berry
/cache/cosmiconfig-npm-9.0.0-47d78cf275-10c0.zip/node_modules/cosmiconfig/dist/loaders.js","stack":"Error [E
RR_MODULE_NOT_FOUND]: Cannot find module '/Users/herockk/Workspaces/example/.yarn/__virtual__/stylelint-pret
tier-virtual-4b73a1c895/3/.yarn/berry/cache/stylelint-prettier-npm-5.0.0-f95c073012-10c0.zip/node_modules/st
ylelint-prettier/recommended.js' imported from /Users/herockk/Workspaces/example/.yarn/__virtual__/cosmiconf
ig-virtual-a7403f58a3/3/.yarn/berry/cache/cosmiconfig-npm-9.0.0-47d78cf275-10c0.zip/node_modules/cosmiconfig
/dist/loaders.js\n    at new NodeError (node:internal/errors:406:5)\n    at finalizeResolution (node:interna
l/modules/esm/resolve:233:11)\n    at moduleResolve (node:internal/modules/esm/resolve:852:10)\n    at defau
ltResolve (node:internal/modules/esm/resolve:1050:11)\n    at nextResolve (node:internal/modules/esm/hooks:8
33:28)\n    at resolve$1 (file:///Users/herockk/Workspaces/example/.pnp.loader.mjs:1976:12)\n    at nextReso
lve (node:internal/modules/esm/hooks:833:28)\n    at Hooks.resolve (node:internal/modules/esm/hooks:278:30)\
n    at MessagePort.handleMessage (node:internal/modules/esm/worker:168:24)\n    at [nodejs.internal.kHybrid
Dispatch] (node:internal/event_target:807:20)"}

It kind of looks like a peer dependency issue, however stylelint does run successfully from the command line, so I'm not confident this is the problem. My guess is that stylelint spawns a subprocess that does not inherit the registered PnP loader.

Mouvedia commented 3 months ago

This is just a deprecation warning. i.e. it can be ignored with NODE_OPTIONS='--no-deprecation'

kherock commented 3 months ago

@Mouvedia I'm referring to the rest of the error output. here's the error after formatting the stack

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/Users/herockk/Workspaces/example/.yarn/__virtual__/stylelint-prettier-virtual-4b73a1c895/3/.yarn/berry/cache/stylelint-prettier-npm-5.0.0-f95c073012-10c0.zip/node_modules/stylelint-prettier/recommended.js' imported from /Users/herockk/Workspaces/example/.yarn/__virtual__/cosmiconfig-virtual-a7403f58a3/3/.yarn/berry/cache/cosmiconfig-npm-9.0.0-47d78cf275-10c0.zip/node_modules/cosmiconfig/dist/loaders.js
    at new NodeError (node:internal/errors:406:5)
    at finalizeResolution (node:internal/modules/esm/resolve:233:11)
    at moduleResolve (node:internal/modules/esm/resolve:852:10)
    at defaultResolve (node:internal/modules/esm/resolve:1050:11)
    at nextResolve (node:internal/modules/esm/hooks:833:28)
    at resolve$1 (file:///Users/herockk/Workspaces/example/.pnp.loader.mjs:1976:12)
    at nextResolve (node:internal/modules/esm/hooks:833:28)
    at Hooks.resolve (node:internal/modules/esm/hooks:278:30)
    at MessagePort.handleMessage (node:internal/modules/esm/worker:168:24)
    at [nodejs.internal.kHybridDispatch] (node:internal/event_target:807:20)
kherock commented 2 months ago

I got this to work in VSCode 1.90 after unplugging stylelint-prettier. Here's my .yarn/sdks/stylelint/lib/index.cjs file

#!/usr/bin/env node

const {existsSync} = require(`fs`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);

const relPnpApiPath = "../../../../.pnp.cjs";

const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);

const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);

if (existsSync(absPnpApiPath)) {
  if (!process.versions.pnp) {
    // Setup the environment to be able to require stylelint
    require(absPnpApiPath).setup();
    if (isPnpLoaderEnabled && register) {
      register(pathToFileURL(absPnpLoaderPath));
    }
  }
}

// Defer to the real stylelint your application uses
module.exports = absRequire(`stylelint`);

and in my VSCode settings.json

  "stylelint.stylelintPath": ".yarn/sdks/stylelint/lib/index.cjs",

Maybe it's time for yarn to reintroduce a stylelint SDK with the register hook added in https://github.com/yarnpkg/berry/issues/6219?