rolaveric / karma-systemjs

Karma plugin for using SystemJS as a module loader
MIT License
40 stars 19 forks source link

base in Baseurl breaks import of modules from other systemjs bundles #34

Open piebe opened 9 years ago

piebe commented 9 years ago

My code uses an external created systemjs bundle.

The bundle is loaded by using karma.config.files. ie

   files: [
      {pattern: 'bower_components/external_bundle.js', included: true}
   ],

When running the tests the bundle is loaded and the containing modules are registered. For example jquery can be found in System.defined as http://localhost:9876/angular.js

However when the test (or the code being tested) requests the module it searches for http://localhost:9876/base/angular.js and the module cannot be found since the base part is not part of the registered name...

rolaveric commented 9 years ago

I've never used it myself, but does bundle property in the SystemJS config help? https://github.com/systemjs/systemjs/blob/master/docs/config-api.md#bundle

The biggest pain is that you need to list out the modules included in the bundle, or at least one which you know will always get loaded. We could change 'karma-systemjs' to always load any bundles it finds in the SystemJS config ahead of time.

piebe commented 9 years ago

I'm not sure how this would solve the problem? The bundle is actually already loaded properly. The problem is that the module names inside the bundle register with a name that does not start with /base/ while karma-systemjs changes the baseurl to /base/. The code under test does search the modules out of the bundle with the /base/ prefix and the modules are not found.

I found a workaround by listing all modules inside the bundle in config.paths and let them jump out of the base directory. Again using angular.js as an example:

paths: {
   'angular.js': '../angular.js'
}  

However, given the number of modules and bundles I will be working with, this is not a very practical solution.

rolaveric commented 9 years ago

Wow, ok. I always assumed the baseURL was only ever used when retrieving modules from the server. Once they were retrieved (eg. Run through System.register()), I assumed the baseURL was forgotten. Though I suppose that would cause problems if you legitimately had to go below the base. eg. Folders 'app/' and 'lib/', and 'app/' is the baseURL.

Hmmm. I'm not sure. I can only think of 3 possible solutions:

I know https://github.com/Workiva/karma-jspm supports bundles. I'll take a closer look at how they solve it.

richburdon commented 9 years ago

I think I have a related issue:

My config.js (in src/main/javascript) has:

System.config({
  "baseURL": "/js"
}

In my karma.conf.js (in /) I can fix "paths":

"basePath": "src/main/javascript",
"config": {
    "paths": {
      // Add extra ../ to counteract '/js' above:
      "babel": "../../../../node_modules/babel-core/browser.js",

But I can't fix "files":

  "files": [
    "app/testing/**/*.js",
  ],

So I get errors:

[web-server]: 404: /base/js/app/main.js   (unwanted /js)

In this case, I just want to karma-systemjs to recognize the config.js baseURL param and adjust path requests accordingly (i.e., in this case serve "files" from "/js"). Or provide a "config" baseURL option.

[Side question]: AFAYAC, what are the pros and cons of karma-systemjs vs karma-jspm?

rolaveric commented 9 years ago

Hi @richburdon

Your issue isn't related because it doesn't look like you're using bundles anywhere. You can still use paths for files, you just need to understand that files paths will be mapped to /base/{baseURL}/{path} on the browser. So you should be able to use paths['app/testing/*'] = '/src/main/javascript/app/testing/*'; or something similar to get your paths working.

For your side question, the philosophical differences between karma-systemjs and karma-jspm is that karma-jspm assumes you're using JSPM, and therefore can make certain predictions by using your package.json file, while karma-systemjs can only assume you're using SystemJS - nothing more, nothing less. My personal recommendation is that if you're using JSPM, then use karma-jspm.

Also sorry @piebe, I haven't had the time to look into bundles yet. I'd be more than happy to merge in a PR if anyone else gets to it first.

antoineol commented 8 years ago

Hi, I think I have a similar issue as described by @piebe. In karma.conf.js config object, I register the Angular 2 dev bundle that registers many modules:

files: [
  "../../node_modules/angular2/bundles/angular2.dev.js",
  // ...
],

The bundle registrations in angular2.dev.js are in the format:

System.register("angular2/angular2", ["angular2/common", /* ... other dependencies */], true, function(require, exports, module) {
  // ...
});

I can see in logs/Chrome dev tool that the angular 2 bundle is well loaded, but all other imports are not. Example, errors from Karma logs:

11 12 2015 21:03:22.490:WARN [web-server]: 404: /base/angular2/http.js
11 12 2015 21:03:22.554:WARN [web-server]: 404: /base/angular2/http/testing.js
11 12 2015 21:03:22.569:WARN [web-server]: 404: /base/angular2/testing.js
11 12 2015 21:03:22.606:WARN [web-server]: 404: /base/angular2/angular2.js

I think it is caused by imports like import {...} from 'angular2/angular2.

But I don't know if it is related to the /base/ prefix. When I apply @piebe's workaround, I get instead:

11 12 2015 21:13:57.521:WARN [web-server]: 404: /angular2/testing.js
11 12 2015 21:13:57.676:WARN [web-server]: 404: /angular2/http/testing.js
11 12 2015 21:13:57.706:WARN [web-server]: 404: /angular2/angular2.js

Loading modules this way works well when the Angular 2 bundle is loaded in index.html through a <script> tag.Unfortunately, we cannot rely on browser to load these files since we need to automate with Karma :) So far I haven't found a way to make it work without wepback preparing a test bundle, which is too heavy.

Any idea about how to solve it? Thanks a lot!

bryanforbes commented 8 years ago

Currently, there are two ways to solve this problem with karma-systemjs. The first is to add bundles and meta configuration in either your SystemJS config file, or in the systemjs.config property of your karma config. I have all of my angular2 bundles copied to <baseURL>/lib, so the following config reflects that:

System.config({
    defaultJSExtensions: true,
    bundles: {
        'lib/router.dev': [ // path to bundle file
            'angular2/router' // module ID contained within the bundle
        ],
        'lib/http.dev': [
            'angular2/http'
        ],
        'lib/testing.dev': [
            'angular2/testing',
            'angular2/router/testing',
            'angular2/http/testing'
        ],
        'lib/angular2.dev': [
            'angular2/core',
            'angular2/common',
            'angular2/compiler',
            'angular2/platform/browser',
            'angular2/src/facade/lang'
        ],
        'lib/Rx': [
            'rxjs/Rx'
        ]
    },
    meta: {
        'lib/es6-shim': {
            exports: 'Symbol',
            format: 'global'
        },
        'lib/Rx': {
            format: 'register',
            deps: [ 'lib/es6-shim' ]
        },
        'lib/angular2.dev': {
            format: 'register',
            deps: [ 'lib/Rx' ]
        },
        'lib/router.dev': {
            format: 'register',
            deps: [ 'lib/angular2.dev' ]
        },
        'lib/http.dev': {
            format: 'register',
            deps: [ 'lib/angular2.dev' ]
        },
        'lib/testing.dev': {
            format: 'register',
            deps: [ 'lib/router.dev', 'lib/http.dev' ]
        }
    }
});

This will tell SystemJS where to find the correct bundles for the angular2 module IDs. You'll also need to add entries for these files in systemjs.serveFiles.

The second way to solve this is to pass an object in the systemjs.serveFiles array. However this exposes a problem: the files array in the karma configuration has its objects normalized before framework.js is run, so objects passed through this array will not match any paths known to karma unless they are manually normalized. My karma configuration for this solution currently looks like this (found at test/karma.conf.js):

var path = require('path');
config.set({
    basePath: '..',

    // ...

    systemjs: {
        configFile: 'www/system.config.js',

        // before SystemJS is loaded
        includeFiles: [
            'www/lib/es6-shim.js'
        ],

        serveFiles: [
            { pattern: path.resolve(__dirname, '../www/lib/Rx.js'), included: true },
            { pattern: path.resolve(__dirname, '../www/lib/angular2.dev.js'), included: true },
            { pattern: path.resolve(__dirname, '../www/lib/router.dev.js'), included: true },
            { pattern: path.resolve(__dirname, '../www/lib/http.dev.js'), included: true },
            { pattern: path.resolve(__dirname, '../www/lib/testing.dev.js'), included: true },
            '**/*.js',
            '**/*.css',
            '**/*.html'
        ]
    },

    // ...
});

IMO, both solutions are valid (it just depends on how much you rely on the loader in your setup), but the second solution (at this time) is a bit clunky.

bryanforbes commented 8 years ago

I spoke too soon on the second method I outlined above. It doesn't quite work as easily as that. The bundles are going to be included on the page before the loader is configured, which means they will get registered before the baseURL is set. This means that the module angular2/core will be registered as the module http://localhost:9876/angular2/core. Once the loader is configured, the baseURL will be set to something in http://localhost:9876/base/ and any lookup of angular2/core will look for the module http://localhost:9876/base/angular2/core, which doesn't exist. To alleviate this problem, the following paths and packages configuration have to be passed to systemjs.config in your karma config:

packages: {
    angular2: { defaultExtension: false },
    rxjs: { defaultExtension: false }
},
paths: {
    systemjs: 'lib/system.src.js',
    'system-polyfills': 'lib/angular2-polyfills.js',
    'angular2/*': '/angular2/*',
    'rxjs/*': '/rxjs/*'
}

This extra paths and packages configuration could be avoided if there was a configuration option to include files as script tags after configuring the loader, but before importing spec files.