webpack-contrib / extract-text-webpack-plugin

[DEPRECATED] Please use https://github.com/webpack-contrib/mini-css-extract-plugin Extracts text from a bundle into a separate file
MIT License
4.01k stars 513 forks source link

Reloading extracted css with hot module replacement #30

Closed nickdima closed 9 years ago

nickdima commented 10 years ago

Is it possible to have hot module replacement for an extracted css file that I load via a css link tag in my html's head tag? I have HMR working for my javascript but not sure how to make it work for extracted css. This is my css related config:

entry:
    styles: ['webpack-dev-server/client?http://localhost:3001', 'webpack/hot/dev-server', './app.scss']
loaders: [
  {
    test: /\.scss$/
    loader: ExtractTextPlugin.extract "style-loader", "css-loader!sass-loader?" + JSON.stringify
      outputStyle: 'expanded'
      includePaths: [
        path.resolve __dirname, './app'
        path.resolve __dirname, './bower_components'
        require('node-bourbon').includePaths
        path.resolve __dirname, './vendor/css'
      ]
  }
plugins: [new ExtractTextPlugin "css/#{nameBundle}.css"]
kellyrmilligan commented 7 years ago

I simply created a separate entry and required my scss file.

require('scss/main.scss')

then in my layout file, based on NODE_ENV, in the head of the document spit out script tags if in dev, or link tags for prod where extract text plugin is used.

in webpack, I have the hot module replacement plugin with style, css, postcss, and scss loaders configured.

when I update the css, it updates on the page.

since it's in a script tag in the head, it must be parsed before the rest of the document.

L8D commented 7 years ago

@kellyrmilligan if you're only using external stylesheets in production, then you'd use the extract-text-webpack-plugin in your production webpack configuration, and you shouldn't need hot loading to work. This issue is for those who want to use external stylesheets in development with hot loading support (instead of using style-loader).

You shouldn't need to conditionally add any script tags if you're using style-loader, since it should add the stylesheets to your main JavaScript bundle. Are you using webpack exclusively for stylesheets?

kellyrmilligan commented 7 years ago

@L8D yes, that's what I am doing. I think I posted too soon. my goal is to have css with hot loading in dev, and just the external stylesheet in prod. I want the separate entry point for css, so it can be in the head of the document, since the app I am working on is a universally rendered app. I still had the regular import in my apps entry point, so need to put the code mentioned above to update the link rel for the stylesheet.

are you just updating the link that gets generated by style loader in the snippet above?

kellyrmilligan commented 7 years ago

I guess that's maybe a third solution. have the entry point in the head of the document for css, and import it in your entry point too. this way hot reloading just works for css. extract text plugin will do a bit more work for the build, but not a ton. you could also remove the css entry point for the build to not have it build twice.

jmdfm commented 7 years ago

@drosen0 Thanks, although I'm using ES6 and import statements so not really sure how that would translate. I could use require I guess but it would be inconsistent.

kellyrmilligan commented 7 years ago

@L8D would it be possible for you to post the relevant parts in a repo somewhere? if I just do the entry point for css, the file will hot update but not update the page, which is why i'm guessing you have the code that updates the link tag.

kellyrmilligan commented 7 years ago

@kirkstrobeck or @drosen0 , any way to get a full example in a gist?

  1. are you just always using extract text plugin?
  2. then have an entry point that listens for hot reloads when you change the scss and it's recompiled to update the link tag?

much obliged!

L8D commented 7 years ago

@kirkstrobeck that code doesn't actually assign the new property to the <link>. It's a no-op

geryit commented 7 years ago
if (module.hot) {
  document.querySelectorAll('link[href][rel=stylesheet]').forEach((link) => {
    const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`);
    link.href = nextStyleHref;
  });
}

thanks to @drosen0

cristian-eriomenco commented 7 years ago

Can this become a core feature of the plugin (or an extension on the plugin) so we get rid of the bicycle problem?

kirkstrobeck commented 7 years ago

@kellyrmilligan

kellyrmilligan commented 7 years ago

@kirkstrobek I think I get it. I'll try and come up with a fully working example to post somewhere. I think that would be true most valuable thing here.

kellyrmilligan commented 7 years ago

@kirkstrobeck where did your code go? I see it referenced , but not on this issue anymore?

kellyrmilligan commented 7 years ago

so far I have my webpack 2 config using extract text plugin, and in my main client entry i'm importing the scss. I have tried a few versions of the code above, but after the first load the module.hot is not firing again to update the css with the new date/timestamp. the extracted css file is being updated when i update just the scss, but the main entry point is not firing the code to update the timestamp.

const path = require('path')
const webpack = require('webpack')
const AssetsPlugin = require('assets-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')

const NODE_ENV = process.env.NODE_ENV || 'development'
const port = 8080
const host = 'http://localhost'

module.exports = {
  entry: {
    app: [
      'webpack-dev-server/client?http://localhost:8080',
      'webpack/hot/only-dev-server',
      './src/client.js'
    ]
  },
  output: {
    filename: NODE_ENV === 'development'
      ? 'js/[name].bundle.js'
      : 'js/[name].[hash].bundle.js',
    path: path.resolve(__dirname, './build/public'),
    publicPath: NODE_ENV === 'development'
      ? host + ':' + port + '/static/'
      : '/static/'
  },
  module: {
    rules: [
      {
        test: /\.(js)$/,
        use: ['babel-loader'],
        exclude: /(node_modules)/
      },
      {
        test: /\.(json)$/,
        use: ['json-loader']
      },
      {
        test: /\.scss$/,
        loader: ExtractTextPlugin.extract({
          loader: [
            {
              loader: 'css-loader',
              query: {
                sourceMap: true
              }
            },
            {
              loader: 'postcss-loader'
            },
            {
              loader: 'sass-loader',
              query: {
                sourceMap: true
              }
            }
          ]
        })
      }
    ]
  },
  plugins: [
    new AssetsPlugin({
      path: path.resolve(__dirname, './build'),
      prettyPrint: true
    }),
    new webpack.DefinePlugin({
      'process.env': {
        'NODE_ENV': JSON.stringify(NODE_ENV)
      }
    }),
    new webpack.HotModuleReplacementPlugin(),
    new ExtractTextPlugin(NODE_ENV === 'development'
      ? 'styles/bundle.css'
      : 'styles/[contentHash].bundle.css')
  ],
  resolve: {
    extensions: [
      '.js', '.json'
    ],
    modules: [
      path.resolve(__dirname, './src'),
      'node_modules'
    ],
    alias: {
      react: path.resolve(__dirname, './node_modules/react'),
      'react-dom': path.resolve(__dirname, './node_modules/react-dom')
    }
  },
  devtool: NODE_ENV === 'development'
    ? 'eval-source-map'
    : 'source-map',
  devServer: {
    stats: 'errors-only',
    publicPath: '/static/',
    port: port,
    hot: NODE_ENV === 'development'
  }
}

entry point:

import 'scss/main.scss'
if (module.hot && process.env.NODE_ENV === 'development') {
  document.querySelectorAll('link[href][rel=stylesheet]').forEach((link) => {
    const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`)
    link.href = nextStyleHref
  })
}

the first time it runs, and I see the timestamp. after an update, I do not.

kellyrmilligan commented 7 years ago

so I am now trying something closer to @L8D's solution, in my main entry I import a js file that just serves to update the css tag.

import './css'  //css.js module file

then in that module:

import 'scss/main.scss'

if (module.hot && process.env.NODE_ENV === 'development') {
  const cssNode = document.getElementById('css-bundle')
  cssNode.href = cssNode.href.replace(/(\?\d+)?$/, `?${Date.now()}`)
  module.hot.accept()
}

and the hot reload works only when I change a .js file, not an scss file. The console triggers App updated. recompiling... then nothing happens. is this a difference between webpack 1 and 2?

L8D commented 7 years ago

@kellyrmilligan my guess is that webpack 2 is not reloading your css.js because it's trying to optimize module imports and doesn't consider css.js to depend on anything inside scss/main.scss and so it doesn't trigger a reload.

kellyrmilligan commented 7 years ago

but it did in webpack 1? is the implementation i'm describing what you ended up having working?

iloginov commented 7 years ago

Well, let me add my 2 cents.

My configuration is following:

I have separate webpack config for server and client:

const webpack = require('webpack');
const path = require('path');
const ManifestPlugin = require('webpack-manifest-plugin');

const outputDir   = 'dist';
const publicPath  = process.env.NODE_ENV === 'production' ? '/' : 'http://localhost:3000/';

const plugins = [
  // Always expose NODE_ENV to webpack, you can now use `process.env.NODE_ENV`
  // inside your code for any environment checks; UglifyJS will automatically
  // drop any unreachable code.
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
  }),
  new webpack.NamedModulesPlugin(),
  new ManifestPlugin({
    publicPath,
    writeToFileEmit: true
  })
];

module.exports = {
  entry: {
    bundle: [ './client.js' ]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, outputDir),
    publicPath: '/'
    // necessary for HMR to know where to load the hot update chunks
  },
  context: path.resolve(__dirname, 'src'),
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: [
          'babel-loader',
        ],
        exclude: /node_modules/
      },
      {
          test: /\.less$/,
          use: [
            'style-loader',
            'css-loader?modules',
            'less-loader',
          ]
      },
            {
                test: /.ttf$/,
                loader: 'url-loader'
            },
            {
                test: /.woff$/,
                loader: 'url-loader'
            },
            {
                test: /.eot$/,
                loader: 'url-loader'
            }
    ],
  },
  plugins
};
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

const outputDir = 'dist';

const plugins = [
    // Always expose NODE_ENV to webpack, you can now use `process.env.NODE_ENV`
    // inside your code for any environment checks; UglifyJS will automatically
    // drop any unreachable code.
    new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
    }),
    new ExtractTextPlugin({ filename: 'bundle.css', disable: false, allChunks: true }),
    new webpack.NamedModulesPlugin()
];

module.exports = {
    target: 'node',
    entry: {
        server: './server.js',
    },
    context: path.resolve(__dirname, 'src'),
    plugins,
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            },
            {
                test: /\.less$/,
                loader: ExtractTextPlugin.extract({
                    fallbackLoader: "style-loader",
                    loader: [
                        'css-loader?modules',
                        'less-loader',
                    ]
                })
            },
            {
                test: /.ttf$/,
                loader: 'url-loader'
            },
            {
                test: /.woff$/,
                loader: 'url-loader'
            },
            {
                test: /.eot$/,
                loader: 'url-loader'
            }
        ]
    },
    output: {
        filename: '[name].js',
        path: path.resolve(__dirname, outputDir)
    }
}

As you see, server use ExtractTextPlugin while client use simple style-loader.

I use Gulp for running both server and client build with single command:

const gulp = require('gulp');
const gutil = require('gulp-util');
const nodemon = require('gulp-nodemon');
const webpack = require('webpack');
const webpackDevServer = require('webpack-dev-server');
const clean = require('gulp-clean')

const frontendConfigDev = './webpack.client.config.dev';
const backendConfigDev = './webpack.server.config.dev';
const outputDir = 'dist';
const hostname = 'localhost';
const port = 3000;

gulp.task('clean-output', () => {
    return gulp
        .src(outputDir, { force: true })
        .pipe(clean());
});

gulp.task('frontend-build-dev', (callback) => {
    process.env.NODE_ENV = 'development';

    const cfg = Object.create(require(frontendConfigDev));

    cfg.devtool = 'eval';
    cfg.plugins.unshift(new webpack.LoaderOptionsPlugin({ debug: true }));

    webpack(cfg, function(err, stats) {
        if(err) throw new gutil.PluginError("webpack", err);
        gutil.log("[webpack]", stats.toString({
            // output options
        }));
        callback();
    });
});

gulp.task('frontend-watch', () => {
    process.env.NODE_ENV = 'development';

    const cfg = Object.create(require(frontendConfigDev));

    cfg.devtool = 'eval';

    cfg.plugins.unshift(new webpack.LoaderOptionsPlugin({ debug: true }));
    cfg.plugins.unshift(new webpack.HotModuleReplacementPlugin());
    cfg.output.publicPath = `http://${hostname}:${port}/`;

    cfg.entry.bundle.unshift('react-hot-loader/patch');
    cfg.entry.bundle.unshift(`webpack-dev-server/client?http://${hostname}:${port}/`);
    cfg.entry.bundle.unshift('webpack/hot/only-dev-server');

    const compiler = webpack(cfg);
    const server = new webpackDevServer(compiler, {
        publicPath: '/',
        stats: {
            colors: true
        },
        hot: true,
        headers: { 'Access-Control-Allow-Origin': '*' },
        contentBase: outputDir
    });

    server.listen(port, hostname, (err) => {
        if (err) {
            throw new gutil.PluginError('webpack-dev-server', err);
        }
        gutil.log('[webpack-dev-server]', `http://${hostname}:${port}/`);
    });
});

gulp.task("webpack", function(callback) {
    // run webpack
    webpack({
        // configuration
    }, function(err, stats) {
        if(err) throw new gutil.PluginError("webpack", err);
        gutil.log("[webpack]", stats.toString({
            // output options
        }));
        callback();
    });
});

gulp.task('backend-build-dev', (callback) => {
    process.env.NODE_ENV = 'development';

    const cfg = Object.create(require(backendConfigDev));

    webpack(cfg, function(err, stats) {
        if(err) throw new gutil.PluginError("webpack", err);
        gutil.log("[webpack]", stats.toString({
            // output options
        }));
        callback();
    });
});

gulp.task('backend-watch', [ 'backend-build-dev' ], () => {
    process.env.NODE_ENV = 'development';

    nodemon({
        script: 'dist/server.js',
        watch: 'src',
        ext: 'js jsx less css',
        tasks: [ 'backend-build-dev' ]
    });
});

gulp.task('watch', ['backend-build-dev', 'frontend-build-dev']);
gulp.task('watch', ['backend-watch', 'frontend-watch']);

You should notice that nodemon watches for less and css files to update bundle.css and avoid flushing on F5 in browser.

Last part is server.js:

import express from 'express';
import path from 'path';
import React from 'react';
import { renderToString } from 'react-dom/server';

import App from './components/app';

//
//  Read actual filenames from manifest
//
import { readFileSync } from 'jsonfile';
import fs from 'fs';

const manifestPath = path.resolve('dist/manifest.json');
let manifest;

fs.watchFile(manifestPath, (curr, _) => {
    if (curr.ino != 0) {
        console.log('Manifest found.');
        manifest = readFileSync(manifestPath);
    }
});

if (fs.existsSync(manifestPath)) {
    console.log('Manifest found.');
    manifest = readFileSync(manifestPath);
}

const app = express();

app.get('/', function (req, res) {
  const html = renderToString(
        <App />
    )
  res.send(renderPage(html));
});

function renderPage(html) {
  return `
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="http://localhost:3000/bundle.css"]}">
      </head>
      <body>
        <div id="root">${html}</div>
        <script src="${manifest['bundle.js']}"></script>
      </body>
    </html>
   `
}

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
    console.log(`Server is listening on: ${PORT}`);
});

My prod configuration use ExtractTextPlugin for both client and server.

Hope, that will help someone.

shepherdwind commented 7 years ago

I write a loader(css-hot-loader), which supprot hot module replacement for an extracted css file.

LG0012 commented 7 years ago

Yeah, but your loader only for less, right?

shepherdwind commented 7 years ago

@LG0012 less is just a example , You can use any other loader, such as css, sass.

FrankFan commented 7 years ago

It's take me several hours on this problem, but finally find solution here. Thanks

helly0d commented 7 years ago

For everybody that still uses old-fashioned packages like webpack 1 and doing crazy stuff with multiple bundles and ExtractTextPlugin, I have updated @drosen0's solution. This version will not trigger a FOUT each time HMR finishes a build.

if (process.env.NODE_ENV === "development") {
  if (module.hot) {
    const reporter = window.__webpack_hot_middleware_reporter__;
    const success = reporter.success;
    const DEAD_CSS_TIMEOUT = 2000;

    reporter.success = function() {
      document.querySelectorAll("link[href][rel=stylesheet]").forEach((link) => {
        const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`);
        const newLink = link.cloneNode();
        newLink.href = nextStyleHref;

        link.parentNode.appendChild(newLink);
        setTimeout(() => {
          link.parentNode.removeChild(link);
        }, DEAD_CSS_TIMEOUT);
      });
      success();
    };
  }
}

It duplicates the old nodes attaching new time-stamps and removing the old node after 2 seconds. This should be straight forward for development only.

kellyrmilligan commented 7 years ago

@helly0d would it be possible to create a gist with the full setup?

ufukomer commented 7 years ago

@helly0d thank you! this works. 😇🙏🏻

Slumber86 commented 7 years ago

I found easier to do something like this:

webpackConfig.plugins = [
...
  new ExtractTextPlugin({
    disable: process.env.NODE_ENV === "development",
    filename  : '[name].[contenthash].css',
    allChunks : true
})
]

...

webpackConfig.module.rules.push({
  test : /\.scss$/,
  use: ExtractTextPlugin.extract({
      fallback: 'style-loader',
      use: [{
          loader: 'css-loader',
          options: {
              sourceMap: false,
              import: false,
              url: false
          }
      }, {
          loader: 'sass-loader',
          options: {
              sourceMap: true,
              outputStyle: 'expanded',
              includePaths: [...project.paths.client('styles'), './node_modules',]
          }
      }]
    })
})

For me, it works...

nathanboktae commented 7 years ago

None of these last few suggestions worked for me mainly due to webpack 2 not seeing that my style files were true dependencies of any javascript that required them. webpack-hot-middleware didn't either. But it's possible to subscribe to all changes via webpack/hot/emitter 's webpackHotUpdate event:

if (module.hot) {
  var hotEmitter = require("webpack/hot/emitter");
  hotEmitter.on("webpackHotUpdate", function(currentHash) {
    document.querySelectorAll('link[href][rel=stylesheet]').forEach((link) => {
      const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`)
      link.href = nextStyleHref
    })
  })
}
mieszko4 commented 7 years ago

@nathanboktae it works well for me, except it refreshes the styles each time I make a change regardless if it is a change in javascript or css. I am wondering if there is a way to check if change was made in css.

Also, in chrome every time I make a change I see an annoying style flash. In firefox there is no such flash - it is all good.

helly0d commented 7 years ago

@mieszko4 In order to avoid that annoying style flash ( aka FOUT ). You could combine the solution I gave above with the event based solution provided by @nathanboktae like this:

if (module.hot) {
  const hotEmitter = require("webpack/hot/emitter");
  const DEAD_CSS_TIMEOUT = 2000;

  hotEmitter.on("webpackHotUpdate", function(currentHash) {
    document.querySelectorAll("link[href][rel=stylesheet]").forEach((link) => {
      const nextStyleHref = link.href.replace(/(\?\d+)?$/, `?${Date.now()}`);
      const newLink = link.cloneNode();
      newLink.href = nextStyleHref;

      link.parentNode.appendChild(newLink);
      setTimeout(() => {
        link.parentNode.removeChild(link);
      }, DEAD_CSS_TIMEOUT);
    });
  })
}

Basically what this does, is to insert new links with the updated timestamp in the query, and remove the old link tags after 2 seconds. This way you will avoid that moment of FOUT because the new link tags will overwrite the rules from the old ones, once they have loaded their srcs. The removal of the old links is for clean-up purpose.

mieszko4 commented 7 years ago

Thanx @helly0d! Your solution works well if there are no custom fonts in my case.

I looked more into the problem and I realized that the flash is actually caused by redownloading custom fonts that I have defined in my scss. After each change in my scss or js, chrome redownloads the same font (the same filename). I will post more if I figure out how to resolve that problem.

mieszko4 commented 7 years ago

It seems that chrome does not redownload fonts if styles are defined in <style />. I do not have enough knowledge to figure it out. It seems it would be best but also very hard (if possible at all?) if it was solved with <style /> patches instead of entire file refresh since the source map file maps to multiple source files in the project.

nathanboktae commented 7 years ago

Great @helly0d I was about to merge our solutions too 👏

@milworm that article's solution is is 2x the lines, adds a totally unnecessary AJAX call, and keeps a 2nd copy of your stylesheets in memory in JS. This one is much better.

nathanboktae commented 7 years ago

For the maintainers (@bebraw, @TheLarkInn, etc) the principle of keeping development and production as close as possible is extremely important, and a 2.5 year old issue with 85 comments figuring out how to do it is a testament to that. I would be great to have first class HMR support in extract-text-webpack-plugin, if not at least an official, documented solution.

mieszko4 commented 7 years ago

@nathanboktae, @bebraw, @TheLarkInn It is not only about keeping developement and production as close as possible. With extract-text-webpack-plugin I have source files under webpack:// in my chrome developer tools which is really awesome as they follow the tree structure of my project. Without extract-text-webpack-plugin in my chrome developer tools they appear flat under :cloud: (no-domain and if I include resolve-url-loader source maps do not work (issue with resolve-url-loader).

milworm commented 7 years ago

@nathanboktae , well, you are right about second copy, but I don't think it's something bad. I wouldn't say that it's a bad solution. At least it works and doesn't have any issues mentioned in this thread.

nathanboktae commented 7 years ago

@milworm No, You still have flickering as you're just setting href directly. You are polluting the network console with an XHR request (including when only JS changes). That also causes another delay.

TheLarkInn commented 7 years ago

@nathanboktae @mieszko4 PR's are welcomed! 😄 ❤️ If you think you can have a fool proof non-breaking solution why not?

The thing is that the team itself cannot and will not (currently) invest the time in tackling this vs the laundry list of user voted features that will take priority. However, it sounds that there are quite a few people here who believe that they would like this feature so I would love to see some collaboration and I'm happy to answer internal api questions, and foster any learning needed.

mieszko4 commented 7 years ago

I think this pr is great for starters: https://github.com/webpack-contrib/extract-text-webpack-plugin/pull/457

nathanboktae commented 7 years ago

@TheLarkInn thanks for recognizing the need, and of course the hardwork on WebPack. 👏

milworm commented 7 years ago

@nathanboktae well, the ajax-call takes around 15ms on macbook, so it doesn't seem to be a lot.

IAMtheIAM commented 7 years ago

Here's my updated loader config

This configuration allows for Hot Module replacement with SCSS files using webpack dev server, and extracttextplugin for production mode to emit actual .css files. Dev server mode simulated using extracted CSS because it loads the files in the when ?sourcemap=true on a previous loader

I also don't use the stylesUrl property, I import the .scss file outside of the @component decorator so that the styles load in the global context, rather than scoped by component. Here's my working config

{
        test: /\.(scss)$/,
        use:
          isDevServer ? [
              {
                loader: 'style-loader',
              },            
              {
                loader: 'css-loader',
                options: { sourceMap: true }
              },
              {
                loader: 'postcss-loader',
                options: { postcss: [AutoPrefixer(autoPrefixerOptions)], sourceMap: true }
              },
              {
                loader: 'sass-loader',
                options: { sourceMap: true }
              },
              {
                loader: 'sass-resources-loader',
                options: {
                  resources: [
                    './src/assets/styles/variables.scss',
                    './src/assets/styles/mixins.scss']
                }
              }, 
              /**
               * The sass-vars-loader will convert the 'vars' property or any module.exports of 
               * a .JS or .JSON file into valid SASS and append to the beginning of each 
               * .scss file loaded.
               *
               * See: https://github.com/epegzz/sass-vars-loader
               */
              {
                loader: '@epegzz/sass-vars-loader?',
                options: querystring.stringify({
                  vars: JSON.stringify({
                    susyIsDevServer: susyIsDevServer
                  })
                })
              }] : // dev mode
          ExtractTextPlugin.extract({
            fallback: "css-loader",
            use: [
              {
                loader: 'css-loader',
                options: { sourceMap: true }
              },
              {
                loader: 'postcss-loader',
                options: { postcss: [AutoPrefixer(autoPrefixerOptions)], sourceMap: true }
              },
              {
                loader: 'sass-loader',
                options: { sourceMap: true }
              },
              {
                loader: 'sass-resources-loader',
                options: {
                  resources: [
                    './src/assets/styles/variables.scss',
                    './src/assets/styles/mixins.scss']
                }
              }, {
                loader: '@epegzz/sass-vars-loader?',
                options: querystring.stringify({
                  vars: JSON.stringify({
                    susyIsDevServer: susyIsDevServer
                  })
                  // // Or use 'files" object to specify vars in an external .js or .json file
                  // files: [
                  //    path.resolve(helpers.paths.appRoot + '/assets/styles/sass-js-variables.js')
                  // ],
                })
              }],
            publicPath: '/' // 'string' override the publicPath setting for this loader
          })
      },

app.component.css

import './app.style.scss'

/**
 * AppComponent Component
 * Top Level Component
 */
@Component({
   selector: 'body',
   encapsulation: ViewEncapsulation.None,
   host: { '[class.authenticated]': 'appState.state.isAuthenticated' },
   templateUrl: './app.template.html'
})
orpheus commented 7 years ago

chain css-hot-loader with ExtractTextPlugin: https://www.npmjs.com/package/css-hot-loader

I now have no Flash of Unstyled Content because of the extracted text, and my css hot reloads on save because of css-hot-loader

doomsbuster commented 7 years ago

HMR needs css-loader and style-loader that support hot module css replacement. If you are using less or sass, use the less-loader or sass-loader and then simply use the loader configuration like below:

    module: {
        rules: [{
            test: /\.js$/,
            exclude: /node_modules|__tests__|__uitests__|__mocks__/,
            use: {
                loader: 'babel-loader'
            }
        }, {
            test: /\.less$/,
            exclude: /node_modules/,
            include: path.resolve(__dirname, 'ui/styles'),
            use: ['style-loader', 'css-loader', 'less-loader']
        }, {
            test: /\.(png|jpe?g|gif|svgz?|woff2?|eot)$/i,
            include: path.resolve(__dirname, 'ui/images'),
            use: {
                loader: 'file-loader',
                options: {
                    name: '[name].[ext]'
                }
            }
        }, {
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
        }]
    },
sheerun commented 6 years ago

I've created https://github.com/sheerun/extracted-loader that is dedicated to this use case. Usage:

config.module.rules.push({
  test: /\.css$/,
  use: ['extracted-loader'].concat(ExtractTextPlugin.extract({
    /* Your configuration here */
  }))
})

config.plugins.push(new ExtractTextPlugin('index.css'))

No configuration necessary :)

LG0012 commented 6 years ago

got disconnect... pathofexile.com not working :/

On Sat, Dec 9, 2017 at 9:39 PM, Adam Stankiewicz notifications@github.com wrote:

I've created https://github.com/sheerun/extracted-loader that is dedicated to this use case. Usage:

config.module.rules.push({ test: /.css$/, use: ['extracted-loader'].concat(ExtractTextPlugin.extract({ / Your configuration here / })) }) config.plugins.push(new ExtractTextPlugin('index.css'))

No configuration necessary :)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/30#issuecomment-350500493, or mute the thread https://github.com/notifications/unsubscribe-auth/AIAZGgJGLLaYLxuH51tX8UMoK5QevZdSks5s-uHvgaJpZM4CrsZZ .