Relative imports from tailwind.config.js result into errors with CLI
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.
Read the code now at https://github.com/muhammadsammy/tailwindcss-classnames/blob/44b0cca7de60fe0816d025ce79df59d68506e2c9/src/cli/core/GeneratedFileWriter.ts#L90. A few options that came to my mind:
- ~~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
- Write the content to temporary file, which is located at the same dir as original tailwind config. Remove this file after type generation
- 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.
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.
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?
Btw why don't you use
./tailwind.config.jsconfig path as a default?
I'm not sure if I understand this correctly, could you please elaborate more?
Btw why don't you use
./tailwind.config.jsconfig 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.
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!
@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?
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"
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"
}
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
> [email protected] 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 [email protected]
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
Thanks for trying! Would you share your tailwind.config.js file so I can debug this error?
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 ;)
It should be fixed now by https://github.com/muhammadsammy/tailwindcss-classnames/commit/0e401e3f0633282e19dfc5fe6274538072719a7a. Please update to the latest version (2.0.7)
It works, thanks!
Thank you for checking!