浏览代码

Make service worker opt-in in development

Jasper van Merle 6 年之前
父节点
当前提交
576f32dae1

+ 1 - 0
Dockerfile

@@ -1,6 +1,7 @@
 FROM ruby:2.6.0
 FROM ruby:2.6.0
 
 
 ENV LANG=C.UTF-8
 ENV LANG=C.UTF-8
+ENV ENABLE_SERVICE_WORKER=true
 
 
 WORKDIR /devdocs
 WORKDIR /devdocs
 
 

+ 1 - 0
Dockerfile-alpine

@@ -1,6 +1,7 @@
 FROM ruby:2.6.0-alpine
 FROM ruby:2.6.0-alpine
 
 
 ENV LANG=C.UTF-8
 ENV LANG=C.UTF-8
+ENV ENABLE_SERVICE_WORKER=true
 
 
 WORKDIR /devdocs
 WORKDIR /devdocs
 
 

+ 1 - 0
assets/javascripts/app/config.coffee.erb

@@ -14,3 +14,4 @@ app.config =
   release: <%= Time.now.utc.httpdate.to_json %>
   release: <%= Time.now.utc.httpdate.to_json %>
   mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css'
   mathml_stylesheet: '<%= App.cdn_origin %>/mathml.css'
   service_worker_path: '/service-worker.js'
   service_worker_path: '/service-worker.js'
+  service_worker_enabled: <%= App.environment == 'production' || !ENV["ENABLE_SERVICE_WORKER"].nil? %>

+ 1 - 1
assets/javascripts/app/serviceworker.coffee

@@ -2,7 +2,7 @@ class app.ServiceWorker
   $.extend @prototype, Events
   $.extend @prototype, Events
 
 
   @isEnabled: ->
   @isEnabled: ->
-    !!navigator.serviceWorker
+    !!navigator.serviceWorker and app.config.service_worker_enabled
 
 
   constructor: ->
   constructor: ->
     @registration = null
     @registration = null

+ 0 - 6
assets/javascripts/app/settings.coffee

@@ -5,7 +5,6 @@ class app.Settings
     'manualUpdate'
     'manualUpdate'
     'fastScroll'
     'fastScroll'
     'arrowScroll'
     'arrowScroll'
-    'bypassCache'
     'docs'
     'docs'
     'dark'
     'dark'
     'layout'
     'layout'
@@ -92,11 +91,6 @@ class app.Settings
     @set 'size', value
     @set 'size', value
     return
     return
 
 
-  setBypassCache: (value) ->
-    @set 'bypassCache', value
-    app.serviceWorker?.updateInBackground()
-    return
-
   dump: ->
   dump: ->
     @store.dump()
     @store.dump()
 
 

+ 6 - 1
assets/javascripts/templates/pages/offline_tmpl.coffee

@@ -44,7 +44,12 @@ canICloseTheTab = ->
   if app.ServiceWorker.isEnabled()
   if app.ServiceWorker.isEnabled()
     """ Yes! Even offline, you can open a new tab, go to <a href="//devdocs.io">devdocs.io</a>, and everything will work as if you were online (provided you installed all the documentations you want to use beforehand). """
     """ Yes! Even offline, you can open a new tab, go to <a href="//devdocs.io">devdocs.io</a>, and everything will work as if you were online (provided you installed all the documentations you want to use beforehand). """
   else
   else
-    """ No. Service Workers aren't available in your browser (or are disabled), so loading <a href="//devdocs.io">devdocs.io</a> offline won't work.<br>
+    reason = "aren't available in your browser (or are disabled)"
+
+    if app.config.env != 'production'
+      reason = "are disabled in your development instance of DevDocs (enable them by setting the ENABLE_SERVICE_WORKERS environment variable)"
+
+    """ No. Service Workers #{reason}, so loading <a href="//devdocs.io">devdocs.io</a> offline won't work.<br>
         The current tab will continue to function even when you go offline (provided you installed all the documentations beforehand). """
         The current tab will continue to function even when you go offline (provided you installed all the documentations beforehand). """
 
 
 app.templates.offlineDoc = (doc, status) ->
 app.templates.offlineDoc = (doc, status) ->

+ 1 - 1
assets/javascripts/templates/pages/root_tmpl.coffee.erb

@@ -8,7 +8,7 @@ app.templates.intro = """
     <p>Thanks for downloading DevDocs. Here are a few things you should know:
     <p>Thanks for downloading DevDocs. Here are a few things you should know:
     <ol class="_intro-list">
     <ol class="_intro-list">
       <li>Your local version of DevDocs won't self-update. Unless you're modifying the code,
       <li>Your local version of DevDocs won't self-update. Unless you're modifying the code,
-          I&nbsp;recommend using the hosted version at <a href="https://devdocs.io">devdocs.io</a>.
+          we&nbsp;recommend using the hosted version at <a href="https://devdocs.io">devdocs.io</a>.
       <li>Run <code>thor docs:list</code> to see all available documentations.
       <li>Run <code>thor docs:list</code> to see all available documentations.
       <li>Run <code>thor docs:download &lt;name&gt;</code> to download documentations.
       <li>Run <code>thor docs:download &lt;name&gt;</code> to download documentations.
       <li>Run <code>thor docs:download --installed</code> to update all downloaded documentations.
       <li>Run <code>thor docs:download --installed</code> to update all downloaded documentations.

+ 0 - 4
assets/javascripts/templates/pages/settings_tmpl.coffee

@@ -15,10 +15,6 @@ app.templates.settingsPage = (settings) -> """
         <input type="checkbox" form="settings" name="layout" value="_sidebar-hidden"#{if settings['_sidebar-hidden'] then ' checked' else ''}>Automatically hide and show the sidebar
         <input type="checkbox" form="settings" name="layout" value="_sidebar-hidden"#{if settings['_sidebar-hidden'] then ' checked' else ''}>Automatically hide and show the sidebar
         <small>Tip: drag the edge of the sidebar to resize it.</small>
         <small>Tip: drag the edge of the sidebar to resize it.</small>
       </label>
       </label>
-      <label class="_settings-label">
-        <input type="checkbox" form="settings" name="bypassCache" value="1"#{if settings.bypassCache then ' checked' else ''}>Bypass Service Worker cache
-        <small>When this is checked, the Service Worker will always fetch the latest version of requested files. Useful when making changes to the DevDocs source code.</small>
-      </label>
     </div>
     </div>
   </div>
   </div>
 
 

+ 0 - 7
assets/javascripts/views/content/settings_page.coffee

@@ -14,7 +14,6 @@ class app.views.SettingsPage extends app.View
     settings.dark = app.settings.get('dark')
     settings.dark = app.settings.get('dark')
     settings.smoothScroll = !app.settings.get('fastScroll')
     settings.smoothScroll = !app.settings.get('fastScroll')
     settings.arrowScroll = app.settings.get('arrowScroll')
     settings.arrowScroll = app.settings.get('arrowScroll')
-    settings.bypassCache = app.settings.get('bypassCache')
     settings[layout] = app.settings.hasLayout(layout) for layout in app.settings.LAYOUTS
     settings[layout] = app.settings.hasLayout(layout) for layout in app.settings.LAYOUTS
     settings
     settings
 
 
@@ -33,10 +32,6 @@ class app.views.SettingsPage extends app.View
     app.settings.set('fastScroll', !enable)
     app.settings.set('fastScroll', !enable)
     return
     return
 
 
-  toggleBypassCache: (enable) ->
-    app.settings.setBypassCache(!!enable)
-    return
-
   toggle: (name, enable) ->
   toggle: (name, enable) ->
     app.settings.set(name, enable)
     app.settings.set(name, enable)
     return
     return
@@ -80,8 +75,6 @@ class app.views.SettingsPage extends app.View
         @toggleSmoothScroll input.checked
         @toggleSmoothScroll input.checked
       when 'import'
       when 'import'
         @import input.files[0], input
         @import input.files[0], input
-      when 'bypassCache'
-        @toggleBypassCache input.checked
       else
       else
         @toggle input.name, input.checked
         @toggle input.name, input.checked
     return
     return

+ 0 - 2
docs/adding-docs.md

@@ -2,8 +2,6 @@ Adding a documentation may look like a daunting task but once you get the hang o
 
 
 **Note:** please read the [contributing guidelines](../.github/CONTRIBUTING.md) before submitting a new documentation.
 **Note:** please read the [contributing guidelines](../.github/CONTRIBUTING.md) before submitting a new documentation.
 
 
-**Note:** when editing any of the files in the `assets` directory or the `public` directory, you'll have to bypass the service worker cache. To do this, go to the Preferences page on your local instance of DevDocs, check "Bypass Service Worker cache" and refresh the page.
-
 1. Create a subclass of `Docs::UrlScraper` or `Docs::FileScraper` in the `lib/docs/scrapers/` directory. Its name should be the [PascalCase](http://api.rubyonrails.org/classes/String.html#method-i-camelize) equivalent of the filename (e.g. `my_doc` → `MyDoc`)
 1. Create a subclass of `Docs::UrlScraper` or `Docs::FileScraper` in the `lib/docs/scrapers/` directory. Its name should be the [PascalCase](http://api.rubyonrails.org/classes/String.html#method-i-camelize) equivalent of the filename (e.g. `my_doc` → `MyDoc`)
 2. Add the appropriate class attributes and filter options (see the [Scraper Reference](./scraper-reference.md) page).
 2. Add the appropriate class attributes and filter options (see the [Scraper Reference](./scraper-reference.md) page).
 3. Check that the scraper is listed in `thor docs:list`.
 3. Check that the scraper is listed in `thor docs:list`.

+ 22 - 2
lib/app.rb

@@ -205,8 +205,28 @@ class App < Sinatra::Application
       ].compact
       ].compact
     end
     end
 
 
-    def bypass_cache?
-      !memoized_cookies['bypassCache'].nil?
+    def service_worker_cache_name
+      # Returns the digest of the last modified asset
+      # When a manifest exist, this digest is only calculated once based on the asset manifest because it never changes without a server restart
+      # If a manifest does not exist, it is calculated every time this method is called because the assets can change while the server is running
+      if File.exist?(App.assets_manifest_path)
+        return @@service_worker_cache_name ||=
+          Sprockets::Manifest
+            .new(nil, App.assets_manifest_path)
+            .files
+            .values
+            .sort_by {|file| file["mtime"]}
+            .reverse
+            .first["digest"]
+      else
+        last_modified_file = App.sprockets
+          .each_file
+          .to_a
+          .reject {|file| file.start_with?(App.docs_path)}
+          .max_by {|file| File.mtime(file)}
+
+        return App.sprockets.pack_base64digest(App.sprockets.file_digest(last_modified_file))
+      end
     end
     end
 
 
     def redirect_via_js(path)
     def redirect_via_js(path)

+ 3 - 7
views/service-worker.js.erb

@@ -1,6 +1,6 @@
-<%# Use the hash of the application.js file as cache name or 'app' if not running in production %>
-<%# This ensures that the cache is always updated if the hash of the application.js file changes %>
-const cacheName = '<%= javascript_path('application', asset_host: false).scan(/application-([^\.]+)\.js/).last&.first || 'app' %>';
+<%# The name of the cache to store responses in %>
+<%# If the cache name changes DevDocs is assumed to be updated %>
+const cacheName = '<%= service_worker_cache_name %>';
 
 
 <%# Url's to cache when the service worker is installed %>
 <%# Url's to cache when the service worker is installed %>
 const urlsToCache = [
 const urlsToCache = [
@@ -32,9 +32,6 @@ self.addEventListener('activate', event => {
 <%# Handle HTTP requests %>
 <%# Handle HTTP requests %>
 self.addEventListener('fetch', event => {
 self.addEventListener('fetch', event => {
   event.respondWith((async () => {
   event.respondWith((async () => {
-    <% if bypass_cache? %>
-    return fetch(event.request);
-    <% else %>
     const cachedResponse = await caches.match(event.request);
     const cachedResponse = await caches.match(event.request);
     if (cachedResponse) return cachedResponse;
     if (cachedResponse) return cachedResponse;
 
 
@@ -53,6 +50,5 @@ self.addEventListener('fetch', event => {
 
 
       throw err;
       throw err;
     }
     }
-    <% end %>
   })());
   })());
 });
 });