parcel-bundler / parcel

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

GLSL transformer fails to find nested vendor dependencies (works in v1, fails in v2) #7253

Closed keeffEoghan closed 2 years ago

keeffEoghan commented 3 years ago

🐛 bug report

Parcel v2 fails to correctly resolve glslify nested vendor dependencies (from e.g: node_modules).

This behaviour worked correctly in v1.

However, the @parcel/transformer-glsl introduced in #3352 fails to correctly resolve nested dependencies in vendor dependencies.

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

For both versions, the initial configuration is:

package.json (devDependencies differ with each setup)

{
  "name": "parcel-glsl-test",
  "packageManager": "yarn@3.1.0",
  "engines": { "node": "v16.13.0", "yarn": "3.1.0" },
  "scripts": { "start": "parcel ./src/index.html" },
  "dependencies": { "glsl-easings": "^1.0.0", "typescript": "^4.4.4" },
  "devDependencies": {}
}

src/index.js

import frag from './frag.glsl';

console.log(frag);

src/frag.glsl

precision mediump float;

// Import via project directory with further nested imports - works.
#pragma glslify: f = require('./f');

varying float x;

void main() { gl_FragColor = vec4(f(x)); }

src/f.glsl

// Import via node_modules without further nested imports - works.
#pragma glslify: e0 = require('glsl-easings/back-in');

// Import via node_modules with further nested imports - fails.
#pragma glslify: e1 = require('glsl-easings/bounce-in');

// Import via project directory without further nested imports - works.
#pragma glslify: b = require('./a/b');

float f(float x) { return e0(e1(b(x))); }

#pragma glslify: export(f);

src/a/b.glsl

float b(float p) { return p*2.0; }

#pragma glslify: export(b);

.yarnrc.yml (does not use PnP mode)

yarnPath: .yarn/releases/yarn-3.1.0.cjs
nodeLinker: node-modules

See also glsl-easings/back-in.glsl, which has no nested imports.

See also glsl-easings/bounce-in.glsl, which has a further nested import.

Each setup also has a slightly different src/index.html entry-point, as shown in the below sections.

🤔 Expected Behavior

Parcel glslify transform should correctly resolve all nested dependencies, including those in vendor (e.g: node_modules) directories, to produce built GLSL code. The Parcel v1 (parcel-bundler@1.12.5) glslify transform behaves correctly for nested vendor dependencies.

Installed and run with:

yarn add -D --mode=skip-build parcel-bundler@1.12.5
yarn start

The above code should print correct code similar to this to the browser console:

precision mediump float;
#define GLSLIFY 1

// Import via project directory with further nested imports - works.
// Import via node_modules without further nested imports - works.
#ifndef PI
#define PI 3.141592653589793
#endif

float backIn(float t) {
  return pow(t, 3.0) - t * sin(t * PI);
}

// Import via node_modules with further nested imports - fails.
#ifndef PI
#define PI 3.141592653589793
#endif

float bounceOut(float t) {
  const float a = 4.0 / 11.0;
  const float b = 8.0 / 11.0;
  const float c = 9.0 / 10.0;

  const float ca = 4356.0 / 361.0;
  const float cb = 35442.0 / 1805.0;
  const float cc = 16061.0 / 1805.0;

  float t2 = t * t;

  return t < a
    ? 7.5625 * t2
    : t < b
      ? 9.075 * t2 - 9.9 * t + 3.4
      : t < c
        ? ca * t2 - cb * t + cc
        : 10.8 * t * t - 20.52 * t + 10.72;
}

float bounceIn(float t) {
  return 1.0 - bounceOut(1.0 - t);
}

// Import via project directory without further nested imports - works.
float b_0(float p) { return p*2.0; }

float f(float x) { return backIn(bounceIn(b_0(x))); }

varying float x;

void main() { gl_FragColor = vec4(f(x)); }

This setup adds to the above devDependencies for Parcel v1: package.json

  "devDependencies": { "glslify-bundle": "^5.1.1", "glslify-deps": "^1.3.2", "parcel-bundler": "^1.12.5" }

This setup uses the following HTML entry-point (without script type="module"): src/index.html

<!DOCTYPE html><html><script src="./index.js"></script></html>

😯 Current Behavior

The Parcel v2 (parcel@2.0.0) glslify transform fails to resolve nested vendor dependencies (e.g: node_modules), though it correctly handles nested project dependencies (within the project's own directories).

Installed and run with:

yarn add -D --mode=skip-build parcel@2.0.0
yarn start

The CLI displays an error message, showing paths that seem to be relative to the project directory, not the vendor (e.g: node_modules) directories as would be expected:

yarn start
Server running at http://localhost:1234
🚨 Build failed.

@parcel/core: Failed to resolve './bounce-out' from './src/frag.glsl'

@parcel/resolver-default: Cannot load file './bounce-out' in './src'.

This setup adds to the above devDependencies for Parcel v2: package.json

  "devDependencies": { "@parcel/transformer-glsl": "^2.0.0", "parcel": "^2.0.0" }

This setup uses the following HTML entry-point (with script type="module"): src/index.html

<!DOCTYPE html><html><script src="./index.js" type="module"></script></html>

Note that commenting out this line in src/f.glsl:

// #pragma glslify: e1 = require('glsl-easings/bounce-in');

Causes the build to succeed and the following to be printed to the browser console:

precision mediump float;
#define GLSLIFY 1

// Import via project directory with further nested imports - works.
// Import via node_modules without further nested imports - works.
#ifndef PI
#define PI 3.141592653589793
#endif

float backIn(float t) {
  return pow(t, 3.0) - t * sin(t * PI);
}

// Import via node_modules with further nested imports - fails.
// #pragma glslify: e1 = require('glsl-easings/bounce-in');

// Import via project directory without further nested imports - works.
float b(float p) { return p*2.0; }

float f(float x) { return backIn(e1(b(x))); }

varying float x;

void main() { gl_FragColor = vec4(f(x)); }

This demonstrates that the problem is likely with only nested vendor dependencies, not the project's own local dependencies.

💁 Possible Solution

I expect this probably has something to do with the way vendor paths are resolved in the glslify transform for Parcel v2; I had a look but am not familiar enough with the APIs to attempt a fix:

🔦 Context

This issue has caused me to upgrade to Parcel v2, fail to solve the problem after following misleading error messages, then roll back to v1 - at least 3 times over the last year. It blocks me from migrating any WebGL projects to Parcel v2, which in turn prevents me from benefitting from any of the latest developments and up-to-date support of Parcel v2. It seems that a simple glslify transformer fix here in v2, looking at a correctly-working setup in v1, would solve all these issues.

💻 Code Sample

Included throughout above.

🌍 Your Environment

Included throughout above.

mischnic commented 2 years ago

The reason that glsl-easings appears to be the culprit is the

    "engines" {
        "node": "..."
    }

line which causes Parcel to not bundle node_modules (so that glsl-easings/back-in import in GLSL is also theoretically excluded). So you need to remove that in any case.

Actual bug: https://github.com/parcel-bundler/parcel/pull/7263

keeffEoghan commented 2 years ago

Can confirm this now works correctly in today's v2.0.1 release! Also thanks @mischnic, I learned about the engines etc config today - though it seems that this now works with that field there anyway, doesn't seem to need node_modules to be bundled! (At least for my test)

keeffEoghan commented 2 years ago

Ah, however, it fails with a new error when attempting to run parcel build ... (rather than the dev server parcel ... which works now); the error is:

🚨 Build failed.

@parcel/transformer-glsl: Got unexpected null

  Error: Got unexpected null
  at nullthrows (.../node_modules/nullthrows/nullthrows.js:7:15)
  at resolve (.../node_modules/@parcel/core/lib/Transformation.js:578:100)
  at async Depper.resolve (.../node_modules/@parcel/transformer-glsl/lib/GLSLTransformer.js:72:26)

Since this is only with production build, I tried again with --no-scope-hoist/--no-optimize/--no-content-hash, but both scripts give identical errors; of these 3 scripts only start works:

{
  "scripts": {
    "start": "parcel --log-level verbose ./src/index.html",
    "dev": "parcel build --log-level verbose ./src/index.html --no-scope-hoist --no-optimize --no-content-hash",
    "build": "parcel build --log-level verbose ./src/index.html"
  }
}

Any ideas?

mischnic commented 2 years ago

I only get that with engines.node (which as I've said should be removed)

(But that error message is not ideal)

keeffEoghan commented 2 years ago

Right you are, I got overconfident seeing the start command work with engines, should've rechecked from scratch - all working now, thank you so much :)

Also, adding this to package.json lets you keep the engines field (I use it to indicate what versions of external/global tools I used with a project):

  "targets": {
    "default": {
      "engines": {}
    }
  }