laravel / vite-plugin

Laravel plugin for Vite.
MIT License
798 stars 150 forks source link

Build fails with `ENOTDIR` on fresh Laravel 10 install with recent NodeJS Docker images #247

Closed michaelk83 closed 1 year ago

michaelk83 commented 1 year ago

Overview

Not sure if this is a Laravel configuration issue or a Vite bug. On a fresh install of Laravel 10 (default configuration) and using recent NodeJS images (NodeJS >= 18.x), npm run build fails with ENOTDIR.

ViteJS bug report: https://github.com/vitejs/vite/issues/14457

System Info

Kubuntu 20.04, PHP 8.2.8, Composer 2.5.8, Laravel installer 5.1.1; Laravel 10.24.0, Vite 4.4.9, laravel-vite-plugin 0.8.0; Fails with NodeJS 20.7.0 and 18.18.0; Works with NodeJS 16.20.2 (see steps to reproduce). NPM 9.6.7 and others (doesn't seem to matter, see steps to reproduce).

Web browser: not applicable. Running in Sail? No

Steps To Reproduce

// Setup:
podman pull docker.io/library/node:20-bullseye-slim    // Or probably same with Docker
podman pull docker.io/library/node:18-bullseye-slim
podman pull docker.io/library/node:16-bullseye-slim
composer global require laravel/installer
laravel new test-vite
   // Select the default options: no starter kit, PHPunit, no git
cd test-vite
podman run -it --rm -v .:/mnt:z -w /mnt node:20-bullseye-slim npm install

// Testing:
podman run -it --rm -v .:/mnt:z -w /mnt node:20-bullseye-slim npm run build    // FAILS. node 20.7.0, npm 10.1.0
podman run -it --rm -v .:/mnt:z -w /mnt node:18-bullseye-slim npm run build    // FAILS. node 18.18.0, npm 9.8.1
podman run -it --rm -v .:/mnt:z -w /mnt node:16-bullseye-slim npm run build    // WORKS. node 16.20.2, npm 8.19.4
// Also works with node 16.20.2, npm 9.6.7 (from a previously installed global `npm_modules` volume).

Logs

> build
> vite build

vite v4.4.9 building for production...
transforming (1) resources/js/app.jsnode:internal/process/promises:289
            triggerUncaughtException(err, true /* fromPromise */);
            ^

[Error: ENOTDIR: not a directory, scandir '/mnt/vendor/phpunit/phpunit/phpunit'] {
  errno: -20,
  code: 'ENOTDIR',
  syscall: 'scandir',
  path: '/mnt/vendor/phpunit/phpunit/phpunit'
}

Node.js v20.7.0
// Same with Node.js v18.18.0

vendor/phpunit/phpunit/phpunit does exist, but is a PHP executable. I don't know why Vite is trying to read it.

jessarcher commented 1 year ago

Hi @michaelk83,

I've tested this with the Node versions mentioned using nvm (i.e. without podman), and it works fine.

I wasn't able to replicate your podman instructions. When trying to npm install I get the following error:

image

I've never had much luck with podman and permissions. It won't let me read anything in the mounted volume:

image

However, if I use the z flag on the mount, then it all works for me:

image

Tested on Fedora 38 with podman 4.6.2.

michaelk83 commented 1 year ago

Hi, thanks for looking into this!

The permission problem you ran into appears to be from SElinux. Adding the z mount flag tells podman to relabel the mount to bypass the SElinux restrictions. There's no SElinux on Ubuntu (it defaults to AppArmor AFAIK), so I don't have this issue.

I have podman 3.4.2, but I don't think that that should make any difference...

@sapphi-red at vitejs was also not able to reproduce the build error on Docker + WSL. I suppose if this was happening to everybody, you would've run into it yourselves by now. I posted a debug log on the vitejs issue, but it doesn't seem very helpful.

This is very strange. I'm getting the build error with the pristine Laravel installation. I haven't changed any configuration or touched any files. If you pulled the NodeJS image in the last day or two, it should be the same node image as well, with all the same stuff inside it... I've just retried the instructions again, and got the build error as reported. Adding the z flag makes no difference for me.

The specific node image hash that I have is: docker.io/library/node@sha256:14a0a001f3d65c8594d6adec9f9fa76ec5d36f07d279c9004c78d929eca198ed. You should see the same in the RepoDigests portion of the podman inspect node:20-bullseye-slim output.

Can you confirm that your Laravel, vite, and node versions are the same as what I listed? Any idea what else might be causing this, and how I can debug this further?

michaelk83 commented 1 year ago

I've just renamed vendor/phpunit/phpunit/phpunit to vendor/phpunit/phpunit/phpunit2, and now the build succeeds. If I rename it back as it was, the error returns. The question is, why is Vite trying to read vendor/phpunit/phpunit/phpunit? @sapphi-red

jessarcher commented 1 year ago

This is my node image hash - seems the same, although not sure why I have two.

"RepoDigests": [
     "docker.io/library/node@sha256:14a0a001f3d65c8594d6adec9f9fa76ec5d36f07d279c9004c78d929eca198ed",
     "docker.io/library/node@sha256:86ed0f70880231adc0fb66c2edbba5de350d8587999e2fe4e1f59c11a4cbb3b4"
],

Same Laravel, Vite, and Node versions as you mentioned with a clean install of Laravel following your reproduction steps.

Very strange that just renaming the phpunit file fixed it. I was wondering if it was a symlink or maybe the executable permission was causing it, but that would be the same after renaming.

Does it happen if you change refresh to false in your vite.config.js file?

Are you able to try removing the laravel-vite-plugin and configuring Vite manually? Most of the plugin features are for dev mode rather than build mode, so you should be able to set up a pretty minimal config something like this on a clean Laravel install:

import { defineConfig } from 'vite';

export default defineConfig({
    publicDir: false,
    build: {
        manifest: true,
        outDir: 'public/build',
        rollupOptions: {
            input: ['resources/css/app.css', 'resources/js/app.js'],
        },
    },
});
michaelk83 commented 1 year ago

My RepoDigests is the same. I think the 1st one is the compressed hash in the remote repo, and the 2nd one is the uncompressed hash.

The fact that renaming the file fixes the build suggests to me that Vite may be trying to scan that specific path. But I have no idea why. When it's renamed, it probably just doesn't find it and ignores it.

refresh: false doesn't fix it. Removing laravel-vite-plugin doesn't fix it, even removing app.js:

import { defineConfig } from 'vite';

export default defineConfig({
    publicDir: false,
    build: {
        manifest: true,
        outDir: 'public/build',
        rollupOptions: {
            //input: ['resources/css/app.css', 'resources/js/app.js'],
            input: ['resources/css/app.css'],
        },
    },
});

So this looks like an internal Vite bug. But for some reason it's not reproducible by the devs. I don't know why my setup behaves differently. There doesn't seem to be any difference except the runtime environment.

jessarcher commented 1 year ago

Super weird! Have you tried shelling into the container and looking at the directory listing (e.g. ls -lash vendor/phpunit/phpunit) and checking whether you can cat the file.

You could also try messing around with the file. E.g. change its permissions (make it non-executable, etc), and maybe try replacing it with an empty file and then an empty directory.

I can't imagine Vite cares about that specific file name, but maybe it's looking for paths ending in unit? Does it fail if you rename it to foophpunit or even foounit? It wouldn't explain why I can't replicate it, but maybe something about your environment is causing that file to present as a directory instead of a file? Grasping at straws here.

Going to close this issue for now as it doesn't seem caused by this plugin, but feel free to keep posting in case it helps others. I'd love to know if you get any further with it!

michaelk83 commented 1 year ago

I did notice that if I disable app.js as above, the error output is just a bit different:

vite v4.4.9 building for production...
node:internal/process/promises:289
            triggerUncaughtException(err, true /* fromPromise */);
...

(Instead of transforming (1) resources/js/app.jsnode:internal/process/promises:289 ...)

I can read the file from both the host and the container. It's an executable PHP script. Other tests:

So it looks like it's thrown off by the nesting of any file that's named the same as its parent folder. And it only happens with node >= 18.x. Node 16.x works fine.

jessarcher commented 1 year ago

So it looks like it's thrown off by the nesting of any file that's named the same as its parent folder. And it only happens with node >= 18.x. Node 16.x works fine.

Interesting! Great catch! Still really strange that it only happens in your environment, but at least you've found an explanation for why a PHP script seemed to be breaking something in Node/Vite.