parcel-bundler / parcel

The zero configuration build tool for the web. 📦🚀
https://parceljs.org
MIT License
43.52k stars 2.26k forks source link

Service Worker: Failed to construct 'URL': Invalid URL #9011

Open RichardJECooke opened 1 year ago

RichardJECooke commented 1 year ago

🐛 bug report

My PWA works fine when using the Parcel dev server on my localhost. So I tried building and deploying to a webhost and it breaks.

When using the build command parcel build --no-content-hash --no-source-maps --no-optimize src/index.html, and copying the output to a server

dist/index.html                       1.63 KB    1.09s
dist/app.webmanifest                    450 B    967ms
dist/index.5375eb8a.js            ⚠️  2.48 MB    1.07s
dist/serviceWorker.020ed210.js        2.28 KB    1.07s

I get the error caught (in promise) TypeError: Failed to construct 'URL': Invalid URL at $db13d2c0f4fb9b17$var$registerServiceWorker (index.5375eb8a.js:66710:30).

Here's the code that gives the error (taken from https://parceljs.org/languages/javascript/#service-workers)

async function registerServiceWorker(): Promise<void>
{
    const serviceWorkerUrl = new URL('./serviceWorker.ts', import.meta.url);
    console.log(`Registering service worker file located at ${serviceWorkerUrl} ...`);
    if ('serviceWorker' in navigator)
    {
        try
        {
            const registration = await navigator.serviceWorker.register(serviceWorkerUrl, {type: 'module', scope: '/' });
            console.log('Service worker registered', registration);
        }
        catch (error)
        {
            console.error('Error registering service worker:', error);
        }
    }
    else console.warn('Service workers are not supported.');
}

I can't debug this because I can't see what URL Parcel is trying to generate here.

If I don't try to put the URL in a string, like this:

async function registerServiceWorker(): Promise<void>
{
    if ('serviceWorker' in navigator)
    {
        try
        {
            const registration = await navigator.serviceWorker.register(new URL('./serviceWorker.ts', import.meta.url), {type: 'module', scope: '/' });
            console.log('Service worker registered', registration);
        }
        catch (error)
        {
            console.error('Error registering service worker:', error);
        }
    }
    else console.warn('Service workers are not supported.');
}

and build, Parcel now doesn't add a hash to the service worker filename

dist/index.html                     1.63 KB    1.16s
dist/app.webmanifest                  450 B    992ms
dist/index.5375eb8a.js          ⚠️  2.48 MB    1.15s
dist/serviceWorker.js               2.68 KB    1.15s

and I get a different error when browsing to the site:

A bad HTTP response code (404) was received when fetching the script.
index.5375eb8a.js:66716 Error registering service worker: TypeError: Failed to register a ServiceWorker for scope ('https://journal.example.app/') with script ('https://journal.example.app/undefined'): A bad HTTP response code (404) was received when fetching the script.
$

🎛 Configuration (.babelrc, package.json, cli command)

package.json

{
  "version": "0.1.0",
  "comments": "",
  "scripts": {
    "build": "rm -r dist;    rm -r .parcel-cache;     parcel build --no-content-hash --no-source-maps --no-optimize src/index.html",
    "check": "clear && vue-tsc --noEmit",
    "run": "rm -r .parcel-cache; parcel src/index.html"
  },
  "dependencies": {
    "@parcel/service-worker": "^2.8.3",
    "crypto-js": "^4.1.1",
    "dayjs": "^1.11.3",
    "dropbox": "^10.34.0",
    "idb": "^7.1.1",
    "jszip": "^3.10.1",
    "ramda": "^0.29.0",
    "uuid": "^8.3.2",
    "vue": "^3.2.37",
    "vue-router": "^4.0.16",
    "vue-tsc": "^0.38.2"
  },
  "devDependencies": {
    "@parcel/packager-raw-url": "^2.8.3",
    "@parcel/transformer-vue": "^2.6.2",
    "@parcel/transformer-webmanifest": "^2.8.3",
    "@types/node": "^18.0.1",
    "@types/ramda": "^0.29.0",
    "@types/serviceworker": "^0.0.67",
    "@types/uuid": "^8.3.4",
    "@vue/tsconfig": "^0.1.3",
    "assert": "^2.0.0",
    "browserify-zlib": "^0.2.0",
    "buffer": "^5.7.1",
    "crypto-browserify": "^3.12.0",
    "events": "^3.3.0",
    "https-browserify": "^1.0.0",
    "os-browserify": "^0.3.0",
    "parcel": "^2.6.2",
    "path-browserify": "^1.0.1",
    "process": "^0.11.10",
    "querystring-es3": "^0.2.1",
    "stream-browserify": "^3.0.0",
    "stream-http": "^3.2.0",
    "typescript": "^4.7.4",
    "url": "^0.11.0",
    "util": "^0.12.4"
  },
  "serve": {
    "port": 1234,
    "headers": {
      "Service-Worker-Allowed": "/"
    }
  }
}

🤔 Expected Behavior

The service worker should load and I should be able to log its path to the console. The service worker filename should not be given a hash no matter what code calls it.

😯 Current Behavior

Parcel's path assignment for the service worker is broken. Parcel keeps changing the service worker filename.

🌍 Your Environment

Software Version(s)
Parcel 2.6.2
Node 16.15.0
npm/Yarn 8.5.5
Operating System Ubuntu 22.04, but this is occurring on some web server and I don't know any of it's OS details. Just Apache or whatever
RichardJECooke commented 1 year ago

And here's the generated JS code, which looks roughly right to me, at least serviceWorker.js has the right name.

(parcelRequire("2JpsI")).register(JSON.parse('{"cpKN1":"index.5375eb8a.js","exyRE":"serviceWorker.js"}'));

...

var $8308987b7f2d1c72$exports = {};

$8308987b7f2d1c72$exports = new URL((parcelRequire("2JpsI")).resolve("exyRE"), import.meta.url).toString();

async function $db13d2c0f4fb9b17$var$registerServiceWorker() {
    if ("serviceWorker" in navigator) try {
        const registration = await navigator.serviceWorker.register($8308987b7f2d1c72$exports, {
            scope: "/"
        });

If I put $8308987b7f2d1c72$exports = new URL((parcelRequire("2JpsI")).resolve("exyRE"), "http://journal.example.app").toString(); in the console it gives me the right filename.

RichardJECooke commented 1 year ago

My ts.config of my root folder is

{
    "extends": "@vue/tsconfig/tsconfig.web.json",
    "include":
    [
        "env.d.ts", "src/**/*", "src/**/*.vue"
        ,"src/external/dropbox/dropbox_sdk_10_32_0.js"
    ],
    "compilerOptions":
    {
        "alwaysStrict": true,
        "baseUrl": ".",
        "importsNotUsedAsValues": "remove",
        "isolatedModules": false,
        "lib": ["ES2021", "DOM", "webworker"],
        "module": "ES2020",
        "moduleResolution": "node",
        "noImplicitAny": true,
        "paths": { "@/*": ["./src/*"] },
        "preserveConstEnums": true,
        "preserveValueImports": false,
        "removeComments": true,
        "sourceMap": true,
        "strict": true,
        "strictNullChecks": true,
        "target": "ES2021",
        "types": ["node",  "dropbox"]
    },
}
mischnic commented 1 year ago

navigator.serviceWorker.register(new URL('./serviceWorker.ts', import.meta.url), {type: 'module', scope: '/' })

That is definitely the version you should be using. If you put the url constructor outside, then Parcel doesn't know that serverWorker.ts should be the service worker. Not sure yet why that then fails to load the correct url

RichardJECooke commented 1 year ago

Thanks @mischnic. Here's a reproducible example: https://we.tl/t-6RVGridxhJ

Run:

npm update
npm run dev # browse to localhost:1234 and see that it works
npm run preview # browse to localhost:1234 and see that it doesn't work
eliyen commented 1 year ago

I can confirm this issue.

Uncaught (in promise) DOMException: Failed to register a ServiceWorker for scope ('https://test.local/') with script ('https://test.local/undefined')

Parcel tries to load an undefined URL. My service worker URL should be https://test.local/service_worker.js.

Interestingly, if I pass the --no-scope-hoist flag when building, then Parcel loads the correct URL.

nneil commented 5 months ago

I have this problem with 2.12.0. Has no progress really been made in over a year? This is a show-stopping bug.

Is the value of import.meta.url really meant to be file:///src/index.ts even when serving from a web server?