Skip to content
This repository has been archived by the owner on Apr 8, 2020. It is now read-only.

Css preprocessor? #132

Closed
nojaf opened this issue Jun 15, 2016 · 29 comments
Closed

Css preprocessor? #132

nojaf opened this issue Jun 15, 2016 · 29 comments

Comments

@nojaf
Copy link

nojaf commented Jun 15, 2016

Consider adding a css preprocessor option like Sass or Less.
Because who writes plain old css these days?

@laskoviymishka
Copy link
Contributor

This is easily handle by adding Sass or Less loader in webpack.config. Or you propose to made it in yo-templates? This possibly should be an option of generator.

@nojaf
Copy link
Author

nojaf commented Jun 15, 2016

Yes indeed an extra option in the yeoman thing

@SteveSandersonMS
Copy link
Member

Thanks for the suggestion. I'm not sure whether we'll handle it by adding further options to the generator, or by adding docs describing how to do it.

But it should be really straightforwards - just amend your webpack.config.js to specify a loader for your SASS/LESS/etc files. In that sense it's more of a webpack question than anything about this project, but I appreciate we should make that obvious in docs so people know there's no other hidden steps they need to follow :)

@RobCannon
Copy link

I highly recommend that you make it an option in the generator, otherwise you will get a lot of issues for people trying to set it up themselves.

@colltoaction
Copy link

colltoaction commented Jul 28, 2016

I'm actually having trouble with getting sass to work with prerendering. Does anyone have a working webpack.config.js for sass?

EDIT:

I'm no webpack expert, but I made it work. I don't really understand the implications of the following settings, maybe someone can shed some light. Based on webpack-contrib/css-loader#59

            {
                test: /\.scss$/,
                include: /ClientApp/,
                loaders: [
                    'css-loader/locals?module&localIdentName=[name]__[local]___[hash:base64:5]',
                    'sass-loader'
                ]
            }

@shobman
Copy link

shobman commented Aug 7, 2016

An option in the generator would go awesome. I'm having trouble getting it to work for less.

I added the loader to the webpack.config.js file directly after the existing .css line:
{ test: /\.less/, loader: extractCSS.extract('css!less') }

It works when i run 'webpack' directly at the CLI, but when I run 'dotnet run' there's an exception in the middleware when I make the first page request.

Exception:
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[0]
      An unhandled exception has occurred: Call to Node module failed with error: Error: Webpack compilation reported errors. Compiler output follows: Hash: 42861fb47b6854d4d6b6
      Version: webpack 1.13.1
      Time: 2609ms
                  Asset     Size  Chunks             Chunk Names
      webpack-output.js  19.6 kB       0  [emitted]  main

      ERROR in ./ClientApp/components/Counter.less
      Module build failed: Error: Cannot find module './../../node_modules/css-loader/lib/css-base.js'
          at Function.Module._resolveFilename (module.js:438:15)
          at Function.Module._load (module.js:386:25)
          at Module.require (module.js:466:17)
          at require (internal/module.js:20:19)
          at Object.<anonymous> (/Users/simon/code/Zenith.Core/src/Zenith.Core.Erp.Yo/node_modules/css-loader/index.js!/Users/simon/code/Zenith.Core/src/Zenith.Core.Erp.Yo/node_modules/less-loader/index.js!/Users/simon/code/Zenith.Core/src/Zenith.Core.Erp.Yo/ClientApp/components/Counter.less:62:19)

Has anyone got this working?

@colltoaction
Copy link

@shobman did you check my last comment? I couldn't make it work with extracttext, but it does without it.

@colltoaction
Copy link

colltoaction commented Aug 9, 2016

I'm sorry, I was terribly wrong. The issue here is that we need different webpack bundles for both configurations (we need different module loaders for sass browser vs sass server as the webpack maintainers show here), but this is not supported by aspnet-webpack.

See how the configuration is tweaked to work in the backend here: https://github.com/aspnet/JavaScriptServices/blob/master/src/Microsoft.AspNetCore.SpaServices/npm/aspnet-webpack/src/LoadViaWebpack.ts#L37. And if you try to use an array as configuration (allowed by webpack as seen here) you get this exception https://github.com/webpack/webpack/blob/8ff6cb5fedfc487665bb5dd8ecedf5d4ea306b51/lib/MultiCompiler.js#L39.

This is effectively breaking one of webpacks features, and it makes me wonder if this is the correct approach. Maybe instead of overriding the configuration we could define two configurations, and let aspnet-webpack search for the one it needs.

I'm close to making this work by manually tweaking LoadViaWebpack.ts, I'll share when it does work.

@colltoaction
Copy link

Well, I made it work, but not 100% with angular2. Either you:

  1. use extractCSS but lose style encapsulation
  2. don't use extractCSS at all, at the cost of not having styles in case client-side javascript is disabled (and also for the prerendered page before the framework kicks-in)

Steps:

  1. Read this to understand what are the requirements for the webpack side of things

    1. Ignore the css-loader?module parameter if you're using angular2: it'll break things because angular2 has built-in style scoping
  2. Edit webpack.config.js

    1. Choose one of the two loaders (neither works 100%)
              {
                  test: /\.scss$/,
                  include: /ClientApp/,
                  //loader: "css-loader!sass-loader" // WORKS but not with javascript disabled
                  loader: extractCSS.extract("style-loader", "css-loader!sass-loader") // WORKS but doesn't scope styles
              }
  3. Edit node_modules/aspnet-webpack/LoadViaWebpack.js (must do it again if updating the module!)

    1. Remove the client-side loader by adding
          webpackConfig.module = webpackConfig.module || {};
          webpackConfig.module.loaders = webpackConfig.module.loaders || [];
          webpackConfig.module.loaders = webpackConfig.module.loaders.filter(function (loader) {
              return !loader.test.test("style.scss");
          });
    1. Add the new server-side loader by adding
          webpackConfig.module.loaders.push({
              test: /\.scss$/,
              include: /ClientApp/,
              loader: "css-loader/locals!sass-loader"
          });

Hope someone figures out how to make this fully work!

@colltoaction
Copy link

It almost works using loaders: ['raw-loader', 'sass-loader'] as suggested in this post, but then you are missing the css-loader and don't get url resolving (e.g. for images).

If you try to use loaders: ['to-string-loader', 'css-loader', 'sass-loader'] or similar you get the Cannot find module './../../node_modules/css-loader/lib/css-base.js' error that was mentioned by @shobman, which I'm pretty sure is an error in the webpack prerendering configuration. I'll take a look later.

@colltoaction
Copy link

@SteveSandersonMS @laskoviymishka I know you're not providing a built-in css-preprocessor support, but maybe you could take a look at the issue we're having when doing it ourselves?

The "cannot find module" error looks like a bug in aspnet-webpack because it does work with server-side prerendering disabled. E.g. this works:

<app
     asp-prerender-webpack-config="webpack.config.js">Loading...</app>

Do you want me to open a new issue? Maybe provide a repro? I think you'll hit the issue pretty easily if you try to set up a project with sass, but let me know :).

@SteveSandersonMS
Copy link
Member

SteveSandersonMS commented Aug 20, 2016

@tinchou Are you on the current version of Microsoft.AspNetCore.NodeServices? The current version is 1.0.0-beta-000011. Versions prior to beta 10 had an issue around module resolution that might possibly have caused your Cannot find module errors.

I ask this because I just tried setting up a CSS preprocessor (I used LESS), and in all cases it just worked fine. It works both with loader: extractCSS.extract('css!less') to strip out the generated CSS into a regular standalone .css file, and with loaders: ['to-string-loader', 'css-loader', 'less-loader'] (and in component, styles: [require('../../styles/site.less')]) for Angular 2 style encapsulation. I have not experienced any Cannot find module errors in any case.

And if you try to use an array as configuration (allowed by webpack as seen here) you get this exception

I see - thanks for letting me know!

For WebpackDevMiddleware.ts, it does seem reasonable that we could and should support array-style Webpack configurations. It just needs to preprocess them all in the same way, and then return to .NET an array of PublicPath values instead of just one.

For LoadViaWebpack.ts, I don't quite see how it makes sense to support array-style Webpack configurations, unless the developer also has a way of specifying which of their webpack configs is the one they want to use to build this particular file (given that more than one of the configs might be able to build it, possibly in different ways). Maybe if you need this, it would make sense to have each of your configs in separate files, and then combine them into a top-level one like this:

// Top level webpack.config.js
module.exports = [
    require('./webpack.config.first.js'),
    require('./webpack.config.second.js')
];

... and then when using prerendering, you could specify which config you want to use to build the boot module, e.g.:

asp-prerender-webpack-config="webpack.config.second.js"

What do you think?

@colltoaction
Copy link

@SteveSandersonMS I had just updated to RC5, and it's not yet supported in universal (see angular/universal#511).

I'll try it when they release an updated package and I'll post here.

@SteveSandersonMS
Copy link
Member

I've added some detailed docs on ways to configure LESS/SASS/etc loading: https://github.com/aspnet/JavaScriptServices/tree/dev/src/Microsoft.AspNetCore.SpaServices#example-a-simple-webpack-setup-that-builds-less

Closing now as I'm fairly convinced this works (it certainly does work as documented). But if anyone finds issues with LESS and Angular 2 RC5 in particular, please file a new issue with repro info!

@colltoaction
Copy link

Thank you very much! I'm still waiting on angular universal to update their
package to try this out.
On Thu, Sep 1, 2016 at 8:44 AM Steve Sanderson notifications@github.com
wrote:

Closed #132 #132.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#132 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABeg9OOZu7QzmbSNxfM4wn_QwiDWl9KBks5qlrqqgaJpZM4I2HIr
.

@tuan
Copy link

tuan commented Sep 6, 2016

@SteveSandersonMS
I followed the documentation to configure less loading, but I can only make it work when I import less files in boot-client.tsx. If I import less files in, say, ClientApp/components/Layout.tsx, I got this error

Exception: Call to Node module failed with error: Error: Webpack compilation reported errors. Compiler output follows: Hash: 73b83ecc6b0630c3f216
Version: webpack 1.13.2
Time: 4079ms
Asset Size Chunks Chunk Names
webpack-output.js 93.9 kB 0 [emitted] main

ERROR in ./ClientApp/css/layout.less
Module build failed: Error: Cannot find module './../../node_modules/css-loader/lib/css-base.js'
at Function.Module._resolveFilename (module.js:455:15)

If I run webpack manually, I could see the style bundle output correctly.

@tuan
Copy link

tuan commented Sep 6, 2016

Related issue: #241

I removed my webpack config for preprocessing less files and just tried to import relative css file, and I got the same error.

Below is the diff between my changes and the sample code:

diff --git a/ClientApp/boot-client.tsx b/ClientApp/boot-client.tsx
index 4dc12af..0199b23 100644
--- a/ClientApp/boot-client.tsx
+++ b/ClientApp/boot-client.tsx
@@ -1,4 +1,3 @@
-import './css/site.css';
 import 'bootstrap';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
diff --git a/ClientApp/components/Layout.tsx b/ClientApp/components/Layout.tsx
index b9f877b..6290cb7 100644
--- a/ClientApp/components/Layout.tsx
+++ b/ClientApp/components/Layout.tsx
@@ -1,5 +1,6 @@
 import * as React from 'react';
 import { NavMenu } from './NavMenu';
+import '../css/site.css';

 export interface LayoutProps {
     body: React.ReactElement<any>;

@SteveSandersonMS
Copy link
Member

I can only make it work when I import less files in boot-client.tsx

That is as expected. It works to import them in boot-client.tsx, because code in that file doesn't run on the server. If you import them to some other file that does execute on the server, then you're trying to run css-loader the server, which it doesn't support (it would be meaningless for css-loader to support server-side execution - there's no DOM to put the styles into).

You should either reference your client-only resources from boot-client (so they only load on the client), or you'll need to implement some mechanism for making them not trigger any errors if they are loaded on the server. Theoretically you could implement your own version of css-loader that detects if it's running inside Node and if so does nothing.

@tuan
Copy link

tuan commented Sep 8, 2016

That makes sense. Thanks for the explanation.
I've decided to solve this by creating a prerender bundle that uses css-loader/locals which doesn't embed CSS but only exports the identifier mappings. I then use this bundle in asp-prerender-webpack-config in the Home\Index.cshtml.

@paulinfrancis
Copy link

@tuan Great that you got it working! Would you be able to share your fix slightly more in depth? I'm struggling with the exact same problem, but being new to Webpack, I don't quite have enough context to implement your fix. Thanks :)

@tuan
Copy link

tuan commented Sep 9, 2016

@paulinfrancis
I use sass so in my webpack.config.js I have

...

loaders = [
    // other loaders
    { test: /\.scss$/, loader: extractCSS.extract(['css', 'sass']) }
]

...

This does not work if I import sass files in a file that is rendered on the server because of the reason Steven pointed out.
So I created another config just for creating a bundle that will be executed on the server. Let's name it webpack.config.prerender.js. In this config I have

....

loaders = [
    // other loaders
    { test: /\.scss$/, loader: 'css/locals!sass' }
]

....

Note: I don't use extract text plugin in this config. Also, I use css/locals instead of css loader.
You can create a helper to share the common settings between your webpack.config.js and webpack.config.prerender.js

In Home\index.html, I use the new webpack.config.prerender.js instead. Here's the diff

 <div id="react-app" asp-prerender-module="ClientApp/boot-server"
 -                    asp-prerender-webpack-config="webpack.config.js">Loading...</div>
 +                    asp-prerender-webpack-config="webpack.config.prerender.js">Loading...</div>

Hope this helps!

@colltoaction
Copy link

colltoaction commented Sep 19, 2016

Hey guys, I finally updated my packages and I'm ready to sort this out. @SteveSandersonMS says it would be meaningless for css-loader to support server-side execution - there's no DOM to put the styles into, but I think this is not correct.

In my scenario, I'm trying to pass SCSS to Angular 2, and the only reason I need to use css-loader is because otherwise the url() links are wrong. E.g.:

@Component({
    selector: "nav-menu",
    template: require("./nav-menu.component.html"),
    styles: [require("css-loader!sass-loader!./nav-menu.component.scss").toString()],
})
export class NavMenuComponent {
//....

If I were writing the style directly in the styles property (no require involved), then Angular would have no problem with this.

So, if it is really true that css-loader doesn't work on server-side, then I'll ask them what's wrong with that (remember this loader only grabs a CSS file and transforms it into another, no DOM manipulation involved without other loaders). I think it should work but we have a bug somewhere that gives us cannot find module './../../node_modules/css-loader/lib/css-base.js'.

My next step is to download a starter template that doesn't use ASP.NET (like this one https://github.com/angular/universal-starter) and check if it does work.

@MarkPieszak I've seen you collaborating on integrating this and Universal, so maybe you can share your thoughts? Thanks! :)

@RobCannon
Copy link

If this solution needs to use require to load the .html and .css/.scss files, doesn't that mean I can't use other third-party components that don't use require? That would be a big problem.

@MarkPieszak
Copy link
Contributor

@tinchou @RobCannon You should be able to do sass with require & loaders taking advantage of everything if I'm not mistaken. Are you not able to right now?

The reason we use require is to simply inject the raw text from the file and insert it into template or styles (webpack does that) then the loaders come and do the rest. Also that way we don't have to be specific about the URL location of our files, which is always nice :)

require("./some-styles.scss");

If you have the loaders set up correctly, I believe roughly like so:

loaders: [
      // Support for CSS as raw text
      {test: /\.css$/, loader: 'raw'},

      //sass loader implementation
      {test: /\.scss$/, loaders: ["style", "css", "sass"]},

I'd have to double check the exact setup.

@colltoaction
Copy link

colltoaction commented Sep 20, 2016

@MarkPieszak, thanks for your quick reply! This is what I'm trying to use and that's not working with ASP.NET. The error is the one above: the server can't find the css loader and fails to prerender.

@SteveSandersonMS
Copy link
Member

@tinchou Yes, you're right! I was conflating css-loader with style-loader, but now I see how you're using css-loader without style-loader. I'm now looking into what stops css-loader from working standalone during server-side execution.

@colltoaction
Copy link

Awesome! Feels good not to be crazy after all 😜

On Tue, Sep 20, 2016, 07:19 Steve Sanderson notifications@github.com
wrote:

@tinchou https://github.com/tinchou Yes, you're right! I was conflating
css-loader with style-loader, but now I see how you're using css-loader
without style-loader. I'm now looking into what stops css-loader from
working standalone during server-side execution.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#132 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABeg9Jv8ZDi420cFl5mNvWhjKpQKekU5ks5qr7MzgaJpZM4I2HIr
.

@SteveSandersonMS
Copy link
Member

OK, I tracked it down. It was an incompatibility between css-loader and ExternalsPlugin, because css-loader dynamically generates a relative-path require to one of its own files (i.e., ./../../node_modules/css-loader/lib/css-base.js), but ExternalsPlugin doesn't handle relative paths properly (it ignores the data.context value, which indicates what relative paths should be resolved against, and just emits the relative path string directly even if the module it's emitting will go in a totally different path).

Instead of trying to get ExternalsPlugin fixed, an easier solution was just to remove it, and replace it with webpack-node-externals instead, which also happens to be a much more actively-maintained plugin, so is probably a better choice anyway.

I've published an update to aspnet-webpack, so if you update to the latest version (i.e., run npm install aspnet-webpack), then you should find the problem disappears.

Also I'm changing the default Angular2Spa template so that it uses css-loader by default now, because otherwise (like you've pointed out), things like url(...) references in CSS wouldn't work, and lots of people would struggle with that. See 925f47f

In conclusion, thanks @tinchou for persisting with pointing this out - things are much better now this is fixed!

@colltoaction
Copy link

Dude it works great. Thanks to you!

On Tue, Sep 20, 2016 at 12:57 PM Steve Sanderson notifications@github.com
wrote:

OK, I tracked it down. It was an incompatibility between css-loader and
ExternalsPlugin, because css-loader dynamically generates a relative-path
require to one of its own files (i.e.,
./../../node_modules/css-loader/lib/css-base.js), but ExternalsPlugin
doesn't handle relative paths properly (it ignores the data.context
value, which indicates what relative paths should be resolved against, and
just emits the relative path string directly even if the module it's
emitting will go in a totally different path).

Instead of trying to get ExternalsPlugin fixed, an easier solution was
just to remove it, and replace it with webpack-node-externals
https://www.npmjs.com/package/webpack-node-externals instead, which
also happens to be a much more actively-maintained plugin, so is probably a
better choice anyway.

I've published an update to aspnet-webpack, so if you update to the
latest version (i.e., run npm install aspnet-webpack), then you should
find the problem disappears.

Also I'm changing the default Angular2Spa template so that it uses
css-loader by default now, because otherwise (like you've pointed out),
things like url(...) references in CSS wouldn't work, and lots of people
would struggle with that. See 925f47f
925f47f

In conclusion, thanks @tinchou https://github.com/tinchou for
persisting with pointing this out - things are much better now this is
fixed!


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#132 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABeg9PcJwgrOnGnhH8Jk6SWsALL3iVcFks5qsAJ2gaJpZM4I2HIr
.

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

No branches or pull requests

9 participants