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

Relative imports from tailwind.config.js result into errors with CLI #120

Open
aivenkimmob opened this issue Jan 12, 2021 · 16 comments · Fixed by #170
Open

Relative imports from tailwind.config.js result into errors with CLI #120

aivenkimmob opened this issue Jan 12, 2021 · 16 comments · Fixed by #170
Assignees
Labels
bug Something isn't working

Comments

@aivenkimmob
Copy link

aivenkimmob commented Jan 12, 2021

We have design tokens specified in a separate tokens.json (located in the same directory relative to tailwind.config.js) file, which is populated automatically from Figma designs. This data is imported in our tailwind.config.js:

const tokens = require('./tokens.json');

When running tailwindcss-classnames command, this error happens:

? Tailwind configuration filename tailwind.config.js
? Name of the file with generated types tailwindcss-classnames.ts
? Name or path of the file with the custom types 

(node:203572) UnhandledPromiseRejectionWarning: Error: Cannot find module './tokens.json'
Require stack:
- /home/kimmob/code/design-system/node_modules/tailwindcss-classnames/lib/cli/core/GeneratedFileWriter.js
- /home/kimmob/code/design-system/node_modules/tailwindcss-classnames/lib/cli/index.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:966:15)
    at Function.Module._load (internal/modules/cjs/loader.js:842:27)
    at Module.require (internal/modules/cjs/loader.js:1026:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at eval (eval at GeneratedFileWriter.generateFileContent (/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/lib/cli/core/GeneratedFileWriter.js:84:88), <anonymous>:4:16)
    at GeneratedFileWriter.generateFileContent (/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/lib/cli/core/GeneratedFileWriter.js:84:88)
    at GeneratedFileWriter.<anonymous> (/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/lib/cli/core/GeneratedFileWriter.js:44:81)
    at step (/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/node_modules/tslib/tslib.js:143:27)
    at Object.next (/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/node_modules/tslib/tslib.js:124:57)
    at fulfilled (/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/node_modules/tslib/tslib.js:114:62)
(node:203572) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)

I replaced the node require import with direct file reading:

const tokens = JSON.parse(fs.readFileSync(`${__dirname}/tokens.json`, { encoding: 'utf8' }))

but it doesn't help, this error is thrown instead:

? Tailwind configuration filename tailwind.config.js
? Name of the file with generated types tailwindcss-classnames.ts
? Name or path of the file with the custom types 

(node:205483) UnhandledPromiseRejectionWarning: Error: ENOENT: no such file or directory, open '/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/lib/cli/core/tokens.json'
    at Object.openSync (fs.js:458:3)
    at Object.readFileSync (fs.js:360:35)
    at eval (eval at GeneratedFileWriter.generateFileContent (/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/lib/cli/core/GeneratedFileWriter.js:84:88), <anonymous>:7:30)
    at GeneratedFileWriter.generateFileContent (/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/lib/cli/core/GeneratedFileWriter.js:84:88)
    at GeneratedFileWriter.<anonymous> (/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/lib/cli/core/GeneratedFileWriter.js:44:81)
    at step (/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/node_modules/tslib/tslib.js:143:27)
    at Object.next (/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/node_modules/tslib/tslib.js:124:57)
    at fulfilled (/home/kimmob/code/design-system/node_modules/tailwindcss-classnames/node_modules/tslib/tslib.js:114:62)
(node:205483) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:205483) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Looks like all relative imports or file reads from tailwind.config.js are going to break, since the GeneratedFileWriter resolves file paths relative to its own path (node_modules/tailwindcss-classnames/lib/cli/core) for generation purposes.

@aivenkimmob
Copy link
Author

aivenkimmob commented Jan 12, 2021

Read the code now at

eval(this._configFileData.replace(/(['"])?plugins(['"])? *: *\[(.*|\n)*?],?/g, '')),
. A few options that came to my mind:

  1. Set the working directory before running eval. EDIT: I tried this but it doesn't affect how require works, as it's relative to the GeneratedFileWriter.js file
  2. Write the content to temporary file, which is located at the same dir as original tailwind config. Remove this file after type generation
  3. Use something else than eval, where the module context can be manipulated. Node has some seemingly related helpers but didn't yet find out if they allow this https://nodejs.org/api/vm.html#vm_new_vm_script_code_options

The eval trick seems to be quite fragile, relying on certain format ( eval(this._configFileData.replace(/(['"])?plugins(['"])? *: *\[(.*|\n)*?],?/g, '')),). The config file can be any Javascript, so the code should rather look into the exported data, by importing the tailwind config as a JS module, rather than analyzing and transforming the textual representation of code.

If textual representation is relied on, a proper e.g. esprima parser should be used.

@muhammadsammy muhammadsammy added the bug Something isn't working label Jan 13, 2021
@muhammadsammy
Copy link
Owner

muhammadsammy commented Jan 13, 2021

Thanks for reporting and the suggestions!

The eval trick seems to be quite fragile, relying on certain format ( eval(this._configFileData.replace(/(['"])?plugins(['"])? *: *\[(.*|\n)*?],?/g, '')),). The config file can be any Javascript, so the code should rather look into the exported data, by importing the tailwind config as a JS module, rather than analyzing and transforming the textual representation of code.

If textual representation is relied on, a proper e.g. esprima parser should be used.

Totally agree. Using node's vm module seems like a great option that may prevent other potential issues with using eval too.

A workaround that might work is to publish the tokens to npm or to add its contents directly to the tailwindconfig file by a script, but this would not be an acceptable solution.

@erictheswift
Copy link

I'd suggest looking how tailwind cli itself handles imports like these before reinventing the wheel.
I've caught the same issue — npx tailwindcss cli works ok, npx tailwindcss-classnames fails.

Btw why don't you use ./tailwind.config.js config path as a default?

@muhammadsammy
Copy link
Owner

Btw why don't you use ./tailwind.config.js config path as a default?

I'm not sure if I understand this correctly, could you please elaborate more?

@erictheswift
Copy link

Btw why don't you use ./tailwind.config.js config path as a default?

I'm not sure if I understand this correctly, could you please elaborate more?

./tailwind.config.js in your package root is kinda 'default' path for config that works by a convention.
By default tailwindcss CLI works even if you don't specify config path override. tailwindcss-classnames — doesn't (requires explicitly setting config path)

I think it's quite useful for CLI user to have reasonable defaults — assuming most of us use default convention based configurations.

@muhammadsammy
Copy link
Owner

It's set as the default for the prompt only. Maybe instead of falling back to the prompt if it's not supplied, CLI should prompt only if the tailwind.config.js was not found. I opened #144 to track it. Thanks for the suggestions!

@muhammadsammy muhammadsammy self-assigned this Feb 23, 2021
muhammadsammy added a commit that referenced this issue Feb 23, 2021
Fixes #120
This is a workaround for errors from relative imports inside the config
by using the `__dirname` global in imports
@muhammadsammy muhammadsammy linked a pull request Feb 24, 2021 that will close this issue
@muhammadsammy muhammadsammy reopened this Feb 24, 2021
@muhammadsammy
Copy link
Owner

@aivenkimmob @erictheswift

I replaced the node require import with direct file reading:

const tokens = JSON.parse(fs.readFileSync(`${__dirname}/tokens.json`, { encoding: 'utf8' }))

I managed to make a workaround by setting the __dirname global in vm to the dirname of the config file https://github.com/muhammadsammy/tailwindcss-classnames/pull/170/files#diff-d6758a93ce6f7a10e3692442b3e03f7f84afe3031cf3bdb3ebbeb5f3c114af9aR75

This was released in v2.0.6. Could you confirm it to be working?

@erictheswift
Copy link

npx tailwindcss-classnames -i app/styles/index.css
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "ReferenceError: process is not defined".] {
  code: 'ERR_UNHANDLED_REJECTION'
}
npm ERR! code 1
npm ERR! command failed
npm ERR! command sh -c tailwindcss-classnames "-i" "app/styles/index.css"

@muhammadsammy
Copy link
Owner

npx tailwindcss-classnames -i app/styles/index.css

You should pass the tailwind.config.js file after the -i flag.

However, I noticed that running with npx does not work anymore, but the CLI works if tailwindcss-classnames is installed locally and added in an npm script in package.json file:
npm run generate-css-types

"scripts": {
  "generate-css-types": "tailwindcss-classnames -i path/to/tailwind.config.js"
}

@erictheswift
Copy link

Still the same result when executing via npm run x.
Error reporting is not clear enough (swallows details) to understand what is going on without digging into sources

npm run build-tailwindcss-classnames
> processapp_frontend@1.0.1 build-tailwindcss-classnames
> tailwindcss-classnames -i tailwind.config.js
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "ReferenceError: process is not defined".] {
  code: 'ERR_UNHANDLED_REJECTION'
}
npm ERR! code 1
npm ERR! path ~/Dev/minefield
npm ERR! command failed
npm ERR! command sh -c tailwindcss-classnames -i tailwind.config.js

22 verbose stack Error: command failed
22 verbose stack     at ChildProcess.<anonymous> (~/.n/lib/node_modules/npm/node_modules/@npmcli/promise-spawn/index.js:64:27)
22 verbose stack     at ChildProcess.emit (node:events:378:20)
22 verbose stack     at maybeClose (node:internal/child_process:1067:16)
22 verbose stack     at Process.ChildProcess._handle.onexit (node:internal/child_process:301:5)
23 verbose pkgid processapp_frontend@1.0.1
24 verbose cwd ~/Dev/minefield
25 verbose Darwin 20.3.0
26 verbose argv "~/.n/bin/node" "~/.n/bin/npm" "run" "build-tailwindcss-classnames"
27 verbose node v15.9.0
28 verbose npm  v7.5.3
29 error code 1
30 error path ~/Dev/minefield
31 error command failed
32 error command sh -c tailwindcss-classnames -i tailwind.config.js
33 verbose exit 1

@muhammadsammy
Copy link
Owner

Thanks for trying!
Would you share your tailwind.config.js file so I can debug this error?

@erictheswift
Copy link

Ah yep that appears to be mine: config part referencing process.env seems to be the reason.
BTW in my case it's impossible to configure tailwind correctly without reading env vars. Possibly this should be fixed for me if you provide parent context global-s (at least process) in nested vm context object ;)

@muhammadsammy
Copy link
Owner

muhammadsammy commented Feb 25, 2021

It should be fixed now by 0e401e3. Please update to the latest version (2.0.7)

@erictheswift
Copy link

It works, thanks!

@muhammadsammy
Copy link
Owner

Thank you for checking!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants
@erictheswift @muhammadsammy @aivenkimmob and others