Running the EJS CLI on an entire directory recursively requires a shell loop because the tool processes single files. You can use find to locate all template files and pipe them into ejs. This approach ensures every file in subdirectories is processed without manual intervention.
Understanding the Limitation
Theejs command-line interface is designed to compile a single input file into a single output file. It does not have a built-in flag like --recursive or -r to traverse directories automatically. When you attempt to pass a directory path directly to ejs, it typically fails with an error indicating that the input must be a file. This limitation exists because the CLI expects a clear one-to-one mapping between source templates and destination HTML files, handling local variables and partials for that specific context.
Attempting to run npx ejs ./src/views will result in an immediate failure. The tool cannot infer how to name the output files when multiple inputs are involved. Therefore, the solution relies on external shell commands to iterate through the directory structure and invoke the EJS compiler for each individual file found.
The Shell Loop Solution
The most robust method involves using thefind utility combined with a while loop. This technique works across Linux, macOS, and Windows environments using Git Bash or WSL. The process identifies all files ending in .ejs and processes them sequentially.
First, ensure your project structure has a source directory containing templates and a destination directory for the compiled output. For example, let templates be the source and dist be the target.
Here is the standard command sequence:
find ./templates -name "*.ejs" | while read -r filePath; do
outPath="${filePath%.ejs}.html"
mkdir -p "$(dirname "$outPath")"
npx ejs "$filePath" -o "$outPath"
done
This script performs several critical steps. The find command searches recursively for any file with the .ejs extension. The while read loop processes each line of output individually. Inside the loop, we calculate the output path by replacing the .ejs extension with .html. The mkdir -p command ensures that the directory structure for the output file exists before attempting to write to it. Finally, npx ejs compiles the specific file to the calculated output location.
If you encounter issues with spaces in filenames, this method handles them correctly because read -r preserves the string integrity. Avoid using xargs for this task unless you carefully handle quoting, as xargs can break on filenames containing special characters if not configured properly.
Handling Variables and Data
When running EJS in a batch mode via a loop, you may need to pass data to the templates. The EJS CLI accepts data via-e (local variable) or -i (input JSON file). If all templates share the same global data, you can pass a JSON file to every iteration.
find ./templates -name "*.ejs" | while read -r filePath; do
outPath="${filePath%.ejs}.html"
mkdir -p "$(dirname "$outPath")"
npx ejs "$filePath" -i data.json -o "$outPath"
done
This approach is efficient if your site configuration or user data remains constant across all generated pages. However, if different templates require different data contexts, you must modify the loop logic to determine which data file corresponds to which template. This might involve naming conventions, such as storing data.json next to the corresponding .ejs file, and reading it dynamically within the loop.
For dynamic per-file data, you can check for the existence of a sibling JSON file:
find ./templates -name "*.ejs" | while read -r filePath; do
outPath="${filePath%.ejs}.html"
dataFile="${filePath%.ejs}.json"
mkdir -p "$(dirname "$outPath")"
if [ -f "$dataFile" ]; then
npx ejs "$filePath" -i "$dataFile" -o "$outPath"
else
npx ejs "$filePath" -o "$outPath"
fi
done
This conditional logic ensures that only templates with associated data files receive extra variables, preventing errors from missing data references in simple static pages.
Alternative Approaches
While the shell loop is lightweight and requires no additional dependencies, some developers prefer dedicated build tools. Tools like Gulp, Grunt, or Webpack have plugins specifically designed for EJS processing. These tools offer more advanced features such as watching for changes, minifying output, and managing complex asset pipelines. For instance, a Gulp setup can watch thetemplates directory and automatically recompile changed files. This is superior for development workflows where frequent regeneration is necessary. However, for simple deployment scripts or CI/CD pipelines, the shell-based approach remains preferable due to its simplicity and lack of runtime overhead.
If you are working in a Node.js environment extensively, consider using the EJS programmatic API instead of the CLI. You can write a small Node script that uses fs and path modules to achieve the same recursive compilation with greater control over error handling and custom filters.
For more information on navigating directory structures efficiently, see our guide on Finding Directories with Recursive Subdirectories that Don't Overlap in Linux Bash: A Printer-Friendly Guide. This resource provides deeper insights into using find for complex file operations.
Troubleshooting Common Errors
One common issue is permission denied errors when writing to the output directory. Ensure the user running the script has write access to the destination folder. Another frequent error occurs when the output path exceeds the maximum path length on certain operating systems. Keeping directory structures shallow helps mitigate this. If the script hangs or produces empty files, verify that the.ejs files are valid syntax. The EJS CLI will fail silently or produce empty output if there are unclosed tags or invalid expressions. Use npx ejs --help to review available flags and ensure you are passing arguments in the correct order.
Additionally, if you are using Windows Command Prompt instead of PowerShell or Bash, the syntax for loops and variable expansion differs significantly. The provided examples assume a POSIX-compliant shell. For Windows CMD, you would need to use a for /f loop with different variable syntax, which is less readable and more prone to errors.
Frequently Asked Questions
Can I use npm scripts to run this recursively?
You can wrap the find command in a package.json script tag. Define a script like "build:views" that contains the full find loop. This allows you to trigger the build with npm run build:views. This keeps your build process portable and documented within the project.
Does this work with Nunjucks or Handlebars?
Yes, the shell loop pattern is agnostic to the templating engine. Replace npx ejs with npx nunjucks-precompile or your preferred CLI tool. Just ensure the output extension and argument flags match the specific tool's requirements.
How do I handle partials in recursive compilation?
Partials are resolved relative to the current file's directory during compilation. As long as your partial paths are relative or correctly registered in a global config passed via -i, the recursive loop will handle them correctly. Ensure partials do not depend on variables defined only in the parent template.
Is there a risk of infinite loops with symlinks?
Yes, if your directory structure contains symbolic links pointing back to parent directories, find might enter an infinite loop. Use the -not -type l option in your find command or rely on the default behavior of most modern find implementations which do not follow symlinks by default unless specified.
Why are my output files empty?
Empty outputs usually indicate a syntax error in the EJS template or a failed variable interpolation that resolves to nothing. Check the console output for warnings. Also, verify that the source file actually contains content after the EJS tags are evaluated.