Skip to content
This repository has been archived by the owner on Mar 6, 2024. It is now read-only.

SASS loader support #14

Closed
trueter opened this issue Mar 11, 2016 · 30 comments
Closed

SASS loader support #14

trueter opened this issue Mar 11, 2016 · 30 comments

Comments

@trueter
Copy link

trueter commented Mar 11, 2016

HappyPack is making us very happy, thanks for that!

How would I go about enabling happypack for this loader:

{ 
    test: /\.scss$/, 
    loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap&' 
},

Cheers!

@amireh
Copy link
Owner

amireh commented Mar 11, 2016

😁 I'm glad to hear that!

Unfortunately, the sass-loader wouldn't work with HappyPack as it is since it uses a subset of the APIs that aren't supported, like the ones for resolving imports.

I did start an effort to fully support the loader APIs but it got really ugly and I'm not sure if it's worth the trouble (or if it will end up being an improvement in the first place, so much data to transfer between the processes and not all of it is serializable.)

I am ready to take another fresh look at solving this though, so I'll be notifying you if something works out.

@trueter
Copy link
Author

trueter commented Mar 12, 2016

I might be willing to invest some time into this, too. What would be a good place to start?

@amireh
Copy link
Owner

amireh commented Mar 12, 2016

So, we need a way to forward certain API calls from the worker threads all the way up to the webpack compiler, get the result from the compiler, then pass them back down to the loaders running in the worker threads. This is for APIs like resolve() which are used by less-loader, sass-loader, etc.

I've been keeping a list of the supported loader APIs in this wiki page.

The first approach I took, see pull #15, was based around serializing the arguments for the API calls, sending them over to the main thread, asking the primary webpack compiler instance to carry out the routine, then forwarding back the (serialized) result. However, this isn't bullet-proof because some loaders actually rely on some this.compiler and this._compilation attributes which are not serializable. Or, in the case of less-loader, the loader actually reaches out to plugin instances which is ... bleh.

The other approach I thought of was to spawn actual webpack compiler instances in each of the worker threads, with the configuration file that is used by the main thread. That way, those APIs would simply be handled by the in-process compiler instance. In theory, this should work for more cases than the first approach, but I'm not sure how fast it will be, especially when it comes to resolving because I know webpack caches a lot of things internally.

@amireh amireh changed the title Chained loaders SASS loader support Mar 25, 2016
@amireh
Copy link
Owner

amireh commented Mar 30, 2016

@trueter this should be working in #15 , see the example under /examples/sass-loader which uses all of style-loader, css-loader, autoprefixer-loader, and sass-loader.

Can you try it out?

@trueter
Copy link
Author

trueter commented Mar 31, 2016

@amireh Will do!

@trueter
Copy link
Author

trueter commented Apr 1, 2016

@amireh My builds don't seem to finish unfortunately.
The dev webpack configuration of this repository https://github.com/erikras/react-redux-universal-hot-example is very similar to mine. If you can make it work on that config it should probably work with me too.
Note: I'm also using the bootstrap-loader, but removing that from the chunks has not helped the situation.

My config:

{ test: /\.scss$/, loader: 'happypack/loader?id=sass' },
new HappyPack({
            id: 'sass',
            loaders: [
                'style!css?module&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap&'
            ]
        }),

Thanks for your work!

@amireh
Copy link
Owner

amireh commented Apr 9, 2016

So, with the new changes in 2.0, it seems to work fine against react-redux-universal-hot-example. I ran it against webpack/dev.config.js and here are the parts I modified:

// @file: react-redux-universal-hot-example/webpack/dev.config.js
var HappyPack = require('happypack');
var happyThreadPool = HappyPack.ThreadPool({ size: 5 });
// ...

module.exports = {
  // ...
  module: {
    loaders: [
      { test: /\.jsx?$/, exclude: /node_modules/, loaders: ['babel?' + JSON.stringify(babelLoaderQuery), 'eslint-loader'], happy: { id: 'jsx' }, },
      { test: /\.json$/, loader: 'json-loader', happy: { id: 'json' } },
      { test: /\.less$/, loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!less?outputStyle=expanded&sourceMap', happy: { id: 'less' } },
      { test: /\.scss$/, loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap', happy: { id: 'sass' } },
      { test: /\.woff2?(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff", happy: { id: 'woff' } },
      { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream", happy: { id: 'ttf' } },
      { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file", happy: { id: 'eot' } },
      { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml", happy: { id: 'svg' } },
      { test: webpackIsomorphicToolsPlugin.regular_expression('images'), loader: 'url-loader?limit=10240' }
    ]
  },
  // ...
  plugins: [
    // ...
    new HappyPack({ id: 'jsx',  enabled: true, threadPool: happyThreadPool, }),
    new HappyPack({ id: 'json', enabled: true, threadPool: happyThreadPool, }),
    new HappyPack({ id: 'less', enabled: true, threadPool: happyThreadPool, }),
    new HappyPack({ id: 'sass', enabled: true, threadPool: happyThreadPool, }),
    new HappyPack({ id: 'woff', enabled: true, threadPool: happyThreadPool, }),
    new HappyPack({ id: 'ttf',  enabled: true, threadPool: happyThreadPool, }),
    new HappyPack({ id: 'eot',  enabled: true, threadPool: happyThreadPool, }),
    new HappyPack({ id: 'svg',  enabled: true, threadPool: happyThreadPool, }),
  ]
};

The build used to take 11s without happypack, and now 6.7s with happypack. Obviously, you don't need to run it for the woff/ttf/eot/svg loaders, I just did it for the kicks!

Let me know how it goes !

@hanjukim
Copy link

I was able to get huge speed-up react-redux-universal-hot-example with happypack v2. Great job!!

First run:

[0] Hash: 28bb41120e0eb600baf1
[0] Version: webpack 1.12.14
[0] Time: 22019ms
[0]                                 Asset     Size  Chunks             Chunk Names
[0]  895e63a443e1f52190ff2e680e6b8fab.eot  45.7 kB          [emitted]
[0]  2ebfa0ec479b492063025dee283710eb.png  13.4 kB          [emitted]
[0] 25541de1fabfeb22782d2e80705f4e2c.woff  23.4 kB          [emitted]
[0]  32cbf9a7401f21f0a30165b9a48c0f29.eot  45.6 kB          [emitted]
[0] eabc4d64a388affa90889749651bdc14.woff  23.5 kB          [emitted]
[0]  6f485b07c500a6bb259f1e1ba96a36ac.eot  20.6 kB          [emitted]
[0] a5d3420018ff508cc78a7fd48f2bd3ed.woff  23.8 kB          [emitted]
[0]  0113f744b7b0ab52111ccad106448268.eot  20.7 kB          [emitted]
[0] 531636879a1d058b53d3ae187f257320.woff  23.6 kB          [emitted]
[0]          main-28bb41120e0eb600baf1.js  10.5 MB       0  [emitted]  main
[0]        vendor-28bb41120e0eb600baf1.js  5.83 MB       1  [emitted]  vendor
[0] webpack built 28bb41120e0eb600baf1 in 22019ms

Second run:

[0] Hash: 28bb41120e0eb600baf1
[0] Version: webpack 1.12.14
[0] Time: 11256ms
[0]                                Asset     Size  Chunks             Chunk Names
[0] 2ebfa0ec479b492063025dee283710eb.png  13.4 kB          [emitted]
[0]         main-28bb41120e0eb600baf1.js  10.5 MB       0  [emitted]  main
[0]       vendor-28bb41120e0eb600baf1.js  5.83 MB       1  [emitted]  vendor
[0] webpack built 28bb41120e0eb600baf1 in 11256ms

and found several bugs.

  • Font looks not same disabling 'woff' fixes problem. So there's something wrong.
  • SourceMap looks missing. DevTool shows raw babel output.
  • npm run build produces error with sass.
[Error: "extract-text-webpack-plugin" loader is used without the corresponding plugin, refer to https://github.com/webpack/extract-text-webpack-plugin for the usage example]

@amireh
Copy link
Owner

amireh commented Apr 11, 2016

Font looks not same disabling 'woff' fixes problem. So there's something wrong.

I don't think it's actually worth supporting file-loader or url-loader as they seem to do little processing and would hardly benefit from paralellization. If you think otherwise, feel free to open a ticket so that we can look into them.

SourceMap looks missing. DevTool shows raw babel output.

Even though the wiring is there, I still didn't implement source-map support. Primarily because I never worked with source-maps and I don't know what they look like when the loaders emit them; are they objects which can be serialized as JSON or are they plain text that can be written to a file directly? webpack docs are very scarce on this topic...

If you guys can shed some light on this or point me to some resources, I can look into supporting them. (At work we just use eval as a devtool in development so I never actually needed sourcemaps)

npm run build produces error with sass.

extract-text-webpack-plugin

So I played a little bit more with the ExtractText plugin (after my failed attempts in #12) and I did found a workaround which isn't very optimal but it seems to work! I got the redux example repo to work with it (the prod config, which npm run build uses) with the following adjustments:

// @file webpack/prod.config.js
var HappyPack = require('happypack');
var happyThreadPool = HappyPack.ThreadPool({ size: 5 });
// ...

module.exports = {
  // ...
  module: {
    loaders: [
      { test: /\.jsx?$/, exclude: /node_modules/, loader: 'happypack/loader?id=jsx' },
      { test: /\.json$/, loader: 'happypack/loader?id=json' },
      { test: /\.less$/, loader: ExtractTextPlugin.extract('style', 'happypack/loader?id=less') },
      { test: /\.scss$/, loader: ExtractTextPlugin.extract('style', 'happypack/loader?id=sass') },
      // ...
    ]
  },
  plugins: [
    // ...
    createHappyPlugin('jsx', [strip.loader('debug'), 'babel']),
    createHappyPlugin('json', [ 'json-loader' ]),
    createHappyPlugin('less', [ 'css?modules&importLoaders=2&sourceMap!autoprefixer?browsers=last 2 version!less?outputStyle=expanded&sourceMap=true&sourceMapContents=true' ]),
    createHappyPlugin('sass', [ 'css?modules&importLoaders=2&sourceMap!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap=true&sourceMapContents=true' ]),
  ],
  // ...
};

function createHappyPlugin(id, loaders) {
  return new HappyPack({
    id: id,
    loaders: loaders,
    threadPool: happyThreadPool,

    // disable happy caching with HAPPY_CACHE=0
    cache: process.env.HAPPY_CACHE !== '0',

    // make happy more verbose with HAPPY_VERBOSE=1
    verbose: process.env.HAPPY_VERBOSE === '1',
  });
}

The trick was to happify all the loaders except for style-loader, then pass that to the ExtractText plugin. This way, we actually use both happypack and webpack for compiling those sources (which is crazy) but should still be faster since the bulk of the work doesn't happen in the extract-text plugin but instead in sass/less/css etc.

This is the configuration bit I'm talking about:

{
  loaders: [{ loader: ExtractTextPlugin.extract('style', 'happypack/loader?id=sass') }],
  plugins: [new HappyPack({ id: 'sass', loaders: [ 'css', 'sass' ] })]
}

If you guys can confirm that ExtractText is working this way, I will add this bit to the README and add it to the supported loaders list! Thanks

@hanjukim
Copy link

I don't think it's actually worth supporting file-loader or url-loader as they seem really simple and do not need happifying. If you think otherwise, feel free to open a ticket so that we can look into them.

I don't think it's needed too.

If you guys can confirm that ExtractText is working this way, I will add this bit to the README and add it to the supported loaders list! Thanks

Passing loaders setting to HappyPack is working. Great job!

@amireh
Copy link
Owner

amireh commented Apr 11, 2016

Great! I'll update the docs when I get the chance, and open a ticket for SourceMap support.

Closing this, thanks errybody. 👋

@jackfengji
Copy link

@amireh I get "loaderContext.loadModule is not a function" error in less-loader when I used the configuration in your comment. I'm using happypack^2.1.1 and less-loader 2.2.3

@MagicDuck
Copy link

I'm getting the same problem as @jackfengji (same versions)

@soda-x
Copy link

soda-x commented Aug 17, 2016

@amireh I got a problem.

 { test: /\.less$/, loader: ExtractTextPlugin.extract('style', 'happypack/loader?id=less') },
createHappyPlugin('less', [ 'css?modules&importLoaders=2&sourceMap!autoprefixer?browsers=last 2 version!less?outputStyle=expanded&sourceMap=true&sourceMapContents=true' ]),

Finally the *.css would not minisize

@amireh
Copy link
Owner

amireh commented Aug 17, 2016

@jackfengji, @MagicDuck, regarding the loadModule error, it has been supported as of 2.2.0, so upgrading to the 2.2.x line should fix it for you.

@pigcan What is your problem exactly? Logs would be nice.

@soda-x
Copy link

soda-x commented Aug 18, 2016

When I use extract-text-webpack-plugin with happypack in my project I did found the css file would not minimize.

@smnbbrv
Copy link

smnbbrv commented Sep 14, 2016

@pigcan this can be solved by providing the query parameter to the css-loader, e.g.

new HappyPack({ id: 'sass', loaders: [
  'css?minimize', 'custom-autoprefixer',
  'sass?includePaths=' + path.resolve(__dirname, './foundation/scss')
] }),

@delijah
Copy link

delijah commented Sep 18, 2016

I am getting the following error when trying your solution @amireh:

[TypeError: Cannot read property 'applyPluginsWaterfall' of undefined]

@AndyOGo
Copy link

AndyOGo commented Sep 27, 2016

@amireh
I'm facing the same error as @delijah

I use:
happypack@2.2.1
webpack@1.13.2
less-loader@2.2.3
postcss-loader@0.13.0

Child extract-text-webpack-plugin:
+ 134 hidden modules

ERROR in ./~/happypack/loader.js?id=less!./docs/less/docs.less
Module build failed: Error: Cannot read property 'applyPluginsWaterfall' of undefined
TypeError: Cannot read property 'applyPluginsWaterfall' of undefined
    at Object.module.exports (../style-guide/node_modules/postcss-loader/index.js:86:32)
    at applySyncOrAsync (../style-guide/node_modules/happypack/lib/applyLoaders.js:341:21)
    at apply (../style-guide/node_modules/happypack/lib/applyLoaders.js:268:5)
    at ../style-guide/node_modules/happypack/lib/applyLoaders.js:274:9
    at ../style-guide/node_modules/less-loader/index.js:70:3
    at ../style-guide/node_modules/less/lib/less/render.js:35:17
    at ../style-guide/node_modules/less/lib/less/parse.js:63:17
    at Object.finish [as _finish] (../style-guide/node_modules/less/lib/less/parser/parser.js:183:28)
    at Object.ImportVisitor._onSequencerEmpty (../style-guide/node_modules/less/lib/less/visitors/import-visitor.js:35:14)
    at ImportSequencer.tryRun (../style-guide/node_modules/less/lib/less/visitors/import-sequencer.js:50:14)
    at ../style-guide/node_modules/happypack/lib/HappyLoader.js:46:23
    at Object.Thread::4:6 (../style-guide/node_modules/happypack/lib/HappyPlugin.js:321:7)
    at ChildProcess.acceptMessageFromWorker (../style-guide/node_modules/happypack/lib/HappyThread.js:66:32)
    at emitTwo (events.js:105:20)
    at ChildProcess.emit (events.js:185:7)
    at handleMessage (internal/child_process.js:718:10)
    at Pipe.channel.onread (internal/child_process.js:444:11)

@AndyOGo
Copy link

AndyOGo commented Sep 27, 2016

@amireh
My reduced webpack setup:

import webpack from 'webpack'
...

import createHappyPlugin, { getEnvId } from '../lib/createHappyPlugin'

export default {
  resolve: {
    modulesDirectories: [
      'less',
      'node_modules',
    ],
    extensions: ['', '.js', '.jsx'],
  },
  module: {
    loaders: [{
      test: /\.less/,
      loader: ExtractTextPlugin.extract('style', `happypack/loader?id=${getEnvId('less')}`),
      // w/o HappyPack this works
      // loader: ExtractTextPlugin.extract('style', [
      //   'css?importLoaders=2&sourceMap',
      //   'postcss-loader',
      //   'less?outputStyle=expanded&sourceMap=true&sourceMapContents=true',
      // ]),
    }],
  },
  plugins: [
    createHappyPlugin('less', [
      'css?importLoaders=2&sourceMap',
      'postcss-loader',
      'less?outputStyle=expanded&sourceMap=true&sourceMapContents=true',
    ]),
    new CleanPlugin([
      path.resolve(__dirname, '../dist/bundles'),
    ], {
      root: path.resolve(__dirname, '..'),
    }),
    new ExtractTextPlugin('[name].css', {
      allChunks: true,
    }),
  ],
  postcss: () => [
    pseudoelements,
    autoprefixer,
    cssmqpacker({ sort: true }),
    csswring,
  ],
}
import HappyPack from 'happypack'

const threadPool = HappyPack.ThreadPool({ size: 5 })
const env = process.env.NODE_ENV

export const getEnvId = (id) => `${env}-${id}`
const createHappyPlugin = (id, loaders) => new HappyPack({
  id: getEnvId(id),
  loaders,
  threadPool,

  // disable happy caching with HAPPY_CACHE=0
  cache: process.env.HAPPY_CACHE !== '0',

  // make happy more verbose with HAPPY_VERBOSE=1
  verbose: process.env.HAPPY_VERBOSE === '1',
})

export default createHappyPlugin

@smnbbrv
Copy link

smnbbrv commented Sep 27, 2016

@AndyOGo @delijah This looks like plugins compatibility problem. I had the same and solved it with a custom loader:

// webpack-autoprefixer.config.js

const autoprefixerBrowsers = ['> 0.5%', 'ie 9', 'ie 10'];

let autoprefixer = require('autoprefixer'),
    postcss = require('postcss'),
    processor = postcss([autoprefixer({ browsers: autoprefixerBrowsers })]);

module.exports = function (source) {
    return processor.process(source);
}

in fact this can be full postcss cycle loader, just change the name and the content. Then to use it:

// webpack.config.js

new HappyPack({ id: 'sass', loaders: [
  'css?minimize', 'custom-autoprefixer',
  'sass?includePaths=' + path.resolve(__dirname, './foundation/scss')
] }),

// ...

module.exports = {
    // ...
    // allows to register a custom loader
    resolveLoader: {
        alias: { // custom-autoprefixer is used because there is no way to pass it to happypack otherwise
            'custom-autoprefixer': path.join(__dirname, './webpack-autoprefixer.config.js')
        }
    }
    // ...

@AndyOGo
Copy link

AndyOGo commented Sep 27, 2016

@smnbbrv
Hey thank you very much.
This works like a charm:)

@delijah
Copy link

delijah commented Sep 30, 2016

This sounds more like a workaround than a solution, doesn't it?

@AndyOGo
Copy link

AndyOGo commented Sep 30, 2016

@delijah
You are absolutely right, it's a workaround.
Very honestly, I see way more workarounds every day than real bugfixes, fixing the root cause once and forever. But hey if it works, it's an improvement?
Do workarounds qualify for long term solution, well rather not, maybe sometimes.

@smnbbrv
Copy link

smnbbrv commented Sep 30, 2016

@delijah I am just trying to help, you can also wait for the bugfix :)

@AndyOGo
Copy link

AndyOGo commented Sep 30, 2016

@delijah
Sorry if you felt offended. It wasn't meant like that. As I said I totally agree with you and would rather like to have the bugfix. I just wanted to share my personal experience/opinion.
Keep on helping, and sure I will wait for it ;)

@delijah
Copy link

delijah commented Sep 30, 2016

Nonono. I didn't feel offended. Sorry if my comment did not sound politely, that was not my intention. So what i wanted to say:

Thank you for your investigations. At the moment i prefer to hope/wait for a bugfix, but i think this workaround will help a lot of people that are dealing with the same error.

Thanks again so far.

@wangcansunking
Copy link

I have this problem too in less @import.
When will the problem solved?
Thanks very much.

@amireh
Copy link
Owner

amireh commented Feb 20, 2017

When will the problem solved?

You're free to send a PR. :)

@jaunkst
Copy link

jaunkst commented Jun 2, 2017

ExtractTextPlugin.extract({ use: ['css-loader', 'sass-loader'] }) is breaking down pretty bad with large numbers of scss entries. I don't think this is related to happy-pack, I am seeing really bad performance with pure ExtractTextPlugin 2k + scss files.

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

No branches or pull requests