减少生产中的webpack包

时间:2017-04-04 08:50:48

标签: reactjs webpack production

我正在尝试使用webpack减少我的生产包。现在我有这个输出

Webpack compilation completed
Hash: c3bd2a36378a1a1de0e5
Version: webpack 2.3.3
Time: 34359ms
                                      Asset       Size  Chunks                    Chunk Names
       b56ef938f034f4024bf903e4a84661ed.png    14.9 kB          [emitted]         
                app.4d4b60bfa2635b5338de.js     403 kB       0  [emitted]  [big]  app
             vendor.8415656eaa2192fe6d27.js     976 kB       1  [emitted]  [big]  vendor
   app.3751d8e65855ec1a82d774639233c975.css     209 kB       0  [emitted]         app
vendor.659d5b29c7a69fcec90ce077c2814915.css    2.15 kB       1  [emitted]         vendor
                                 index.html  682 bytes          [emitted]         
Child html-webpack-plugin for "index.html":
         Asset    Size  Chunks  Chunk Names
    index.html  545 kB       0

我尝试使用webpack-bundle-analyser分析我的捆绑包,然后我

Bundle analyser report

我可以做些什么来减少我使用的库的大小吗?在我的代码中我正在使用

import xxx from 'module/lib/xxx'

而不是

import { xxx } from 'module'

我尝试了很多东西,但我无法继续下去,所以如果你有更多的想法,我很乐意尝试。

以下是我的配置

webpack.config.js

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const path = require('path')
const webpack = require('webpack')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const project = require('./project.config')

const __DEV__ = project.globals.__DEV__
const __PROD__ = project.globals.__PROD__
const __TEST__ = project.globals.__TEST__

const APP_ENTRIES = [project.paths.client('index.js')]

if (__DEV__) {
  APP_ENTRIES.unshift(
    'react-hot-loader/patch',
    `webpack-dev-server/client?http://${project.server_host}:${project.server_port}`,
    'webpack/hot/only-dev-server'
  )
}

const config = {
  devtool: project.compiler_devtool,

  externals: {
    leaflet: 'L',
  },

  entry: {
    app: APP_ENTRIES,
    vendor: project.compiler_vendors,
  },

  output: {
    filename: `[name].[${project.compiler_hash_type}].js`,
    path: project.paths.dist(),
    publicPath: project.compiler_public_path,
  },

  resolve: {
    mainFields: ['module', 'browser', 'main'],
    modules: [
      project.paths.client(),
      'node_modules',
    ],
    alias: {
      config: path.resolve(project.paths.client(), 'config', project.flavor),
    },
  },

  module: {
    rules: [{
      test: /\.js$/,
      loader: 'babel-loader',
      exclude: /node_modules/,
    }, {
      test: /\.css$/,
      use: [{
        loader: 'style-loader',
      }, {
        loader: 'css-loader',
        options: {
          minimize: true,
        },
      }],
    }, {
      test: /\.scss$/,
      use: [{
        loader: 'style-loader',
      }, {
        loader: 'css-loader',
        options: {
          localIdentName: '[name]__[local]___[hash:base64:5]',
          modules: true,
          importLoaders: 1,
          minimize: true,
        },
      }, {
        loader: 'postcss-loader',
        options: {
          plugins: () => [
            require('autoprefixer'),
          ],
        },
      }, {
        loader: 'sass-loader',
        options: {
          includePaths: [].concat(project.paths.client('styles')),
          data: '@import "base.scss";',
        },
      }],
    }, {
      test: /\.(png|jpg)$/,
      loader: 'url-loader',
      options: {
        limit: 8192,
      },
    }],
  },

  plugins: [
    new BundleAnalyzerPlugin(),
    new LodashModuleReplacementPlugin({
      currying: true,
      flattening: true,
      paths: true,
      placeholders: true,
      shorthands: true,
    }),
    new webpack.DefinePlugin(project.globals),
    new webpack.optimize.OccurrenceOrderPlugin(),
  ],

  node: {
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
  },
}

if (__DEV__) {
  config.plugins.push(
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new HtmlWebpackPlugin({
      template: project.paths.public('index.html'),
      hash: false,
      filename: 'index.html',
      inject: 'body',
    })
  )
} else if (__PROD__) {
  config.plugins.push(
    new webpack.LoaderOptionsPlugin({
      minimize: true,
      debug: false,
    }),
    new HtmlWebpackPlugin({
      inject: true,
      template: project.paths.public('index.html'),
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true,
      },
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        screw_ie8: true, // React doesn't support IE8
        warnings: false,
      },
      mangle: {
        screw_ie8: true,
      },
      output: {
        comments: false,
        screw_ie8: true,
      },
    }),
    new webpack.optimize.AggressiveMergingPlugin())
}

if (!__TEST__) {
  config.plugins.push(
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
    })
  )
}

if (__TEST__) {
  config.module.rules.push({
    test: /\.spec\.js?$/,
    loader: 'babel-jest',
    exclude: /node_modules/,
  })
}

if (!__DEV__) {
  config.module.rules
    .filter(rule => String(rule.test).includes('css'))
    .forEach((rule) => {
      const first = rule.use[0]
      const rest = rule.use.slice(1)
      rule.use = ExtractTextPlugin.extract({
        fallback: first,
        use: rest,
      })
    })

  config.plugins.push(
    new ExtractTextPlugin({
      filename: '[name].[contenthash].css',
      allChunks: true,
    }))
}

module.exports = config

project.config.js

const path = require('path')
const pck = require('../package.json')

const environments = require('./env.config')

const config = {
  env: process.env.NODE_ENV || 'development',
  flavor: process.env.NODE_FLAVOR || 'local',

  path_base: path.resolve(__dirname, '..'),
  dir_client: 'src',
  dir_dist: 'dist',
  dir_public: 'public',

  server_host: 'localhost',
  server_port: process.env.PORT || 3000,

  compiler_devtool: 'eval',
  compiler_public_path: '/',
  compiler_hash_type: 'hash',
  compiler_fail_on_warning: false,
  compiler_stats: {
    chunks: false,
    chunkModules: false,
    colors: true,
  },
  compiler_vendors: Object.keys(pck.dependencies).filter(module => !['antd'].some(forbidden => forbidden === module)),
}

function base(...args) {
  return path.resolve(...[config.path_base].concat(args))
}

config.paths = {
  base,
  client: base.bind(null, config.dir_client),
  dist: base.bind(null, config.dir_dist),
  public: base.bind(null, config.dir_public),
}

config.globals = {
  'process.env': {
    NODE_ENV: JSON.stringify(config.env),
  },
  NODE_ENV: config.env,
  __DEV__: config.env === 'development',
  __PROD__: config.env === 'production',
  __TEST__: config.env === 'test',
}

const overrides = environments[config.env]
if (overrides) {
  Object.assign(config, overrides(config))
}

module.exports = config

env.config.js

module.exports = {
  development: config => ({
    compiler_public_path: `http://${config.server_host}:${config.server_port}/`,
  }),
  production: () => ({
    compiler_public_path: '/',
    compiler_devtool: false,
    compiler_hash_type: 'chunkhash',
    compiler_stats: {
      chunks: false,
      chunkModules: false,
      colors: true,
    },
  }),
}

.babelrc

{
  "presets": [
    ["es2015", {"modules": false}],
    "react"
  ],
  "plugins": [
    "lodash",
    ["transform-runtime", {
      "polyfill": false,
      "regenerator": true
    }],
    ["import", { "libraryName": "antd", "style": "css" }],
    ["transform-imports", {
      "lodash": {
        "transform": "lodash/${member}",
        "preventFullImport": true
      },
      "lodash-es": {
        "transform": "lodash/${member}",
        "preventFullImport": true
      }
    }]
  ],
  "env": {
    "test": {
      "plugins": [
        "system-import-transformer",
        "transform-es2015-modules-commonjs"
      ]
    }
  }
}

1 个答案:

答案 0 :(得分:2)

我有webpack 6.0.1。根据我测试过的cron-job.org,可以为webpack.config.js使用以下配置建议:

//webpack.config.js
module.exports = {
  ...
  devtool: 'cheap-module-source-map',
  ...
  plugins : [
    ...
    new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new webpack.HashedModuleIdsPlugin({
      hashFunction: 'sha256',
      hashDigest: 'hex',
      hashDigestLength: 4
    }),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
  ],

  ...

  optimization: {
    namedModules: false,
    namedChunks: false,
    nodeEnv: 'production',
    flagIncludedChunks: true,
    occurrenceOrder: true,
    sideEffects: true,
    usedExports: true,
    concatenateModules: true,
    splitChunks: {
      cacheGroups: {
        commons: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendor',
            chunks: 'all'
        }
      },
      minSize: 30000,
      maxAsyncRequests: 5,
      maxAsyncRequests: 3,      
    },
    noEmitOnErrors: true,
    minimize: true, 
    minimizer: [
      // we specify a custom UglifyJsPlugin here to get source maps in production
      new UglifyJsPlugin({
        cache: true,
        parallel: true,
        uglifyOptions: {
          compress: false,
          ecma: 6,
          mangle: true
        },
        sourceMap: true
      })
    ],
    removeAvailableModules: true,
    removeEmptyChunks: true,
    mergeDuplicateChunks: true,    
  },
...
}

评论:

  1. webpack.optimize.ModuleConcatenationPlugin()-合并作用域 将所有模块合并到一个闭包中,并允许您的代码具有 在浏览器中更快的执行时间
  2. webpack.HashedModuleIdsPlugin()-导致哈希基于 模块的相对路径,生成四个字符串作为 模块ID
  3. webpack.optimize.OccurrenceOrderPlugin()-更改 id的分布以获得经常使用的最小id长度 ID
  4. webpack.NoEmitOnErrorsPlugin()-跳过发射阶段 只要编译时有错误。这样可以确保没有 发出包含错误的资产