Html VueJS/浏览器缓存生产构建

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45867357/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-29 14:53:02  来源:igfitidea点击:

VueJS/browser caching production builds

htmlvue.jsbrowser-cache

提问by ierdna

I have a VueJS app. Whenever I run npm run buildit creates a new set of dist/*files, however, when I load them on the server (after deleting the old build), and open the page in browser, it loads the old build (from cache i assume). When I refresh the page, it loads the new code no problem.

我有一个 VueJS 应用程序。每当我运行npm run build它时,它都会创建一组新dist/*文件,但是,当我将它们加载到服务器上(删除旧版本后),并在浏览器中打开页面时,它会加载旧版本(我假设来自缓存)。当我刷新页面时,它加载新代码没问题。

This is my index.html:

这是我的 index.html:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
        <meta http-equiv="cache-control" content="max-age=0" />
        <meta http-equiv="cache-control" content="no-cache" />
        <meta http-equiv="expires" content="-1" />
        <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
        <meta http-equiv="pragma" content="no-cache" />
        <link rel="stylesheet" href="/static/css/bootstrap.min.css"/>
    </head>
    <body>
        <div id="app"></div>
    </body>
</html>

Is there a way to force it to load new code every time or (ideally) to check if the old files are gone from the server, then refresh the browser?

有没有办法强制它每次加载新代码,或者(理想情况下)检查旧文件是否从服务器上消失,然后刷新浏览器?

回答by For the Name

We struggled with this same issue and found that some people's browsers would not even pull the latest version unless they manually refreshed. We had problems with caching at various layers, including the CDN where we hosted files.

我们努力解决同样的问题,发现有些人的浏览器甚至不会拉最新版本,除非他们手动刷新。我们在各层缓存时遇到问题,包括我们托管文件的 CDN。

We also struggled with maintaining versions and being able to rapidly redeploy a previous version if something goes wrong.

我们还努力维护版本,并在出现问题时快速重新部署以前的版本。

Our solution (using project based on vue-cli Webpack):

我们的解决方案(使用基于 vue-cli Webpack 的项目):

1) We build the distribution to have a version specific folder instead of 'static'. This also helps us track builds and 'undo' a deployment if needed. To change the 'static' directory, change 'assetsSubDirectory' under 'build' in index.js and change 'assetsPublicPath' to your CDN path.

1) 我们将发行版构建为具有特定于版本的文件夹而不是“静态”文件夹。这也有助于我们跟踪构建并在需要时“撤消”部署。要更改“静态”目录,请更改 index.js 中“build”下的“assetsSubDirectory”并将“assetsPublicPath”更改为您的 CDN 路径。

2) We use Webpack Assets Manifestto build a manifest.json file pointing to all the assets. Our manifest includes a hash of all files, as its a high security application.

2) 我们使用Webpack Assets Manifest构建一个指向所有资产的 manifest.json 文件。我们的清单包含所有文件的哈希值,因为它是一个高安全性的应用程序。

3) We upload the versioned folder (containing the js and css) to our CDN.

3) 我们将版本化文件夹(包含 js 和 css)上传到我们的 CDN。

4) (Optional) We host a dynamic index.html file on the backend server. The links to the stylesheet and scripts are filled in by the backend server using a template system pulled from the data on the manifest.json (see #5). This is optional as you could use the force-reload option as in the comment below, which isn't a great experience but does work.

4)(可选)我们在后端服务器上托管一个动态 index.html 文件。样式表和脚本的链接由后端服务器使用从 manifest.json 上的数据中提取的模板系统填充(参见 #5)。这是可选的,因为您可以使用下面评论中的 force-reload 选项,这不是很好的体验,但确实有效。

5) To publish a new version, we post the manifest.json to the backend server. We do this via a GraphQL endpoint but you could manually put the json file somewhere. We store this in the database and use it to populate the index.html and also use it to verify files using the file hash (to validate our CDN was not hacked).

5) 为了发布新版本,我们将 manifest.json 发布到后端服务器。我们通过 GraphQL 端点执行此操作,但您可以手动将 json 文件放在某处。我们将其存储在数据库中并使用它来填充 index.html 并使用它来使用文件哈希验证文件(以验证我们的 CDN 未被黑客入侵)。

Result: immediate updates and an easy ability to track and change your versions. We found that it will immediately pull the new version in almost all user's browsers.

结果:即时更新和轻松跟踪和更改版本的能力。我们发现它会立即在几乎所有用户的浏览器中拉取新版本。

Another bonus: We are building an application that requires high security and hosting the index.html on our (already secured) backend enabled us to more easily pass our security audits.

另一个好处:我们正在构建一个需要高安全性的应用程序,并且在我们的(已经安全的)后端托管 index.html 使我们能够更轻松地通过我们的安全审计。



Edit 2/17/19

编辑 2/17/19

We found that corporate networks were doing proxy caching, despite no-cache headers. IE 11 also seems to ignore cache headers. Thus, some users were not getting the most up to date versions.

我们发现,尽管没有缓存标头,但企业网络仍在进行代理缓存。IE 11 似乎也忽略了缓存头。因此,一些用户没有获得最新版本。

We have a version.json that is incremented/defined at build time. Version number is included in manifest.json. The build bundle is automatically uploaded to S3. We then pass the manifest.json to the backend (we do this on an entry page in Admin area). We then set the "active" version on that UI. This allows us to easily change/revert versions.

我们有一个在构建时递增/定义的 version.json。版本号包含在 manifest.json 中。构建包会自动上传到 S3。然后我们将 manifest.json 传递给后端(我们在管理区域的入口页面上执行此操作)。然后我们在该 UI 上设置“活动”版本。这使我们可以轻松更改/还原版本。

The backend puts the "currentVersion" as a Response Header on all requests. If currentVersion !== version (as defined in version.json), then we ask the user to click to refresh their browser (rather than force it on them).

后端将“currentVersion”作为所有请求的响应头。如果 currentVersion !== version (在 version.json 中定义),那么我们要求用户单击以刷新他们的浏览器(而不是强加于他们)。

回答by Sean Ray

Based on this comprehensive answer on cache headers, your best bet is going to be solving this on the server side if you have control of it, as anything in the <meta>tags will get overridden by headers set by the server.

基于这个关于缓存标头的综合答案,如果你可以控制它,你最好的选择是在服务器端解决这个问题,因为<meta>标签中的任何内容都会被服务器设置的标头覆盖。

The comments on the question indicate that you are serving this app with nginx. Using the linked answer above, I was able to set the Cache-Control, Expiresand Pragmaheaders for any requests for files ending in .htmlthis way in my nginx config:

对该问题的评论表明您正在使用 nginx 为该应用程序提供服务。使用上面链接的答案,我能够在我的 nginx 配置中为以这种方式结尾的文件的任何请求设置Cache-Control,ExpiresPragma标头.html

server {

  ...other config

  location ~* \.html?$ {
    expires -1;
    add_header Pragma "no-cache";
    add_header Cache-Control "no-store, must-revalidate";
  }
}

This successfully forces the browser to request the latest index.htmlon every page reload, but still uses the cached assets (js/css/fonts/images) unless there are new references in the latest html response.

这成功地强制浏览器index.html在每个页面重新加载时请求最新的,但仍然使用缓存的资产 (js/css/fonts/images),除非最新的 html 响应中有新的引用。

回答by Connor Leech

To delete the cache you can run rm -rf node_modules/.cache

要删除缓存,您可以运行 rm -rf node_modules/.cache

This deletes your cache. You can run a new build before deploying.

这将删除您的缓存。您可以在部署之前运行新构建。

I was having the same issue where I ran a production build, but then even when running locally my code would point to the production build instead of my latest changes.

我在运行生产版本时遇到了同样的问题,但是即使在本地运行时,我的代码也会指向生产版本而不是我的最新更改。

I believe this is a related issue: https://github.com/vuejs/vue-cli/issues/2450

我相信这是一个相关的问题:https: //github.com/vuejs/vue-cli/issues/2450

回答by Devin

I serve a Nuxt app alongside a very lightweight Express app that handles server-side authentication and other things. My solution is to let Express store the current git hash in a cookie when a user logs in. This value is then placed in a Vuex store along with the current time (and other info about the current user).

我提供了一个 Nuxt 应用程序和一个非常轻量级的 Express 应用程序,用于处理服务器端身份验证和其他事情。我的解决方案是让 Express 在用户登录时将当前的 git hash 存储在 cookie 中。然后将该值与当前时间(以及有关当前用户的其他信息)一起放入 Vuex 存储中。

On a route change, a router middleware checks how long it's been since we last compared the store' hash with the actual one. If it's been more than a minute, we request the current Hash from Express and force a server-side render for the current route if the value has changed.

在路由更改时,路由器中间件会检查自我们上次将商店的哈希值与实际哈希值进行比较以来已经过去了多长时间。如果已经超过一分钟,我们会从 Express 请求当前 Hash,如果值已更改,则强制为当前路由进行服务器端渲染。

Here's the relevant code:

这是相关的代码:

server/index.js

服务器/index.js

const getRevision = function() {
  return require('child_process')
    .execSync('git rev-parse --short HEAD')
    .toString()
    .trim()
}

app.get(
  '/login',

  async function(req, res, next) {
    // (log in the user)

    const revision = getRevision()
    res.cookie('x-revision', revision)

    return res.redirect('/')
  }
)

app.get(
  '/revision',

  function(req, res) {
    const revision = getRevision()
    return res.send(revision)
  }
)

store/index.js

商店/index.js

export const actions = {
  nuxtServerInit({ dispatch }) {
    const revisionHash = this.$cookies.get('x-revision')
    dispatch('auth/storeRevision', revisionHash)
  }
}

store/auth.js

商店/auth.js

export const state = () => ({
  revision: { hash: null, checkedAt: null }
})

export const mutations = {
  setRevision(store, hash) {
    store.revision = { hash: hash, checkedAt: Date.now() }
  }
}

export const actions = {
  storeRevision({ commit }, revisionHash) {
    commit('setRevision', revisionHash)
  },

  touchRevision({ commit, state }) {
    commit('setRevision', state.revision.hash)
  }
}

middleware/checkRevision.js

中间件/checkRevision.js

export default async function({ store, route, app, redirect }) {
  const currentRevision = store.state.auth.revision
  const revisionAge = Date.now() - currentRevision.checkedAt

  if (revisionAge > 1000 * 60) {
    // more than a minute old
    const revisionHash = await app.$axios.$get(
      `${process.env.baseUrl}/revision`
    )

    if (revisionHash === currentRevision.hash) {
      // no update available, so bump checkedAt to now
      store.dispatch('auth/touchRevision')
    } else {
      // need to render server-side to get update
      return redirect(`${process.env.baseUrl}/${route.fullPath}`)
    }
  }

  return undefined
}

回答by ADM-IT

If you use asp.net core you can try the following trick among with the webpack which generates js files with the hash at the end of the name eg. my-home-page-vue.30f62910.js. So your index.html contains: <link href=/js/my-home-page-vue.30f62910.js rel=prefetch>which means whenever you change the my-home-page.vueit will generate a new hash in the filename.

如果您使用 asp.net 核心,您可以在 webpack 中尝试以下技巧,它会生成名称末尾带有哈希值的 js 文件,例如。my-home-page-vue.30f62910.js. 所以您的 index.html 包含: <link href=/js/my-home-page-vue.30f62910.js rel=prefetch>这意味着每当您更改my-home-page.vue它时,它都会在文件名中生成一个新的哈希值。

The only thing you need is to add a cache restriction against the index.html

您唯一需要做的是对 index.html 添加缓存限制

In your Startup.cs:

在您的 Startup.cs 中:

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    // ....
    app.UseStaticFiles(new StaticFileOptions
    {
      // Make sure your dist folder is correct
      FileProvider = new PhysicalFileProvider(Path.Combine(_env.ContentRootPath, "ClientApp/dist")),
      RequestPath = "",
      OnPrepareResponse = context =>
      {
        if (context.Context.Request.Path.StartsWithSegments("/index.html", StringComparison.OrdinalIgnoreCase))
        {
          context.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store");
          context.Context.Response.Headers.Add("Expires", "-1");
        }
      },
    });
    // ....
}