tailwindlabs / tailwindcss-forms

A plugin that provides a basic reset for form styles that makes form elements easy to override with utilities.
https://tailwindcss-forms.vercel.app
MIT License
4.22k stars 223 forks source link

"MODULE_NOT_FOUND" if tailwind config is in another directory #124

Closed saites closed 2 years ago

saites commented 2 years ago

What version of @tailwindcss/forms are you using?

v0.5.2

What version of Node.js are you using?

v18.6.0 (via the official node:18 Docker image)

What browser are you using?

N/A

What operating system are you using?

Debian Bullseye container, OpenSUSE host

Reproduction repository

https://github.com/saites/issue-tailwindcss-forms-non-local-config/

Describe your issue

If my config is not in the same directory, but specified with --config, this fails with Cannot find module @tailwindcss/forms. I don't know if this is an issue with the plugin or with tailwindcss. See the example repo for a minimal example, which runs using Docker.

The only difference between the working and failing example is that the failing example uses a non-local config file, whereas the working on uses a config file in the same directory:

[19:42]> diff works.sh fails.sh 
8c8
<        --config /app/tailwind.config.js \
---
>        --config /code/tailwind.config.js \

The actual config files are identical (see the Dockerfile).

Running ./works.sh works:

[19:43]> ./works.sh 
Sending build context to Docker daemon  49.66kB
Step 1/6 : FROM node:18
 ---> 7e9550136fca
Step 2/6 : RUN mkdir /app /code
 ---> Using cache
 ---> f1794cde37ab
Step 3/6 : COPY tailwind.config.js main.css index.html /code/
 ---> Using cache
 ---> 1684c2b33b98
Step 4/6 : RUN cp /code/tailwind.config.js /app/tailwind.config.js
 ---> Using cache
 ---> d160a106acaa
Step 5/6 : WORKDIR /app
 ---> Using cache
 ---> d8443c56b8bf
Step 6/6 : RUN npm install -D tailwindcss @tailwindcss/forms
 ---> Using cache
 ---> 5f7f417804ca
Successfully built 5f7f417804ca
Successfully tagged local/tailwindcss:latest

Done in 110ms.

but ./fails.sh fails with Error: Cannot find module '@tailwindcss/forms':

[19:46]> ./fails.sh 
Sending build context to Docker daemon  73.73kB
Step 1/6 : FROM node:18
 ---> 7e9550136fca
Step 2/6 : RUN mkdir /app /code
 ---> Using cache
 ---> f1794cde37ab
Step 3/6 : COPY tailwind.config.js main.css index.html /code/
 ---> Using cache
 ---> 1684c2b33b98
Step 4/6 : RUN cp /code/tailwind.config.js /app/tailwind.config.js
 ---> Using cache
 ---> d160a106acaa
Step 5/6 : WORKDIR /app
 ---> Using cache
 ---> d8443c56b8bf
Step 6/6 : RUN npm install -D tailwindcss @tailwindcss/forms
 ---> Using cache
 ---> 5f7f417804ca
Successfully built 5f7f417804ca
Successfully tagged local/tailwindcss:latest
node:internal/modules/cjs/loader:956
  const err = new Error(message);
              ^

Error: Cannot find module '@tailwindcss/forms'
Require stack:
- /code/tailwind.config.js
- /app/node_modules/tailwindcss/lib/cli.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:956:15)
    at Module._load (node:internal/modules/cjs/loader:804:27)
    at Module.require (node:internal/modules/cjs/loader:1022:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/code/tailwind.config.js:8:5)
    at Module._compile (node:internal/modules/cjs/loader:1120:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1174:10)
    at Module.load (node:internal/modules/cjs/loader:998:32)
    at Module._load (node:internal/modules/cjs/loader:839:12)
    at Module.require (node:internal/modules/cjs/loader:1022:19) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '/code/tailwind.config.js',
    '/app/node_modules/tailwindcss/lib/cli.js'
  ]
}

Node.js v18.6.0
saites commented 2 years ago

The problem seems to be that something (either tailwindcss or this plugin) is trying to resolve the import relative the config file path, rather than the current working directory. Both the works.sh and fails.sh script will succeed if I modify the tailwind.config.js file with an absolute path to the node_modules directory, i.e.,

module.exports = { 
  content: ["/code/**/*.{html,js}"],
  theme: {
    extend: {}, 
  },  
  plugins: [
    require('/app/node_modules/@tailwindcss/forms'),
  ],  
}
bradlc commented 2 years ago

Hey @saites. This is just how Node.js module resolution works:

_If the module identifier passed to require() is not a core module, and does not begin with '/', '../', or './', then Node.js starts at the directory of the current module, and adds /node_modules, and attempts to load the module from that location. Node.js will not append node_modules to a path already ending in node_modules._

If it is not found there, then it moves to the parent directory, and so on, until the root of the file system is reached.

The key thing here is that when using require() modules are resolved relative to the current module, not the working directory.

Here is your directory structure:

/app/
├─ node_modules/
│  ├─ @tailwindcss/
│  │  ├─ forms/
/code/
├─ index.html
├─ main.css
├─ tailwind.config.js

The require('@tailwindcss/forms') appears in the /code/tailwind.config.js file, which means that Node.js will look in the following directories for that module:

As you can see, neither of these directories exist so resolution fails and you get the error you are seeing.

It's difficult to give specific recommendations without seeing your actual project, but in your minimal reproduction running npm install in the root directory or /code instead of /app would fix the issue. I hope that helps!

saites commented 2 years ago

Thanks, makes sense. I had a feeling this is a consequence of my unfamiliarity with node. I was thinking of the --config argument as static config, not as an actual script to execute. The resolution makes sense, thinking through what that require must be doing.

My project uses a Rust server to render and serve Handlebars templates, so my only use of Node is to generate this CSS. As a consequence, the example in the repo isn't actually that different from how I'm using the CLI tool -- I'm running it in a Docker container with the relevant directories mounted, and hence, I had the config in a different directory than the original install. I think going forward, I'll probably just bake it into the image.

Thanks again for the help, and the great work with Tailwind.