加快反应网站初始加载

时间:2017-11-22 13:35:18

标签: javascript node.js iis webpack gzip

我正在构建一个React站点,我不确定我是否正确编译,因为第一次加载库需要大约5秒钟(之后它会被缓存,所以它更快)。 我需要帮助才能找到我做错的事情并减慢加载时间。

我在这里发布我正在使用的文件。

的package.json

{
 "name": "MySite",
 "version": "0.1.0",
 "description": "Something",
 "private": true,
 "devDependencies": {
  "babel-plugin-react-html-attrs": "^2.0.0",
  "babel-plugin-transform-runtime": "^6.23.0",
  "babel-polyfill": "^6.23.0",
  "babel-preset-stage-2": "^6.24.1",
  "babili-webpack-plugin": "^0.1.2",
  "compression-webpack-plugin": "^1.0.0",
  "react-hot-loader": "^1.3.1",
  "react-scripts": "1.0.10",
  "serve-favicon": "^2.4.3",
  "webpack-dev-server": "^2.5.1",
  "webpack-hot-middleware": "^2.18.2"
 },
 "dependencies": {
  "ajv": "^5.2.2",
  "axios": "^0.16.2",
  "babel-core": "^6.25.0",
  "babel-loader": "^7.1.1",
  "babel-plugin-transform-class-properties": "^6.24.1",
  "babel-plugin-transform-decorators-legacy": "^1.3.4",
  "babel-plugin-transform-runtime": "^6.23.0",
  "babel-polyfill": "^6.23.0",
  "babel-preset-es2015": "^6.24.1",
  "babel-preset-react": "^6.24.1",
  "babel-preset-stage-0": "^6.24.1",
  "babel-preset-stage-2": "^6.3.13",
  "circular-dependency-plugin": "^3.0.0",
  "classnames": "^2.2.5",
  "clean-webpack-plugin": "^0.1.16",
  "copy-webpack-plugin": "^4.0.1",
  "css-loader": "^0.28.4",
  "dotenv": "^4.0.0",
  "es6-promise": "^4.1.1",
  "file-loader": "^0.11.2",
  "firebase": "^4.1.3",
  "history": "^4.6.3",
  "immutable": "^3.8.1",
  "invariant": "^2.2.2",
  "isomorphic-fetch": "^2.2.1",
  "jsdom": "^11.1.0",
  "jsonwebtoken": "^7.4.1",
  "konva": "^1.6.3",
  "less": "^2.3.1",
  "less-loader": "^4.0.5",
  "react": "^15.6.1",
  "react-addons-css-transition-group": "^15.6.0",
  "react-async-script": "^0.9.1",
  "react-async-script-loader": "^0.3.0",
  "react-bootstrap": "^0.31.1",
  "react-dnd": "^2.4.0",
  "react-dnd-html5-backend": "^2.4.1",
  "react-dom": "^15.6.1",
  "react-fontawesome": "^1.6.1",
  "react-ga": "^2.2.0",
  "react-google-recaptcha": "^0.9.6",
  "react-gravatar": "^2.6.3",
  "react-helmet": "^5.1.3",
  "react-input-range": "^1.2.1",
  "react-konva": "^1.1.3",
  "react-modal": "^2.2.2",
  "react-page-click": "^4.0.1",
  "react-recaptcha": "^2.3.2",
  "react-redux": "^5.0.5",
  "react-router": "^3.0.2",
  "react-router-redux": "^4.0.0",
  "react-tag-input": "^4.7.2",
  "react-toggle": "^4.0.1",
  "redux": "^3.7.2",
  "redux-form": "^7.0.1",
  "redux-logger": "^2.3.2",
  "redux-thunk": "^2.2.0",
  "rimraf": "^2.6.1",
  "single-module-instance-webpack-plugin": "0.0.4",
  "style-loader": "^0.18.2",
  "superagent": "^3.5.2",
  "webpack": "^3.5.0",
  "webpack-bundle-analyzer": "^2.9.0",
  "xml2js": "^0.4.17",
  "xmldom": "^0.1.27",
  "xpath": "0.0.24"
 },
 "scripts": {
  "start": "node devServer.js --progress --verbose",
  "clean": "rimraf ./dist",
  "dev": "webpack -d --watch --progress --display-error-details --display-reasons",
  "start1": "webpack-dev-server  --config ./webpack.config.comphotdeploy -d --progress --colors --host localhost --port 28080 --hot --inline --content-base src",
  "builddev": "webpack -d --progress --display-error-details --display-reasons",
  "build": "webpack -p --progress --verbose",
  "dist": "rimraf ./dist&&webpack -d --display-error-details --display-reasons",
  "start-react": "SET PORT=9999&&SET DEVTOOL=source-map&&react-scripts start",
  "build-react": "react-scripts build",
  "test": "react-scripts test --env=jsdom",
  "eject": "react-scripts eject"
 },
 "eslintConfig": {
  "extends": "./node_modules/react-scripts/config/eslint.js"
 }
}

当我构建在线部署时,我正在使用命令

  

npm run build

webpack.config.js

var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

var webpack = require('webpack');
var path = require('path');

var ProvidePlugin = require("webpack/lib/ProvidePlugin");
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
var LimitChunkCountPlugin = require("webpack/lib/optimize/LimitChunkCountPlugin");
var DedupePlugin = require("webpack/lib/optimize/DedupePlugin");
var SingleModuleInstancePlugin = require('single-module-instance-webpack-plugin');
var CleanWebpackPlugin = require('clean-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var CircularDependencyPlugin = require('circular-dependency-plugin');

//var BUILD_DIR =  path.resolve(__dirname,'/opt/glassfish/glassfish/domains/domain1/applications/Client/html');
var BUILD_DIR =  path.resolve(__dirname,'dist');

var APP_DIR = path.resolve(__dirname, 'src');
var CLIENT_DIR = path.resolve(__dirname, 'src/client');

// Load environment variables from .env file. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set.
// https://github.com/motdotla/dotenv
require('dotenv').config({silent: true});

var PrintChunksPlugin = function() {};
PrintChunksPlugin.prototype.apply = function(compiler) {
    compiler.plugin('compilation', function(compilation, params) {
        compilation.plugin('after-optimize-chunk-assets', function(chunks) {
            console.log(chunks.map(function(c) {
                return {
                    id: c.id,
                    name: c.name
/*,
                    includes: c.modules.map(function(m) {
                        return m.request;
                    })
*/
                };
            }));
        });
    });
};

var config = {
    devtool: 'cheap-module-source-map',
    entry: {
        app: APP_DIR + '/index.js'
    },

    output: {
        path:BUILD_DIR,
        filename: "[name].bundle.js",
        sourceMapFilename: "[name].bundle.js.map",
        chunkFilename: "[name]-chunk.js",
        //publicPath: BUILD_DIR
    },

  watch: false,
  watchOptions: {
    poll: true,
    aggregateTimeout: 300,
    number: 1000
  },
  module : {
      loaders : [
          {
              test : /\.jsx?/,
              include : APP_DIR,
              exclude: /node_modules/,
              loaders: ['react-hot-loader', 'babel-loader?' + JSON.stringify({
                cacheDirectory: true,
                plugins: [
                  'transform-runtime',
                  'react-html-attrs',
                  'transform-class-properties',
                  'transform-decorators-legacy'
                ],
                presets: [
                    [
                        "es2015",
                        {
                            "modules": false
                        }
                        ],
                'react',
                'stage-2']
          })]
      },
      // CSS
      // "postcss" loader applies autoprefixer to our CSS.
      // "css" loader resolves paths in CSS and adds assets as dependencies.
      // "style" loader turns CSS into JS modules that inject <style> tags.
      // In production, we use a plugin to extract that CSS to a file, but
      // in development "style" loader enables hot editing of CSS.
      {
          test: /\.css$/,
          include: path.join(__dirname, 'src/style'),
          loader: 'style-loader!css-loader'
      },
      // "file" loader makes sure those assets get served by WebpackDevServer.
      // When you `import` an asset, you get its (virtual) filename.
      // In production, they would get copied to the `build` folder.
      {
          test: /\.(ico|jpg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/,
          exclude: /\/favicon.ico$/,
          loader: 'file-loader',
          query: {
            name: '[path][name][hash].[ext]',
            publicPath: '/'
          }
      },
      {
          test: /\.(ico)(\?.*)?$/,
          exclude: /node_modules/,
          loader: 'file-loader',
          query: {
              name: './images/[name].[ext]'
          }
      },
      {
          test: /\.xml$/,
          loader: 'file-loader',
          query: {
              name: './[name].[ext]'
          }
      },
  ]
  },

     // use EnableCircularDependencyPlugin=true|false to check the option
  plugins: (function() {
        var plugins = [
              new webpack.DefinePlugin({
                  // A common mistake is not stringifying the "production" string.
                  'process.env': { 'NODE_ENV': JSON.stringify('production') },

                  // DISABLE redux-devtools HERE
                  __DEVTOOLS__: false 
              }),

            new CopyWebpackPlugin([
                { from: APP_DIR + '/index.html', to: BUILD_DIR + '/index.html' },
                { from: APP_DIR + '/sitemap.xml', to: BUILD_DIR + '/sitemap.xml' },
                { from: APP_DIR + '/Robots.txt', to: BUILD_DIR + '/Robots.txt' },
                { from: APP_DIR + '/images/favicon.ico', to: BUILD_DIR + '/images/favicon.ico' },
                { from: APP_DIR + '/images/favicon.png', to: BUILD_DIR + '/images/favicon.png' },
                { from: APP_DIR + '/images/favicon-16x16.png', to: BUILD_DIR + '/images/favicon-16x16.png' },
                { from: APP_DIR + '/images/favicon-32x32.png', to: BUILD_DIR + '/images/favicon-32x32.png' },
                { from: APP_DIR + '/images/favicon-48x48.png', to: BUILD_DIR + '/images/favicon-48x48.png' },
                { from: APP_DIR + '/images/favicon-57x57.png', to: BUILD_DIR + '/images/favicon-57x57.png' },
                { from: APP_DIR + '/images/favicon-60x60.png', to: BUILD_DIR + '/images/favicon-60x60.png' },
                { from: APP_DIR + '/images/favicon-72x72.png', to: BUILD_DIR + '/images/favicon-72x72.png' },
                { from: APP_DIR + '/images/favicon-76x76.png', to: BUILD_DIR + '/images/favicon-76x76.png' },
                { from: APP_DIR + '/images/favicon-96x96.png', to: BUILD_DIR + '/images/favicon-96x96.png' },
                { from: APP_DIR + '/images/favicon-114x114.png', to: BUILD_DIR + '/images/favicon-114x114.png' },
                { from: APP_DIR + '/images/favicon-120x120.png', to: BUILD_DIR + '/images/favicon-120x120.png' },
                { from: APP_DIR + '/images/favicon-144x144.png', to: BUILD_DIR + '/images/favicon-144x144.png' },
                { from: APP_DIR + '/images/favicon-152x152.png', to: BUILD_DIR + '/images/favicon-152x152.png' },
                { from: APP_DIR + '/images/favicon-160x160.png', to: BUILD_DIR + '/images/favicon-160x160.png' },
                { from: APP_DIR + '/images/favicon-180x180.png', to: BUILD_DIR + '/images/favicon-180x180.png' },
                { from: APP_DIR + '/images/favicon-192x192.png', to: BUILD_DIR + '/images/favicon-192x192.png' }
            ]),
            new webpack.HotModuleReplacementPlugin(),
            new webpack.NoEmitOnErrorsPlugin(),

            new BundleAnalyzerPlugin({analyzerMode: 'static'}),
            //new PrintChunksPlugin()

            new webpack.optimize.CommonsChunkPlugin({
                name: 'vendor',
                minChunks: function (module) {
                   // this assumes your vendor imports exist in the node_modules directory
                   return module.context && module.context.indexOf('node_modules') !== -1;
                }
            }),

            //CommonChunksPlugin will now extract all the common modules from vendor and main bundles
            new webpack.optimize.CommonsChunkPlugin({
                name: 'manifest' //But since there are no more common modules between them we end up with just the runtime code included in the manifest file
            }),
        ];


        // HERE IS OPTION CONDITION
        // edit .env file change to EnableCircularDependencyPlugin=false will bypass it
        if (process.env.EnableCircularDependencyPlugin=="true") {
            plugins.push(new CircularDependencyPlugin({
                // exclude detection of files based on a RegExp
                exclude: /a\.js|node_modules/,
                // add errors to webpack instead of warnings
                failOnError: true
            }));
        }

        return plugins;
    })(),
    node: {
        net: 'empty',
    dns: 'empty'
    }
};

module.exports = config;

的index.html

<!doctype html>
<html>
<head>
</head>
    <body>
        <div id="app"></div>
        <script src="manifest.bundle.js"></script>
        <script src="vendor.bundle.js" ></script>
        <script src="app.bundle.js" ></script>
    </body>
</html>

编译完成后,我最终得到以下文件:

  • vendor.bundle.js(统计尺寸:3.94MB,解析尺寸:1.45MB,Gzip尺寸:420.02KB)
  • app.bundle.js(统计尺寸:910.96KB,解析尺寸:459.62KB,Gzip尺寸:89.2KB)
  • A-chunk.js(统计尺寸:652.29KB,解析尺寸:279.4KB,Gzip尺寸:54.31KB)
  • B-chunk.js(统计尺寸:598.87KX,解析尺寸:257.76KB,Gzip尺寸:51.04KB)

另一件奇怪的事情是,我可以在A-chunk.js中看到一些只应该在B-Chunk.js中的文件。

我的猜测是vendo.bundle.js因为它的大小而减慢了初始加载速度。

真实文件(我部署的文件)的大小是Parsed大小,而不是Gzip大小。 看起来gzip压缩没有开始。

这就是我不知道为什么它不会自动完成的原因。 是否有一个命令可以执行/添加以自动获取gzip压缩文件,还是我应该手动执行的步骤?

取得了一些进展。

在webpack.config.js中我添加了以下内容:

var CompressionPlugin = require('compression-webpack-plugin');

new CompressionPlugin({
 asset: "[path].gz[query]",
 algorithm: "gzip",
 test: /\.js$|\.css$|\.html$/,
 threshold: 10240,
 minRatio: 0.8
})

所以现在我生成了js和js.gz文件。

我还更新了我的web.config以添加以下内容:

  <httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files">
    <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll"/>
    <dynamicTypes>
      <add mimeType="text/*" enabled="true"/>
      <add mimeType="message/*" enabled="true"/>
      <add mimeType="application/javascript" enabled="true"/>
      <add mimeType="*/*" enabled="false"/>
    </dynamicTypes>
    <staticTypes>
      <add mimeType="text/*" enabled="true"/>
      <add mimeType="message/*" enabled="true"/>
      <add mimeType="application/javascript" enabled="true"/>
      <add mimeType="*/*" enabled="false"/>
    </staticTypes>
  </httpCompression>
  <urlCompression doStaticCompression="true" doDynamicCompression="true"/>

但到目前为止,这并没有改变任何事情。据我所知,gz文件没有加载。 (我正在使用https://varvy.com/pagespeed/来测试更改)

接下来我尝试编写一组IIS规则来返回.js.gz文件。 但由于我不是一个专业的IIS,我没有取得很好的成果使它工作......

这是我到目前为止所做的:

  <rewrite>
    <rules>
      <rule name="Return gzip" stopProcessing="true">
        <match url="^.*\.(js)$" />
        <action type="Rewrite" url="{R:1}.gz" />
      </rule>
    </rules>
  </rewrite>

但我认为我需要一种方法将标题内容类型更改为gzip才能使其正常工作。 只是不确定该怎么做......

1 个答案:

答案 0 :(得分:0)

  

我需要帮助才能找到我做错的事情并减慢加载时间。

优化SPA应用程序有不同的方法,包括加载时间,带宽量和性能,而定型解决方案是创建一个复合/多块包并优化&amp;压缩它。

但是现代浏览器的ES8 +时代,有一个新的最佳解决方案:使用原生ES6模块,并部署具有此类用途的应用程序。请参阅以下文章:https://philipwalton.com/articles/deploying-es2015-code-in-production-today/https://www.contentful.com/blog/2017/04/04/es6-modules-support-lands-in-browsers-is-it-time-to-rethink-bundling/。这种方法可以提高性能并减少服务器负载。