UPDATE: I tried Jeff Barczewski's answer below, and though I no longer get the error below for plugins, I am now getting a different error:
Error: TypeError: Cannot read property 'normalize' of undefined
In module tree:
mymodule/core
at Object.<anonymous> (/usr/local/lib/node_modules/requirejs/bin/r.js:1193:35)
UPDATE 2:
Since Jeff is correct that Dojo's plugins are not compatible with RequireJS, I decided to switch to using grunt-dojo instead for building dojo. I still use RequireJS for my own code and simply override the dojo dependencies to be ignored.
Original Post:
I'm trying to use grunt to compile a single JS file to lower the amount of HTTP requests browsers need to make. Since Dojo 1.9 is AMD-compliant, I figured I'd use Grunt's requirejs plugin to optimize my code. However, I am receiving the following error, both when using the Grunt plugin and when using r.js directly:
>> Tracing dependencies for: mymodule/core
>> TypeError: Cannot call method 'createElement' of undefined
>> In module tree:
>> mymodule/core
>> dojo/behavior
>> dojo/query
>> dojo/selector/_loader
{ [Error: TypeError: Cannot call method 'createElement' of undefined
In module tree:
mymodule/core
dojo/behavior
dojo/query
dojo/selector/_loader
at eval (eval at <anonymous> (/Users/EugeneZ/Workspace/presentment/web/js/node_modules/grunt-contrib-requirejs/node_modules/requirejs/bin/r.js:23690:38), <anonymous>:6:24)
]
originalError:
{ [TypeError: Cannot call method 'createElement' of undefined]
moduleTree:
[ 'dojo/selector/_loader',
'dojo/query',
'dojo/behavior',
'mymodule/core' ],
fileName: '/Users/EugeneZ/Workspace/presentment/web/js/dojo_release/dojo/selector/_loader.js' } }
Looking at the code for the _loader Dojo module, it's assuming it's running in a browser and relying on the document global:
var document;
var testDiv = document.createElement("div");
But why does requirejs not allow this? I've searched their documentation and can't find any way to turn this check off. I'm assuming I'm misunderstanding something or doing something wrong, but can't figure it out.
Here is the requirejs-relevant portion of my Gruntfile.js:
requirejs: {
compile: {
options: {
'baseUrl': './',
'paths': {
'dojo': 'dojo_release/dojo',
'dojox': 'dojo_release/dojox',
'dijit': 'dojo_release/dijit',
'mymodule' : 'core/mymodule',
'osi': 'osi',
'demo': 'demo',
'slick': 'core/slick'
},
'name': 'mymodule/core',
'out': './mymodule.js'
}
}
}
Dojo has several plugins that it uses that are not compatible with the r.js builder/optimizer. The best thing to do is to log an issue on https://bugs.dojotoolkit.org to have someone add the necessary plugin hooks so this can be resolved.
An alternative is to switch over to using the dojo builder, but it doesn't appear to create code that requirejs can use, so you would need to use their loader too, not requirejs. (dojo builder uses some proprietary? {cache:...} option for doing its dependencies rather than just inlining defines, so I could not find a way to build and load with requirejs).
Another work around (until dojo fixes the plugins to be compatible) if you want to stay with requirejs (like I did), you can exclude files that use these dojo plugins from the optimization and just have those files loaded separately unoptimized. So most of your files can be optimized except for ones using these plugins. Its not perfect but it gets you closer. Requirejs will simply load most of the files in optimized fashion and then just fetch those excluded ones individually at runtime.
To do this, add to your r.js build.js exclusions for specific files that use plugins which error out. So after running the build and you get that error, add to your paths the file that is using the plugin, ie. the second to last in the stack trace.
So add to your r.js build options
paths: {
'dojo/query': 'empty:', // this will exclude it from the build
Then run your build again and repeat until you have gotten all the other files that error.
When I was trying to build dojo's dgrid, I ended up with the following exclusions:
paths: {
// r.js having issues building these, the plugins are not
// compatible so let them load normally unoptimized
'dgrid/extensions/ColumnHider': 'empty:',
'put-selector/put': 'empty:'
'dojo/i18n': 'empty:',
'dojo/selector/_loader': 'empty:',
'dojo/query': 'empty:',
'dgrid/extensions/ColumnResizer': 'empty:',
'dgrid/List': 'empty:',
'xstyle/css': 'empty:'
Your list may vary based on what you are using.
Then just have these files (and their dependencies) available when running and requirejs will load them as it does in development. So at least the majority of your files will be loaded optimized from one file, then these will load after.
Note: Asking dojo to make the plugins r.js builder/optimizer compatible is the ultimate solution so we wouldn't need to do this hack. So even if you use this work around, please add an issue for dojo so this can get resolved once and for all. Mention all of the files that you ended up having to exclude to help the developers know what to fix. Then post a comment here for others to +1.
Related
In my project I have long used require.js together with the pdf.js library. Pdf.js have until recently been putting itself on the global object. I could still use it in my requirejs config by using a shim. The pdfjs library will in turn load another library called pdf.worker. In order to find this module the solution was to add a property to the global PDFJS object called workerSrc and point to the file on disk. This could be done before or after loading the pdfjs library.
The pdfjs library uses the pdf.worker to start a WebWorker and to do so it needs the path to a source file.
When I tried to update the pdfjs library in my project to a new version (1.5.314) the way to load and include the library have changed to use UMD modules and now everything get's a bit tricky.
The pdfjs library checks if the environment is using requirejs and so it defines itself as a module named "pdfjs-dist/build/pdf". When this module loads it checks for a module named "pdfjs-dist/build/pdf.worker". Since I have another folder structure I have added them to my requirejs config object with a new path:
paths: {
"pdfjs-dist/build/pdf": "vendor/pdfjs/build/pdf",
"pdfjs-dist/build/pdf.worker": "vendor/pdfjs/build/pdf.worker"
}
This is to make the module loader to find the modules at all. In development this works great. When I try to use the requirejs optimizer in my grunt build step however, it will put all of my project files into one single file. This step will try to include the pdf.worker module as well and this generates an error:
Error: Cannot uglify2 file: vendor/pdfjs/build/pdf.worker.js. Skipping
it. Error is: RangeError: Maximum call stack size exceeded
Since the worker source needs to be in a single file on disk I don't want this module to be included.
So I've tried two different config-settings in the requirejs config.
The first attempt was to override the paths property in my grunt build options:
paths: {
"pdfjs-dist/build/pdf.worker": "empty:"
}
The second thing to test is to exclude it from my module:
modules: [{
name: "core/app",
exclude: [
"pdfjs-dist/build/pdf.worker"
]
}]
Both techniques should tell the optimizer not to include the module but both attempts ended up with the same error as before. The requirejs optimizer still tries to include the module into the build and the attempt to uglify it ends up with a RangeError.
One could argue that since the uglify step fails it will not be included and I can go about my bussiness, but if the uglify step should happen to start working at a new update of pdfjs - what then?
Can anyone help me figure out why the requirejs config won't just exclude it in the build step and how to make it do so.
I found out what the core of my problem was and now I have a way to solve the problem and make my build process to work. My build step in grunt is using grunt-contrib-requirejs and I needed to override some options in the config for this job.
I didn't want the pdf.worker module to be included in my concatenated and minified production code.
I didn't want r.js to minify it only to later exclude it from the concatenated file.
I tried to solve the first problem thinking that it would mean that the second problem also should be solved. When I figured out the two were separate I finally found a solution.
In the r.js example on github there is a property named fileExclusionRegExp. This is what I now use to tell r.js not to copy the file over to the build folder.
fileExclusionRegExp: /pdf.worker.js/
Second, I need to tell the optimizer to not include this module in the concatenated file. This is done by overriding the paths property for this module to the value of "empty:".
paths: {
"pdfjs-dist/build/pdf.worker": "empty:"
}
Now my grunt build step will work without errors and all is well.
Thanks to async5 for informing me about the bug with uglify and the pdf.worker. The workaround is applied in another grunt task that uglify the worker and copies it into the build-folder separately. The options object for the grunt-contrib-uglify task will need this property in order to not break the pdf.worker file:
compress: {
sequences: false
}
Now my project works great when built for production.
I've come across an interesting problem in NodeJS that pops up now and then at work, and I haven't been able to figure it out.
I've written a back-end in CoffeeScript, which I then compile with grunt-contrib-coffee into Javascript in a ~/bin directory. I also include a library that I privately host on Bitbucket with the appropriate private keys, and install through npm. This library too is written in coffeescript.
Usually I'm able to include this library in Javascript without any headaches, using a simple require just like I would for any other library. However, occasionally one of the servers that's using the back-end gets updates, and it stops working. When I go check the error, it's always the same - 'require' passes, but instead of loading the actual library in JavaScript, it returns an empty object ({}). Running the code in coffeescript still works, but regardless of what I do - recompile, reinstall all dependencies, remove and clone the repository, the problem persists.
I've run out of ideas of what it might be myself, so I'm hoping that someone here has come across the problem before and might be able to point me in the right direction.
In the library package.json:
{
"name": "graph-engine",
"main": "./lib/graph"
}
In the library's graph.coffee
class Graph
constructor: () ->
# Perform init
module.exports = Graph
Then in the app's package.json:
{
"graph-engine": "git+ssh://git#bitbucket.org:<team>/graph-engine.git"
}
Finally, in the app itself:
GraphEngine = require "graph-engine"
engineInstance = new GraphEngine()
This works fine in coffeescript, but when compiling the app using grunt with the following setup for grunt-contrib-coffee:
coffee:
glob_to_multiple:
expand: true
flatten: false
cwd: 'src'
src: ['**/*.coffee']
dest: 'bin'
ext: '.js'
It fails to load the library correctly when running the compiled application, instead returning an empty object. Again, I'd like to emphasise that this does not always happen, and as such I didn't include any code or json files as I believed that it was unrelated.
Although the exact reason for the randomness of the behaviour eludes me to this day, I have found that it is related to these libraries being written in CoffeeScript.
It seems that depending on the load order, occasionally third party libraries would register coffee script and my own libraries would load correctly, whereas other times these libraries would load after my own libraries had loaded, resulting in an inability to load coffeescript. Therefore, the solution turned out to be fairly straightforward - register coffeescript. This can be done by putting the following at the start of the node.js app:
require('coffee-script/register')
Refer to the documentation for further information.
In my Angular JS app, I'm using a lot of third party packages, mainly maintained via Bower.
When I use Grunt to concatenate all of them into one mega file, I'm getting errors when I load my page, for example that
Uncaught ReferenceError: angular is not defined and
GET http://localhost:8080/theproj/v4/dist/app/bootstrap.css.map 404 (Not Found)
What is the best way to properly concatenate all these files to ensure that everything loads in the right order and doesn't cause problems?
First issue: A lot of times third party libraries must be loaded in a particular order. That looks like like it's the source of your first issue. Something is trying to use angular and it's getting loaded before the angular code. You should refactor your grunt task to use a pre-defined order for third party libraries.
Second issue: You probably are missing the .map file. This is a file used by Chrome dev tools to show you the original source for the css (sass or less). Either provide the map file, or delete the reference to it from bootstrap.css. Or just ignore the error, it's only an error when you have chrome dev tools open, and doesn't actually affect your application.
For the issue of the correct order for your javascript files, i had that problem in a larger project where noone really had a clue which was the correct order.
Luckily we found that the Google Closure Compiler does exactly this: https://github.com/google/closure-compiler
You give it all your js files and it analyzes them all and concatenates them in order
$ java -jar compiler.jar --js_output_file=out.js in1.js in2.js in3.js ...
There is even a grunt plugin for the connection: https://github.com/gmarty/grunt-closure-compiler
'closure-compiler': {
frontend: {
closurePath: '/src/to/closure-compiler',
js: 'static/src/frontend.js',
jsOutputFile: 'static/js/frontend.min.js',
maxBuffer: 500,
options: {
compilation_level: 'ADVANCED_OPTIMIZATIONS',
language_in: 'ECMASCRIPT5_STRICT'
}
}
},
Another way would be to change your javascripts into AMD or CommonJS modules, this way you don't have to worry about the correct order. RequireJS (http://requirejs.org/docs/start.html) is a possibility for AMD for example or Webpack (http://webpack.github.io/) ...or many many others.
I am refactoring my application to use RequireJS (using the sbt-web abstraction in Play Framework, but that isn't germane).
First, I simply sought to use a shim to load FineUploader since it isn't AMD-compatible, but I encountered a "MegaPixImage is not defined" error. That seemed weird to me since FineUploader has no dependencies (as third-party stuff is built-in).
But after seeing this Stack Overflow post, I downloaded the library separately and set up my RequireJS configuration this way (in CoffeeScript):
requirejs.config(
paths:
megapiximage: './megapiximage'
fineUploader: './custom.fineuploader'
shim:
fineUploader:
exports: 'fineUploader'
deps: ['jquery', 'megapiximage' ]
)
require(['./main'], (main) ->
require(['fineUploader', './myfile'])
return
)
However, I still get the same error even though the library is AMD-compatible.
Any insight into how I should set up my RequireJS configuration is appreciated.
The FineUploader package references the 'MegaPixImage' variable, so you need to make that available. I think the following should work:
require(['fineUploader', 'megapiximage'], function(fineUploader, MegaPixImage) {
// initialize your fineuploader here
})
Note that you have actually included the MegaPixImage library twice now. I therefore decided to keep FineUploader out of my RequireJS setup and add it as a seperate javascript file in the HTML.
In order to share AMD modules between the browser and node, I'm using RequireJS in both places (see RequireJS in Node).
server/dataDao.js:
var requirejs = require('requirejs');
var dataDao = module.exports = {};
requirejs(['client/resource'], function (Resource) {
...
});
client/resource.js:
define(['underscore'], function (_) {
...
});
This has generally been working ok, as long as the shared module does not involve any dependencies that don't work on the server.
I would like to introduce use.js, however, to avoid manually wrapping non-AMD compatible libs like underscore. To do so, I have to prefix listed dependencies with use! (to trigger the use plugin in RequireJS).
client/resource.js:
define(['use!underscore'], function (_) {
...
});
This works well in the browser, but causes errors in Node:
Error: Calling node's require("use") failed with error: Error: Cannot find module 'use'
I believe this is not specific to the use plugin, but also text etc.
Has anyone else encountered this problem?
With version 0.3 of use I was able to get it working in node. Per the comment on your question, it may be a configuration issue -- it took me a couple of tries to get the pathing right.