requireJS an entire folder

2019-01-15 12:23发布

问题:

Is it possible to "require" an entire folder using requireJS.

For example, I have a behaviors folder with a ton of behavior js files. I'd really like to be able to simply use require(['behaviors/*'], function() {...}); to load everything in that folder rather than having to keep that list up to date. Once compressed and optimized I'd have all those files lump together, but for development it's easier to work with them individually.

回答1:

javascript in browser has no filesystem access and so it can't scan a directory for files. If you are building your app in a scripting language like php or ruby you could write a script that scans the directory and adds the file names to the require() call.



回答2:

While JavaScript in the browser can't (and shouldn't) see the file system I created a Grunt task that will do just this. I'm currently still working on it touching it up here and there but you're welcome to take a look.

https://npmjs.org/package/require-wild

npm install require-wild

In your case all you'd have to do is setup the task's settings

grunt.initConfig({
    requireWild: {
        app: {
            // Input files to look for wildcards (require|define)
            src: ["./**/*.js"], 

            // Output file contains generated namespace modules
            dest: "./namespaces.js", 

            // Load your require config file used to find baseUrl - optional
            options: { requireConfigFile: "./main.js" }
        }
    }
}); 

grunt.loadNpmTasks("require-wild");

grunt.registerTask('default', ['requireWild']);

Then run the grunt task. Your file will be generated. Modify your main.js to load namespaces.js

require(['namespaces'], function () { ... });

Thus now allowing modules under src to use dependencies with grunt's glob pattern matching.

require(['behaviors/**/*'], function (behaviors) { }

This ideology assumes that you have a meaningful file structure.



回答3:

I know this is old, but I'd like to share my solution:

For this solution you need JQuery

1) Create a bash script that will list all the js files in "MyDirectory/", and save it to "directoryContents.txt":

#!/bin/bash
  #Find all the files in that directory...
  for file in $( find MyDirectory/ -type f -name "*.js" )
        do
          fileClean=${file%.js} #Must remove .js from the end!
          echo -n "$fileClean " >> MyDirectory/directoryContents.txt
        done
  • File will look like this:

MyDirectory/FirstJavascriptFile MyDirectory/SecondJavascriptFile MyDirectory/ThirdJavascriptFile

  • Problem with my script! Puts an extra " " at the end, that messes things up! Make sure to remove the excess space at the end of directoryContents.txt

2) Then in your Client side JS code:

  • do a "GET" request to retrieve the text file
  • For each entry (split by the space), 'require' that file:

.

$.get( "MyDirectory/directoryContents.txt", {}, function( data ) {
    var allJsFilesInFolder = data.split(" ");
    for(var a=0; a<allJsFilesInFolder.length; a++)
    {
        require([allJsFilesInFolder[a]], function(jsConfig) 
        {
            //Done loading this one file
        });
    }
}, "text");

I was having a problem with this code not finishing before my other code, so Here's my extended answer:

define([''], function() {

return {

    createTestMenu: function() 
    {
        this.loadAllJSFiles(function(){
            //Here ALL those files you need are loaded!
        });
    },

    loadAllJSFiles: function(callback)
    {   
        $.get( "MyDirectory/directoryContents.txt", {}, function( data ) {
            var allJsFilesInFolder = data.split(" ");
            var currentFileNum = 0;
            for(var a=0; a<allJsFilesInFolder.length; a++)
            {
                require([allJsFilesInFolder[a]], function(jsConfig) 
                {
                    currentFileNum++;
                    //If it's the last file that needs to be loaded, run the callback.
                    if (currentFileNum==allJsFilesInFolder.length)
                    {
                        console.log("Done loading all configuration files.");
                        if (typeof callback != "undefined"){callback();}
                    }
                });
            }
        }, "text");
    }
}
});

What I ended up doing was everytime my Node server boots, it will run the bash script, populating directoryContents.txt. Then My client side just reads directoryContents.txt for the list of files, and requires each in that list.

Hope this helps!



回答4:

There isn't really a way to do this conceptually on the fly (that I know of).

There's a few work arounds though:

Use grunt and concat and then just require that behemoth...I know, kinda sucky.

What I think is a better solution... use a require hierarchy like so:

require('/js/controllers/init', function(ctrls){
    ctrls(app, globals);
});

// /js/controllers/init.js
define('js/controllers/index', 'js/controllers/posts', function(index, posts){
    return function protagonist(app, globals){
        var indexModule = index(app, globals);
        var indexModule = posts(app, globals);

        return app || someModule;
    };
});

// /js/controllers/index.js
define('js/controllers/index', 'js/controllers/posts', function(index, posts){
    return function protagonist(app, globals){
        function method1(){}
        function method2(){}

        return {
           m1: method1,
           m2: method2
        };
    };
});

Note that "protagonist" function. That allows you to initialize modules before their use, so now you can pass in a 'sandbox' -- in this case app and globals.

Realistically, you wouldn't have /js/controllers/index.js... It should probably be something like /js/controllers/index/main.js or /js/controllers/index/init.js so that there is a directory adjacent to (sibling of) /js/controllers/init.js called "index". This will make your modules scalable to a given interface -- you can simply swap modules out and keep your interface the same.

Hope this helps! Happy coding!



回答5:

I wrote a library to solve this problem. Eventually someone else came along and improved my library, here it is:

https://github.com/smartprocure/directory-metagen

You can use my lib with Gulp or whatever - it generates metadata for your project and RequireJS can use that metadata to require the desired files from the filesystem.

Using this lib will produce a RequireJS module that looks something like this:

define(
    [
        "text!app/templates/dashboardTemplate.ejs",
        "text!app/templates/fluxCartTemplate.ejs",
        "text!app/templates/footerTemplate.ejs",
        "text!app/templates/getAllTemplate.ejs",
        "text!app/templates/headerTemplate.ejs",
        "text!app/templates/homeTemplate.ejs",
        "text!app/templates/indexTemplate.ejs",
        "text!app/templates/jobsTemplate.ejs",
        "text!app/templates/loginTemplate.ejs",
        "text!app/templates/overviewTemplate.ejs",
        "text!app/templates/pictureTemplate.ejs",
        "text!app/templates/portalTemplate.ejs",
        "text!app/templates/registeredUsersTemplate.ejs",
        "text!app/templates/userProfileTemplate.ejs"
    ],
    function(){

        return {

            "templates/dashboardTemplate.ejs": arguments[0],
            "templates/fluxCartTemplate.ejs": arguments[1],
            "templates/footerTemplate.ejs": arguments[2],
            "templates/getAllTemplate.ejs": arguments[3],
            "templates/headerTemplate.ejs": arguments[4],
            "templates/homeTemplate.ejs": arguments[5],
            "templates/indexTemplate.ejs": arguments[6],
            "templates/jobsTemplate.ejs": arguments[7],
            "templates/loginTemplate.ejs": arguments[8],
            "templates/overviewTemplate.ejs": arguments[9],
            "templates/pictureTemplate.ejs": arguments[10],
            "templates/portalTemplate.ejs": arguments[11],
            "templates/registeredUsersTemplate.ejs": arguments[12],
            "templates/userProfileTemplate.ejs": arguments[13]
        }
    });

You can then require modules in your front-end like so:

var footerView = require("app/js/jsx/standardViews/footerView");

however, as you can see this is too verbose, so the magic way is like so:

name the dependency above as allViews!

now you can do:

var allViews = require('allViews');
var footerView = allViews['standardViews/footerView'];

There are two advantages to requiring directories whole:

(1) in production, with the r.js optimizer, you can point to one dependency (module A) and it can then easily trace all of A's dependencies that represent a entire directory

(2) in development, you can require whole directories up front and then use synchronous syntax to require dependencies because you know they have already been loaded

enjoy "RequireJS-Metagen"

https://github.com/smartprocure/directory-metagen

https://www.npmjs.com/package/requirejs-metagen

https://github.com/ORESoftware/requirejs-metagen