如何针对SSR优化我的webpack设置?

时间:2017-11-04 01:01:52

标签: webpack serverside-rendering

我目前在我的反应应用程序中有3个webpack脚本,一个用于服务器,两个用于客户端。

如何优化代码和构建?我有一种感觉,它可以做得更好,样式,字体加载服务器渲染构建。也许我做的一些事情是不必要的?我是否需要在服务器构建上使用My folder structure . ├── /build/ # Here webpack builds goes ├── /src/ │ ├── /client/ # React app files │ ├── /server/ # Server rendering │ ├── /shared/ # shared react components and modules (in case i need more clients) ├── /bin/ # project utils and helpers ├── /webpack/ # webpack scripts ├── ... # etc. └── package.json

package.json

我的 "scripts": { "prebuild": "yarn build:clean", "build": "yarn build:server && yarn build:client", "build:dll": "webpack --display-chunks --color --config ./webpack/webpack.dll.babel.js", "build:client": "webpack --config ./webpack/webpack.prod.babel.js --color -p --progress", "build:server": "webpack --config ./webpack/webpack.server.babel.js --color -p --progress", "prestart": "yarn install && yarn build", "start": "cross-env NODE_ENV=production node build/server", "predev": "cross-env NODE_ENV=development yarn install && yarn build:dll", "dev": "cross-env NODE_ENV=development webpack --config ./webpack/webpack.server.babel.js && node build/server", }, 脚本如下所示:

// Common Webpack configuration used by webpack.config.development and webpack.config.production
const path = require("path");
const webpack = require("webpack");
const autoprefixer = require("autoprefixer");
const e2c = require("electron-to-chromium");
const GLOBALS = require('../bin/helpers/globals');

const ExtractTextPlugin = require("extract-text-webpack-plugin");

const isProd = process.env.NODE_ENV === 'production';
const postcssLoaderOptions = {
  plugins: [
    autoprefixer({ browsers: e2c.electronToBrowserList("1.4") }),
  ],
  sourceMap: !isProd,
};

GLOBALS['process.env'].__CLIENT__ = true;

module.exports = {
  target: 'web', // Make web variables accessible to webpack, e.g. window
  output: {
    filename: 'js/[name].[hash].js',
    chunkFilename: 'js/[name].[hash].chunk.js',
    path: path.resolve(process.cwd(), 'build'),
    publicPath: "/"
  },
  resolve: {
    modules: ["node_modules"],
    alias: {
      client: path.resolve(process.cwd(), "src/client"),
      shared: path.resolve(process.cwd(), "src/shared"),
      server: path.resolve(process.cwd(), "src/server")
    },
    extensions: [".js", '.jsx', ".json", ".scss"],
    mainFields: ["browser", "module", 'jsnext:main', "main"],
  },
  plugins: [
    new webpack.NormalModuleReplacementPlugin(
      /\/Bundles.js/,
      './AsyncBundles.js'
    ),
    new webpack.IgnorePlugin(/vertx/),
    new webpack.ProvidePlugin({
      Promise: 'imports-loader?this=>global!exports-loader?global.Promise!es6-promise',
      fetch: "imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch", // fetch API
      $: "jquery",
      jQuery: "jquery",
    }),
    new webpack.DefinePlugin(GLOBALS),
    new ExtractTextPlugin({
      filename: "css/[name].[hash].css",
      disable: false,
      allChunks: true
    })
  ],
  module: {
    rules: [
      // JavaScript / ES6
      {
        test: /\.(js|jsx)?$/,
        include: [
          path.resolve(process.cwd(), "src/client"),
          path.resolve(process.cwd(), "src/shared"),
        ],
        exclude: /node_modules/,
        use: "babel-loader"
      },
      // Json
      {
        test: /\.json$/,
        use: 'json-loader',
      },
      //HTML
      {
        test: /\.html$/,
        include: [
          path.resolve(process.cwd(), "src/client"),
        ],
        use: [
          {
            loader: "html-loader",
            options: {
              minimize: true
            }
          }
        ]
      },
      // Images
      // Inline base64 URLs for <=8k images, direct URLs for the rest
      {
        test: /\.(png|jpg|jpeg|gif|svg)(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 8192,
            name: "images/[name].[ext]?[hash]"
          }
        }
      },
      // Fonts
      {
        test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10000,
            mimetype: 'application/font-woff',
            name: "fonts/[name].[ext]?[hash]",
          }
        }
      },
      {
        test: /\.(ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: 'file-loader',
          options: {
            limit: 10000,
            name: "fonts/[name].[ext]?[hash]",
          }
        }
      },
      // Styles
      {
        test: /\.s?css$/,
        include: [
          path.resolve(process.cwd(), "src/client"),
          path.resolve(process.cwd(), "src/shared"),
        ],
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: [
            {
              loader: "css-loader",
              options: {
                sourceMap: true,
                modules: true,
                importLoaders: 1,
                localIdentName: '[local]_[hash:base64:3]'
              }
            },
            {
              loader: "postcss-loader",
              options: postcssLoaderOptions
            },
            {
              loader: "sass-loader",
              options: {
                sourceMap: true,
                outputStyle: "compressed"
              }
            }
          ]
        })
      },
    ]
  }
};

这是我的webpack脚本:

webpack.base.babel.js

/**
 * WEBPACK DLL GENERATOR
 *
 * This profile is used to cache webpack's module
 * contexts for external library and framework type
 * dependencies which will usually not change often enough
 * to warrant building them from scratch every time we use
 * the webpack process.
 */

const { join } = require('path');
const merge = require("webpack-merge");
const webpack = require('webpack');
const config = require("./webpack.base.babel");
const dllPlugin = require('../bin/dllPlugin');

if (!dllPlugin.enabled) { process.exit(0); }

module.exports = merge(config, {
  context: process.cwd(),
  entry: dllPlugin.dlls ? dllPlugin.dlls : dllPlugin.entry(),
  devtool: 'eval',
  output: {
    filename: '[name].dll.js',
    path: dllPlugin.path,
    library: '[name]',
  },
  plugins: [
    new webpack.DllPlugin({ name: '[name]', path: join(dllPlugin.path, '[name].json') }),
  ],
  performance: {
    hints: false,
  },
});

webpack.dll.babel.js

const path = require('path');
const merge = require("webpack-merge");
const webpack = require("webpack");
const config = require("./webpack.base.babel");

const OfflinePlugin = require('offline-plugin');
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = merge(config, {
  devtool: "nosources-source-map",
  // In production, we skip all hot-reloading stuff
  entry: [
    'babel-polyfill', // Needed for redux-saga es6 generator support
    path.join(process.cwd(), 'src/client/app.js'), // Start with app.js
  ],
  performance: {
    assetFilter: (assetFilename) => !(/(\.map$)|(^(main\.|favicon\.))/.test(assetFilename)),
  },
  plugins: [
    new webpack.LoaderOptionsPlugin({
      minimize: true
    }),
    new HtmlWebpackPlugin({
      template: "src/client/index.html",
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
      inject: true,
    }),
    // Shared code
    new webpack.optimize.CommonsChunkPlugin({
      name: "vendor",
      children: true,
      minChunks: 2,
      async: true,
    }),
    // Avoid publishing files when compilation fails
    new webpack.NoEmitOnErrorsPlugin(),
    // Put it in the end to capture all the HtmlWebpackPlugin's
    // assets manipulations and do leak its manipulations to HtmlWebpackPlugin
    new OfflinePlugin({
      relativePaths: false,
      publicPath: '/',

      // No need to cache .htaccess. See http://mxs.is/googmp,
      // this is applied before any match in `caches` section
      excludes: ['.htaccess'],

      caches: {
        main: [':rest:'],

        // All chunks marked as `additional`, loaded after main section
        // and do not prevent SW to install. Change to `optional` if
        // do not want them to be preloaded at all (cached only when first loaded)
        additional: ['*.chunk.js'],
      },

      // Removes warning for about `additional` section usage
      safeToUseOptionalCaches: true,

      AppCache: false,
    }),
  ]
});

webpack.prod.babel.js

const merge = require("webpack-merge");
const webpack = require("webpack");
const config = require("./webpack.base.babel");
const dllPlugin = require('../bin/dllPlugin');
const path = require('path');
const fs = require('fs');
const cheerio = require('cheerio');
const logger = require('../src/server/middleware/logger');

const CircularDependencyPlugin = require('circular-dependency-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

/**
 * We dynamically generate the HTML content in development so that the different
 * DLL Javascript files are loaded in script tags and available to our application.
 */
function templateContent() {
  const html = fs.readFileSync(path.join(process.cwd(), 'src/client/index.html')).toString();

  if (!dllPlugin.enabled) { return html; }

  const doc = cheerio(html);
  const body = doc.find('body');
  const dllNames = !dllPlugin.dlls ? ['reactDeps'] : Object.keys(dllPlugin.dlls);

  dllNames.forEach(dllName => body.append(`<script data-dll='true' src='/${dllName}.dll.js'></script>`));

  return doc.toString();
}

/**
 * Select which plugins to use to optimize the bundle's handling of
 * third party dependencies.
 *
 * If there is a dllPlugin key on the project's package.json, the
 * Webpack DLL Plugin will be used.  Otherwise the CommonsChunkPlugin
 * will be used.
 *
 */
function dependencyHandlers() {
  // If the package.json does not have a dllPlugin property, use the CommonsChunkPlugin
  if (!dllPlugin.enabled) {
    return [
      // Shared code
      new webpack.optimize.CommonsChunkPlugin({
        name: "vendor",
        children: true,
        minChunks: 2,
        async: true,
      }),
    ];
  }

  /**
   * If DLLs aren't explicitly defined, we assume all production dependencies listed in package.json
   * Reminder: You need to exclude any server side dependencies by listing them in dllConfig.exclude
   */
  if (!dllPlugin.dlls) {
    const manifestPath = path.join(dllPlugin.path, 'reactDeps.json');
    if (!fs.existsSync(manifestPath)) {
      logger.error('The DLL manifest is missing. Please run `npm run build:dll`');
      process.exit(0);
    }

    return [
      new webpack.DllReferencePlugin({
        context: process.cwd(),
        manifest: require(manifestPath),
      })
    ];
  }

  // If DLLs are explicitly defined, we automatically create a DLLReferencePlugin for each of them.
  const dllManifests = Object.keys(dllPlugin.dlls).map((name) => path.join(dllPlugin.path, `${name}.json`));
  return dllManifests.map((manifestPath) => {
    if (!fs.existsSync(path)) {
      if (!fs.existsSync(manifestPath)) {
        logger.error(`The following Webpack DLL manifest is missing: ${path.basename(manifestPath)}`);
        logger.error(`Expected to find it in ${dllPlugin.path}`);
        logger.error('Please run: npm run build:dll');

        process.exit(0);
      }
    }

    return new webpack.DllReferencePlugin({
      context: process.cwd(),
      manifest: require(manifestPath),
    });
  });
}

const plugins = [
  new webpack.LoaderOptionsPlugin({
    debug: true,
    cache: true
  }),
  new HtmlWebpackPlugin({
    inject: true, // Inject all files that are generated by webpack, e.g. bundle.js
    templateContent: templateContent(),
  }),
  new webpack.NamedModulesPlugin(),
  new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading
  new CircularDependencyPlugin({
    exclude: /a\.js|node_modules/, // exclude node_modules
    failOnError: false, // show a warning when there is a circular dependency
  }),
];

module.exports = merge(config, {
  devtool: "source-map",
  entry: [
    'webpack-hot-middleware/client?reload=true',
    'babel-polyfill', // Needed for redux-saga es6 generator support
    'react-hot-loader/patch',
    path.join(process.cwd(), 'src/client/app.js'), // Start with js/app.js
  ],
  // Don't use hashes in dev mode for better performance
  output: {
    filename: '[name].js',
    chunkFilename: '[name].chunk.js',
  },
  performance: {
    hints: false,
  },
  plugins: dependencyHandlers().concat(plugins),
});

webpack.dev.babel.js

const path = require("path");
const webpack = require("webpack");
const nodeExternals = require("webpack-node-externals");
const GLOBALS = require('../bin/helpers/globals');
const autoprefixer = require("autoprefixer");
const e2c = require("electron-to-chromium");

const ExtractTextPlugin = require("extract-text-webpack-plugin");

const isProd = process.env.NODE_ENV === 'production';
const postcssLoaderOptions = {
  plugins: [
    autoprefixer({ browsers: e2c.electronToBrowserList("1.4") }),
  ],
  sourceMap: !isProd,
};

GLOBALS['process.env'].__CLIENT__ = false;

module.exports = {
  target: "node", // in order to ignore built-in modules like path, fs, etc.
  externals: [nodeExternals()], // in order to ignore all modules in node_modules folder
  devtool: isProd ? "cheap-module-source-map" : "source-map", // original source (lines only) || original source
  node: {
    //in order to make webpack disable __dirname/__filename injection
    __dirname: false,
    __filename: false
  },
  entry: "./src/server",
  output: {
    filename: "index.js",
    path: path.resolve(process.cwd(), 'build/server'),
  },
  resolve: {
    modules: ["node_modules"],
    alias: {
      bin: path.resolve(process.cwd(), "bin"),
      client: path.resolve(process.cwd(), "src/client"),
      shared: path.resolve(process.cwd(), "src/shared"),
      server: path.resolve(process.cwd(), "src/server")
    },
    extensions: [".js", '.jsx', ".json", ".scss"],
  },
  module: {
    rules: [
      // JavaScript / ES6
      {
        test: /\.jsx?$/,
        include: [
          path.resolve(process.cwd(), "bin"),
          path.resolve(process.cwd(), "webpack"),
          path.resolve(process.cwd(), "src/client"),
          path.resolve(process.cwd(), "src/shared"),
          path.resolve(process.cwd(), "src/server"),
        ],
        use: "babel-loader",
      },
      // Json
      {
        test: /\.json$/,
        use: 'json-loader',
      },
      // Images
      // Inline base64 URLs for <=8k images, direct URLs for the rest
      {
        test: /\.(png|jpg|jpeg|gif|svg)$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 8192,
            name: "images/[name].[ext]?[hash]",
            emitFile: false
          }
        }
      },
      // Fonts
      {
        test: /\.(woff|woff2|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 8192,
            name: "fonts/[name].[ext]?[hash]",
            emitFile: false
          }
        }
      },
      // Styles
      {
        test: /\.s?css$/,
        include: [
          path.resolve(process.cwd(), "src/client"),
          path.resolve(process.cwd(), "src/shared"),
        ],
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: [
            {
              loader: "css-loader",
              options: {
                sourceMap: !isProd,
                modules: true,
                importLoaders: 1,
                localIdentName: '[local]_[hash:base64:3]'
              }
            },
            {
              loader: "postcss-loader",
              options: postcssLoaderOptions
            },
            {
              loader: "sass-loader",
              options: {
                sourceMap: !isProd,
                outputStyle: "compressed"
              }
            }
          ]
        })
      },
    ]
  },
  plugins: [
    new webpack.LoaderOptionsPlugin({
      minimize: isProd,
      debug: !isProd,
    }),
    new webpack.DefinePlugin(GLOBALS),
    new webpack.NamedModulesPlugin(),
    new ExtractTextPlugin({
      filename: "css/[name].[hash].css",
      disable: false,
      allChunks: true
    })
  ]
};

webpack.server.babel.js

class EntryManager(models.Manager):
   def with_documents(self):
       vector = SearchVector('title', weight='A') + \
                SearchVector('slug', weight='B') + \
                SearchVector('content', weight='C') + \
                SearchVector('category', weight='D') + \
                SearchVector(StringAgg('tags__slug', delimiter=' ')) + \
                SearchVector('chron_date')
       if SearchVector('chron_date') == "":
           SearchVector('chron_date') == None
       if SearchVector('clock') == "":
           SearchVector('clock') == None
       return self.get_queryset().annotate(document=vector)

如何改善设置?

1 个答案:

答案 0 :(得分:0)

我之前使用的一个惊人的例子就是这个:

https://github.com/faceyspacey/universal-demo

你基本上可以获得Next.js的所有功能但没有隐藏的webpack配置。希望能帮助你指明正确的方向。