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

CSS preprocessors? #181

Closed
punmechanic opened this issue Dec 11, 2016 · 51 comments
Closed

CSS preprocessors? #181

punmechanic opened this issue Dec 11, 2016 · 51 comments

Comments

@punmechanic
Copy link

punmechanic commented Dec 11, 2016

Just wondering how Svelte ties in to existing CSS preprocessors? I'm not seeing any documentation around this, and I'm not expecting it to just magically work if I add less or scss content to my style tags.

This would mainly be useful when using existing frameworks like Bootstrap.

@Rich-Harris
Copy link
Member

Right now, this isn't supported. It's come up previously in the context of compile-to-JS languages (#98), and tangentially in the context of creating components from multiple files (i.e. a linked stylesheet/script) in #65. But until then, it would be a case of preprocessing the CSS and creating a single-file component to hand off to Svelte.

@Rich-Harris Rich-Harris added this to the one day milestone Dec 11, 2016
@nsaunders
Copy link

FWIW, we made our own svelte-cli analog that runs the stylesheet through the less compiler and then hands the result off to the svelte compiler. I can't open-source it because my employer doesn't understand the benefits, but it wasn't hard. Currently I am trying to modify a fork of the svelte-loader to use our compiler that wraps less and svelte.

@dschulten
Copy link

dschulten commented Jan 1, 2017

@therealnicksaunders What did you use as extension point in svelte-cli? It would make sense to introduce an official compiler-plugin api to svelte-cli which allows to process compilation steps. See also #194

@nsaunders
Copy link

@dschulten we didn't need most of the options in svelte-cli so I wrote our version from scratch. It consumes our xxxxx-svelte library that has a compile method that takes care of less precompilation and then passes the result to svelte's compile method. The API pretty much matches svelte, with the main difference being that it is asynchronous due to the less API being asynchronous.

I guess my point was that it was not terribly difficult as an interim solution, but I agree that this is going to be something a lot of people want out-of-the-box.

Maybe a good first step would be to make svelte's compile method asynchronous. That would have allowed me to fork svelte-cli, svelte-loader, et cetera, and leave them mostly unmodified (just swapping out the svelte dependency in favor of our own version).

@Rich-Harris Rich-Harris mentioned this issue Jan 4, 2017
@colshacol
Copy link

I don't think it would take more than an hour or two to create a simple npm package that would pre-parse the Svelte HTML files for <style> enclosures, pass that through the compiler of your choice, then pass the entire file on to Svelte. It would be hacky, but it'd work for now.

I may work on this this evening.

@praneybehl
Copy link

Has this been implemented yet?

@Rich-Harris
Copy link
Member

Not yet, no

@kazzkiq
Copy link

kazzkiq commented Apr 26, 2017

The only thing keeping Svelte from being approved for a big project right now is the lack of official support for SASS/SCSS by the framework (which is a must for all company projects where I work).

Has anyone managed to "hack" SASS support into Svelte? If yes, do you guys have any tips or perhaps a code sample to share?

@PaulBGD
Copy link
Member

PaulBGD commented Apr 26, 2017

The main issue is that Svelte can't just add support for SASS, it has to add full support for any CSS preprocessors. Svelte also modifies the generated CSS, so the build process has to actually be handled by Svelte as well.

@piyushchauhan2011
Copy link

I'm currently just using the scss through webpack build process. Having to use the hash based css names through sass, ahh - someone might need to have a go at that or CSS modules thing. I think, due to compilation everything is very limited to change, like supporting Sass and TypeScript. I would love to learn more about the patterns that could be invented for it to become approved for bigger projects.

@marianoviola
Copy link

My current CSS build process consists in creating a single-file component as suggested by @Rich-Harris, importing it in my global style file which is then processed by postcss-nextcss.

However, I'm experiencing the following issues:

  • Svelte doesn't support some postcss-nextcss features which I regularly use in my projects such as custom properties sets, custom media queries/ranges and nested selectors (raising an error during compilation)
  • Every time the components.css file is updated (in my case by Rollup), I need to rerun the global style build script in order to include the updated components. In this way I'm able to reuse global custom properties

At the end the build process works but I can't use many useful nextcss features in scoped style, and the overall build process is quite slow causing unwanted Browsersync reloads.

Do you have any suggestion to improve it?

This was referenced Aug 7, 2017
@jslegers
Copy link

jslegers commented Aug 7, 2017

@Rich-Harris

Instead of implementing features like transitions (#525) & easing functions (#549) in Svelte, doesn't it make more sense to add support for CSS preprocessors like Less & Sass and use preprocessors to add custom transition behavior to components?

It seems to me that high level features like transitions & easing don't really belong in a framework like Svelte, whereas preprocessor support is pretty much essential for many companies to even consider using a framework like Svelte.

@Rich-Harris
Copy link
Member

Preprocessors can't help you with transitions (which are already implemented, but need a section in the docs) — that needs to be a framework-level feature. No amount of CSS could do this, for example:

https://svelte.technology/repl?version=1.29.0&gist=f94338ee682bddccb8596874cad5b68d

For one thing, you can't use an easing function like that in a CSS animation. For another, if Svelte were to remove the <span> from the DOM immediately (because it didn't know about the out transition), it would be impossible to smoothly fade it out. Svelte transitions let you build fairly complex (and data-driven, via parameters) intro/outro effects that would be impossible with CSS itself, but that use CSS animations under the hood (i.e. off the main thread).

Realistically, we have two choices:

  1. Wait until v2, so we can make svelte.compile async — without that, it's impossible to plug in support for some preprocessors
  2. Let build tool plugins like rollup-plugin-svelte do the preprocessing. That's not ideal, because it needs to be implemented by every build tool plugin separately

In the meantime, it's totally possible to use preprocessors with something like this pseudo-code:

code = code
  .replace(/<style>(.+)<\/style>/, (m, styles) => `<style>${preprocess(styles)}</style>`)

@jslegers
Copy link

jslegers commented Aug 7, 2017

No amount of CSS could do this. For one thing, you can't use an easing function like that in a CSS animation.

Really? Doesn't the transition-timing-function property in combination with the cubic-bezier keyword allow you do achieve pretty much the same result, but with better performance?

Pretty much every modern browser supports this, including IE > 9.

Or is there something I'm missing here?

if Svelte were to remove the <span> from the DOM immediately (because it didn't know about the out transition), it would be impossible to smoothly fade it out.

Sure, it does require some trickery to do transitions in CSS right, especially if you're hiding & showing things. But again, this is nothing that can't be done with just SCSS.

Svelte transitions let you build fairly complex (and data-driven, via parameters) intro/outro effects that would be impossible with CSS itself, but that use CSS animations under the hood (i.e. off the main thread).

Again, that's precisely what languages like SCSS are for. Sure, many people use them only for parameterization, but the language is far more powerful than that.

Wait until v2, so we can make svelte.compile async — without that, it's impossible to plug in support for some preprocessors

Works for me, I guess. When is v2 expected to be released?

In the meantime, it's totally possible to use preprocessors with something like this pseudo-code:

code = code
  .replace(/<style>(.+)<\/style>/, (m, styles) => `<style>${preprocess(styles)}</style>`)

No thanks!

Having to use dirty hacks like this as workarounds to implement a missing feature as essential as SCSS support is a good reason not to use this framework for the time being. I think I'll just wait ;-)

@nsaunders
Copy link

@jslegers Pretty much what @Rich-Harris said. On our project, we built a tiny CSS preprocessing Webpack loader that is applied to *.html files before they are handed off to the svelte-loader. The loader looks for inline LESS stylesheets via a regex similar to Rich's, runs their contents through the LESS compiler, and then replaces the original stylesheet with the processed stylesheet. We used the less-loader's getOptions service which even made it easy to tie into Webpack's module loading system for LESS imports and such.

This has worked flawlessly for us, so maybe your team can do something similar?

@jslegers
Copy link

jslegers commented Aug 7, 2017

This has worked flawlessly for us, so maybe your team can do something similar?

I'm looking at Svelte first and foremost as a foundation upon which to build version 2 of Cascade Framework, which is a one-man project I do in my spare time. The project being first and foremost a CSS framework makes native SCSS support pretty essential.

At my employer's, the current approach for the frontend of our products is to use TypeScript + React + Webpack. I'd really love to recommend Svelte + Rollup or Svelte + Webpack as an alternative for React + Webpack, but here as well I consider native SCSS support a rather essential feature. TypeScript support (#98) would also be pretty essential. And even that wouldn't be enough, I'm afraid, to convince my colleagues to make that kind of investment. I'll need a much stronger case to convince anyone to replace React + Webpack with an alternative that sure looks promising but doesn't seem mature enough just yet.

@rhengles
Copy link

rhengles commented Aug 7, 2017

There is already an issue for that - #65 - and it's on the roadmap (#622) under "Medium term".

@Rich-Harris
Copy link
Member

Or is there something I'm missing here?

Yep. cubic-bezier is very limited. You can't do the elastic easing function I showed above, for example.

Sure, it does require some trickery to do transitions in CSS right, especially if you're hiding & showing things. But again, this is nothing that can't be done with just SCSS.

There's no way to style an element that's no longer in the DOM. You have to delay the removal of the node from the DOM. In other words, the framework needs to understand whether or not a given element has an outro transition — i.e., a framework-level feature.

Again, that's precisely what languages like SCSS are for.

SCSS can't do things like vary the duration of a transition based on some value that isn't known until runtime.

Works for me, I guess. When is v2 expected to be released?

When it's done 😉

Just as an aside, saying things like 'that wouldn't be enough, I'm afraid, to convince my colleagues to make that kind of investment' doesn't motivate the maintainers of OSS projects to drop everything to support your use case — it just creates antagonism. This is a project that the other maintainers and I work on in our spare time. If you want a particular feature to land sooner, here's the link you're looking for!

@jslegers
Copy link

jslegers commented Aug 7, 2017

Yep. cubic-bezier is very limited. You can't do the elastic easing function I showed above, for example.

* oops *

What about a sequence of key-frames?

There's no way to style an element that's no longer in the DOM. You have to delay the removal of the node from the DOM.

In such a case, can't you just add/remove a class with a CSS transition, timeout to allow the transition to render and then remove the element from the DOM?

SCSS can't do things like vary the duration of a transition based on some value that isn't known until runtime.

Fair enough. How often do you need that, though?
The number of use cases that requires the duration of a transition to be determined at runtime seems pretty small to me.

Just as an aside, saying things like 'that wouldn't be enough, I'm afraid, to convince my colleagues to make that kind of investment' doesn't motivate the maintainers of OSS projects to drop everything to support your use case — it just creates antagonism.

I'm sorry if I sound antagonistic. That's 100% unintended. In fact, I haven't been as exited about any open source project as much I am about Svelte in years.

And Rollup looks just as promising by what I've seen so far, so kudos for that as well! 👍

Anyway, I absolutely love where you're going with Svelte & I'm seriously considering it as a foundation for my own open source projects once it has features like native SCSS support & named scoping (#570).

I guess I'll have to work some more on my social skills if I hadn't made that clear enough ;-)

@Rich-Harris
Copy link
Member

Thanks — it's all cool, it's just very hard to balance all the competing feature requests while keeping the project focused and maintainable. Svelte will never 'natively' support SCSS, insofar as that means including node-sass and using it without some form of configuration, because that kind of coupling is dangerous. It's more likely to look something like this:

import svelte from 'svelte';
import scss from 'svelte-scss';

const code = `
<div><p>styled with SCSS</p></div>

<style type='scss'>
  div {
    p {
      color: red;
    }
  }
</style>
`;

svelte.compile(code, { preprocess: { scss } })
  .then(result => ...);

That way, we just need to define a preprocessor interface and it can be reused with LESS, postcss, etc.

@jslegers
Copy link

jslegers commented Aug 8, 2017

it's just very hard to balance all the competing feature requests while keeping the project focused and maintainable.

I know.
I work in R&D professionally & have my own open source projects on the side.
I know where you're coming from.
I feel your pain ;-)

I guess I'm just trying to throw in my 5 cents as I really really love where the Svelte core framework is heading in general. It's just missing a few features that are pretty essential to me, and I guess I'm just frustrated that features like transitions are getting so much attention that could have been used on improving the core, the build process or the docs...

But hey... it's not my project and I don't even have enough time for my own projects at the moment, so sorry again if I come off as ungrateful or greedy :-)

It's more likely to look something like this

What would work for me, I guess, would be an officially supported way to plug node-sass into the standard build process.

As an "end user", I expect to be able to write my components like this...

<div><p>styled with SCSS</p></div>
<style>
  div {
    p {
      color: red;
    }
  }
</style>

... and just run npm run build, the exact same way I'd do it if it my styles were CSS instead of SCSS.

Am I OK with having an extra build step in the build process? Sure!
Am I OK with having to install a separate plugin to add this feature? Sure!

I see why you'd want to separate preprocessor support from the core library, but adding SCSS or Less support shouldn't be any more complicated than adding a plugin... and I don't want to have to write my own plugins or look for third party plugins for something as elementary as SCSS support!

"End users" shouldn't have to know anything about the build process other than how to run it. They should be able to treat it like a black box. It shouldn't matter to an "end user" of Svelte whether it's Rollup or Webpack that's under the hood and how many build steps are used to get from src to dist. And they shouldn't have to write their own build processes for adding preprocessor support.

Also, I don't want to risk losing my preprocessor support every time I upgrade the Svelte build process. Without at least an officially supported plugin for preprocessor support, the chances of that occuring are pretty high.

@PaulBGD
Copy link
Member

PaulBGD commented Aug 8, 2017

What would work for me, I guess, would be an officially supported way to plug node-sass into the standard build process.

That would be the point of this issue then!

@nsaunders
Copy link

I'm going to add some fuel to the fire here and state my current position that CSS preprocessing is a separate concern and should not be handled by Svelte at all. svelte-cli might give the impression that Svelte is the build process, but, frankly, for any non-trivial project (like the kind that would require CSS preprocessing), Svelte should be merely one step in the build process. Let the Svelte compiler itself focus on the native languages of the Web (CSS, HTML, JavaScript) and keep unnecessary complexity out!

Instead of lobbying for plugins or CSS preprocessing in the Svelte core, perhaps our little Svelte community should tackle the problem of real build tools like Webpack, Browserify, Rollup, et cetra, lacking the necessary plugins/loaders to process HTML modules. (For example, as I previously explained, Webpack has less-loader, but I couldn't find a loader that could be applied to an HTML file with inlined LESS). This is a relatively easy problem for just about anybody to solve, as it only requires knowledge of the build tool and some string manipulation. It is way easier than working on the source of the Svelte compiler.

I think it's up to @Rich-Harris to guide the direction of this "anti-framework". But if I were him, I would strongly prefer to outsource the CSS preprocessing problem instead of wasting my time trying to build and maintain a plugin API that is perhaps unnecessary.

esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 25, 2017
esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 25, 2017
esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 25, 2017
esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 25, 2017
esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 25, 2017
esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 25, 2017
esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 26, 2017
esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 26, 2017
esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 26, 2017
esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 27, 2017
esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 27, 2017
esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 29, 2017
esarbanis added a commit to esarbanis/svelte that referenced this issue Nov 29, 2017
@Rich-Harris
Copy link
Member

Rich-Harris commented Dec 3, 2017

As of 1.44, Svelte comes with a preprocess function, which provides a standard way to transform components before they get compiled:

const processed = await svelte.preprocess(source, {
  markup: ({ content }) => {
    // `content` is the entire component string
    return { code: '...', map: {...} };
  },

  style: ({ content, attributes }) => {
    // `content` is what's inside the <style> element, if present
    // `attributes` is a map of attributes on the element
    if (attributes.type !== 'text/scss') return;
    return { code: '...', map: {...} };
  },

  script: ({ content, attributes }) => {
    // `content` is what's inside the <script> element, if present
    // `attributes` is a map of attributes on the element
    if (attributes.type !== 'text/coffeescript') return;
    return { code: '...', map: {...} };
  }
});

The style and script preprocessors will run after the markup preprocessor. Each preprocessor can return a) nothing (in which case no transformation takes place), b) a { code, map } object, or c) a Promise that resolves to a) or b). Note that sourcemaps are currently discarded, but will be used in future versions of Svelte.

This is separated from the compiler by design — it allows (for example) component authors to write components using SASS or whatever, but still ship uncompiled 'vanilla' components (so that consumers of those components don't need to know what languages were used). It also means that svelte.compile retains its synchronous API.

I don't envisage that most people will use svelte.preprocess directly, but rather something like this (note: svelte-preprocess-postcss doesn't exist yet!). Substitute [your-preferred-tool] for Rollup:

// rollup.config.js
import svelte from 'rollup-plugin-svelte';
import postcss from 'svelte-preprocess-postcss';

export default {
  // ...
  plugins: [
    svelte({
      preprocess: {
        style: postcss({ plugins: [...] })
      }
    })
  ]
};

If anyone is interested in helping out with official svelte-preprocess-[thing] modules, shout!

@Rich-Harris
Copy link
Member

Update: rollup-plugin-svelte now supports the preprocess option.

@sp00m
Copy link

sp00m commented Dec 3, 2017

That's awesome, great work @Rich-Harris, thanks for working on this!

@kazzkiq
Copy link

kazzkiq commented Dec 3, 2017

Because a simple reaction just isn't enough for this one:

wow

@Rich-Harris
Copy link
Member

😀

At @lukeed's request, I put together a small demo:

@lukeed
Copy link
Member

lukeed commented Dec 4, 2017

I feel special 😚 Thanks for the quick demo

@scottbedard
Copy link
Contributor

Awesome, nice work!

@marianoviola
Copy link

Great work @esarbanis and @Rich-Harris!

P.S. this is a slight adaptation of the rollup.config.js contained in the demo using PostCSS instead of Sass: https://gist.github.com/marianoviola/c9bbf5e80359df7d109bf73476475c2e

@Vishwaas
Copy link

Vishwaas commented Oct 7, 2018

Anybody has a sample webpack.config for the above plugin?

@colshacol
Copy link

I totally forgot to do this... almost a year and a half ago now...

#181 (comment)

@Vishwaas
Copy link

Vishwaas commented Oct 9, 2018

Yeah I was planning to do that but wanted to check if somebody has.
Also I tried to do the below only to realize the extracted file is css and not .scss or .less
A.

  1. Use MiniCssExtractPlugin (which extracts the css)
  2. Use less/sass to preprocess that
  3. pass the css generated by preprocessor to svelt

Or

B.

  1. Use emit:true in svelte loader
  2. preprocess emitted css

@tobias-kuendig
Copy link

For anyone lurking around this comment:

Check this out:
https://github.com/kaisermann/svelte-preprocess

@asos-tomp
Copy link

Here's an interesting problem:

I've written a custom transition that's using mask-image. Is there any way to get style preprocessor to understand css created by the transition compiler? 'cos right now I need to add some browser-prefixed variants manually..

e.g. I have a transition which must return:

return `mask-image: linear-gradient(${stops.join(",")});-webkit-mask-image: linear-gradient(${stops.join(",")});`

...rather than:

return `mask-image: linear-gradient(${stops.join(",")});`

Would be nice if svelte-preprocess-postcss and the like could find this css and process it.

@colshacol
Copy link

colshacol commented Apr 12, 2020

I don't think it would take more than an hour or two to create a simple npm package that would pre-parse the Svelte HTML files for <style> enclosures, pass that through the compiler of your choice, then pass the entire file on to Svelte. It would be hacky, but it'd work for now.

I may work on this this evening.

... 3 years later I have returned to apologize to you all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests