Reactgo: Spike Webpack 2.0

Created on 30 Nov 2016  路  8Comments  路  Source: reactGo/reactGo

All 8 comments

Is this stable or under development?
Webpack in npm is still 1.13.3

I think they are still working on the docs, and there are still a couple of issues they need to iron out. Maybe I should change the title to spike hehe

iirc docs were the only thing holding back 2.0 release - but other plugins/packages still have some teething issues. API stable enough to making the changes though, I think any dealbreaker issues should be pretty apparent.

I have successfully migrated my app based on reactGo to Webpack 2. As always for Webpack, when it works, it seems to be straightforwarded, but it takes time to tweak the settings to make things work. In general, a few string replacement in the webpack config, "resolve" takes different options as webpack 1, a few modules need to be upgraded to work with webpack 2.2.0-rc.3, and stylelint-webpack-plugin is not compatible with Webpack 2 yet. Also postcss-loader needs to have its config in a separate file postcss.config.js in the project directory.

/**
 * webpack.config.js
 *
 * process.env.NODE_ENV is used to determine to return production config or not (an array with both browser and server config)
 * if not, env is used to determine to return browser-rendering config (for hot module replacement) or server-side rendering config (for node)
 * env is a string passed by "webpack --env" on command line or calling this function directly
 * if env contains substring 'browser', then returns browser-rendering config, otherwise server-rendering config
 * NOTE: browser/server is client/server-side rendering respectively in universial/isomophoric javascript
 */
const fs = require('fs');
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = (env = '') => {
  const isProd = process.env.NODE_ENV === 'production';
  const browserRender = (env.indexOf('browser') >= 0);
  debug.info(`running webpack.config.js; isProd=${isProd}; browserRender=${browserRender};`);

  /**
   * __dirname is unreliable after webpack-ed to another directory
   * so process.cwd() is used instead to determine the correct base directory
   */
  const __DIR__ = process.cwd();

  const PATHS = {
    app: path.resolve(__DIR__, 'app'),
    javascript: path.resolve(__DIR__, 'public', 'js'),
    compiled: path.resolve(__DIR__, 'compiled'),
    public: '/js/', // use absolute path for css-loader?
    modules: path.resolve(__DIR__, 'node_modules')
  };

  const externals = fs.readdirSync('node_modules')
    .filter(x => ['.bin'].indexOf(x) === -1)
    .reduce((acc, cur) => Object.assign(acc, { [cur]: 'commonjs ' + cur }), {});

  const urlLoader = (limit = 10240) => ({
    test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/,
    loader: 'url-loader',
    query: { name: '[hash].[ext]', limit }, // name for file-loader
    include: PATHS.app
  });

  const babelLoader = (production = false, browser = false) => {
    const obj = {
      test: /\.js$|\.jsx$/,
      loader: 'babel-loader',
      query: {
        presets: ['es2015', 'react', 'stage-0'],
        plugins: ['transform-decorators-legacy']
      },
      exclude: PATHS.modules
    };
    if (production) {
      obj.query.plugins.push(
        'transform-react-remove-prop-types',
        'transform-react-constant-elements',
        'transform-react-inline-elements'
      );
    } else if (browser) {
      obj.query.presets.unshift('react-hmre');
    }
    return obj;
  };

  const cssLoader = (production = false, browser = false) => {
    /*
     * https://github.com/webpack/css-loader/issues/59 - use css-loader/locals on server-side rendering
     * :local(.className) used to declare className in the local scope,
     * local identifiers are exported by css-loader
     *
     * modules enable CSS Modules spec https://github.com/css-modules/css-modules
     *
     * importLoaders (int): That many loaders after the css-loader are applied to @import-ed resources.
     *
     */
    const queryModules = 'modules';
    const queryImportLoaders = 'importLoaders=1';
    const queryLocal = 'localIdentName=[name]__[local]___[hash:base64:5]';

    const queryStrExtractTextPlugin = '?' + queryModules + '&' + queryImportLoaders;
    const queryStr = queryStrExtractTextPlugin + '&' + queryLocal;

    const serverSide = 'css-loader/locals' + queryStr + '!postcss-loader';
    const browserSide = 'style-loader!css-loader' + queryStr + '!postcss-loader';

    const myLoader = browser ? browserSide : serverSide;

    const prodBrowserRenderLoader = ExtractTextPlugin.extract({
      fallbackLoader: 'style-loader',
      loader: 'css-loader' + queryStrExtractTextPlugin + '!postcss-loader'
    });
    const loader = production && browser ? prodBrowserRenderLoader : myLoader;
    const obj = { test: /\.css$/, include: PATHS.app, loader };
    return obj;
  };

  const webpackPlugins = (production = false, browser = false) => {
    if (!production && !browser) {
      return [
        // new webpack.EnvironmentPlugin(['NODE_ENV']),
        new webpack.IgnorePlugin(/vertx/)
      ];
    }
    if (!production && browser) {
      return [
        new webpack.HotModuleReplacementPlugin(),
        new webpack.EnvironmentPlugin(['NODE_ENV']),
        new webpack.NoErrorsPlugin()
        // stylelint-webpack-plugin does not support webpack 2 yet
        // new styleLintPlugin({
        //   configFile: path.resolve(__DIR__, '.stylelintrc'),
        //   context: path.resolve(__DIR__, 'app'),
        //   files: '**/*.?(sa|sc|c)ss'
        // })
      ];
    }
    if (production && !browser) {
      return [
        // new webpack.EnvironmentPlugin(['NODE_ENV']),
        new webpack.IgnorePlugin(/vertx/),
        new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } })
      ];
    }
    if (production && browser) {
      const filename = '../css/main.css';
      const allChunks = true;
      const compress = { warnings: false };

      return [
        new ExtractTextPlugin({ filename, allChunks }), // extracted css to css/main.css
        new webpack.EnvironmentPlugin(['NODE_ENV']),
        // new webpack.optimize.CommonsChunkPlugin({ names: ['vendor', 'manifest'] }),
        new webpack.optimize.UglifyJsPlugin({ compress })
      ];
    }
    return [];
  };

  const hotMiddlewareScript = 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true';

  const resolve = {
    modules: [PATHS.app, PATHS.modules],
    extensions: ['.js', '.jsx', '.css'],
  };

  /*
   * PRODUCTION WEBPACK CONFIG
   */
  const prodServerRender = {
    devtool: 'source-map',
    context: PATHS.app,
    entry: { server: '../server/index' },
    target: 'node',
    node: { __dirname: true, __filename: true },
    externals,
    output: {
      path: PATHS.compiled,
      filename: '[name].js',
      publicPath: PATHS.public,
      libraryTarget: 'commonjs2'
    },
    module: { rules: [babelLoader(isProd, false), cssLoader(isProd, false), urlLoader()] },
    resolve,
    plugins: webpackPlugins(true, false)
  };
  const prodBrowserRender = {
    devtool: 'cheap-module-source-map',
    context: PATHS.app,
    entry: { app: ['./client'], vendor: ['react'] },
    node: { __dirname: true, __filename: true },
    output: {
      path: PATHS.javascript,
      filename: '[name].js', // filename: '[name].[hash:6].js',
      chunkFilename: '[name].[chunkhash:6].js', // for code splitting. will work without but useful to set
      publicPath: PATHS.public
    },
    module: { rules: [babelLoader(isProd, true), cssLoader(isProd, true), urlLoader()] },
    resolve,
    plugins: webpackPlugins(true, true)
  };


  /*
   * DEVELOPMENT WEBPACK CONFIG
   */
  const devBrowserRender = {
    devtool: 'eval-source-map',
    context: PATHS.app,
    entry: { app: ['./client', hotMiddlewareScript], vendor: ['react'] },
    node: { __dirname: true, __filename: true },
    output: {
      path: PATHS.javascript,
      filename: '[name].js',
      publicPath: PATHS.public
    },
    module: { rules: [babelLoader(isProd, true), cssLoader(isProd, true), urlLoader()] },
    resolve,
    plugins: webpackPlugins(false, true),
    performance: { hints: false } // Disable performance hints during development
  };

  const devServerRender = {
    devtool: 'sourcemap',
    context: PATHS.app,
    entry: { server: '../server/index' },
    target: 'node',
    node: { __dirname: true, __filename: true },
    externals,
    output: {
      path: PATHS.compiled,
      filename: '[name].dev.js',
      publicPath: PATHS.public,
      libraryTarget: 'commonjs2',
    },
    module: { rules: [babelLoader(isProd, false), cssLoader(isProd, false), urlLoader()] },
    resolve,
    plugins: webpackPlugins(false, false),
    performance: { hints: false }
  };

  const prodConfig = [prodBrowserRender, prodServerRender];
  const devConfig = browserRender ? devBrowserRender : devServerRender;
  const configuration = isProd ? prodConfig : devConfig;

  console.log(configuration);
  return configuration;
};
/*
 * postcss.config.js
 *
 * use by webpack to configure postcss-loader
 * see usage in https://github.com/postcss/postcss-loader
 *
 */
const path = require('path');
const postcssImport = require('postcss-import');
const postcssCssnext = require('postcss-cssnext');
const postcssReporter = require('postcss-reporter');

module.exports = {
  plugins: [
    postcssImport({ path: path.resolve(process.cwd(), './app/css') }),
    postcssCssnext({ browsers: ['> 1%', 'last 2 versions'] }),
    postcssReporter({ clearMessages: true })
  ]
};

Amazing work @lapinskicho !! Would you like to create a PR? Otherwise I'm super happy to use this as a reference implementation!

If @lapinskicho cant make PR, I will. I also succeeded in migrating to webpack@2

I just did. It is #806

Was this page helpful?
0 / 5 - 0 ratings

Related issues

slavab89 picture slavab89  路  6Comments

choonkending picture choonkending  路  9Comments

zacharybrady picture zacharybrady  路  4Comments

jrodl3r picture jrodl3r  路  7Comments

choonkending picture choonkending  路  4Comments