stylelint icon indicating copy to clipboard operation
stylelint copied to clipboard

Add support for running in a browser

Open oskarkrawczyk opened this issue 6 years ago • 24 comments

I'm currently trying to replace CSSLint in @jsfiddle with Stylelint. Got it working thanks to a proof-of-concept made here https://github.com/m-allanson/stylelint-browser-demo

Unfortunately turns out this works fine but with a very old version of Stylelint, specifically the 5.x branch. Once updated deps to the newest Stylelint, the whole client-side bundle breaks apart with a slightly cryptic Error: Cannot find module "." error.

I was wondering if anyone around here can help out figure out if it's even possible to run the newest Stylelint on the client-side (this is a requirement for JSFiddle as our env only supports linting that way).


A bit more on what my config looks like:

webpack.config:

import webpack from "webpack"
import path from "path"

export default {
  entry: "./site/index.js",
  output: {
    path: path.resolve(__dirname, "site"),
    publicPath: "/",
    filename: "bundle.js",
  },
  resolve: {
    root: path.resolve(__dirname),
    alias: {
      "resolve-from": "empty-module",
      cosmiconfig: "empty-module",
      doiuse: "empty-module",
      globby: "empty-module",
      globjoin: "empty-module",
      multimatch: "empty-module",
      path: "empty-module",
    },
    modulesDirectories: [
      "node_modules",
      "site",
    ]
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: "babel-loader",
        exclude: /node_modules/,
      },
      {
        test: /\.json$/,
        loader: "json-loader",
      },
    ],
  },
  plugins: [
    new webpack.optimize.DedupePlugin(),
    new webpack.ContextReplacementPlugin(/stylelint/, /NEVER_MATCH^/),
  ],
  node: {
    fs: "empty",
    module: "empty",
  },
}

package.json:

{
  "name": "stylelint-browser-demo",
  "version": "0.0.1",
  "description": "A proof-of-concept to demo stylelint running in a browser.",
  "main": "site/bundle.js",
  "scripts": {
    "start": "npm run build && http-server ./site",
    "start-dev": "webpack-dev-server --content-base site/ --hot --inline",
    "build": "webpack --progress --colors && stat -f%z site/bundle.js",
    "lint": "eslint . --ignore-path .gitignore",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "m-allanson",
  "license": "MIT",
  "dependencies": {
    "babel-cli": "^6.1.18",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.6.0",
    "http-server": "^0.9.0",
    "imports-loader": "^0.6.5",
    "json-loader": "^0.5.4",
    "stylelint": "^9.10.1",
    "stylelint-config-standard": "^18.2.0",
    "webpack": "^1.12.14"
  },
  "devDependencies": {
    "eslint": "^2.4.0",
    "eslint-config-stylelint": "^1.0.0",
    "webpack-dev-server": "^1.14.1"
  },
  "engines": {
    "node": "5.9.1",
    "npm": "3.7.3"
  },
  "eslintConfig": {
    "extends": "stylelint",
    "env": {
      "browser": true
    },
    "rules": {
      "arrow-spacing": 2,
      "no-var": 2,
      "object-shorthand": 2,
      "prefer-const": 2,
      "template-curly-spacing": 2
    }
  },
  "babel": {
    "presets": [
      "babel-preset-es2015"
    ]
  }
}

The build bundle.js is attached - bundle.js.zip

Lint method is exposed as Stylelint.verify()

@ai Any chance you could help out? (we had a brief chat about this on Twitter).

oskarkrawczyk avatar Feb 01 '19 11:02 oskarkrawczyk

Seems like we have too dymanic require somewhere.

I think client side use case is important. PostCSS and Autoprefixer both support it.

ai avatar Feb 01 '19 11:02 ai

Unfortunately turns out this works fine but with a very old version of Stylelint, specifically the 5.x branch

We ran into our own difficulties trying to get stylelint to run client-side. We ended up going server-side for our demo on the website. However, this was a couple of years ago and the tooling might have moved on enough to take another crack at it.

Seems like we have too dymanic require somewhere.

The heaviest parts of stylelint are the:

  • parsers
  • rules

I believe we use dynamic requires for these.

They were added to improve the bad performance outlined in https://github.com/stylelint/stylelint/issues/2454. However, there's probably a better way. One that improves performance and caters for the client side use case.


I've made a couple of attempts recently to use ncc but the load-time of stylelint seemed to worsen so I didn't pursue it.

A good way forward might be to adopt prettier's approach to building. They are dealing with similar problems to us:

  • the need for a client-side (standalone) build
  • bundling a number of (pretty heavy) parsers

And they have far more contributors dedicating their time to the problem.

Or it might be that unpicking some of our dynamic requires to support your webpack config will require less effort and time.

Has anyone else got any ideas?

jeddy3 avatar Feb 01 '19 15:02 jeddy3

I think we can have internal API for client-side and auto-load API for CLI:

// Client-side

let core = require('stylelint/…')
let scss = require('postcss-scss')

core.check(source, { parser: scss })
// CLI
let autoloader = require('stylelint/…')

autoloader.autodetect(source, filename) // will require core and scss dynamically inside

ai avatar Feb 01 '19 15:02 ai

Thanks for chiming in guys (it's rare to see contributors acutally responding on GHI these days).

Or it might be that unpicking some of our dynamic requires to support your webpack config will require less effort and time.

Slightly out of my understanding how all of this might be put together to work, so I think for now I'll stick to using the 5.x branch (6.x also kind of works, but there is a JS error, 8.x doesn't throw errors but for some reason the verify promise doesn't seem to fulfill [on the client side]).

I have my hopes someone has the time to find a way and make Stylelint work on the client, that would really be tremendous.

oskarkrawczyk avatar Feb 03 '19 08:02 oskarkrawczyk

I've looked into this a couple more times, but I wasn't able to make much headway.

Unfortunately, I don't foresee myself having time to dig deeper anytime soon.

I think we can have internal API for client-side and auto-load API for CLI:

Sounds good to me. It'll help with the weight of the parsers. I believe this is similar to what prettier does.

I have my hopes someone has the time to find a way and make Stylelint work on the client, that would really be tremendous.

I'm going to label this issue up as an enhancement and help wanted. Hopefully, this issue will pique someone's interest and they'll champion this feature.

jeddy3 avatar Feb 03 '19 13:02 jeddy3

I'm going to label this issue up as an enhancement and help wanted

Sounds good! I'll push this issue though JSFiddle channel, perhaps someone will have the resources to look into this. If not, do you mind if I post it as a bounty? I want to be 100% sure you're ok with that.

oskarkrawczyk avatar Feb 03 '19 13:02 oskarkrawczyk

If not, do you mind if I post it as a bounty?

Sounds good to me!

jeddy3 avatar Feb 03 '19 14:02 jeddy3

I will promote this issue in PostCSS twitter too.

ai avatar Feb 04 '19 15:02 ai

Couldn't sleep, wanted to see if it could be hacked together to run similarly to the POC done earlier. Hopefully this might help someone in the future.

tl;dr; it's possible in the current state

https://github.com/konpikwastaken/stylelint-browser-poc

konpikwastaken avatar Feb 05 '19 11:02 konpikwastaken

@konpikwastaken great job :+1:

alexander-akait avatar Feb 05 '19 12:02 alexander-akait

It's great to see work happening around this!

@konpikwastaken's comment on their linked repo is possibly the key to making this work well:

The engine should be refactored & decoupled from anything that depends on an actual file system (e.g. reading configs, etc). Majority of the time was spent working around assumptions that the execution environment has IO access (e.g. path, fs).

m-allanson avatar Feb 18 '19 09:02 m-allanson

The engine should be refactored & decoupled from anything that depends on an actual file system (e.g. reading configs, etc).

Sounds great!

I think we can close this issue when someone finds time to do that refactoring.

jeddy3 avatar Feb 18 '19 10:02 jeddy3

The engine should be refactored & decoupled from anything that depends on an actual file system (e.g. reading configs, etc). Majority of the time was spent working around assumptions that the execution environment has IO access (e.g. path, fs).

@m-allanson and I are going to look into this.

It'd be great if we ended up with something similar to how Prettier does it:

<script src="https://unpkg.com/[email protected]/universal.js"></script>
<script src="https://unpkg.com/[email protected]/syntax-scss.js"></script>
<script>
  stylelint.lint("a#{var} { color: red; }", {
    config: { "rules": { "color-named"} },
    syntax: "scss",
  });
</script>

The issue is related to https://github.com/stylelint/stylelint/issues/2454, where we need to resolve some performance issues.

jeddy3 avatar Apr 30 '20 12:04 jeddy3

@m-allanson and I are going to look into this.

In fact we have already spent some time working on this 😄. You can see a live demo of progress here: https://upbeat-yonath-79931e.netlify.app

I think some refactoring of stylelint's internals would make this quite achievable, and would allow us to avoid previous approaches of stubbing out modules that access the filesystem.

Check out this repo to see how the demo works. It's a relatively small amount of code.

m-allanson avatar Apr 30 '20 12:04 m-allanson

Following @jeddy3's changes in #4729 I've got a WIP branch here: https://github.com/stylelint/stylelint/compare/browser-bundle. It's still pretty rough but demonstrates that this should work.

Syntax parsers are bundled separately and can be loaded on demand.

The stylelint bundle comes out at 250KB (minified + gzipped), with the syntax parsers ranging between 30KB and 260KB.

bundle min + gzip min only
stylelint 251K 1.3MB
CSS in JS 260K 1.0MB
HTML 120K 433K
Less 31K 106K
Markdown 145K 509K
Sass 71K 287K
Scss 32K 109K
SugarSS 33K 113K

There's an updated demo at https://stylelint-browser-bundle.netlify.app/. Check the network tab when selecting a syntax to see parsers being loaded in as needed.

m-allanson avatar May 04 '20 22:05 m-allanson

I've opened a draft PR that details the current progress of this feature: https://github.com/stylelint/stylelint/pull/4796

m-allanson avatar May 21 '20 12:05 m-allanson

Any updates on this?

aprTaylor avatar Sep 01 '20 17:09 aprTaylor

VS Code now runs in the browser as well as on desktop. In theory, if browser support ever does get implemented, it may then be possible to have the VS Code extension run in the browser as well since vscode-languageserver has browser support.

adalinesimonian avatar Nov 04 '21 21:11 adalinesimonian

I saw that. Very exciting stuff.

I don't think we've far off. @m-allanson has mostly got us there. I think we'll need to:

  • https://github.com/stylelint/stylelint/issues/5291
  • Remove processors (which can only happen when there's a robust and maintained styled-components custom syntax)

jeddy3 avatar Nov 08 '21 18:11 jeddy3

I don't think we've far off. @m-allanson has mostly got us there. I think we'll need to:

* [Move to ESM #5291](https://github.com/stylelint/stylelint/issues/5291)

I'll need to investigate a few things downstream. I remember I was having issues using ESM in VS Code while testing. However, I don't really remember exactly what went wrong. I'll take a look into it, maybe after I take care of a few issues I'm working on in v1.2.

adalinesimonian avatar Nov 08 '21 23:11 adalinesimonian

I'm experimenting with getting stylelint to work on browser. Demo: https://ota-meshi.github.io/stylelint4b/Playground.html

I now realize that there are a lot of tricks required to get stylelint working on the browser. I would like to share those information.

  • Stub

We need to replace some file-related external modules with modules that work on the browser. https://github.com/ota-meshi/stylelint4b/blob/e210f823992126c610c144fb3e3da817167b2c73/packages/stylelint4b/build/index.js#L51

Also, stylelint's internal module needs to be replaced with modules that works on the browser.

https://github.com/ota-meshi/stylelint4b/blob/e210f823992126c610c144fb3e3da817167b2c73/packages/stylelint4b/build/index.js#L17

  • Replace source code

Some source code needs to be replaced and rewritten for the bundle.

https://github.com/ota-meshi/stylelint4b/blob/e210f823992126c610c144fb3e3da817167b2c73/packages/stylelint4b/build/index.js#L35

  • Configuration and plugin resolution

It hacks the require function to resolve plugins and shareable configs.

https://github.com/ota-meshi/stylelint4b/blob/e210f823992126c610c144fb3e3da817167b2c73/packages/stylelint4b/src/require-shim.js

Users can resolve plugins and shareable configurations by pre-registering aliases.

https://ota-meshi.github.io/stylelint4b/stylelint4b/#stylelint4b-alias https://github.com/ota-meshi/stylelint4b/blob/e210f823992126c610c144fb3e3da817167b2c73/packages/stylelint4b/src/alias-module.js

Unless you use plugins or shareable configs, we probably don't need it.

ota-meshi avatar Nov 12 '22 01:11 ota-meshi

@ota-meshi Thanks for the share. That's an interesting trick! I now understand well what we need for running Stylelint on browsers.

ybiquitous avatar Nov 13 '22 06:11 ybiquitous

Hi all, I was trying to integrate stylelint too for phcode.dev, the browser version of braclkerts.io code editor.

Is it still not supported/how much work is left to port it to the browser? Many thanks for considering my request.

abose avatar Dec 31 '22 13:12 abose

Stylus is a usercsss browser extension, which uses stylelint as the CSS linter in the editor. Therefore we have a prebuilt bundle here: https://github.com/openstyles/stylelint-bundle

We did shim a lots of modules, see: https://github.com/openstyles/stylelint-bundle/blob/master/rollup.config.js

eight04 avatar Jan 02 '23 09:01 eight04