Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Stylelint basic support #158

Merged
merged 4 commits into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Visit [documentation](https://vite-plugin-checker.netlify.app) for usage.
# Visit [documentation](https://vite-plugin-checker.netlify.app) for usage

A Vite plugin that can run TypeScript, VLS, vue-tsc, ESLint in worker thread.
A Vite plugin that can run TypeScript, VLS, vue-tsc, ESLint, Stylelint in worker thread.

<p align="center">
<img alt="screenshot" src="https://user-images.githubusercontent.com/12322740/152739742-7444ee62-9ca7-4379-8f02-495c612ecc5c.png">
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ function sidebar() {
{ text: 'vue-tsc', link: '/checkers/vue-tsc' },
{ text: 'ESLint', link: '/checkers/eslint' },
{ text: 'VLS', link: '/checkers/vls' },
{ text: 'Stylelint', link: '/checkers/stylelint' },
],
},
{
Expand Down
4 changes: 2 additions & 2 deletions docs/checkers/overview.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Checkers overview

vite-plugin-checkers provide built-in checkers. For now, it provides [TypeScript](/checkers/typescript), [ESLint](/checkers/eslint), [vue-tsc](/checkers/vue-tsc), [VLS](/checkers/vls).
vite-plugin-checkers provide built-in checkers. For now, it provides [TypeScript](/checkers/typescript), [ESLint](/checkers/eslint), [vue-tsc](/checkers/vue-tsc), [VLS](/checkers/vls), [Stylelint](/checkers/stylelint).

## How to add a checker

- Set to `true` to use a checker with its default value (except ESLint).
- Set to `true` to use a checker with its default value (except ESLint and Stylelint).
- Leave the field blank or `false` to disable the checker.
- Make sure to install the peer dependencies indicated of each checker.
- Checker can be enabled with an advanced object config.
35 changes: 35 additions & 0 deletions docs/checkers/stylelint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Stylelint

## Installation

1. Make sure [stylelint](https://www.npmjs.com/package/stylelint) and related plugins for your `stylelintrc` are installed as peer dependencies.

::: warning
**(Optional but highly recommended)** Install `meow@^9.0.0` with your package manager. It's needed because of Stylelint dependents on it. It's probably working fine even it's not installed as it's accessed as a phantom dependency. But when you set `hoist=false` of pnpm. It won't be accessible anymore without explicit installation.

:::

2. Add `stylelint` field to plugin config and `options.stylelint.lintCommand` is required. The `lintCommand` is the same as the lint command of your project. The default root of the command uses Vite's [root](https://vitejs.dev/config/#root).

```js
// e.g.
export default {
plugins: [
checker({
stylelint: {
lintCommand: 'stylelint ./src/**/*.{css,vue}', // for example, lint .css & .vue
},
}),
],
}
```

## Configuration

Advanced object configuration table of `options.stylelint`

| field | Type | Default value | Description |
| :----------------- | -------------------------------------------------------------------------------------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| lintCommand | `string` | This value is required | `lintCommand` will be executed at build mode, and will also be used as default config for dev mode when `stylelint.dev.stylelint` is nullable. |
| dev.overrideConfig | [`Stylelint.LinterOptions`](https://github.com/stylelint/stylelint/blob/main/types/stylelint/index.d.ts) | `undefined` | **(Only in dev mode)** You can override the options of the translated from `lintCommand`. Config priority: `stylelint.lint({ cwd: root, ...translatedOptions, ...pluginConfig.stylelint.dev?.overrideConfig, })`. |
| dev.logLevel | `('error' \| 'warning')[]` | `['error', 'warning']` | **(Only in dev mode)** Which level of Stylelint should be emitted to terminal and overlay in dev mode |
2 changes: 1 addition & 1 deletion docs/introduction/introduction.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# About vite-plugin-checker

A Vite plugin that can run TypeScript, VLS, vue-tsc, ESLint in worker thread.
A Vite plugin that can run TypeScript, VLS, vue-tsc, ESLint, Stylelint in worker thread.

<div :style="{ 'display': 'flex' }">
<a href="https://www.npmjs.com/package/vite-plugin-checker" :style="{ 'margin-right': '4px' }"><img src="https://img.shields.io/npm/v/vite-plugin-checker" /></a>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"pre-commit": "pnpm exec lint-staged"
},
"lint-staged": {
"{packages}/**/*.{js,ts}": [
"packages/**/*.{js,ts}": [
"eslint --fix",
"prettier --write",
"git add"
Expand Down
1 change: 1 addition & 0 deletions packages/runtime/src/components/Diagnostic.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
ESLint: '#7b7fe3',
VLS: '#64b587',
'vue-tsc': '#64b587',
Stylelint: '#ffffff',
}

const fileRE = /(?:[a-zA-Z]:\\|\/).*(:\d+:\d+)?/g
Expand Down
10 changes: 10 additions & 0 deletions packages/vite-plugin-checker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
},
"peerDependencies": {
"eslint": ">=7",
"meow": "^9.0.0",
"stylelint": ">=13",
"typescript": "*",
"vite": "^2.0.0 || ^3.0.0-0",
"vls": "*",
Expand All @@ -69,6 +71,12 @@
"eslint": {
"optional": true
},
"meow": {
"optional": true
},
"stylelint": {
"optional": true
},
"typescript": {
"optional": true
},
Expand All @@ -88,8 +96,10 @@
"@types/lodash.pick": "^4.4.6",
"@volar/vue-typescript": "^0.33.0",
"esbuild": "^0.14.27",
"meow": "^9.0.0",
ModyQyW marked this conversation as resolved.
Show resolved Hide resolved
"npm-run-all": "^4.1.5",
"optionator": "^0.9.1",
"stylelint": "^14.0.0",
"tsup": "^6.2.2",
"typescript": "~4.5.5",
"vls": "^0.7.6",
Expand Down
8 changes: 6 additions & 2 deletions packages/vite-plugin-checker/src/Checker.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import invariant from 'tiny-invariant'
import { isInVitestEntryThread, isMainThread } from './utils.js'

import type { ServeAndBuildChecker, BuildInCheckerNames } from './types.js'
import { createScript, Script } from './worker.js'

// still an only issue https://github.com/microsoft/TypeScript/issues/29808#issuecomment-829750974
import type {} from 'vite'
import type { CreateDiagnostic, BuildInCheckers } from './types.js'
import type {
CreateDiagnostic,
BuildInCheckers,
ServeAndBuildChecker,
BuildInCheckerNames,
} from './types.js'

if (!(isMainThread || isInVitestEntryThread)) {
process.stdout.isTTY = true
Expand Down
145 changes: 145 additions & 0 deletions packages/vite-plugin-checker/src/checkers/stylelint/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import chokidar from 'chokidar'
import stylelint from 'stylelint'
import translateOptions from './options'
import path from 'path'
import { fileURLToPath } from 'url'
import { parentPort } from 'worker_threads'

import { Checker } from '../../Checker.js'
import { FileDiagnosticManager } from '../../FileDiagnosticManager.js'
import {
composeCheckerSummary,
consoleLog,
diagnosticToRuntimeError,
diagnosticToTerminalLog,
filterLogLevel,
normalizeStylelintDiagnostic,
toViteCustomPayload,
} from '../../logger.js'
import { ACTION_TYPES, DiagnosticLevel } from '../../types.js'

const manager = new FileDiagnosticManager()

import type { CreateDiagnostic } from '../../types.js'

const __filename = fileURLToPath(import.meta.url)

const createDiagnostic: CreateDiagnostic<'stylelint'> = (pluginConfig) => {
let overlay = true
let terminal = true

return {
config: async ({ enableOverlay, enableTerminal }) => {
overlay = enableOverlay
terminal = enableTerminal
},
async configureServer({ root }) {
if (!pluginConfig.stylelint) return

const translatedOptions = translateOptions(pluginConfig.stylelint.lintCommand)

const logLevel = (() => {
if (typeof pluginConfig.stylelint !== 'object') return undefined
const userLogLevel = pluginConfig.stylelint.dev?.logLevel
if (!userLogLevel) return undefined
const map = {
error: DiagnosticLevel.Error,
warning: DiagnosticLevel.Warning,
} as const

return userLogLevel.map((l) => map[l])
})()

const dispatchDiagnostics = () => {
const diagnostics = filterLogLevel(manager.getDiagnostics(), logLevel)

if (terminal) {
diagnostics.forEach((d) => {
consoleLog(diagnosticToTerminalLog(d, 'Stylelint'))
})
const errorCount = diagnostics.filter((d) => d.level === DiagnosticLevel.Error).length
const warningCount = diagnostics.filter((d) => d.level === DiagnosticLevel.Warning).length
consoleLog(composeCheckerSummary('Stylelint', errorCount, warningCount))
}

if (overlay) {
parentPort?.postMessage({
type: ACTION_TYPES.overlayError,
payload: toViteCustomPayload(
'stylelint',
diagnostics.map((d) => diagnosticToRuntimeError(d))
),
})
}
}

const handleFileChange = async (filePath: string, type: 'change' | 'unlink') => {
const absPath = path.resolve(root, filePath)

if (type === 'unlink') {
manager.updateByFileId(absPath, [])
} else if (type === 'change') {
const { results: diagnosticsOfChangedFile } = await stylelint.lint({ files: filePath })
const newDiagnostics = diagnosticsOfChangedFile
.map((d) => normalizeStylelintDiagnostic(d))
.flat(1)
manager.updateByFileId(absPath, newDiagnostics)
}

dispatchDiagnostics()
}

// initial lint
const { results: diagnostics } = await stylelint.lint({
cwd: root,
...translatedOptions,
...pluginConfig.stylelint.dev?.overrideConfig,
})

manager.initWith(diagnostics.map((p) => normalizeStylelintDiagnostic(p)).flat(1))
dispatchDiagnostics()

// watch lint
const watcher = chokidar.watch([], {
cwd: root,
ignored: (path: string) => path.includes('node_modules'),
})
watcher.add(translatedOptions.files as string)
watcher.on('change', async (filePath) => {
handleFileChange(filePath, 'change')
})
watcher.on('unlink', async (filePath) => {
handleFileChange(filePath, 'unlink')
})
},
}
}

export class StylelintChecker extends Checker<'stylelint'> {
public constructor() {
super({
name: 'stylelint',
absFilePath: __filename,
build: {
buildBin: (pluginConfig) => {
if (pluginConfig.stylelint) {
const { lintCommand } = pluginConfig.stylelint
return ['stylelint', lintCommand.split(' ').slice(1)]
}
return ['stylelint', ['']]
},
},
createDiagnostic,
})
}

public init() {
const createServeAndBuild = super.initMainThread()
module.exports.createServeAndBuild = createServeAndBuild
super.initWorkerThread()
}
}

const stylelintChecker = new StylelintChecker()
stylelintChecker.prepare()
stylelintChecker.init()
Loading