奇怪的缓存行为

时间:2018-01-17 15:45:01

标签: javascript reactjs caching webpack

我们目前正在部署与Webpack捆绑在一起的React应用程序。我们的Webpack配置如下。

我们正在经历一些 - 对我来说 - 非常奇怪的缓存行为;每次部署后,我们的用户都被迫进行硬刷新。如果未进行硬刷新,则会显示index.html模板,并且不会加载JS。

这种情况不会每次都发生,但它足以成为一个问题。

我们的index.html头脑中有以下内容:

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />

index.html基于模板,我们使用HtmlPlugin在编译时注入(散列)脚本和样式表。

的index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
    <title>...</title>
    <meta name="description" content="...">

    <link rel="shortcut icon" type="image/x-icon" href="..." />
    <link href="/portal/styles/app.d4e88655c7001362ca091666c25a9284.css" rel="stylesheet">
  </head>

  <body>
    <div id="root">Loading...</div>
    <script type="text/javascript" src="/portal/scripts/vendor.a2b9a0c858a0cf1aac42.js"></script>
    <script type="text/javascript" src="/portal/scripts/app.42d841eebfd48ad70010.js"></script>
  </body>
</html>

如webpack.config中所示,我们在构建时散列每个文件名。

我的下一步行动(也许)不会将JS分成appvendor,但我宁愿不这样做。

编辑:添加HTML响应标题

编辑2:这主要发生在Windows上的Chrome中,但我们已经看到它发生在OSX上的Safari和Chrome中。

编辑3:添加了有关HTML文件的信息。

HTML响应标题:

Cache-Control:no-cache
Cache-Control:max-age=0, no-cache, no-store, must-revalidate
Connection:keep-alive
Content-Encoding:gzip
Content-Length:821
Content-Type:text/html
Date:Wed, 17 Jan 2018 17:00:09 GMT
Expires:Wed, 17 Jan 2018 17:00:08 GMT
Last-Modified:Wed, 17 Jan 2018 11:15:46 GMT
Pragma:no-cache
Server:nginx/1.7.9

Webpack配置:

const config = {
  entry: {
    app: [
      'babel-polyfill',
      './src/portal/js/App.js'
    ],
    vendor: [
      'axios',
      'es6-promise',
      'prop-types',
      'react',
      'react-dom',
      'react-ga',
      'react-redux',
      'react-router',
      'redux',
      'redux-logger',
      'redux-thunk',
      'shortid',
    ]
  }, // entry point
  output: {
    filename: 'scripts/[name].[chunkhash].js', // output filename with hash'd filename
    path: path.resolve(__dirname, 'dist/portal'),
    publicPath: '/portal/'
  },
  module: {
    rules: [
      {test: /\.json$/, loader: 'json-loader'},
      {
        test: /\.(js|jsx)$/,
        use: [{loader: 'babel-loader'}],
        exclude: /node_modules/
      },
      {
        test: /\.(scss|css)$/,
        use: ExtractTextPlugin.extract({ // extract all styles to a file instead of inlining
          use: [
            {loader: "css-loader", options: {sourceMap: (isDev || isStaging)}},
            {loader: "sass-loader", options: {sourceMap: (isDev || isStaging)}}
          ],
          fallback: "style-loader"
        })
      },
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        loaders: [
            'file-loader?hash=sha512&digest=hex&name=assets/images/[hash].[ext]',
            {
              loader: 'image-webpack-loader',
              query: {
                mozjpeg: {progressive: true},
                gifsicle: {interlaced: false},
                optipng: {optimizationLevel: 4},
                pngquant: {quality: '75-90', speed: 3}
              }
            }
        ]
      },
      {
        test: /\.woff?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: ['url-loader?name=[name].[ext]&limit=100000&mimetype=application/font-woff&name=assets/fonts/[name].[ext]']
      },
      {
        test: /\.woff2?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: ['url-loader?name=[name].[ext]&limit=100000&mimetype=application/font-woff2&name=assets/fonts/[name].[ext]']
      },
      {
        test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: ['file-loader?name=[name].[ext]&limit=100000&mimetype=application/octet-stream&name=assets/fonts/[name].[ext]']
      },
      {
        test: /\.otf(\?.*)?$/,
        loader: 'file-loader?name=[name].[ext]&limit=10000&mimetype=font/opentype&name=assets/fonts/[name].[ext]'
      }

    ]
  },
  devtool: 'source-map',
  plugins: function() {
    // plugins to be used for dev, staging and prod
    let basePlugins = [
      // new CleanPlugin(['dist']),
      new WebpackMd5Hash(),
      new webpack.ProvidePlugin({
        'Promise': 'es6-promise'
      }),
      new ExtractTextPlugin({
        filename: "styles/[name].[contenthash].css",
        allChunks: true,
      }),
      new webpack.optimize.CommonsChunkPlugin({
        names: ['vendor'],
        minChunks: Infinity,
        filename: `scripts/[name].js`
      }),
    ];

    // only prod and staging but not dev - things like minification and copying static files
    if (isProduction || isStaging && !isDev) {
      basePlugins.push(
        new webpack.optimize.UglifyJsPlugin({
          sourceMap: true,
          mangle: true,
          compress: {
            warnings: false
          },
          output: {
            comments: false,
          }
        }),
        new OptimizeCssAssetsPlugin(),
        new FileManagerPlugin({
          onStart: [
            {
              delete: [
               'dist'
              ]
            }
          ],
          onEnd: [
            {
              copy: [
                // static html
                { source: 'src/index.html', destination: 'dist/index.html' },
                { source: 'src/success.html', destination: 'dist/success.html' },
                // static css
                { source: 'src/styles', destination: 'dist/styles' },
                // hide contango build for now until we're ready
                { source: 'src/book/public', destination: 'dist/book' }
              ]
            }
          ]
        }),
        new webpack.optimize.CommonsChunkPlugin({
          names: ['vendor'],
          minChunks: Infinity,
          filename: `scripts/[name].[chunkhash].js`
        })
      );
    }

    if (isProduction) {
      basePlugins.push(
        new HtmlPlugin({
          cache: true,
          template: 'src/portal/index.prod.html'
        }),
        new webpack.DefinePlugin({
          'process.env.NODE_ENV': '"production"',
          API_ENV: '"production"'
        })
      );
    } else if (isStaging) {
      basePlugins.push(
        new HtmlPlugin({
          cache: true,
          template: 'src/portal/index.staging.html'
        }),
        new webpack.DefinePlugin({
          'process.env.NODE_ENV': '"production"',
          API_ENV: '"staging"'
        })
      );
    } else if (isDev) {
      basePlugins.push(
        new HtmlPlugin({
          template: 'src/portal/index.staging.html'
        }),
        new webpack.DefinePlugin({
          'process.env.NODE_ENV': '"staging"',
          API_ENV: '"staging"'
        })
      );
    }
    return basePlugins;
  }(),
  resolve: {
    modules: [
      path.join(__dirname, "src/portal"),
      "node_modules",
    ]
  }
};

0 个答案:

没有答案