DEV Community

Leopold
Leopold

Posted on • Edited on

Automatize tailwind installation on create-react-app with Node.js

Projects configuration might be complicated and boring.

 
I recently went into the process of learning how to automatize some annoying tasks I often do in my projects using Node.js.

I picked tailwind and create-react-app as an example, but this is mainly about showing Node.js scripting possibilities to save time. The final result is to type :

node myscript.js appname
Enter fullscreen mode Exit fullscreen mode

instead of :

npx create-react-app appname
Enter fullscreen mode Exit fullscreen mode

And our normal CRA is generated with tailwind fully installed according to tailwind documentation.

Before jumping into the next part, I advise you to be comfortable with javascript promises and to have a quick look to node.js fs and child process documentation if you don't know them yet.

 

Script overview :

 
Have a look at the tailwind documentation to see how to install it on a CRA.
https://tailwindcss.com/docs/guides/create-react-app
 
Basically, we want to code each step of the installation process explained in the documentation so our script has to handle these things :

  • execute the npx create-react-app appname command.
  • Install tailwind packages.
  • Install a package called craco.
  • Modify the script section of the package.json file.
  • Create a craco.config.js file at the root of the project.
  • Generate a tailwindcss config file.
  • And finally import tailwind css in our index.css file.

We identified each part of the mission, this is just a simple algorithm to code now.
 

The script :

 

Preparation :

Firstly create a javascript file, we want to convert the above steps in js code.
Child process exec method allows us to execute commands and fs to manipulate files.
Sadly their methods work with a callback so we do a quick adaptation to be able to use them with async/await thanks to the "util" module..
Include at the top of your script the following import :

const util = require('util');
const { exec } = util.promisify(require('child_process'));
const { readFile, writeFile, appendFile } = util.promisify(require('fs'));
Enter fullscreen mode Exit fullscreen mode

 

Code :

  • Execute the npx create-react-app appname command :
const installation = async () => {
    await exec(`npx create-react-app ${process.argv[2]}`); 
}
Enter fullscreen mode Exit fullscreen mode

Installation is the main function of our script, and we execute our first command here !
process.argv[2] is the argument given when we launch the script such as :

 node index.js appname
Enter fullscreen mode Exit fullscreen mode

Here process.argv[2] is equal to "appname".

  • The next step is to install packages :
const installation = async () => {
    await exec(`npx create-react-app ${process.argv[2]}`);
    await exec(`cd ${process.argv[2]} && npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9 && npm install @craco/craco`)
}
Enter fullscreen mode Exit fullscreen mode
  • Now we need to get the package.json file to modify the script section including craco script as mentioned in the tailwind doc. At this point the creacte-react-app has been generated and so we have to change our location to be inside the project and import package.json to manipulate it as a javascript object, finally we overwrite the file with the new modification. This is where the fs module come to help us !

We create a new function in charge of doing the package.json modification :

const modifyPackageJson = async () => {
  const data = await readFile(`${process.argv[2]}/package.json`);
  packageJson = JSON.parse(data);
  packageJson.scripts = {
    start: 'craco start',
    build: 'craco build',
    test: 'craco test',
    eject: 'react-scripts eject',
  };
  await writeFile(`${process.argv[2]}/package.json`, JSON.stringify(packageJson));
};
Enter fullscreen mode Exit fullscreen mode
  • Create the craco.config.js file. We will proceed in the same way, create a new function and write the file with the require data :
const addCracoConfig = async () => {
  const cracoConfig = `module.exports = {
    style: {
        postcss: {
            plugins: [
                require('tailwindcss'),
                require('autoprefixer'),
            ],
        },
    },
}`;
  await writeFile(`${process.argv[2]}/craco.config.js`, cracoConfig);
};
Enter fullscreen mode Exit fullscreen mode

Add the 2 last functions we created to the main function :

const installation = async () => {
    await exec(`npx create-react-app ${process.argv[2]}`);
    await exec(`npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9 && npm install @craco/craco`)
    await modifyPackageJson();
    await addCracoConfig();
}
Enter fullscreen mode Exit fullscreen mode
  • Generate the tailwindcss config file :
const installation = async () => {
    await exec(`npx create-react-app ${process.argv[2]}`);
    await exec(`npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9 && npm install @craco/craco`)
    await modifyPackageJson();
    await addCracoConfig();
    await exec(`cd ${process.argv[2]} && npx tailwindcss init`);
}
Enter fullscreen mode Exit fullscreen mode
  • Finally we need to include tailwind in the index.css. Again we use fs but we need to write the new code at the top of the index.css file. So this is going to be a read->write->append operation.

I create a new function called "includeTailwindCss" :

const includeTailwindCss= async () => {
  const tailwindImport = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n`;
  const data = await readFile(`${process.argv[2]}/src/index.css`);
  await writeFile(`${process.argv[2]}/src/index.css`, tailwindImport);
  await appendFile(`${process.argv[2]}/src/index.css`, data);
};
Enter fullscreen mode Exit fullscreen mode

We extract the index.css code in a data variable so that we can overwrite index.css with the new data. Then we append to the index.css the old data stored in the data variable, in this way the tailwind import lines are at the top of our css file.

Add this last function to our installation function and I think we are good now !

The final code should looks like this :

const util = require('util');
const exec = util.promisify(require('child_process').exec);
const readFile = util.promisify(require('fs').readFile);
const writeFile = util.promisify(require('fs').writeFile);
const appendFile = util.promisify(require('fs').appendFile);

const installation = async () => {
  await exec(`npx create-react-app ${process.argv[2]}`);
  await exec(`cd ${process.argv[2]} && npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9 && npm install @craco/craco`);
  await modifyPackageJson();
  await addCracoConfig();
  await exec(`cd ${process.argv[2]} && npx tailwindcss init`);
  await includeTailwindCss();
};

const modifyPackageJson = async () => {
  const data = await readFile(`${process.argv[2]}/package.json`);
  packageJson = JSON.parse(data);
  packageJson.scripts = {
    start: 'craco start',
    build: 'craco build',
    test: 'craco test',
    eject: 'react-scripts eject',
  };
  await writeFile(`${process.argv[2]}/package.json`, JSON.stringify(packageJson));
};

const addCracoConfig = async () => {
  const cracoConfig = `module.exports = {
    style: {
        postcss: {
            plugins: [
                require('tailwindcss'),
                require('autoprefixer'),
            ],
        },
    },
}`;
  await writeFile(`${process.argv[2]}/craco.config.js`, cracoConfig);
};

const includeTailwindCss = async () => {
  const tailwindImport = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n`;
  const data = await readFile(`${process.argv[2]}/src/index.css`);
  await writeFile(`${process.argv[2]}/src/index.css`, tailwindImport);
  await appendFile(`${process.argv[2]}/src/index.css`, data);
};

installation();
Enter fullscreen mode Exit fullscreen mode

Now launch your script in the folder where you want to create your react-app :

node myscript.js appname
Enter fullscreen mode Exit fullscreen mode

And that's all !

Finally you can go further and add real command logs results using node.js EventEmitter, errors handling, publish your script as a npm package etc. This was just an example with tailwind but there are a lot of possibilities everywhere and things to improve.

We can always find things to optimize.

What do you think about my method ? Have you a cleaner way to do this ?

Thanks for reading and have a good day.

Top comments (0)