react-toolbox / react-toolbox

A set of React components implementing Google's Material Design specification with the power of CSS Modules
http://www.react-toolbox.io
MIT License
8.66k stars 971 forks source link

Document webpack setup #246

Closed natew closed 8 years ago

natew commented 8 years ago

Hey, using this on a new stack. I'm trying a variety of loaders to get it working, but I keep getting "empty" classNames on everything:

image

I'm using webpack with this config:

{
        test: /\.scss$/,
        loaders: [
          'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
          'sass?sourceMap&outputStyle=expanded&includePaths[]=' + (path.resolve(runnerRoot, './node_modules'))
        ]
}

Tried a few variations, but when I set a breakpoint in Button.jsx I see:

image

I've tried other configurations where I just get a single string back like ('name-hash.css'), but I see that it's looking for an object.

Just a request for further documentation.

natew commented 8 years ago

Ah just saw this: https://github.com/react-toolbox/react-toolbox-example/blob/master/webpack.config.js

natew commented 8 years ago

Interestingly, I see it on locals in the style import (using the same config as your site):

image

image

javivelasco commented 8 years ago

Hey @natew !! Check you are using updated packages for css loader. I remember in previous versions classes were :global by default but now, if you are not specifying anything else, they should go :local and resolved directly in the import object.

natew commented 8 years ago

Hm, I updated to latest (EDIT: 0.23.1 now), and no change. I see this:

image

Notice react-toolbox is looking for _style2['default'][shape] whereas it comes in like _style2['default'].locals[shape]

natew commented 8 years ago

Perhaps they changed it even further since 0.21.0?

https://github.com/webpack/css-loader/commits/master

bartekus commented 8 years ago

This probably going to turn into a rant but hear me out...

To better understand how webpack based development works we first need to take a look at npm scripts in package.json file. In our case we see three scripts present:

"scripts": {
    "start": "node ./server",
    "build": "cross-env NODE_ENV=production UV_THREADPOOL_SIZE=100 webpack --config ./webpack.config",
    "deploy": "gh-pages -d build"
  },

npm run start or npm start

This will execute server.js script thru node and launch express-based (&webpack(&webpack-dev-middleware)-enhanced) javascript environment for use in frontend development but also to providing the first step in two-step process of merging said front-end to the backend in full javascript development on node.js.

npm run build

This will set cross-env & NODE_ENV=production flags, set UV_THREADPOOL_SIZE to 100 and command webpack to compile the source using the set of instructions in webpack.config. You probably wonder what UV_THREADPOOL_SIZE is and to keep it short, it's used to limit the threadpool that queuing the rendering requests maintained by the libuv cause during sass/SCSS style translation/webpack-code-separation. It's originally introduced in Node v0.10.0 as environment variable by Ben Noordhius in order to honor the use of UV_THREADPOOL_SIZE in Unix, in a subtle way of tipping his hat in thanks to the Open Source Community at large, for providing the ground on which JavaScript could mature to what it is and allow node.js to exist in the first place.

npm run deploy

This will deploy the build directory as gh-pages into github allowing you to take advantage of github free CDN capability to display your frontend creation.

So?

In react development state-tuning or fine-tuning of component state is immensely important albite not easy to execute. Luckly for us Dan Abramov implement React Transform which in turns enables hot reloading React classes using Hot Module Replacement API. Hot Module Replacement is supported natively by Webpack which acts as programmable DSL of sorts ; bridges the production/compilation and execution/feedback environment in code development workflow thru use of React-Transform-HMR and Babel. Keeping all these things in consistent-yet-fluid-state while also performing normal operations and doing so efficiently and error-free, requires incorporating time-stamp identification into each modular bit of data. However since we have no other way of tracking individual code-snippets changes, webpack has to monitor the state of the source files that contain the code and be able to translate that into actual data change that then must be reinjected into life-hot development environment. This allows you as the developer to observe almost instantaneous feedback of the code changes without having to depend on IDE. But remember that webpack doesn't handle anything specifically unless told to/extended by plugins and configured to do so. However depending on the nature of the requirement it can offer almost unlimited configuration flexibility thru it extensibility, modularity and pluggable nature on the module level. So this scrambled code that you see is the result of chunk hashing output used by webpack's to deal with cache staleness problems while also enabling instant feedback with minimal overhead in re-compiling time.

'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
'sass?sourceMap&outputStyle=expanded&includePaths[]=' + (path.resolve(runnerRoot, './node_modules'))

It translates to chunk loading as in code spliting coupled with SourceMaps.

If you look at RT-HMR the pluggin used in this webpack configuration you can see this proxy at work:

import { getForceUpdate, createProxy } from 'react-proxy';
import window from 'global/window';

let componentProxies;
if (window.__reactComponentProxies) {
  componentProxies = window.__reactComponentProxies;
} else {
  componentProxies = {};
  Object.defineProperty(window, '__reactComponentProxies', {
    configurable: true,
    enumerable: false,
    writable: false,
    value: componentProxies
  });
}

export default function proxyReactComponents({ filename, components, imports, locals }) {
  const [React] = imports;
  const [{ hot }] = locals;

...

  if (Object.keys(components).some(key => !components[key].isInFunction)) {
    hot.accept(err => {
      if (err) {
        console.warn(`[React Transform HMR] There was an error updating ${filename}:`);
        console.error(err);
      }
    });
  }

  const forceUpdate = getForceUpdate(React);

  return function wrapWithProxy(ReactClass, uniqueId) {
    const {
      isInFunction = false,
      displayName = uniqueId
    } = components[uniqueId];

    if (isInFunction) {
      return ReactClass;
    }

    const globalUniqueId = filename + '$' + uniqueId;
    if (componentProxies[globalUniqueId]) {
      console.info('[React Transform HMR] Patching ' + displayName);
      const instances = componentProxies[globalUniqueId].update(ReactClass);
      setTimeout(() => instances.forEach(forceUpdate));
    } else {
      componentProxies[globalUniqueId] = createProxy(ReactClass);
    }

    return componentProxies[globalUniqueId].get();
  };
}

Looking at this we can clearly see functions to hash the output as to be able time-stamp the state contained in the application and thus enable webpack to softly reload just the 'bits' that actually changed ala what React does with Virtual-DOM but on live code level.

This is just one of many benefits that webpack allows with its ultra extendability and pluggable module-loading nature. You can fine tune your website/app's (or general project development's) entire life cycle if you like, but webpack alone doesn't deserve all the glory here.

Let us not forget about the considerable power that npm & node alone yield without webpack, all courtesy and beauty of (once laughed at) scripting language, JavaScript - currently undergoing its own renaissance, as it discovers new-old-way of doing things and in the process learns its limits & capitalize on its strengths.

So I might be wrong to call JavaScript "lisp-dialect" but I honestly feel that the workflow with 'flow enabled' react.js in webpack-babel-node-express environment enables one to efficiently create code that previously was reserved to typed languages such as C# and VB.NET and their fancy IDE's; not that JS is weak in that department either, as both Atom and WebStorm are what I'd call 'Trully Sublime' ;)

JavaScript's fast rate of development coupled with it's nature as common language of the web requires deployment and use of tools that can be modified to suit the needs of ever-evolving code. With webpack we can extend javascript to state of homoiconicity that allow concept-to-deployment in continuous-development-cycle style fashion and treading on leading edge of JavaScript language development. While I understand that this can be very overwhelming to some, I also believe that it provides a potent ground for personal growth and innovation that doesn't splinter but rather complements the whole. JavaScript is about diversity and we are best served to hone our abilities in peer-to-peer fashion, feeding-on one-another's ideas and concepts and harmoniously complement each other's efforts. Ultimately together we are far more that individually we can ever hope to be and to me, while not really being a motto of webpack, it certainly speaks the best of it's true value.

I find that this is really great time not only for for JavaScript but whole Open Software as a collective.

//End rant();

Any questions ;) ?

natew commented 8 years ago

@Bartekus A great and long rant, but was that meant for this thread?

bartekus commented 8 years ago

Perhaps not really true to the question that you've asked by appropriate to the title being Document webpack setup ;) Maybe I'm just fishing for a feedback as I'm trying to do a better job of understanding where there are most problems that people experience. My advice is how I've started, by reverse-engineering other programmers creations. Webpack can be enigmatic if you get sidelined by the options and configurations, but underneath it there is subtle clarity and power.

bartekus commented 8 years ago

@natew but more seriously now, let me take a good look at this config of your and see if I can alleviate your webpack ache

natew commented 8 years ago

Actually I think Webpack is pretty messed up at the moment, and hopefully v2 or future changes will help. But that's a huge long post I unfortunately don't have time to write out.

I mean, every package you have to figure out some configuration for it, update loaders, etc. It should be more on the package end.

Also CSS in general is totally broken and shouldn't be in npm at all (imo) or should be fixed if it is in some big way.

Anyway, the fact I still can't get this project working and I've been an advanced webpack user for years now (I made Reapp using it) is saying something.

natew commented 8 years ago

My issue is well documented above, it seems to import the styles as an objects with local key, and react-toolbox is not using local it just tried to find it directly on the import.

bartekus commented 8 years ago

Well then, I shall retract to my cave now :D

bartekus commented 8 years ago

I don't think it's webpack fault alone (or at all really) and to blame it when it's only glorified module loader that solely depend on external plugins to extend it's functionality, is unfounded. Obviously if you look at the javascript field as whole you'll feel the reverberations that babel 6 upgrade has caused, overnight breaking out over 200 modules. But you'd be silly to think that was wrong move when they simply follow specs and this plus people's lack of general ES6 understanding. [Mainly in area of module creators who mix ES5/ES6 modules/syntax and by incorrectly using the same ES6 import syntax on ES5 modules, creating not immediately noticeable havoc]. If I may I remind you that most of webpack developments depend heavily on babel for it's core functionality. So yeah, I kinda find your comments a little bit harsh considering that webpack is only slightly older than babel and both have redefined the rate of javascript development. But with increase in speed the rest of the development cycle has been sped up too which might be frustrating, but only temporarily pain. If majority if doing something wrong, sometimes you gotta play the bad guy and enforce good standards for the benefit of everybody, not just single development and do so in often unpleasant ways...

Where can I find your webpack/server configs?

natew commented 8 years ago

No worries, for me it's just thinking of the future. One day it will be much, much better, so it's worth admitting today it isn't what it could be (without taking away anything from the amazing work of everyone).

Anyway, here's the loader config:

let styleFileLoader = 'file?name=assets/styles/[name]-[hash].css'

// ...

module: {
    loaders: [
      { test: /\.(ttf|eot|woff|svg)$/, loader: 'file?name=assets/files/[name]-[hash].[ext]' },
      { test: /\.json$/, loader: 'json' },
      { test: /\.jsx$/, loader: 'babel' },

      {
        test: /\.css$/,
        loaders: [
          styleFileLoader,
          'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
        ]
      },

      {
        test: /\.(png|jpg|gif)$/,
        loader: 'url?limit=8192&name=[name]-[hash].[ext]'
      },

      {
        test: /\.scss$/,
        loaders: [
          styleFileLoader,
          // 'resolve-url',
          // 'style',
          'css?sourceMap&modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
          'sass?sourceMap'
        ]
      }

Still stuck on this a bit. I removed the file loader and use the other ones but still get the thing where it puts them on .local in the style import.

bartekus commented 8 years ago

Here, try to use this:

import path from "path";
import webpack from "webpack";
import ExtractTextPlugin from "extract-text-webpack-plugin";

export default (DEBUG, PATH, PORT=3000) => ({
  entry: (DEBUG ? [
    `webpack-dev-server/client?http://localhost:${PORT}`,
  ] : []).concat([
    './src/main.less',
    'babel-polyfill',
    './src/main',
  ]),

  output: {
    path: path.resolve(__dirname, PATH, "generated"),
    filename: DEBUG ? "main.js" : "main-[hash].js",
    publicPath: "/generated/"
  },

  cache: DEBUG,
  debug: DEBUG,

  // For options, see http://webpack.github.io/docs/configuration.html#devtool
  devtool: DEBUG && "eval",

  module: {
    loaders: [
      // Load ES6/JSX
      { test: /\.jsx?$/,
        include: [
          path.resolve(__dirname, "src"),
        ],
        loader: "babel-loader",
        query: {
          plugins: ['transform-runtime'],
          presets: ['es2015', 'stage-0', 'react'],
        }
      },

      // Load styles
      { test: /\.less$/,
        loader: DEBUG
          ? "style!css!autoprefixer!less"
          : ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader!less-loader")
      },

      // Load images
      { test: /\.jpg/, loader: "url-loader?limit=10000&mimetype=image/jpg" },
      { test: /\.gif/, loader: "url-loader?limit=10000&mimetype=image/gif" },
      { test: /\.png/, loader: "url-loader?limit=10000&mimetype=image/png" },
      { test: /\.svg/, loader: "url-loader?limit=10000&mimetype=image/svg" },

      // Load fonts
      { test: /\.woff(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&mimetype=application/font-woff" },
      { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" },
    ]
  },

  plugins: DEBUG
    ? []
    : [
      new webpack.DefinePlugin({'process.env.NODE_ENV': '"production"'}),
      new ExtractTextPlugin("style.css", {allChunks: false}),
      new webpack.optimize.DedupePlugin(),
      new webpack.optimize.UglifyJsPlugin({
        compressor: {screw_ie8: true, keep_fnames: true, warnings: false},
        mangle: {screw_ie8: true, keep_fnames: true}
      }),
      new webpack.optimize.OccurenceOrderPlugin(),
      new webpack.optimize.AggressiveMergingPlugin(),
    ],

  resolveLoader: {
    root: path.join(__dirname, "node_modules"),
  },

  resolve: {
    root: path.join(__dirname, "node_modules"),

    modulesDirectories: ['node_modules'],

    alias: {
      environment: DEBUG
        ? path.resolve(__dirname, 'config', 'environments', 'development.js')
        : path.resolve(__dirname, 'config', 'environments', 'production.js')
    },

    // Allow to omit extensions when requiring these files
    extensions: ["", ".js", ".jsx"],
  }
});

General purpose with strong debugging capabilities, but make sure that you don't have .babelrc in the root since babel config is included but in normal conditions babel will always prefer .babelrc over any other source of config fyi

natew commented 8 years ago

I see you don't do any configuration for css-modules, isn't that required to make this work?

bartekus commented 8 years ago

Here we are extracting the .less code, then transpile it thru autoprefixer and finally load it as css using the style-loader. Notice the right to left order:

ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader!less-loader")
felipeleusin commented 8 years ago

After a lot of trial and error I finally got this working with HMR (on Babel6). My loader config is:

{
      test: /(\.scss|\.css)$/,
      loader: 'style!css?sourceMap&modules&importLoaders=2!postcss!sass?sourceMap!toolbox'
}

what did the trick was including modules and importLoaders=2 instead of 1
javivelasco commented 8 years ago

Thanks @felipeleusin , what means the 2?

felipeleusin commented 8 years ago

They set the amount of loaders the css loader is going to include. https://github.com/webpack/css-loader#importing-and-chained-loaders

But I actually think the catch is including the modules param, that sometimes is missing in some examples. I think this should go to the docs.

javivelasco commented 8 years ago

Ok, I agree. The docs need a little bit of love, I'm sure they are a little outdated for some components