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

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

Closed
matthewlewisnewton opened this issue Nov 3, 2021 · 3 comments · Fixed by #7288
Closed

Comments

@matthewlewisnewton
Copy link

matthewlewisnewton commented Nov 3, 2021

🐛 (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
Copy link
Author

matthewlewisnewton commented Nov 3, 2021

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: tensorflow/tfjs#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
Copy link
Member

mischnic commented Nov 3, 2021

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

return micromatch.isMatch(
path.relative(pkg.pkgdir, filePath),
sideEffects,
{matchBase: true},
);

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: #7218 (comment)

@NikhilNanjappa
Copy link

NikhilNanjappa commented Jun 19, 2023

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"
  }

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

Successfully merging a pull request may close this issue.

3 participants