Load node.js module from string in memory - string

How would I require() a file if I had the file's contents as a string in memory, without writing it out to disk? Here's an example:
// Load the file as a string
var strFileContents = fs.readFileSync( "./myUnalteredModule.js", 'utf8' );
// Do some stuff to the files contents
strFileContents[532] = '6';
// Load it as a node module (how would I do this?)
var loadedModule = require( doMagic(strFileContents) );

function requireFromString(src, filename) {
var Module = module.constructor;
var m = new Module();
m._compile(src, filename);
return m.exports;
}
console.log(requireFromString('module.exports = { test: 1}', ''));
look at _compile, _extensions and _load in module.js

The question is already answered by Andrey, but I ran into a shortcoming that I had to solve and which might be of interest for others.
I wanted the module in the memorized string to be able to load other modules via require, but the module path was broken with the above solution (so e.g. needle wasn't found).
I tried to find an elegant solution to maintain the paths, by using some existing function but I ended up with hard wiring the paths:
function requireFromString(src, filename) {
var m = new module.constructor();
m.paths = module.paths;
m._compile(src, filename);
return m.exports;
}
var codeString = 'var needle = require(\'needle\');\n'
+ '[...]\n'
+ 'exports.myFunc = myFunc;';
var virtMod = requireFromString(codeString);
console.log('Available public functions: '+Object.keys(virtMod));
After that I was able to load all existing modules from the stringified module.
Any comments or better solutions highly appreciated!

The require-from-string package does the job.
Usage:
var requireFromString = require('require-from-string');
requireFromString('module.exports = 1');
//=> 1

After analyzing the source code of such solutions as pirates and require-from-string, I came to the conclusion that a simple mock of fs and Module methods would be no worse in terms of support. And in terms of functionality it will be better, because it supports #babel/register, pirates and other modules that changes the module loading process.
You can try this npm module require-from-memory
import fs from 'fs'
import BuiltinModule from 'module'
const Module = module.constructor.length > 1 ? module.constructor : BuiltinModule
function requireFromString(code, filename) {
if (!filename) {
filename = ''
}
if (typeof filename !== 'string') {
throw new Error(`filename must be a string: ${filename}`)
}
let buffer
function getBuffer() {
if (!buffer) {
buffer = Buffer.from(code, 'utf8')
}
return buffer
}
const now = new Date()
const nowMs = now.getTime()
const size = Buffer.byteLength(code, 'utf8')
const fileStat = {
size,
blksize : 4096,
blocks : Math.ceil(size / 4096),
atimeMs : nowMs,
mtimeMs : nowMs,
ctimeMs : nowMs,
birthtimeMs: nowMs,
atime : now,
mtime : now,
ctime : now,
birthtime : now
}
const resolveFilename = Module._resolveFilename
const readFileSync = fs.readFileSync
const statSync = fs.statSync
try {
Module._resolveFilename = () => {
Module._resolveFilename = resolveFilename
return filename
}
fs.readFileSync = (fname, options, ...other) => {
if (fname === filename) {
console.log(code)
return typeof options === 'string'
? code
: getBuffer()
}
console.log(code)
return readFileSync.apply(fs, [fname, options, ...other])
}
fs.statSync = (fname, ...other) => {
if (fname === filename) {
return fileStat
}
return statSync.apply(fs, [fname, ...other])
}
return require(filename)
} finally {
Module._resolveFilename = resolveFilename
fs.readFileSync = readFileSync
fs.statSync = statSync
}
}

Based on Andrey Sidorov & Dominic solutions, I was saddened by the fact of not being able to require a stringified module then I suggest this version *.
Code:
void function() {
'use strict';
const EXTENSIONS = ['.js', '.json', '.node'];
var Module,
path,
cache,
resolveFilename,
demethodize,
hasOwnProperty,
dirname,
parse,
resolve,
stringify,
virtual;
Module = require('module');
path = require('path');
cache = Module._cache;
resolveFilename = Module._resolveFilename;
dirname = path.dirname;
parse = path.parse;
resolve = path.resolve;
demethodize = Function.bind.bind(Function.call);
hasOwnProperty = demethodize(Object.prototype.hasOwnProperty);
Module._resolveFilename = function(request, parent) {
var filename;
// Pre-resolution
filename = resolve(parse(parent.filename).dir, request);
// Adding extension, if needed
if (EXTENSIONS.indexOf(parse(filename).ext) === -1) {
filename += '.js';
}
// If the module exists or is virtual, return the filename
if (virtual || hasOwnProperty(cache, filename)) {
return filename;
}
// Preserving the native behavior
return resolveFilename.apply(Module, arguments);
};
Module._register = function(request, parent, src) {
var filename,
module;
// Enabling virtual resolution
virtual = true;
filename = Module._resolveFilename(request, parent);
// Disabling virtual resolution
virtual = false;
// Conflicts management
if (hasOwnProperty(cache, filename)) {
error = new Error('Existing module "' + request + '"');
error.code = 'MODULE_EXISTS';
throw error;
}
// Module loading
cache[filename] = module = new Module(filename, parent);
module.filename = filename;
module.paths = Module._nodeModulePaths(dirname(filename));
module._compile(stringify(src), filename);
module.loaded = true;
return module;
};
stringify = function(src) {
// If src is a function, turning to IIFE src
return typeof src === 'function'
? 'void ' + src.toString() + '();'
: src;
};
}();
void function() {
var Module,
parentModule,
child;
Module = require('module');
// Creating a parent module from string
parentModule = Module._register('parent', process.mainModule, `
module.exports = {
name: module.filename,
getChild: function() {
return require('child');
}
};
`);
// Creating a child module from function
Module._register('child', parentModule, function() {
module.exports = {
name: module.filename,
getParent: function() {
return module.parent.exports;
}
};
});
child = require('child');
console.log(child === child.getParent().getChild());
}();
Usage:
void function() {
var Module,
parentModule,
child;
Module = require('module');
// Creating a parent module from string
parentModule = Module._register('parent', process.mainModule, `
module.exports = {
name: module.filename,
getChild: function() {
return require('child');
}
};
`);
// Creating a child module from function
Module._register('child', parentModule, function() {
module.exports = {
name: module.filename,
getParent: function() {
return module.parent.exports;
}
};
});
child = require('child');
console.log(child === child.getParent().getChild());
}();
* as you can see, it contains a function formater which provides a way to create some modules from functions.

I think the better way to approach this would be to have a parameter that you could set afterwards...
such as inside the file: myUnalteredModule.js
exports.setChanges = function( args )...
Then you could do:
var loadedModule = require( 'myUnalteredModule' );
loadedModule

Related

Is there a way in nodejs to get all the files in a directory including files that are nested in folders [duplicate]

I have a little problem with my function. I would like to get all files in many directories. Currently, I can retrieve the files in the file passed in parameters. I would like to retrieve the html files of each folder in the folder passed as a parameter. I will explain if I put in parameter "test" I retrieve the files in "test" but I would like to retrieve "test / 1 / *. Html", "test / 2 / . /.html ":
var srcpath2 = path.join('.', 'diapo', result);
function getDirectories(srcpath2) {
return fs.readdirSync(srcpath2).filter(function (file) {
return fs.statSync(path.join(srcpath2, file)).isDirectory();
});
}
The result :
[1,2,3]
thanks !
It looks like the glob npm package would help you. Here is an example of how to use it:
File hierarchy:
test
├── one.html
└── test-nested
└── two.html
JS code:
const glob = require("glob");
var getDirectories = function (src, callback) {
glob(src + '/**/*', callback);
};
getDirectories('test', function (err, res) {
if (err) {
console.log('Error', err);
} else {
console.log(res);
}
});
which displays:
[ 'test/one.html',
'test/test-nested',
'test/test-nested/two.html' ]
I've seen many very long answers, and it's kinda a waste of memory space. Some also use packages like glob, but if you don't want to depend on any package, here's my solution.
const Path = require("path");
const FS = require("fs");
let Files = [];
function ThroughDirectory(Directory) {
FS.readdirSync(Directory).forEach(File => {
const Absolute = Path.join(Directory, File);
if (FS.statSync(Absolute).isDirectory()) return ThroughDirectory(Absolute);
else return Files.push(Absolute);
});
}
ThroughDirectory("./input/directory/");
It's pretty self-explanatory. There's an input directory, and it iterates through that. If one of the items is also a directory, go through that and so on. If it's a file, add the absolute path to the array.
Hope this helped :]
Using ES6 yield
const fs = require('fs');
const path = require('path');
function *walkSync(dir) {
const files = fs.readdirSync(dir, { withFileTypes: true });
for (const file of files) {
if (file.isDirectory()) {
yield* walkSync(path.join(dir, file.name));
} else {
yield path.join(dir, file.name);
}
}
}
for (const filePath of walkSync(__dirname)) {
console.log(filePath);
}
I really liked Smally's Solution but didn't like the Syntax.
Same solution but slightly easier to read:
const fs = require("fs");
const path = require("path");
let files = [];
const getFilesRecursively = (directory) => {
const filesInDirectory = fs.readdirSync(directory);
for (const file of filesInDirectory) {
const absolute = path.join(directory, file);
if (fs.statSync(absolute).isDirectory()) {
getFilesRecursively(absolute);
} else {
files.push(absolute);
}
}
};
Here's mine. Like all good answers it's hard to understand:
const isDirectory = path => statSync(path).isDirectory();
const getDirectories = path =>
readdirSync(path).map(name => join(path, name)).filter(isDirectory);
const isFile = path => statSync(path).isFile();
const getFiles = path =>
readdirSync(path).map(name => join(path, name)).filter(isFile);
const getFilesRecursively = (path) => {
let dirs = getDirectories(path);
let files = dirs
.map(dir => getFilesRecursively(dir)) // go through each directory
.reduce((a,b) => a.concat(b), []); // map returns a 2d array (array of file arrays) so flatten
return files.concat(getFiles(path));
};
With modern JavaScript (NodeJs 10) you can use async generator function and loop through them using for-await...of
// ES modules syntax that is included by default in NodeJS 14.
// For earlier versions, use `--experimental-modules` flag
import fs from "fs/promises"
// or, without ES modules, use this:
// const fs = require('fs').promises
async function run() {
for await (const file of getFiles()) {
console.log(file.path)
}
}
async function* getFiles(path = `./`) {
const entries = await fs.readdir(path, { withFileTypes: true })
for (let file of entries) {
if (file.isDirectory()) {
yield* getFiles(`${path}${file.name}/`)
} else {
yield { ...file, path: path + file.name }
}
}
}
run()
Packed into library:
https://www.npmjs.com/package/node-recursive-directory
https://github.com/vvmspace/node-recursive-directory
List of files:
const getFiles = require('node-recursive-directory');
(async () => {
const files = await getFiles('/home');
console.log(files);
})()
List of files with parsed data:
const getFiles = require('node-resursive-directory');
(async () => {
const files = await getFiles('/home', true); // add true
console.log(files);
})()
You will get something like that:
[
...,
{
fullpath: '/home/vvm/Downloads/images/Some/Some Image.jpg',
filepath: '/home/vvm/Downloads/images/Some/',
filename: 'Some Image.jpg',
dirname: 'Some'
},
]
You can also write your own code like below to traverse the directory as shown below :
var fs = require('fs');
function traverseDirectory(dirname, callback) {
var directory = [];
fs.readdir(dirname, function(err, list) {
dirname = fs.realpathSync(dirname);
if (err) {
return callback(err);
}
var listlength = list.length;
list.forEach(function(file) {
file = dirname + '\\' + file;
fs.stat(file, function(err, stat) {
directory.push(file);
if (stat && stat.isDirectory()) {
traverseDirectory(file, function(err, parsed) {
directory = directory.concat(parsed);
if (!--listlength) {
callback(null, directory);
}
});
} else {
if (!--listlength) {
callback(null, directory);
}
}
});
});
});
}
traverseDirectory(__dirname, function(err, result) {
if (err) {
console.log(err);
}
console.log(result);
});
You can check more information about it here : http://www.codingdefined.com/2014/09/how-to-navigate-through-directories-in.html
I needed to so something similar, in an Electron app: get all subfolders in a given base folder, using TypeScript, and came up with this:
import { readdirSync, statSync, existsSync } from "fs";
import * as path from "path";
// recursive synchronous "walk" through a folder structure, with the given base path
getAllSubFolders = (baseFolder, folderList = []) => {
let folders:string[] = readdirSync(baseFolder).filter(file => statSync(path.join(baseFolder, file)).isDirectory());
folders.forEach(folder => {
folderList.push(path.join(baseFolder,folder));
this.getAllSubFolders(path.join(baseFolder,folder), folderList);
});
}
const fs = require('fs');
const path = require('path');
var filesCollection = [];
const directoriesToSkip = ['bower_components', 'node_modules', 'www', 'platforms'];
function readDirectorySynchronously(directory) {
var currentDirectorypath = path.join(__dirname + directory);
var currentDirectory = fs.readdirSync(currentDirectorypath, 'utf8');
currentDirectory.forEach(file => {
var fileShouldBeSkipped = directoriesToSkip.indexOf(file) > -1;
var pathOfCurrentItem = path.join(__dirname + directory + '/' + file);
if (!fileShouldBeSkipped && fs.statSync(pathOfCurrentItem).isFile()) {
filesCollection.push(pathOfCurrentItem);
}
else if (!fileShouldBeSkipped) {
var directorypath = path.join(directory + '\\' + file);
readDirectorySynchronously(directorypath);
}
});
}
readDirectorySynchronously('');
This will fill filesCollection with all the files in the directory and its subdirectories (it's recursive). You have the option to skip some directory names in the directoriesToSkip array.
Speaking of npm packages - another short option is to use fs-readdir-recursive:
const read = require("fs-readdir-recursive");
const foundFiles = read("test");
console.log(foundFiles);
Output:
[ 'one.html', 'test-nested/some_text.txt', 'test-nested/two.html' ]
If you're interested only in files with specific extension (like .html mentioned in the question) you can filter them using .endsWith():
const filteredFiles = read("test").filter(item => item.endsWith(".html"));
The accepted answer needs to install a package.
If you want a native option that is ES6:
import { readdirSync } from 'fs'
import { join } from 'path'
function walk(dir) {
return readdirSync(dir, { withFileTypes: true }).flatMap((file) => file.isDirectory() ? walk(join(dir, file.name)) : join(dir, file.name))
}
This works for me.
Read root directory with readdirSync
Then map over but flatten as we go
if it's a directory, go recursive; else return the filename
If you rather work synchronously with glob, use the glob.sync() function as mentioned in their documentation. Here's the equivalent example provided by #Paul Mougel but written synchronously:
const glob = require("glob");
var getDirectories = function (src) {
return glob.sync(src + '/**/*');
};
var rest = getDirectories('test');
console.log(res);
A solution with Promises based on globby:
import { globby } from 'globby';
(async () => {
const path = '/path/to/dir';
const files = await globby([`${path}/**/*`]);
console.log(files);
// [
// '/path/to/dir/file1.txt',
// '/path/to/dir/subdir/file2.txt',
// ...
// ]
})()
Synchrone method with two option, simple and efficacy.
const path = require('path');const fs = require('fs');
function toHierarchie_files(pathDir, output_normalize=false, removeEmpty=true)
{
var result = {}, enqueue = [pathDir];
//normalize slash separator if output_normalize is true or just return val
output_normalize = output_normalize == false?val => {return val}:val => {return path.normalize(val)};
//allows absolute or relative path with extended resolution. Returns path normalize absolute to work with or 'none' string.
const path_exist = (path_test) => {var tmpTab = fs.existsSync(path.normalize(path.resolve(path_test))) == true?[path.normalize(path.resolve(path_test))]:['', '../', '../../'].map(val => path.normalize(path.resolve(__dirname, val+path_test))).filter((val, index) => fs.existsSync(path.normalize(path.resolve(__dirname, val+path_test))) == true);return tmpTab.length > 0?tmpTab[0]:'none'};
//Check if file exist and return her type or 'none' string
const getType = (path_test) => {path_test = path_exist(path_test);return path_test == 'none'?'none':fs.lstatSync(path_test).isDirectory() == true?'dir':fs.lstatSync(path_test).isFile() == true?'file':'none';};
function recursive()
{
//init new entrie
var parentDir = enqueue.pop();result[parentDir]=[];
//read dir
fs.readdirSync(path_exist(parentDir)).forEach((file, index) =>{
switch(getType(parentDir+'/'+file))
{
//if detect dir push in queue
case 'dir': enqueue.push(output_normalize(parentDir+'/'+file)); break;
//if file, add in entrie
case 'file': result[parentDir].push(file); break;
//else done
default: break;
};
});
//if optional arg remove empty is true, delete entries if not contains files
if(result[parentDir].length == 0 && removeEmpty == true){Reflect.deleteProperty(result, parentDir);}
//if queue is not empty continue processing
if(enqueue.length > 0){recursive();}
};
//if dir renseign exist, go recusive
if(getType(pathDir) == 'dir'){recursive();}
return result;
};
Result:
{
"public/assets": [
"favicon.ico"
],
"public/assets/js": [
"dede.js",
"test.js"
],
"public/assets/js/css/secure": [
"config.json",
"index.js"
],
"public/assets/css": [
"style.css"
]
}
You can use loop through all the files and directories of the root folder, if it's a directory, then get inside it and repeat the process.
Consider the code below:
const fs = require('fs');
const path = require('path');
const target = './'; // choose the directory to target
var result = []
var filePaths = []
var tempFolder = []
const targetPath = fs.readdirSync(target);
function hit(mainPath = targetPath) {
mainPath.forEach((file) => {
let check = fs.statSync(file);
if (!check.isDirectory()) {
filePaths.push(file)
}
else {
if (file[0] != '.') {
tempFolder.push(file)
}
}
});
// get files from folder
if (tempFolder.length > 0) {
tempFolder.forEach((dir) => {
getFiles(dir)
})
}
// filePaths contains path to every file
}
function getFiles(dir) {
var paths = fs.readdirSync(dir);
var files = [];
paths.forEach(function (file) {
var fullPath = dir + '/' + file;
files.push(fullPath);
});
files.forEach((tempFile) => {
let check = fs.statSync(tempFile);
if (check.isDirectory()) {
getFiles(tempFile)
} else {
filePaths.push(tempFile)
}
})
}
hit(); // main function
Although not perfect in some scenarios, it must be helpful in many.
const getAllFilePath = (path: string) => {
const addData = (_paths: string[]) => {
const newFoldersToScrape: string[] = [];
_paths.forEach(_path => {
fs.readdirSync(_path).forEach((file: string) => {
if (file.indexOf(".") === -1) {
newFoldersToScrape.push(`${_path}/${file}`);
} else {
filePaths.push(`${_path}/${file}`);
}
});
});
foldersToScrape = newFoldersToScrape;
};
const baseDirPath = `<YOUR BASE PATH HERE>/${path}`;
let foldersToScrape: string[] = [];
const filePaths: string[] = [];
addData([baseDirPath]);
while (foldersToScrape.length !== 0) {
addData(foldersToScrape);
}
return filePaths;
};
This is how I did it, I think it is similar to yet simpler than most of the other answers here.
const fs = require('fs')
let files = []
const getFiles = (path) => {
if (fs.lstatSync(path).isDirectory()) { // is this a folder?
fs.readdirSync(path).forEach(f => { // for everything in this folder
getFiles(path + '/' + f) // process it recursively
})
} else if (path.endsWith(".ts")) { // is this a file we are searching for?
files.push(path) // record it
}
}
getFiles("src")
It fills the "files" array with every .ts file under the "src/" directory.
Slightly modified version of #Stephen's response (https://stackoverflow.com/a/66083078/4421370) above that returns the files' path relative to the directory you are searching. Or any arbitrary base path you supply to the function call in-place of the default base. If you want the full path just call it as walkSync(dir, dir).
Search Path is: c:\tmp,
File path is c:\tmp\test\myfile.txt,
Result is test\myfile.txt
Hopefully helpful to some.
const fs = require('fs');
const path = require('path');
function *walkSync(dir, base="") {
const files = fs.readdirSync(dir, { withFileTypes: true })
for (const file of files) {
if (file.isDirectory()) {
yield* walkSync(path.join(dir, file.name), path.join(base, file.name));
} else {
yield path.join(base, file.name);
}
}
}
for (const filePath of walkSync(__dirname)) {
console.log(filePath);
}
Here is a compact pure function that returns all the paths (relatives) in the directory.
import path from 'path'
const getFilesPathsRecursively = (directory: string, origin?: string): string[] =>
fs.readdirSync(directory).reduce((files, file) => {
const absolute = path.join(directory, file)
return [
...files,
...(fs.statSync(absolute).isDirectory()
? getFilesPathsRecursively(absolute, origin || directory)
: [path.relative(origin || directory, absolute)]),
]
}, [])
The solution is written in TypeScript.
modern solution with async/await
No external dependencies.
Asynchronous function (non-blocking like other solutions with readdirSync and statSync)
It is extremely fast because multiple processes work in parallel (it is not waiting for a response from every file in the list).
It has also some naive error handling (if something happens with one file or folder it will not blow whole process)
import path from "path";
import fs from "fs/promises"
export default async function readDirectory(directory: string): Promise<string[]> {
const files = await fs.readdir(directory)
const filesPromises = files.map(async (file) => {
try {
const absolutePath = path.join(directory, file);
const fileStat = await fs.stat(absolutePath)
if (fileStat.isDirectory()) {
return await readDirectory(absolutePath);
} else {
return absolutePath;
}
} catch (err) {
// error handling
return [];
}
});
const filesWithArrays = await Promise.all(filesPromises)
const flatArray = filesWithArrays.reduce<string[]>((acc, fileOrArray) => acc.concat(fileOrArray), []);
return flatArray;
}
usage (if this is a separate file please remember to import)
const results = await readDirectory('some/path');
I did mine with typescript works well fairly easy to understand
import * as fs from 'fs';
import * as path from 'path';
export const getAllSubFolders = (
baseFolder: string,
folderList: string[] = []
) => {
const folders: string[] = fs
.readdirSync(baseFolder)
.filter(file => fs.statSync(path.join(baseFolder, file)).isDirectory());
folders.forEach(folder => {
folderList.push(path.join(baseFolder, folder));
getAllSubFolders(path.join(baseFolder, folder), folderList);
});
return folderList;
};
export const getFilesInFolder = (rootPath: string) => {
return fs
.readdirSync(rootPath)
.filter(
filePath => !fs.statSync(path.join(rootPath, filePath)).isDirectory()
)
.map(filePath => path.normalize(path.join(rootPath, filePath)));
};
export const getFilesRecursively = (rootPath: string) => {
const subFolders: string[] = getAllSubFolders(rootPath);
const allFiles: string[][] = subFolders.map(folder =>
getFilesInFolder(folder)
);
return [].concat.apply([], allFiles);
};

Get all files recursively in directories NodejS

I have a little problem with my function. I would like to get all files in many directories. Currently, I can retrieve the files in the file passed in parameters. I would like to retrieve the html files of each folder in the folder passed as a parameter. I will explain if I put in parameter "test" I retrieve the files in "test" but I would like to retrieve "test / 1 / *. Html", "test / 2 / . /.html ":
var srcpath2 = path.join('.', 'diapo', result);
function getDirectories(srcpath2) {
return fs.readdirSync(srcpath2).filter(function (file) {
return fs.statSync(path.join(srcpath2, file)).isDirectory();
});
}
The result :
[1,2,3]
thanks !
It looks like the glob npm package would help you. Here is an example of how to use it:
File hierarchy:
test
├── one.html
└── test-nested
└── two.html
JS code:
const glob = require("glob");
var getDirectories = function (src, callback) {
glob(src + '/**/*', callback);
};
getDirectories('test', function (err, res) {
if (err) {
console.log('Error', err);
} else {
console.log(res);
}
});
which displays:
[ 'test/one.html',
'test/test-nested',
'test/test-nested/two.html' ]
I've seen many very long answers, and it's kinda a waste of memory space. Some also use packages like glob, but if you don't want to depend on any package, here's my solution.
const Path = require("path");
const FS = require("fs");
let Files = [];
function ThroughDirectory(Directory) {
FS.readdirSync(Directory).forEach(File => {
const Absolute = Path.join(Directory, File);
if (FS.statSync(Absolute).isDirectory()) return ThroughDirectory(Absolute);
else return Files.push(Absolute);
});
}
ThroughDirectory("./input/directory/");
It's pretty self-explanatory. There's an input directory, and it iterates through that. If one of the items is also a directory, go through that and so on. If it's a file, add the absolute path to the array.
Hope this helped :]
Using ES6 yield
const fs = require('fs');
const path = require('path');
function *walkSync(dir) {
const files = fs.readdirSync(dir, { withFileTypes: true });
for (const file of files) {
if (file.isDirectory()) {
yield* walkSync(path.join(dir, file.name));
} else {
yield path.join(dir, file.name);
}
}
}
for (const filePath of walkSync(__dirname)) {
console.log(filePath);
}
I really liked Smally's Solution but didn't like the Syntax.
Same solution but slightly easier to read:
const fs = require("fs");
const path = require("path");
let files = [];
const getFilesRecursively = (directory) => {
const filesInDirectory = fs.readdirSync(directory);
for (const file of filesInDirectory) {
const absolute = path.join(directory, file);
if (fs.statSync(absolute).isDirectory()) {
getFilesRecursively(absolute);
} else {
files.push(absolute);
}
}
};
Here's mine. Like all good answers it's hard to understand:
const isDirectory = path => statSync(path).isDirectory();
const getDirectories = path =>
readdirSync(path).map(name => join(path, name)).filter(isDirectory);
const isFile = path => statSync(path).isFile();
const getFiles = path =>
readdirSync(path).map(name => join(path, name)).filter(isFile);
const getFilesRecursively = (path) => {
let dirs = getDirectories(path);
let files = dirs
.map(dir => getFilesRecursively(dir)) // go through each directory
.reduce((a,b) => a.concat(b), []); // map returns a 2d array (array of file arrays) so flatten
return files.concat(getFiles(path));
};
With modern JavaScript (NodeJs 10) you can use async generator function and loop through them using for-await...of
// ES modules syntax that is included by default in NodeJS 14.
// For earlier versions, use `--experimental-modules` flag
import fs from "fs/promises"
// or, without ES modules, use this:
// const fs = require('fs').promises
async function run() {
for await (const file of getFiles()) {
console.log(file.path)
}
}
async function* getFiles(path = `./`) {
const entries = await fs.readdir(path, { withFileTypes: true })
for (let file of entries) {
if (file.isDirectory()) {
yield* getFiles(`${path}${file.name}/`)
} else {
yield { ...file, path: path + file.name }
}
}
}
run()
Packed into library:
https://www.npmjs.com/package/node-recursive-directory
https://github.com/vvmspace/node-recursive-directory
List of files:
const getFiles = require('node-recursive-directory');
(async () => {
const files = await getFiles('/home');
console.log(files);
})()
List of files with parsed data:
const getFiles = require('node-resursive-directory');
(async () => {
const files = await getFiles('/home', true); // add true
console.log(files);
})()
You will get something like that:
[
...,
{
fullpath: '/home/vvm/Downloads/images/Some/Some Image.jpg',
filepath: '/home/vvm/Downloads/images/Some/',
filename: 'Some Image.jpg',
dirname: 'Some'
},
]
You can also write your own code like below to traverse the directory as shown below :
var fs = require('fs');
function traverseDirectory(dirname, callback) {
var directory = [];
fs.readdir(dirname, function(err, list) {
dirname = fs.realpathSync(dirname);
if (err) {
return callback(err);
}
var listlength = list.length;
list.forEach(function(file) {
file = dirname + '\\' + file;
fs.stat(file, function(err, stat) {
directory.push(file);
if (stat && stat.isDirectory()) {
traverseDirectory(file, function(err, parsed) {
directory = directory.concat(parsed);
if (!--listlength) {
callback(null, directory);
}
});
} else {
if (!--listlength) {
callback(null, directory);
}
}
});
});
});
}
traverseDirectory(__dirname, function(err, result) {
if (err) {
console.log(err);
}
console.log(result);
});
You can check more information about it here : http://www.codingdefined.com/2014/09/how-to-navigate-through-directories-in.html
I needed to so something similar, in an Electron app: get all subfolders in a given base folder, using TypeScript, and came up with this:
import { readdirSync, statSync, existsSync } from "fs";
import * as path from "path";
// recursive synchronous "walk" through a folder structure, with the given base path
getAllSubFolders = (baseFolder, folderList = []) => {
let folders:string[] = readdirSync(baseFolder).filter(file => statSync(path.join(baseFolder, file)).isDirectory());
folders.forEach(folder => {
folderList.push(path.join(baseFolder,folder));
this.getAllSubFolders(path.join(baseFolder,folder), folderList);
});
}
const fs = require('fs');
const path = require('path');
var filesCollection = [];
const directoriesToSkip = ['bower_components', 'node_modules', 'www', 'platforms'];
function readDirectorySynchronously(directory) {
var currentDirectorypath = path.join(__dirname + directory);
var currentDirectory = fs.readdirSync(currentDirectorypath, 'utf8');
currentDirectory.forEach(file => {
var fileShouldBeSkipped = directoriesToSkip.indexOf(file) > -1;
var pathOfCurrentItem = path.join(__dirname + directory + '/' + file);
if (!fileShouldBeSkipped && fs.statSync(pathOfCurrentItem).isFile()) {
filesCollection.push(pathOfCurrentItem);
}
else if (!fileShouldBeSkipped) {
var directorypath = path.join(directory + '\\' + file);
readDirectorySynchronously(directorypath);
}
});
}
readDirectorySynchronously('');
This will fill filesCollection with all the files in the directory and its subdirectories (it's recursive). You have the option to skip some directory names in the directoriesToSkip array.
Speaking of npm packages - another short option is to use fs-readdir-recursive:
const read = require("fs-readdir-recursive");
const foundFiles = read("test");
console.log(foundFiles);
Output:
[ 'one.html', 'test-nested/some_text.txt', 'test-nested/two.html' ]
If you're interested only in files with specific extension (like .html mentioned in the question) you can filter them using .endsWith():
const filteredFiles = read("test").filter(item => item.endsWith(".html"));
The accepted answer needs to install a package.
If you want a native option that is ES6:
import { readdirSync } from 'fs'
import { join } from 'path'
function walk(dir) {
return readdirSync(dir, { withFileTypes: true }).flatMap((file) => file.isDirectory() ? walk(join(dir, file.name)) : join(dir, file.name))
}
This works for me.
Read root directory with readdirSync
Then map over but flatten as we go
if it's a directory, go recursive; else return the filename
If you rather work synchronously with glob, use the glob.sync() function as mentioned in their documentation. Here's the equivalent example provided by #Paul Mougel but written synchronously:
const glob = require("glob");
var getDirectories = function (src) {
return glob.sync(src + '/**/*');
};
var rest = getDirectories('test');
console.log(res);
A solution with Promises based on globby:
import { globby } from 'globby';
(async () => {
const path = '/path/to/dir';
const files = await globby([`${path}/**/*`]);
console.log(files);
// [
// '/path/to/dir/file1.txt',
// '/path/to/dir/subdir/file2.txt',
// ...
// ]
})()
Synchrone method with two option, simple and efficacy.
const path = require('path');const fs = require('fs');
function toHierarchie_files(pathDir, output_normalize=false, removeEmpty=true)
{
var result = {}, enqueue = [pathDir];
//normalize slash separator if output_normalize is true or just return val
output_normalize = output_normalize == false?val => {return val}:val => {return path.normalize(val)};
//allows absolute or relative path with extended resolution. Returns path normalize absolute to work with or 'none' string.
const path_exist = (path_test) => {var tmpTab = fs.existsSync(path.normalize(path.resolve(path_test))) == true?[path.normalize(path.resolve(path_test))]:['', '../', '../../'].map(val => path.normalize(path.resolve(__dirname, val+path_test))).filter((val, index) => fs.existsSync(path.normalize(path.resolve(__dirname, val+path_test))) == true);return tmpTab.length > 0?tmpTab[0]:'none'};
//Check if file exist and return her type or 'none' string
const getType = (path_test) => {path_test = path_exist(path_test);return path_test == 'none'?'none':fs.lstatSync(path_test).isDirectory() == true?'dir':fs.lstatSync(path_test).isFile() == true?'file':'none';};
function recursive()
{
//init new entrie
var parentDir = enqueue.pop();result[parentDir]=[];
//read dir
fs.readdirSync(path_exist(parentDir)).forEach((file, index) =>{
switch(getType(parentDir+'/'+file))
{
//if detect dir push in queue
case 'dir': enqueue.push(output_normalize(parentDir+'/'+file)); break;
//if file, add in entrie
case 'file': result[parentDir].push(file); break;
//else done
default: break;
};
});
//if optional arg remove empty is true, delete entries if not contains files
if(result[parentDir].length == 0 && removeEmpty == true){Reflect.deleteProperty(result, parentDir);}
//if queue is not empty continue processing
if(enqueue.length > 0){recursive();}
};
//if dir renseign exist, go recusive
if(getType(pathDir) == 'dir'){recursive();}
return result;
};
Result:
{
"public/assets": [
"favicon.ico"
],
"public/assets/js": [
"dede.js",
"test.js"
],
"public/assets/js/css/secure": [
"config.json",
"index.js"
],
"public/assets/css": [
"style.css"
]
}
You can use loop through all the files and directories of the root folder, if it's a directory, then get inside it and repeat the process.
Consider the code below:
const fs = require('fs');
const path = require('path');
const target = './'; // choose the directory to target
var result = []
var filePaths = []
var tempFolder = []
const targetPath = fs.readdirSync(target);
function hit(mainPath = targetPath) {
mainPath.forEach((file) => {
let check = fs.statSync(file);
if (!check.isDirectory()) {
filePaths.push(file)
}
else {
if (file[0] != '.') {
tempFolder.push(file)
}
}
});
// get files from folder
if (tempFolder.length > 0) {
tempFolder.forEach((dir) => {
getFiles(dir)
})
}
// filePaths contains path to every file
}
function getFiles(dir) {
var paths = fs.readdirSync(dir);
var files = [];
paths.forEach(function (file) {
var fullPath = dir + '/' + file;
files.push(fullPath);
});
files.forEach((tempFile) => {
let check = fs.statSync(tempFile);
if (check.isDirectory()) {
getFiles(tempFile)
} else {
filePaths.push(tempFile)
}
})
}
hit(); // main function
Although not perfect in some scenarios, it must be helpful in many.
const getAllFilePath = (path: string) => {
const addData = (_paths: string[]) => {
const newFoldersToScrape: string[] = [];
_paths.forEach(_path => {
fs.readdirSync(_path).forEach((file: string) => {
if (file.indexOf(".") === -1) {
newFoldersToScrape.push(`${_path}/${file}`);
} else {
filePaths.push(`${_path}/${file}`);
}
});
});
foldersToScrape = newFoldersToScrape;
};
const baseDirPath = `<YOUR BASE PATH HERE>/${path}`;
let foldersToScrape: string[] = [];
const filePaths: string[] = [];
addData([baseDirPath]);
while (foldersToScrape.length !== 0) {
addData(foldersToScrape);
}
return filePaths;
};
This is how I did it, I think it is similar to yet simpler than most of the other answers here.
const fs = require('fs')
let files = []
const getFiles = (path) => {
if (fs.lstatSync(path).isDirectory()) { // is this a folder?
fs.readdirSync(path).forEach(f => { // for everything in this folder
getFiles(path + '/' + f) // process it recursively
})
} else if (path.endsWith(".ts")) { // is this a file we are searching for?
files.push(path) // record it
}
}
getFiles("src")
It fills the "files" array with every .ts file under the "src/" directory.
Slightly modified version of #Stephen's response (https://stackoverflow.com/a/66083078/4421370) above that returns the files' path relative to the directory you are searching. Or any arbitrary base path you supply to the function call in-place of the default base. If you want the full path just call it as walkSync(dir, dir).
Search Path is: c:\tmp,
File path is c:\tmp\test\myfile.txt,
Result is test\myfile.txt
Hopefully helpful to some.
const fs = require('fs');
const path = require('path');
function *walkSync(dir, base="") {
const files = fs.readdirSync(dir, { withFileTypes: true })
for (const file of files) {
if (file.isDirectory()) {
yield* walkSync(path.join(dir, file.name), path.join(base, file.name));
} else {
yield path.join(base, file.name);
}
}
}
for (const filePath of walkSync(__dirname)) {
console.log(filePath);
}
Here is a compact pure function that returns all the paths (relatives) in the directory.
import path from 'path'
const getFilesPathsRecursively = (directory: string, origin?: string): string[] =>
fs.readdirSync(directory).reduce((files, file) => {
const absolute = path.join(directory, file)
return [
...files,
...(fs.statSync(absolute).isDirectory()
? getFilesPathsRecursively(absolute, origin || directory)
: [path.relative(origin || directory, absolute)]),
]
}, [])
The solution is written in TypeScript.
modern solution with async/await
No external dependencies.
Asynchronous function (non-blocking like other solutions with readdirSync and statSync)
It is extremely fast because multiple processes work in parallel (it is not waiting for a response from every file in the list).
It has also some naive error handling (if something happens with one file or folder it will not blow whole process)
import path from "path";
import fs from "fs/promises"
export default async function readDirectory(directory: string): Promise<string[]> {
const files = await fs.readdir(directory)
const filesPromises = files.map(async (file) => {
try {
const absolutePath = path.join(directory, file);
const fileStat = await fs.stat(absolutePath)
if (fileStat.isDirectory()) {
return await readDirectory(absolutePath);
} else {
return absolutePath;
}
} catch (err) {
// error handling
return [];
}
});
const filesWithArrays = await Promise.all(filesPromises)
const flatArray = filesWithArrays.reduce<string[]>((acc, fileOrArray) => acc.concat(fileOrArray), []);
return flatArray;
}
usage (if this is a separate file please remember to import)
const results = await readDirectory('some/path');
I did mine with typescript works well fairly easy to understand
import * as fs from 'fs';
import * as path from 'path';
export const getAllSubFolders = (
baseFolder: string,
folderList: string[] = []
) => {
const folders: string[] = fs
.readdirSync(baseFolder)
.filter(file => fs.statSync(path.join(baseFolder, file)).isDirectory());
folders.forEach(folder => {
folderList.push(path.join(baseFolder, folder));
getAllSubFolders(path.join(baseFolder, folder), folderList);
});
return folderList;
};
export const getFilesInFolder = (rootPath: string) => {
return fs
.readdirSync(rootPath)
.filter(
filePath => !fs.statSync(path.join(rootPath, filePath)).isDirectory()
)
.map(filePath => path.normalize(path.join(rootPath, filePath)));
};
export const getFilesRecursively = (rootPath: string) => {
const subFolders: string[] = getAllSubFolders(rootPath);
const allFiles: string[][] = subFolders.map(folder =>
getFilesInFolder(folder)
);
return [].concat.apply([], allFiles);
};

Using Babel to Get ApolloClient to ES5 CommonJS Module Format for Node Environment

Using Babel to Get ApolloClient to ES5 CommonJS Module Format
Im trying to use Babel to get the apollo-client module to work as ES5 in a non-browser, node environment. I've gone through step below which always give me the same result. Im trying to figure out if that result is right result for a node environment. When I import the babel processed documents into my project and call a method that should be exported, im getting, cannot find module. For context, the project is a fusetools.com demo. Fusetools does not support ES2015 Promises so the idea is that with the babel es2015 preset, it should work. I'm mostly chasing this down to learn something but it would be great if I could get it to work. Any comments on an easier way to do this, now that I understand it better, would be greatly appreciated. The project where I babeled the code can be found here. The fusetools project where i used the transformed code is here.
The error I get is :
LOG: Error: JavaScript error in MainView.ux line 9: Name: Fuse.Scripting.Error
Error message: require(): module not found: js/apollo-client/ApolloClient.js
File name: MainView.ux
Line number: 9
Source line: var ApolloClient = require('js/apollo-client/ApolloClient.js');
This is the code im trying to reach:
```
"use strict";
var networkInterface_1 = require('./transport/networkInterface');
var isUndefined = require('lodash.isundefined');
var assign = require('lodash.assign');
var isString = require('lodash.isstring');
var store_1 = require('./store');
var QueryManager_1 = require('./core/QueryManager');
var storeUtils_1 = require('./data/storeUtils');
var fragments_1 = require('./fragments');
var getFromAST_1 = require('./queries/getFromAST');
var DEFAULT_REDUX_ROOT_KEY = 'apollo';
function defaultReduxRootSelector(state) {
return state[DEFAULT_REDUX_ROOT_KEY];
}
var ApolloClient = function () {
function ApolloClient(_a) {
var _this = this;
var _b = _a === void 0 ? {} : _a,
networkInterface = _b.networkInterface,
reduxRootKey = _b.reduxRootKey,
reduxRootSelector = _b.reduxRootSelector,
initialState = _b.initialState,
dataIdFromObject = _b.dataIdFromObject,
resultTransformer = _b.resultTransformer,
resultComparator = _b.resultComparator,
_c = _b.ssrMode,
ssrMode = _c === void 0 ? false : _c,
_d = _b.ssrForceFetchDelay,
ssrForceFetchDelay = _d === void 0 ? 0 : _d,
_e = _b.mutationBehaviorReducers,
mutationBehaviorReducers = _e === void 0 ? {} : _e,
_f = _b.addTypename,
addTypename = _f === void 0 ? true : _f,
queryTransformer = _b.queryTransformer;
this.middleware = function () {
return function (store) {
_this.setStore(store);
return function (next) {
return function (action) {
var returnValue = next(action);
_this.queryManager.broadcastNewStore(store.getState());
return returnValue;
};
};
};
};
if (reduxRootKey && reduxRootSelector) {
throw new Error('Both "reduxRootKey" and "reduxRootSelector" are configured, but only one of two is allowed.');
}
if (reduxRootKey) {
console.warn('"reduxRootKey" option is deprecated and might be removed in the upcoming versions, ' + 'please use the "reduxRootSelector" instead.');
this.reduxRootKey = reduxRootKey;
}
if (queryTransformer) {
throw new Error('queryTransformer option no longer supported in Apollo Client 0.5. ' + 'Instead, there is a new "addTypename" option, which is on by default.');
}
if (!reduxRootSelector && reduxRootKey) {
this.reduxRootSelector = function (state) {
return state[reduxRootKey];
};
} else if (isString(reduxRootSelector)) {
this.reduxRootKey = reduxRootSelector;
this.reduxRootSelector = function (state) {
return state[reduxRootSelector];
};
} else if (typeof reduxRootSelector === 'function') {
this.reduxRootSelector = reduxRootSelector;
} else {
this.reduxRootSelector = null;
}
this.initialState = initialState ? initialState : {};
this.networkInterface = networkInterface ? networkInterface : networkInterface_1.createNetworkInterface({ uri: '/graphql' });
this.addTypename = addTypename;
this.resultTransformer = resultTransformer;
this.resultComparator = resultComparator;
this.shouldForceFetch = !(ssrMode || ssrForceFetchDelay > 0);
this.dataId = dataIdFromObject;
this.fieldWithArgs = storeUtils_1.storeKeyNameFromFieldNameAndArgs;
if (ssrForceFetchDelay) {
setTimeout(function () {
return _this.shouldForceFetch = true;
}, ssrForceFetchDelay);
}
this.reducerConfig = {
dataIdFromObject: dataIdFromObject,
mutationBehaviorReducers: mutationBehaviorReducers
};
this.watchQuery = this.watchQuery.bind(this);
this.query = this.query.bind(this);
this.mutate = this.mutate.bind(this);
this.setStore = this.setStore.bind(this);
this.resetStore = this.resetStore.bind(this);
}
ApolloClient.prototype.watchQuery = function (options) {
this.initStore();
if (!this.shouldForceFetch && options.forceFetch) {
options = assign({}, options, {
forceFetch: false
});
}
fragments_1.createFragment(options.query);
var fullDocument = getFromAST_1.addFragmentsToDocument(options.query, options.fragments);
var realOptions = Object.assign({}, options, {
query: fullDocument
});
delete realOptions.fragments;
return this.queryManager.watchQuery(realOptions);
};
;
ApolloClient.prototype.query = function (options) {
this.initStore();
if (!this.shouldForceFetch && options.forceFetch) {
options = assign({}, options, {
forceFetch: false
});
}
fragments_1.createFragment(options.query);
var fullDocument = getFromAST_1.addFragmentsToDocument(options.query, options.fragments);
var realOptions = Object.assign({}, options, {
query: fullDocument
});
delete realOptions.fragments;
return this.queryManager.query(realOptions);
};
;
ApolloClient.prototype.mutate = function (options) {
this.initStore();
var fullDocument = getFromAST_1.addFragmentsToDocument(options.mutation, options.fragments);
var realOptions = Object.assign({}, options, {
mutation: fullDocument
});
delete realOptions.fragments;
return this.queryManager.mutate(realOptions);
};
;
ApolloClient.prototype.subscribe = function (options) {
this.initStore();
var fullDocument = getFromAST_1.addFragmentsToDocument(options.query, options.fragments);
var realOptions = Object.assign({}, options, {
document: fullDocument
});
delete realOptions.fragments;
delete realOptions.query;
return this.queryManager.startGraphQLSubscription(realOptions);
};
ApolloClient.prototype.reducer = function () {
return store_1.createApolloReducer(this.reducerConfig);
};
ApolloClient.prototype.initStore = function () {
if (this.store) {
return;
}
if (this.reduxRootSelector) {
throw new Error('Cannot initialize the store because "reduxRootSelector" or "reduxRootKey" is provided. ' + 'They should only be used when the store is created outside of the client. ' + 'This may lead to unexpected results when querying the store internally. ' + "Please remove that option from ApolloClient constructor.");
}
this.setStore(store_1.createApolloStore({
reduxRootKey: DEFAULT_REDUX_ROOT_KEY,
initialState: this.initialState,
config: this.reducerConfig
}));
this.reduxRootKey = DEFAULT_REDUX_ROOT_KEY;
};
;
ApolloClient.prototype.resetStore = function () {
this.queryManager.resetStore();
};
;
ApolloClient.prototype.setStore = function (store) {
var reduxRootSelector;
if (this.reduxRootSelector) {
reduxRootSelector = this.reduxRootSelector;
} else {
reduxRootSelector = defaultReduxRootSelector;
this.reduxRootKey = DEFAULT_REDUX_ROOT_KEY;
}
if (isUndefined(reduxRootSelector(store.getState()))) {
throw new Error('Existing store does not use apolloReducer. Please make sure the store ' + 'is properly configured and "reduxRootSelector" is correctly specified.');
}
this.store = store;
this.queryManager = new QueryManager_1.QueryManager({
networkInterface: this.networkInterface,
reduxRootSelector: reduxRootSelector,
store: store,
addTypename: this.addTypename,
resultTransformer: this.resultTransformer,
resultComparator: this.resultComparator,
reducerConfig: this.reducerConfig
});
};
;
return ApolloClient;
}();
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = ApolloClient;
//# sourceMappingURL=ApolloClient.js.map
```
Any and all comments I might learn from are appreciated. Thank you.
One way to do this would be to use webpack like this:
const webpack = require('webpack');
const path = require('path');
module.exports = {
// watch: true,
entry: {
ApolloClient: './config/ApolloClient.js',
createNetworkInterface: './config/createNetworkInterface.js',
Redux: './config/Redux.js',
},
output: {
path: path.join(__dirname, 'build/Libs'),
filename: '[name].js',
library: '[name]',
libraryTarget: 'commonjs',
},
module: {
rules: [
{
use: 'babel-loader',
test: /\.js$/,
exclude: /node_modules/,
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
}),
],
};
Then in config directory you could have:
/* ApolloClient.js */
import { ApolloClient } from 'apollo-client';
export default ApolloClient;
and
/* createNetworkInterface.js */
import { createNetworkInterface } from 'apollo-client/transport/networkInterface';
export default createNetworkInterface;
plus if you want to have Redux as well:
/* Redux.js */
import * as Redux from 'redux';
export default Redux;
However I was not able to get gql done this way and had to use bolav's fusepm.
Which you would use exactly as bolav has mention, first install it globally:
npm install -G fusepm and then fusepm npm graphql-tag
Once you have all these in place you can require them as follow:
var Redux = require('build/Libs/Redux');
var ApolloClient = require('build/Libs/ApolloClient');
var createNetworkInterface = require('build/Libs/createNetworkInterface');
var gql = require('fusejs_lib/graphql-tag_graphql-tag.umd');
This way still could use some TLC but for now, it works and get's the job done:
var networkInterface = createNetworkInterface.createNetworkInterface({
uri: 'http://localhost:8000/graphql',
});
var client = new ApolloClient.ApolloClient({
networkInterface,
});
client.query({
query: gql`
query {
allPosts {
edges {
node {
id
headline
summary(length: 80)
body
createdAt
updatedAt
personByAuthorId {
firstName
lastName
}
}
}
}
}
`,
})
.then(data => data.data.allPosts.edges.forEach(node => pages.add(createPage(node))))
.catch(error => console.log(error));
Also if you like I've setup a whole project along with server that might be of an interest to you: fuseR
I made fusepm, which has a mode to convert npm modules to run them under FuseTools. It's still has a lot of bugs, but at least I managed to come longer than you:
fuse create app apolloc
cd apolloc
npm install apollo-client
fusepm npm apollo-client
And then in your javascript:
<JavaScript>
var ApolloClient = require('fusejs_lib/apollo-client.js');
</JavaScript>
fusepm uses Babel, with some custom plugins.

Ember blueprint that injects environment config?

I'm relatively new to Ember and was wondering if there is a way to create a blueprint/generator that would inject a new value into the environment config while maintaining all existing configuration. Is there some Ember magic that allows an existing file to act as the blueprint template? My ideal implementation would look something like this:
ember g platform foo
// config/environment.js
module.exports = function(environment) {
var ENV = {
// Existing config values here...
APP: {
platforms: {
foo: 'abc123' // Generator injects the 'foo' platform and a GUID
}
};
// Existing environment-specific settings here...
return ENV;
};
Is this something that would be more easily accomplished using Node's fs.readFile() and fs.writeFile()? If so, how could I parse environment.js?
No there's no existing magic in Ember to my knowledge sorry. When you generate a route, something very similar to what you are talking about happens but the code is rather complex. The ember generate route new_route function has a call to this function
function addRouteToRouter(name, options) {
var routerPath = path.join(options.root, 'app', 'router.js');
var source = fs.readFileSync(routerPath, 'utf-8');
var routes = new EmberRouterGenerator(source);
var newRoutes = routes.add(name, options);
fs.writeFileSync(routerPath, newRoutes.code());
}
which then exectutes interpreter level like code to add the router and revert it back to code:
module.exports = EmberRouterGenerator;
var recast = require('recast');
var traverse = require('es-simpler-traverser');
var Scope = require('./scope');
var DefineCallExpression = require('./visitors/define-call-expression.js');
var findFunctionExpression = require('./helpers/find-function-expression');
var hasRoute = require('./helpers/has-route');
var newFunctionExpression = require('./helpers/new-function-expression');
var resourceNode = require('./helpers/resource-node');
var routeNode = require('./helpers/route-node');
function EmberRouterGenerator(source, ast) {
this.source = source;
this.ast = ast;
this.mapNode = null;
this.scope = new Scope();
this.visitors = {
CallExpression: new DefineCallExpression(this.scope, this)
};
this._ast();
this._walk();
}
EmberRouterGenerator.prototype.clone = function() {
var route = new EmberRouterGenerator(this.source);
return route;
};
EmberRouterGenerator.prototype._ast = function() {
this.ast = this.ast || recast.parse(this.source);
};
EmberRouterGenerator.prototype._walk = function() {
var scope = this.scope;
var visitors = this.visitors;
traverse(this.ast, {
exit: function(node) {
var visitor = visitors[node.type];
if (visitor && typeof visitor.exit === 'function') {
visitor.exit(node);
}
},
enter: function(node) {
var visitor = visitors[node.type];
if (visitor && typeof visitor.enter === 'function') {
visitor.enter(node);
}
}
});
};
EmberRouterGenerator.prototype.add = function(routeName, options) {
if (typeof this.mapNode === 'undefined') {
throw new Error('Source doesn\'t include Ember.map');
}
var route = this.clone();
var routes = route.mapNode.arguments[0].body.body;
route._add.call(
route,
routeName.split('/'),
routes,
options
);
return route;
};
EmberRouterGenerator.prototype._add = function(nameParts, routes, options) {
options = options || {};
var parent = nameParts[0];
var name = parent;
var children = nameParts.slice(1);
var route = hasRoute(parent, routes);
if (!route) {
if (options.type === 'resource') {
route = resourceNode(name, options);
routes.push(route);
} else {
route = routeNode(name, options);
routes.push(route);
}
}
if (children.length > 0) {
var routesFunction = findFunctionExpression(route.expression.arguments);
if (!routesFunction) {
routesFunction = newFunctionExpression();
route.expression.arguments.push(routesFunction);
}
this._add(children, routesFunction.body.body, options);
}
};
EmberRouterGenerator.prototype.remove = function(routeName) {
if (typeof this.mapNode === 'undefined') {
throw new Error('Source doesn\'t include Ember.map');
}
var route = this.clone();
var routes = route.mapNode.arguments[0].body.body;
var newRoutes = route._remove.call(
route,
routeName.split('/'),
routes
);
if (newRoutes) {
route.mapNode.arguments[0].body.body = newRoutes;
}
return route;
};
EmberRouterGenerator.prototype._remove = function(nameParts, routes) {
var parent = nameParts[0];
var name = parent;
var children = nameParts.slice(1);
var route = hasRoute(parent, routes);
var newRoutes;
if (children.length > 0) {
var routesFunction = route.expression && findFunctionExpression(route.expression.arguments);
if (routesFunction) {
newRoutes = this._remove(children, routesFunction.body.body);
if (newRoutes) {
routesFunction.body.body = newRoutes;
}
return routes;
}
} else {
if (route) {
routes = routes.filter(function(node) {
return node !== route;
});
return routes;
} else {
return false;
}
}
};
EmberRouterGenerator.prototype.code = function(options) {
options = options || { tabWidth: 2, quote: 'single' };
return recast.print(this.ast, options).code;
};
So then there's the alternative, which involves reading the file, adding in your new environment in the correct place after parsing the file correctly, and then writing the stream back. The complexity of what you are wanting to do probably outweighs the time it would take to do this manually IMO. If this is something you are doing often, maybe consider writing a script in another language that's better(read as more people use it for this) at textual file manipulation

Swig-template default extension

Can I set what extension will be? For example:
.html or .htm
And can I set custom extension for some of layouts? Like:
.xml
Swig doesn't care/know about extensions. You could try writing a custom loader to do this for you. Just copy the filesystem loader and have it check if a path given doesn't include an extension, and if it does, use your default.
var fs = require('fs'),
path = require('path');
module.exports = function (basepath, encoding) {
var ret = {};
encoding = encoding || 'utf8';
basepath = (basepath) ? path.normalize(basepath) : null;
ret.resolve = function (to, from) {
if (basepath) {
from = basepath;
} else {
from = (from) ? path.dirname(from) : process.cwd();
}
return path.resolve(from, to);
};
ret.load = function (identifier, cb) {
if (!fs || (cb && !fs.readFile) || !fs.readFileSync) {
throw new Error('Unable to find file ' + identifier + ' because there is no filesystem to read from.');
}
// Start of added code...
var extension = path.extname(identifier);
if (!extension) {
// Set this to whatever you want as a default
// If the extension exists, like 'foo.xml', it won't add the '.html'
identifier += '.html';
}
// End of added code
identifier = ret.resolve(identifier);
if (cb) {
fs.readFile(identifier, encoding, cb);
return;
}
return fs.readFileSync(identifier, encoding);
};
return ret;
};

Resources