Search code examples
javascriptangularjskarma-runnergoogle-closure-librarycommonjs

Uncaught ReferenceError: require is not defined


For a project that I am working on I have been using a hodgepodge of JavaScript libraries. The main logic of my code is broken down into multiple commonjs modules. I use google closure to combine the modules into one output js file which I use within my AngularJS application.

The problem I am having is trying to perform tests with testacular. There error I receive is Uncaught ReferenceError: require is not defined. It is happening because, unlike google closure, testacular doesn't understand commonjs modules. There are a couple work arounds I can do, but I was hoping to make it work without having to restructure my code.

  1. I can restucture the modules so that I'm no longer using commonjs. I don't like this because it feels like a step backwards. I want my code to be modular.
  2. I can run testacular on the compiled js from google closure. I don't mind doing it this way, but I have not been able to trigger everything to work on file changes. Testacular can re-run itself on file changes, but I haven't seen anyway to make google closure re-compile on changes.
  3. Finally, I can enable commonjs module in testacular. Ideally this is the way I want to go, but it may not be the easiest.

Has anyone else run into a similar problem? I'm open for trying different things; I just don't want anything hacky.

javaclassstreamreader.spec.js:

"use strict"

var JavaClassStreamReader = require('../javaclassstreamreader.js').JavaClassStreamReader;

describe('javaclassstreamreader', function() {

  it('reader can be constructed', function() {
    var dataView = {
      byteLength : 0
    };
    //FIXME load dataView

    var reader = new JavaClassStreamReader(dataView);
    expect(reader.dataView).toBe(dataView);
    expect(reader.offset).toBe(0);
    expect(reader.maxOffset).toBe(0);
  });

});

javaclassstreamreader.js:

function JavaClassStreamReader(dataView, initialOffset, maxBytesToRead) {
  this.dataView = dataView;
  this.offset = initialOffset || 0;
  this.maxOffset = this.offset + (maxBytesToRead || this.dataView.byteLength);
}
//... code trucated ...

Solution

  • I was not able to make it work with require, but I do have a partial solution.

    grunt.js:

    /*global module:false*/
    module.exports = function(grunt) {"use strict";
    
      // Project configuration.
      grunt.initConfig({
        pkg : '<json:package.json>',
        meta : {
          banner : '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + '<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' + '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
        },
        lint : {
          files : ['grunt.js', 'src/*.js', 'src/public/js/**/*.js', 'src/specs/**/*.js']
        },
        watch : {
          files : '<config:lint.files>',
          tasks : 'default'
        },
        exec : {
          ensure_generated_directory : {
            command : 'mkdir -p generated/js/'
          }
        },
        clean : {
          all : ['generated']
        },
        jshint : {
          files : '<config:lint.files>',
          options : {
            curly : true,
            eqeqeq : true,
            forin : true,
            immed : true,
            latedef : true,
            newcap : true,
            noarg : true,
            sub : true,
            undef : true,
            unused : true,
            strict : true,
            boss : true,
            eqnull : true,
            es5 : true,
            browser : true,
            jquery : true,
            devel : true
          },
          globals : {
            //jasmine
            describe : false,
            it : false,
            expect : false,
            //commonjs
            require : false,
            exports : true,
            //angular
            angular : false
          }
        },
        'closure-compiler' : {
          frontend : {
            closurePath : 'closure-compiler',
            js : ['src/*.js', 'src/public/js/**/*.js'],
            jsOutputFile : 'generated/js/complete-app.js',
            options : {
              externs : 'externs.js',
              compilation_level : 'SIMPLE_OPTIMIZATIONS',
              language_in : 'ECMASCRIPT5_STRICT',
              logging_level : 'ALL',
              debug : null,
              warning_level : 'verbose',
              summary_detail_level : 3,
              formatting : ['PRETTY_PRINT', 'PRINT_INPUT_DELIMITER'],
              common_js_entry_module : 'src/public/js/app.js',
              process_common_js_modules : null,
              process_jquery_primitives : null,
              common_js_module_path_prefix : 'src'
            }
          }
        },
        testacularServer : {
          integration : {
            options : {
              keepalive : true
            },
            configFile : 'testacular.conf.js',
            autoWatch : false,
            singleRun : true
          }
        }
      }); 
    
      // Default task.
      grunt.registerTask('default', 'lint exec:ensure_generated_directory closure-compiler testacularServer:integration');
      grunt.loadNpmTasks('grunt-contrib-watch');
      grunt.loadNpmTasks('grunt-closure-compiler');
      grunt.loadNpmTasks('grunt-exec');
      grunt.loadNpmTasks('grunt-contrib-clean');
      grunt.loadNpmTasks('grunt-testacular');
    };
    

    I can run grunt watch and I get a similar result. grunt lints, then compiles, then runs testacular. This isn't as fast as I was hoping. testacular starts and stops the server each time.