preactjs / preact-cli

๐Ÿ˜บ Your next Preact PWA starts in 30 seconds.
MIT License
4.69k stars 375 forks source link

Develop and debug ServiceWorker #955

Open JCofman opened 4 years ago

JCofman commented 4 years ago

Hi ๐Ÿ‘‹ I had some issues adding a cusotm serviceWorker. I think it would be helpfull to provide some more information on the docs https://preactjs.com/cli/service-worker/ site. I am willing to help :)

Do you want to request a feature or report a bug? Both? :)

What is the current behavior? I am not sure how I can properly debug / update and develop the serviceWorker during preact watch. As mentioned in the docs https://preactjs.com/cli/service-worker/ I can write a custom sw.js and add the base functionality from the default preact serviceWorker.

When I try to do it like mentioned in the docs I

  1. create a sw.js into the src folder and copy paste default code from the docs
  2. add --sw preact watch --sw to include the service worker during development phase (it's currently not mentioned in the referenced docs but I did not find another way otherwise I would get the default sw-debug.js output.
  3. run preact watch --sw visiting (0.0.0.0:8080) results in an error Bildschirmfoto 2020-01-27 um 15 16 13
  4. Adding resolves the above issue
    // preact.config.js
    module.exports = config => {
    config.node.process = true;
    };
  5. But then I get an error with the copy pasted code since the workbox SDK is not included.

If the current behaviour is a bug, please provide the steps to reproduce.

mentioned above

What is the expected behaviour? Add an explanation / guide about how to edit serviceWorker behaviour during preact watch so that you can customize the default serviceWorker behavior.

If this is a feature request, what is motivation or use case for changing the behaviour? Better PWA workflow

Please mention other relevant information.

Please paste the results of preact info here.

Environment Info:

  System:
    OS: macOS 10.15.2
    CPU: (8) x64 Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
  Binaries:
    Node: 12.13.1 - ~/.nvm/versions/node/v12.13.1/bin/node
    Yarn: 1.21.1 - /usr/local/bin/yarn
    npm: 6.13.6 - ~/.nvm/versions/node/v12.13.1/bin/npm
  Browsers:
    Chrome: 79.0.3945.130
    Firefox: 72.0.2
    Safari: 13.0.4
  npmPackages:
    preact: ^10.2.1 => 10.2.1
    preact-cli: ^3.0.0-rc.7 => 3.0.0-rc.7
    preact-render-to-string: ^5.1.4 => 5.1.4
    preact-router: ^3.2.0 => 3.2.0
  npmGlobalPackages:
    preact-cli: 3.0.0-rc.7
gtsop commented 4 years ago

Hi, any updates on this? It's an important topic, not sure what the "official" guidelines are myself.

tysonmatanich commented 4 years ago

It's disappointing the Preact service worker doc doesn't seem to cover the info needed to get a simple custom service worker up and running, which states:

"Preact CLI uses InjectManifestPlugin to compile the service worker. This means that in order to use any other workbox module you'll need to install them via NPM and use them in you src/sw.js by importing them."

However I get the following error in the browser's console after when running preact watch --sw:

sw.js:1: Uncaught SyntaxError: Cannot use import statement outside a module

src/sw.js:

import { getFiles, setupPrecaching, setupRouting } from "preact-cli/sw/";
import { registerRoute } from "workbox-routing";
import { NetworkFirst } from "workbox-strategies";

registerRoute(
  ({ url }) => url.pathname.startsWith("/api/"),
  new NetworkFirst()
);
setupRouting();
setupPrecaching(getFiles());

Is the InjectManifestPlugin not actually compiling the service worker?

developit commented 4 years ago

Hi @JCofman - Preact CLI doesn't actually compile SW during development (preact watch). It serves a debug sw.js that is mainly there to replace any stuck production Service Worker, but that's it.

I think it would be interesting to have the option of enabling the SW generation for dev builds.

The code that does the SW generation (below) will need to be modified to support being injected when preact watch --sw is used: https://github.com/preactjs/preact-cli/blob/master/packages/cli/lib/lib/webpack/webpack-client-config.js#L225-L258

Another option might be to have dev mode always use InjectManifestPlugin, even when generating it's basic no-op service worker. That would make it easy to replace the SW with a custom one in a way that works in both dev and prod builds.

tysonmatanich commented 4 years ago

@developit thanks for the suggestions! I'm now using the script below in my preact.config.js which allows my service worker to run with preact watch --sw. I grabbed this from webpack-client-config.js and changed a few things to make it work within preact.config.js. It would be nice however if this was officially supported so if the code gets updated, then mine doesn't get out of sync.

The main issue I'm seeing now is that changing sw.js triggers a compile due to the watch, however the changes don't get reflected in the service worker. To work around this I rerun preact watch --sw each time I change my service worker script. This isn't optimal and I don't expect a hot reload because the way service workers function, however running on page refresh is my expectation (unrelated to Chrome's "Update on reload").

When the recompile happens the following is logged on the server:

โ€ผ WARN InjectManifest has been called multiple times, perhaps due to running webpack in --watch mode. The precache manifest generated after the first call may be inaccurate! Please see https://github.com/GoogleChrome/workbox/issues/1790 for more informatibe perhaps due to runon.

I would like to find a way to toggle off the workbox logging when not needed as its very chatty.

preact.config.js

import Webpack from "webpack";
import { InjectManifest } from "workbox-webpack-plugin";
import { join } from "path";
import { existsSync } from "fs";

export default function (config, env, helpers) {
  if (env.isWatch) {
    let swPath;
    if (env.sw) {
      swPath = join(__dirname, "..", "..", "..", "sw", "sw.js");
      const userSwPath = join(env.src, "sw.js");
      if (existsSync(userSwPath)) {
        swPath = userSwPath;
      } else {
        console.log(
          `Could not find sw.js in ${env.src}. Using the default service worker.`
        );
      }
    }

    if (env.esm && env.sw) {
      config.plugins.push(
        new InjectManifest({
          swSrc: swPath,
          swDest: "sw-esm.js",
          include: [
            /^\/?index\.html$/,
            /\.esm.js$/,
            /\.css$/,
            /\.(png|jpg|svg|gif|webp)$/,
          ],
          webpackCompilationPlugins: [
            new Webpack.DefinePlugin({
              "process.env.ESM": true,
            }),
          ],
        })
      );
    }

    if (env.sw) {
      config.plugins.push(
        new InjectManifest({
          swSrc: swPath,
          include: [
            /index\.html$/,
            /\.js$/,
            /\.css$/,
            /\.(png|jpg|svg|gif|webp)$/,
          ],
          exclude: [/\.esm\.js$/],
        })
      );
    }
  }
}