Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development server inexplicably strips type="module" from <script> tag #9716

Open
gcleaves opened this issue May 15, 2024 · 18 comments
Open

Comments

@gcleaves
Copy link

gcleaves commented May 15, 2024

🐛 bug report

Development server inexplicably strips type="module" from <script> tag, breaking the page. The normal build process does not do that.

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

No config.

Input HTML:

<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <title>My First Parcel App</title>
    <script src="https://unpkg.com/petite-vue" defer init></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/themes/light.css" />
    <script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/shoelace-autoloader.js"></script>

    <style>
        #graph {
          border: 1px solid #e0e0e0;
          width: 100%;
          height: 99%;
        }
      </style>
  </head>
  <body>
    <div v-scope="{ count: 0 }">
      {{ count }}
      <sl-button @click="count++">inc</sl-button>
    </div>
    <div id="graph"></div>
    <sl-drawer label="Drawer" class="drawer-overview">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit.
      <sl-button slot="footer" variant="primary">Close</sl-button>
    </sl-drawer>
    
  </body>
</html>

🤔 Expected Behavior

Don't remove type=module

😯 Current Behavior

type=module is removed from the script tag in development server build only.

💁 Possible Solution

🔦 Context

Can't leverage dev server

💻 Code Sample

npx parcel --no-cache app/index.html

🌍 Your Environment

Mac OS

Software Version(s)
Parcel 2.12.0
Node v21.7.2
npm/Yarn npm
Operating System Mac OS
@gcleaves
Copy link
Author

Further to this, parcel watch behaves the same way, breaking the app, while parcel build works correctly.

@josewal
Copy link

josewal commented Jul 26, 2024

I am also affected by this, any workarounds?

@devongovett
Copy link
Member

In development we don't use esm, so this is intentional. What is broken about it for you?

@LoganDark
Copy link

LoganDark commented Sep 3, 2024

In development we don't use esm, so this is intentional.

What? Is that really true? Development should use ESM so I don't have to build the whole app at once... manual bundle splitting is the worst, and also impossible right now because:

Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules

Is there any way to force the dev server to use ESM properly?

@devongovett
Copy link
Member

devongovett commented Sep 3, 2024

It shouldn't matter. ESM is just a syntax. What parcel outputs is an implementation detail that shouldn't affect the behavior of your source code.

Development should use ESM so I don't have to build the whole app at once

I don't understand this. That has nothing to do with ESM output.

@LoganDark
Copy link

It shouldn't matter. ESM is just a syntax. What parcel outputs is an implementation detail that shouldn't affect the behavior of your source code.

Development should use ESM so I don't have to build the whole app at once

I don't understand this. That has nothing to do with ESM output.

It matters when Parcel has to rebuild the bundle every time I change any module. Because the app can't even start to load until the entire bundle is rebuilt, because Parcel has to come up with a single file that contains every module. Hot reload works once, but if you change a file multiple times in a row, then the other changes have to wait for the first entire bundle rebuild to complete. It's super slow and annoying.

@devongovett
Copy link
Member

The ESM format does not affect whether you bundle or don't bundle. Those are separate concerns. You can bundle both ESM and non-ESM or not bundle both ESM and non-ESM. Switching to type=module does not make it non-bundled.

Parcel delivers HMR updates before packaging finishes via a websocket, so it should update quickly regardless what module format you use.

If you don't want a bundler at all and just want an ESM dev server then I'd suggest a different tool. Parcel is a bundler and will continue to be.

@LoganDark
Copy link

LoganDark commented Sep 3, 2024

The ESM format does not affect whether you bundle or don't bundle. Those are separate concerns. You can bundle both ESM and non-ESM or not bundle both ESM and non-ESM. Switching to type=module does not make it non-bundled.

OK, thanks for the clarification.

Parcel delivers HMR updates before packaging finishes via a websocket, so it should update quickly regardless what module format you use.

This is (partially) incorrect.

If you make one edit, then the HMR update is indeed instantaneous.

However, if you make another edit before the bundle has finished fully rebuilding, then your edit won't be applied until the first update has finished rebuilding the entire bundle.

Our app requires almost a minute to rebuild the whole bundle, so this really hurts.

I don't want to wait for our huge bundle to compile just to iterate rapidly on a single module. :/

I think this would suggest that Parcel builds the edited module first, sends out the update, then begins to rebuild the bundle. Then it refuses to do any other tasks until the bundle build is complete. So any additional HMR updates are delayed.

@PartyWumpus
Copy link

PartyWumpus commented Sep 11, 2024

In development we don't use esm, so this is intentional. What is broken about it for you?

I'm confused, parcel watch and parcel build having totally differently functioning outputs is intentional? I understand it is not often super important that dev and prod are identical, but at least provide an option for parcel watch to output esm (or whatever, to get top level awaits working the same in both) like parcel build can...

@LoganDark
Copy link

LoganDark commented Sep 11, 2024

I'm confused, parcel watch and parcel build having totally different outputs is intentional?

To some degree this is to be expected because production builds don't have hot reload or anything. I just hate that Parcel has to build every module into a single JS file before the webpage can even load. I wish it could build/load modules individually/lazily like Vite does. Native ESM import would be amazing.

@PartyWumpus
Copy link

PartyWumpus commented Sep 11, 2024

To some degree this is to be expected because production builds don't have hot reload or anything.

Obviously the output can't actually be identical, but I would've expected it to function as if it was the same. If this is just a problem with parcel's fundamental design should I just switch to vite? No hate to the devs of this project, just not a fan of non-representative builds.

Edit: doesn't look like vite does what i need (it does top level await, just not some other stuff), guess i have to just rewrite the top level await stuff and just hope this isn't a problem again...

@LoganDark
Copy link

If this is just a problem with parcel's fundamental design should I just switch to vite?

That seems to be the consensus, according to Parcel devs? They say parcel is a bundler so if you don't want it to bundle your code then switch to something else. This is difficult to do for our company monorepo but if that's really the attitude then I'll keep pushing to switch to Vite. It doesn't exactly help that Parcel miscompiles code as well.

@LoganDark
Copy link

LoganDark commented Sep 11, 2024

Vite supports top level await in ES modules, not sure why that would be the limiting factor here. I think Parcel also supports it but only in production builds because dev builds are CJS for no good reason...

@devongovett
Copy link
Member

I'm confused, parcel watch and parcel build having totally differently functioning outputs is intentional?

That's always been the case. Minification, tree shaking, and lots of other stuff doesn't run in development. The other important difference is HMR, which requires a different module format so that modules can be replaced. Native ESM modules cannot be replaced in the browser's module cache, so we need a custom format for that. Regardless, these are implementation details and shouldn't matter too much as long as the behavior is the same.

I wish it could build/load modules individually/lazily like Vite does.

This also has some downsides. It just moves the work from the bundler to the browser. This may actually be slower because the browser needs to download many more modules on each page load, each of which has overhead, vs downloading a single bundle. We did this long before bundlers, e.g. require.js, and bundling was partially a response to this to improve performance. With many modules, bundling is faster. Vite's approach is ok for smaller apps, but slows down as your app grows.

I think this would suggest that Parcel builds the edited module first, sends out the update, then begins to rebuild the bundle. Then it refuses to do any other tasks until the bundle build is complete. So any additional HMR updates are delayed.

This seems like something we could potentially improve. But that's a totally different problem than this initial issue. Perhaps focusing on the issues you're encountering rather than the solution would be a good place to start.

@PartyWumpus
Copy link

PartyWumpus commented Sep 11, 2024

Regardless, these are implementation details and shouldn't matter too much as long as the behavior is the same.

But it isn't, I can do top level awaits in one and not in the other... If you read my comment you'll notice I said "differently functioning outputs", I understand that they will, under the hood, work differently, but it's not just an "implementation detail" if it makes a noticeable difference to the end result...

Edit: To be clear, I understand if parcel doesn't/won't support top level awaits at all, but I would've preferred it not work immediately instead of breaking sometimes, because it wasted my time.

@LoganDark
Copy link

LoganDark commented Sep 11, 2024

This seems like something we could potentially improve. But that's a totally different problem than this initial issue. Perhaps focusing on the issues you're encountering rather than the solution would be a good place to start.

I don't see the bundle as necessary whatsoever in development if the individual module can be rebuilt and applied just fine without ever even thinking about a bundle. So the most obvious solution in my eyes is to stop building everything into a single file during development. Why fix only one symptom when you can fix the root of the problem? Is Parcel determined to stay in the past or something? Is there a reason why the entire bundle has to be rebuilt every time any file changes?

I'm OK filing an issue for the HMR slowness if that sounds like something you'd be interested in fixing.

One of the main reasons I haven't already done that is because I'm pretty sure trying to do anything in parallel is what has resulted in constant "expected content key <id> to exist" errors (or in other words, "there's a good reason it's done this way"), which is why I have to restart the Parcel server nearly every time I switch branches or perform any multi-file refactors. Of course, sometimes instead of throwing any actual error, the server just craps the bed and stops rebuilding anything, even though it still responds to requests. Which is annoying as hell and also super misleading as the code in the browser is perpetually out of date until the server is restarted (which has to be achieved via kill because Ctrl-C stops working).

Not sure how valuable it would be for me to file bug reports for every issue I've ever encountered with Parcel. My experience is that many of the issues I'm having were filed years ago and no effort is being put into diagnosing or fixing them.

(If you say you're all unpaid volunteers and I'm not entitled to anything, then that's why I don't generally file issues with the expectation of them being fixed! Because I know I don't personally have time to investigate them.)

@devongovett
Copy link
Member

devongovett commented Sep 11, 2024

TLA support is a different issue: #4028. Unfortunately it's quite difficult to implement.

I don't see the bundle as necessary whatsoever in development

You would definitely notice the performance issue even in development when you start loading thousands of individual modules as separate requests. This happens every time you load every page, even with browser caching, rather than only when you change a file (and we don't need to rebuild bundles for pages that didn't change). See this thread on the Vite repo for some reports from their users with large apps: vitejs/vite#4803. There are definitely tradeoffs on each side, neither option is a silver bullet.

@LoganDark
Copy link

LoganDark commented Sep 11, 2024

Unfortunately it's quite difficult to implement

I would think so, probably because of scope hoisting or something. And also the fact that module loading suddenly becomes asynchronous, and you might want to cancel the Promises e.g. in the case of HMR.

I authored a Node.js package that let you use await at the top level of require, lmao (not in the browser, though). It's terrible, but I know from using that in practice how wide-reaching the implications can be, since suddenly every require returns a Promise. I'm guessing you'd need to have both async and non-async require if you're going to continue using CJS wrappers for ES modules. But eh, that's a discussion for another thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants