parcel-bundler / parcel

The zero configuration build tool for the web. πŸ“¦πŸš€
https://parceljs.org
MIT License
43.52k stars 2.27k forks source link

`Parcel build` breaking tfjs initialization logic (that works under `parcel serve`) #7239

Closed matthewlewisnewton closed 3 years ago

matthewlewisnewton commented 3 years ago

πŸ› (maybe) bug report

πŸŽ› Configuration (.babelrc, package.json, cli command)

Package.json:

{
  "devDependencies": {
    "parcel": "^2.0.0",
    "typescript": "^4.4.4"
  },
  "scripts": {
    "build": "parcel build index.html",
    "noopt": "parcel build index.html --no-optimize",
    "localhost": "parcel index.html --host localhost"
  },
  "dependencies": {
    "@tensorflow-models/qna": "^1.0.0",
    "@tensorflow/tfjs-backend-cpu": "^3.11.0",
    "@tensorflow/tfjs-backend-webgl": "^3.11.0",
    "@tensorflow/tfjs-converter": "^3.11.0",
    "@tensorflow/tfjs-core": "^3.11.0"
  }
}

Entire repo here: https://github.com/matthewlewisnewton/parcel-build-breakage

To see that it builds and tfjs initializes at localhost:1234 npm run localhost To see it break under build at localhost:8080 npm run build && cd dist && python3 -m http.server 8080

πŸ€” Expected Behavior

parcel build --no-optimize (or ideally, parcel build with optimizations) should not break code that works under parcel serve.

😯 Current Behavior

It looks like tfjs-backend-webgl has side effects that it isn't reporting or something, and parcel prunes them. Running the code served from parcel in development mode works as expected, however when running the code from parcel build I run into the follow error on initialization:

Uncaught Error: Cannot evaluate flag 'CPU_HANDOFF_SIZE_THRESHOLD': no evaluation function found.
    at $a1ec4b60d5bb4c14$export$7dc6752a22ab011a.evaluateFlag (environment.ts:139)
    at $a1ec4b60d5bb4c14$export$7dc6752a22ab011a.get (environment.ts:98)
    at $a1ec4b60d5bb4c14$export$7dc6752a22ab011a.getNumber (environment.ts:111)
    at backend_webgl.ts:90
evaluateFlag @ environment.ts:139
get @ environment.ts:98
getNumber @ environment.ts:111
(anonymous) @ backend_webgl.ts:90

πŸ”¦ Context

This is probably a misbehaving tfjs module, that repo seems to run all kinds of code on module initialization that could break from being re-written / pruned. But I could not find a way to exclude these files from parcel's rewriting in a way that let them stay working.

πŸ’» Code Sample

https://github.com/matthewlewisnewton/parcel-build-breakage

index.html:

<html>
<head><meta charset="UTF-8"><script src="./index.ts" defer type="module"></script></head>
<body>
<h1> Scope hoisting </h1>
<p>Historically, JavaScript bundlers have worked by wrapping each module in a function, which is called when the module is imported. This ensures that each module has a separate isolated scope and side effects run at the expected time, and enables development features like <a href="https://nolanlawson.com/2016/08/15/the-cost-of-small-modules/">runtime performance</a><a href="/features/development/#hot-reloading">hot module replacement</a>. However, all of these separate functions have a cost, both in terms of download size and .</p>
<p>In production builds, Parcel concatenates modules into a single scope when possible, rather than wrapping each module in a separate function. This is called <strong>β€œscope hoisting”.</strong> This helps make minification more effective, and also improves runtime performance by making references between modules static rather than dynamic object lookups.</p>
<p>Parcel also statically analyzes the imports and exports of each module, and removes everything that isn't used. This is called <strong>"tree shaking"</strong> or <strong>"dead code elimination".</strong> Tree shaking is supported for both static and <a href="/features/code-splitting/#tree-shaking">dynamic import</a>, <a href="/languages/javascript/#commonjs">CommonJS</a> and <a href="/languages/javascript/#es-modules">ES modules</a>, and even across languages with <a href="/languages/css/#tree-shaking">CSS modules</a>.</p>
</body>
</html>

index.ts:

import * as tf from '@tensorflow/tfjs-core';
import * as gpu from '@tensorflow/tfjs-backend-webgl';
import * as cpu from '@tensorflow/tfjs-backend-cpu';
import * as qna from '@tensorflow-models/qna';
let break_treeshaking_hack: any = tf;
break_treeshaking_hack = cpu;
break_treeshaking_hack = gpu;

export async function askAboutMinification() {
    const model = await qna.load();
    console.log('model loaded');
    const context = document.body.innerText;
    const answers = await model.findAnswers("What helps make minification more effective?",context);
    console.log('model finished running');
    for (const a of answers) {
       console.log(a);
    }
}
askAboutMinification();

🌍 Your Environment

Software Version(s)
Parcel ^2.0.0
Node v16.12.0
npm/Yarn npm 8.1.2
Operating System Ubuntu 20.04 LTS
matthewlewisnewton commented 3 years ago

For more context, this is the line I'm blowing up at in the production build (but works in parcel serve): https://github.com/tensorflow/tfjs/blob/master/tfjs-core/src/environment.ts#L101

Seems this line is pruned: https://github.com/tensorflow/tfjs/blob/1707695aab46f41b75382e908136841a2b1d48ef/tfjs-backend-webgl/src/flags_webgl.ts#L218

This seems to be a known issue on the tfjs side: https://github.com/tensorflow/tfjs/issues/5182 However, the module in question does have the flags file listed as a side effect since that issue was filed - maybe this does not do what I think it does?

"sideEffects": [
    "./dist/register_all_kernels.js",
    "./dist/flags_webgl.js",
    "./dist/base.js",
    "./dist/index.js",
    "./dist/register_all_kernels.mjs",
    "./dist/flags_webgl.mjs",
    "./dist/base.mjs",
    "./dist/index.mjs",
    "./src/register_all_kernels.mjs",
    "./src/flags_webgl.mjs",
    "./src/base.mjs",
    "./src/index.mjs"
  ]
mischnic commented 3 years ago

I would say that the listed sideffects are correct. The problem is that our glob matching of them doesn't check the right thing:

https://github.com/parcel-bundler/parcel/blob/d4b42567e0267dac94b3fba68232242ced5ec022/packages/utils/node-resolver-core/src/NodeResolver.js#L1175-L1179

This should be true, but ./ seems to confuse micromatch:

> require("micromatch").isMatch("dist/index.js", "./dist/index.js", {matchBase: true})
false

I found a similar problem here: https://github.com/parcel-bundler/parcel/issues/7218#issuecomment-955571056

NikhilNanjappa commented 1 year ago

I'm using webpack & have the same error on my browser -

Cannot evaluate flag 'CANVAS2D_WILL_READ_FREQUENTLY_FOR_GPU': no evaluation function found.

"dependencies": {
    "@tensorflow-models/qna": "^1.0.1",
    "@tensorflow/tfjs-backend-cpu": "^3.15.0",
    "@tensorflow/tfjs-backend-webgl": "^3.15.0",
    "@tensorflow/tfjs-core": "3.15.0",
    "@tensorflow/tfjs-converter": "3.15.0",
    "http-server": "^14.1.1"
  },
  "devDependencies": {
    "webpack": "^5.87.0",
    "webpack-cli": "^5.1.4"
  },
  "peerDependencies": {
    "@types/long": "^4.0.1",
    "@types/offscreencanvas": "~2019.3.0",
    "@types/seedrandom": "2.4.27",
    "@types/webgl-ext": "0.0.30",
    "long": "4.0.0",
    "node-fetch": "~2.6.1",
    "seedrandom": "2.4.3",
    "@types/webgl2": "0.0.6"
  }