【问题标题】:Webpack 4, React project, Bundle sizesWebpack 4,React 项目,Bundle 大小
【发布时间】:2018-07-27 07:22:00
【问题描述】:

在 React 项目中使用 Webpack 4,因此使用的 NPM 包很少,但包大小很大 - 例如 6MB。

我已经阅读了 Webpack 4 文档,了解如何缩小和构建生产就绪包,但文件大小似乎并没有减少那么多。

我也尝试过拆分捆绑包,但没有真正成功。例如,试图将所有与 React 相关的包放入 1 个包中,但这会破坏 Web 应用程序 - 浏览器会抛出错误,说它找不到包。不过我可能做错了什么!

所以我正在寻求一些帮助来改进我的构建设置并将文件拆分为更小的文件,通常只是让事情变得更好。

我的 Webpack 4 配置文件:

const path = require('path');
const precss = require('precss');
const webpack = require('webpack');
const packageJson = require('./package.json');
const autoprefixer = require('autoprefixer');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

const filenames = {
    css: '[name].bundle.css',
    js: '[name].bundle.js',
};

console.log('#########################################');

/* Cache busted names for production */
if (process.env.NODE_ENV === 'production') {
    console.log('#   XXXXXX v', packageJson.version, ' PRODUCTION   #');

    const timestamp = +new Date();

    filenames.css = `[name].bundle.${timestamp}.css`;
    filenames.js = `[name].bundle.${timestamp}.js`;
} else {
    console.log('#   XXXXXX v', packageJson.version, ' DEVELOPMENT   #');
}

console.log('#########################################');
console.log('');

module.exports = (env, options) => ({
    entry: './src/assets/js/Scheduler.jsx',
    output: {
        publicPath: '/',
        filename: `assets/js/${filenames.js}`,
        path: path.resolve(__dirname, 'public'),
    },
    watchOptions: {
        ignored: /node_modules/,
    },
    node: {
        fs: 'empty',
    },
    devtool: (options.mode === 'production') ? 'source-map' : 'cheap-module-source-map',
    devServer: {
        hot: true,
        watchContentBase: true,
        historyApiFallback: true,
        contentBase: path.join(__dirname, 'src'),
    },
    module: {
        rules: [{
            test: /\.(js|jsx)$/,
            exclude: /node_modules|bower_components/,
            use: [
                'babel-loader',
                'eslint-loader',
            ],
        }, {
            test: /\.(css)$/,
            exclude: /node_modules|bower_components/,
            use: [
                MiniCssExtractPlugin.loader,
                {
                    /* Interprets `@import` and `url()` like `import/require()` and will resolve them */
                    loader: 'css-loader',
                    options: {
                        sourceMap: true,
                    },
                },
            ],
        }, {
            test: /\.(scss)$/,
            exclude: /node_modules|bower_components/,
            use: [
                {
                    loader: 'css-hot-loader',
                    options: {
                        sourceMap: true,
                    },
                },
                /**
                 * Commented out as we want to extract the styles into a seperate file which the mini CSS extract plugin will do.
                 * If you want to keep the styles within the scripts, comment this back in and comment out mini CSS extract plugin line below.
                 */
                /*
                {
                    loader: 'style-loader',
                    options: {
                        sourceMap: true,
                    },
                },
                */
                MiniCssExtractPlugin.loader,
                {
                    /* Interprets `@import` and `url()` like `import/require()` and will resolve them */
                    loader: 'css-loader',
                    options: {
                        sourceMap: true,
                    },
                }, {
                    /* Loader for webpack to process CSS with PostCSS */
                    loader: 'postcss-loader',
                    options: {
                        autoprefixer: {
                            browsers: ['last 3 versions'],
                        },
                        plugins: loader => [
                            precss(),
                            autoprefixer(),
                        ],
                        sourceMap: true,
                    },
                }, {
                    /* Loads a SASS/SCSS file and compiles it to CSS */
                    loader: 'sass-loader',
                    options: {
                        sourceMap: true,
                    },
                },
            ],
        }, {
            test: /\.woff2?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
            exclude: /node_modules|bower_components/,
            use: 'url-loader?limit=10000',
        }, {
            test: /\.(ttf|eot|svg)(\?[\s\S]+)?$/,
            exclude: /node_modules|bower_components/,
            use: 'file-loader',
        }, {
            test: /\.(png|jp(e*)g|svg|gif)$/,
            exclude: /node_modules|bower_components/,
            use: [{
                loader: 'url-loader',
                options: {
                    limit: 8000, /* Convert images < 8kb to base64 strings */
                    name: 'assets/img/[name]-[hash].[ext]',
                },
            }],
        }, {
            test: /\.html$/,
            exclude: /node_modules|bower_components/,
            use: {
                loader: 'html-loader',
                options: {
                    minimize: true,
                },
            },
        }, {
            test: /bootstrap\/dist\/js\/umd\//,
            use: 'imports-loader?jQuery=jquery',
        }],
    },
    resolve: {
        extensions: ['*', '.jsx', '.js', '.scss', '.css', '.html'],
    },
    performance: {
        hints: false,
    },
    optimization: {
        runtimeChunk: 'single',
        splitChunks: {
            cacheGroups: {
                commons: {
                    name: 'vendors',
                    chunks: 'initial',
                    test: /node_modules/,
                },
            },
        },
        minimizer: [
            new UglifyJsPlugin({
                cache: true,
                parallel: true,
                sourceMap: true,
            }),
            new OptimizeCSSAssetsPlugin(),
        ],
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: `assets/css/${filenames.css}`,
        }),
        new HtmlWebPackPlugin({
            template: 'src/index.html',
            filename: 'index.html',
            hash: (options.mode === 'production'),
        }),
        new HtmlWebPackPlugin({
            template: 'src/404.html',
            filename: '404.html',
            hash: (options.mode === 'production'),
        }),
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery',
            'window.jQuery': 'jquery',
        }),
        new webpack.HashedModuleIdsPlugin(),
        new CopyWebpackPlugin([
            {
                force: true,
                cache: true,
                to: 'assets/icons',
                from: 'src/assets/icons',
            }, {
                force: true,
                cache: true,
                to: 'assets/img',
                from: 'src/assets/img',
            }, {
                force: true,
                cache: true,
                to: 'assets/fonts',
                from: 'src/assets/fonts',
            }, {
                force: true,
                cache: true,
                to: 'assets/fonts',
                from: 'node_modules/font-awesome/fonts',
            },
        ]),
    ],
});

我的 package.json 文件:

"devDependencies": {
    "@types/file-saver": "^1.3.0",
    "@types/react": "^16.4.7",
    "autoprefixer": "^8.6.5",
    "axios-mock-adapter": "^1.15.0",
    "babel-core": "^6.26.3",
    "babel-eslint": "^8.2.6",
    "babel-jest": "^22.4.4",
    "babel-loader": "^7.1.5",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-preset-airbnb": "^2.5.3",
    "babel-preset-env": "^1.7.0",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-2": "^6.24.1",
    "clean-webpack-plugin": "^0.1.19",
    "copy-webpack-plugin": "^4.5.2",
    "cross-env": "^5.2.0",
    "css-hot-loader": "^1.4.1",
    "css-loader": "^0.28.11",
    "eslint": "^4.19.1",
    "eslint-config-airbnb": "^16.1.0",
    "eslint-loader": "^2.1.0",
    "eslint-plugin-import": "^2.13.0",
    "eslint-plugin-jsx-a11y": "^6.1.1",
    "eslint-plugin-react": "^7.10.0",
    "exports-loader": "^0.7.0",
    "file-loader": "^1.1.11",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "imports-loader": "^0.8.0",
    "jest": "^22.4.4",
    "jest-cli": "^22.4.4",
    "mini-css-extract-plugin": "^0.4.1",
    "moxios": "^0.4.0",
    "node-sass": "^4.9.2",
    "optimize-css-assets-webpack-plugin": "^4.0.3",
    "popper.js": "^1.14.3",
    "postcss-loader": "^2.1.6",
    "precss": "^3.1.2",
    "react-test-renderer": "^16.4.1",
    "redux-mock-store": "^1.5.3",
    "sass-loader": "^6.0.7",
    "sinon": "^5.1.1",
    "style-loader": "^0.20.3",
    "uglifyjs-webpack-plugin": "^1.2.7",
    "url-loader": "^1.0.1",
    "webpack": "^4.16.2",
    "webpack-cli": "^2.1.5",
    "webpack-dev-server": "^3.1.5"
},
"dependencies": {
    "axios": "^0.18.0",
    "babel-polyfill": "^6.26.0",
    "bootstrap": "^4.1.3",
    "classlist-polyfill": "^1.2.0",
    "core-js": "^2.5.7",
    "element-closest": "^2.0.2",
    "enzyme": "^3.3.0",
    "enzyme-adapter-react-16": "^1.1.1",
    "fastclick": "^1.0.6",
    "file-saver": "^1.3.8",
    "font-awesome": "^4.7.0",
    "font-awesome-filetypes": "^1.2.0",
    "fs": "0.0.1-security",
    "jquery": "^3.3.1",
    "jwt-decode": "^2.2.0",
    "loaders.css": "^0.1.2",
    "lodash": "^4.17.10",
    "mailcheck": "^1.1.1",
    "mobile-drag-drop": "^2.3.0-rc.1",
    "mock-local-storage": "^1.0.5",
    "moment": "^2.22.2",
    "moment-range": "^4.0.1",
    "prop-types": "^15.6.2",
    "query-string": "^6.1.0",
    "raf": "^3.4.0",
    "react": "^16.4.1",
    "react-avatar": "^3.1.2",
    "react-block-ui": "^1.1.1",
    "react-confirm": "^0.1.17",
    "react-datepicker": "^1.5.0",
    "react-dom": "^16.4.1",
    "react-form-with-constraints": "^0.9.2",
    "react-hot-loader": "^4.3.4",
    "react-image": "^1.3.1",
    "react-loaders": "^3.0.1",
    "react-number-format": "^3.5.0",
    "react-redux": "^5.0.7",
    "react-router-dom": "^4.3.1",
    "react-toastify": "^4.1.0",
    "reactstrap": "^5.0.0",
    "redux": "^3.7.2",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.3.0",
    "sortablejs": "^1.7.0",
    "vanilla-autofill-event": "^1.0.3",
    "zxcvbn": "^4.4.2"
}

【问题讨论】:

  • 首先检查你是否真的需要这些包中的每一个
  • 是的,我只安装了我需要的库。我还安装了 source-map-explorer 来查看哪些包增加了权重。
  • 1.您确定该构建是生产构建吗? 2. 试试 gzip 包,应该会有所帮助。

标签: reactjs webpack


【解决方案1】:

开发包的大小并不是什么大问题,因为您是在本地计算机上工作的。重要的是生产捆绑应该最小化。因为当您的应用程序上线时,它会使用生产包。因此,较小的捆绑包意味着您的托管平台提供的服务器将更快地发送生产资产。

在生产模式下,您安装的软件包会自动删除您的库中包含的许多用于开发过程的内容。

**Minification**

缩小执行诸如删除空格、删除 cmets、删除不需要的分号、减少十六进制代码长度等操作...

文件仍然是完全有效的代码。您不想尝试阅读或使用它,但它并没有违反任何规则。浏览器可以像读取原始文件一样读取和使用它。

缩小只是改变文本,而文件压缩完全重写文件中的二进制代码。

  const TerserPlugin = require("terser-webpack-plugin");
  optimization:{
  minimize:true //this should be set
  minimizer:[new TerserPluign()]}

在生产中压缩资产

compression-webpack-plugin 默认使用 gzip 算法。 Gzip 是标准的,所有浏览器都可以理解。在 webpack.prod-client.js 中

    `const CompressionPlugin = require("compression-webpack-plugin");

     plugins: [
          new CompressionPlugin(),
        ]`

这将压缩您的资产文件,但“jpeg”图像除外。因为jpeg已经是压缩文件类型了。

我们创建了 gzip 文件,但我们还不能加载它们。因为在网络选项卡中,文件的内容类型应该是 gzip。我们需要更好的服务器配置。

   npm i express-static-gzip

   const expressStaticGzip = require("express-static-gzip");
   server.use(expressStaticGzip("dist")); //make sure you use this middleware first

我们以 gzip 格式下载文件,但浏览器使用的是未压缩版本。

Brotli 是另一种最初由 Google 开发的压缩算法,提供优于 gzip 的压缩。

  const BrotliPlugin = require("brotli-webpack-plugin");
  new BrotliPlugin(),
  server.use(expressStaticGzip("dist", { enableBrotli: true, orderPreference: ["br", "gzip"] }));

现在我们有了一组新的文件:原始文件、gz 版本和 br 版本,它们甚至更小。

Webpack 压缩将在构建运行期间一次性压缩您的文件。然后将这些压缩版本保存到磁盘。然后 express-static-gzip 可以提供这些预编译版本,因此您不会在请求时受到 gzipping 的性能影响。如果 Node.js 直接响应您的 HTTP 请求并且您没有使用上游代理/负载均衡器,这将非常有用。

【讨论】:

    猜你喜欢
    • 2018-11-05
    • 2021-12-18
    • 2021-07-12
    • 1970-01-01
    • 1970-01-01
    • 2019-06-23
    • 2020-03-17
    • 2017-09-01
    • 2018-04-16
    相关资源
    最近更新 更多