browserify/brfs

fs is not defined error when readFileSync is passed a path variable

stewartknapman opened this issue ยท 37 comments

I'm getting the following error when I run this with the brfs transform.

var temp = fs.readFileSync(template_path, 'utf8');
           ^
ReferenceError: fs is not defined
...

Test 1:

var fs  = require('fs');
var temp = fs.readFileSync('./templates/test.html', 'utf8');
console.log(temp);

If I run this with node test.js, or

browserify test.js -o output.js -t brfs
node output.js

I get the expected 'hello world' content from test.html output to the console.
The contents of output.js looks like this:

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){

/* Test 1 */
var temp = "hello world";
console.log(temp);

},{}]},{},[1]);

However if I do the same with Test 2:

var fs  = require('fs');
var template_path = './templates/test.html';
var temp = fs.readFileSync(template_path, 'utf8');
console.log(temp);

node test.js works fine but running browserify and then node output.js gives me the fs is not defined error.
Also output now looks like this:

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){

/* Test 2 */

var template_path = './templates/test.html';
var temp = fs.readFileSync(template_path, 'utf8');
console.log(temp);

},{}]},{},[1]);

I can confirm this.

It seems that the transform for fs.readFileSync() only works when a string is the first parameter.

Following code doesn't work

var fs = require('fs');
var filename = '/tmp/hey.txt';

var text = fs.readFileSync(filename, 'utf8');

But this works

var fs = require('fs');

var text = fs.readFileSync('/tmp/hey.txt', 'utf8');

I can confirm this as well.

ibc commented

Stuck in the same issue. Is this a "feature" or a "limitation" in brfs? I may understand it is a limitation given that the module is supposed to parse JS code in order to replace calls to fs.readFileSync() with the content of the given file.

Anyhow, is there any magic to allow passing variables instead of fixed strings?

It's impossible to support full inlining in the general case without actually running the files, which runs aground of the halting problem. This module is rather limited in the inputs that it accepts.

I can see how this is a limitation now.

It would be pretty cool to be able to do something like this though:

var dir = fs.readdirSync('./_includes');
for (var i in dir) {
  var file = dir[i];
  var filename = file.split('.')[0];
  var path = './_includes/'+ file;
  temps[filename] = fs.readFileSync(path, 'utf8');
}

But the only way I can see that working is that it turns fs.readdirSync and fs.readFileSync into arrays or objects that hold the files content with the file names as keys.

@stewartknapman what you are looking for is similar to bulkify.

The usual steps:

  1. create a node-specific module that has the same API you want and uses Node FS under the hood
  2. create a browserify transform that wraps this node module using static analysis

Now you have a system that works in node and browserify. Example:

https://www.npmjs.org/package/extract-svg-path (the node module)
https://www.npmjs.org/package/extractify-svg-path (the browserify transform)

@mattdesl thanks for that. I'll have a play.

Would be cool if we could at least support this use case:

fs.readFileSync(path.join(__dirname, 'txt', 'file.txt'))

path.join() is now supported, assuming you var path = require('path') beforehand.

This is excellent!

I'm getting an error when passing path.join():

runtime.path = path.join(__dirname, "example.js");

function compile(source, options) {
  options = normalizeOptions(options);

  if (!genOrAsyncFunExp.test(source)) {
    return {
      // Shortcut: no generators or async functions to transform.
      code: (options.includeRuntime === true ? fs.readFileSync(
        runtime.path, "utf-8"
      ) + "\n" : "") + source
    };
  }
...

Is this expected, given the fix above?

Getting an error when using path.join(). Maybe I'm not using it correctly? Can you include string variables in path.join(), which would in turn let you, indirectly, use variables in readFileSync()?

Ditto. Seeing an error even when using path.join.

path.join does not work for variables too.

I'm having this same problem. Using this library to render HTML templates from a JavaScript function that's triggered based on route change.

Code that works:

var location = window.location.hash.slice(2)

var template = fs.readFileSync('app/templates/accounts/login.tpl', 'utf8')

console.log(template)

$('#app').html(template)

But when I change the var template line to the below it does not work:

var template = fs.readFileSync('app/templates/' + location + '.tpl', 'utf8')

You can't use variables since the expression has to be statically analyzable, i.e. Known at build time, not run time.

Alternatives?

Depends what you are doing... You can use bulkify to bulk-require a whole folder at build time, or write your own server if you need more flexibility.

For HTML templates most people just have them all available at runtime and accessible by a key, eg with handlebars.

Hey @mattdesl ,

Thanks for your help and the suggestion.

Check out my comment above, you'll see the use case.

Any idea what to do? Seems like maybe I can't get the job done with just brfs.

D'oh. Does not work for me either. ๐Ÿ˜ž

var viewTemplate = fs.readFileSync(
        __dirname + location.href.match(/index_dev.html/i) ?
        '/tmpl/view.tmpl.html' : '/tmpl/view.tmpl.min.html', 'utf8');
ibc commented

Guys, is it so hard to understand? brfs works at "build" time so it does not eval the JavaScript code within the arguments to fs.readFileSync().

@ibc I completely understand and I think others do as well, however people are still going to post to look for alternatives. Rather than getting frustrated with that, lets post some good alternatives since this package won't work for that use case.

I was using the router Director and trying to render templates based on certain routes (using brfs) which it looks like @dwiyatci is trying to do as well. Since brfs wouldn't work because my project needed to dynamically render the file, I searched for other solutions.

I'm using Vue.js, so I resorted to vue-router rather than using Director (which has no built in functionality to render templates based on route, which lead me to brfs). This probably won't help many of you since it's Vue.js specific, but I may as well explain how I got around it.

Yeah, I guess we were just being frustrated there. For me, I ended up in setting up a grunt task to watch for the templates changes, always readFileSync() the minified ones in the JS code, and then re-bundle. A bit too much of CPU overhead during the development, but yeah. Boiled down to something like:

var fs = require('fs');
var viewTemplate = fs.readFileSync(
    __dirname + '/tmpl/view.tmpl.min.html', 'utf8');

This issue has been closed, but why???
The problem is still there:

$ cat test.js
 var fs  = require('fs');
 var temp = fs.readFileSync('./test.json', 'utf8');
 console.log(temp);
$ node test
{ key : hello world }

$ browserify test.js -o output.js -t brfs
$ node output
{ key : hello world }

$ cat output.js 
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){

 var temp = "{ key : hello world }\n";
 console.log(temp);

},{}]},{},[1]);
$ cat test2.js
 var fs  = require('fs');
 var filename='./test.json';
 var temp = fs.readFileSync(filename, 'utf8');
 console.log(temp);
$ node test2
{ key : hello world }

$ browserify test2.js -o output2.js -t brfs
$ node output2
/output2.js:4
 var temp = fs.readFileSync(filename, 'utf8');
            ^

ReferenceError: fs is not defined
    at Object.__dirname.1 (/output2.js:4:13)
    at s (/output2.js:1:316)
    at e (/output2.js:1:487)
    at Object.<anonymous> (/output2.js:1:505)
    at Module._compile (module.js:541:32)
    at Object.Module._extensions..js (module.js:550:10)
    at Module.load (module.js:458:32)
    at tryModuleLoad (module.js:417:12)
    at Function.Module._load (module.js:409:3)
    at Module.runMain (module.js:575:10)

wepack version is

$ cat node_modules/webpack/package.json | grep id
  "_id": "webpack@1.13.1",
    "webpack-dev-middleware": "^1.0.0",

@ibc ok I see that since it works at build time there is no eval of expressions like

/**
         * Load an Apache2-style ".types" file
         *
         * This may be called multiple times (it's expected).  Where files declare
         * overlapping types/extensions, the last file wins.
         *
         * @param file (String) path of file to load.
         */
        Mime.prototype.load = function(file) {
          // Read file and split into lines
          var map = {},
              content = fs.readFileSync(file, 'ascii'),
              lines = content.split(/[\r\n]+/);

          lines.forEach(function(line) {
            // Clean up whitespace/comments, and split into fields
            var fields = line.replace(/\s*#.*|^\s*|\s*$/g, '').split(/\s+/);
            map[fields.shift()] = fields;
          });

          this.define(map);
        };

the fact is that tons of modules have the fs.readFileSync in the code, so I don't get which is the solution here, and why this issue has been closed without any working/possible (proposal of )a solution.

ibc commented

I don't get which is the solution here, and why this issue has been closed without any working/possible (proposal of )a solution.

It's been closed because brfs has never been supposed to handle dynamic runtime variables and, hence, there is no bug to fix here nor and the author has not the responsibility of providing a solution/proposal/workaround.

phantomjs has it's own api - it's not the same as node api.
read method is equivalent of readFileSync
http://phantomjs.org/api/fs/

sebs commented

You can close this a 20 times. I must second @ibc . Its not helping (and prolly causing bugs in repos using BRTFS)

var fs = require('fs');
Is compulsory at the beginning of the code
Also, the first argument must be a string. Not a concatenated variable, not a variable. It must completely be string.

The fs npm module is stopped their service. They are saying,
This package name is not currently in use, but was formerly occupied by another package. To avoid malicious use

fs is actually a part of the Node API ecosystem. The fs npm name is just reserved for obvious reasons.

Hello Guys,
I am facing a similar problem
I have imported "fs" using import * as fs from "fs" and trying to execute fs.readFile() and fs.readFileSync with there respective arguments.
But, getting an error as "fs" object doesn't have a function readFile() and readFileSync().
I have tried using var fsFile = require("fs");.

I am using in Cypress automation project in typescript.

I created a Stackoverflow question with the details of versions used to reproduce similar issue TypeError: fs.readdir is not a function with Cypress 3.5.0

Hey guys same for me

const fs = require("fs");
console.log(fs);
//output => {}

import * as fs from "fs";
console.log(fs);
//output => {}

import { fs } from "fs";
console.log(fs);
//output => undefined

Any suggestion ?

@elmurphy that code looks expected: in browserify, the fs module is an empty object at runtime. It is impossible to use fs in the browser because there is no way to access the user's file system. The brfs package can transform calls to specific fs functions only at build time.

Hey guys, total noob here, but I had the same problem and solved it (I think) by changing this:

var files = fs.readdirSync(folder);

To this:

var fs = require('fs');
const newLocal = fs.readdirSync(folder);
var files = newLocal;