配置 Karma 以使用 requirejs 加载 pegjs

Configure Karma to load pegjs with requirejs

正在尝试使用 PegJS 和 requirejs 测试项目。 我有几个源文件,实现为通过 require API 加载的 AMD 模块(定义)。 目录结构下:

js/
   somefile.js
   main.js
   parser.js
test/
   parser.spec.js

我已经编写了一个 parser.js 模块来加载 PegJS 语法文件并使用 PegJS 创建一个 peg 解析器:

define(function() {
  'use strict';

  var PEG = require('pegjs');
  var grammarFile = 'grammar.peg'

return {
  parse: function(fs, content, debug) {
    var grammar = fs.readFileSync(grammarFile, 'utf8').toString();
    // Build parser from grammar
    var parser = PEG.buildParser(grammar, { trace: debug });
    [...]

这与在命令行上使用 node.js 执行的 main.js 配合得很好。 现在我想使用 karma、jasmine 和 PhantomJS 测试我的项目。我有一个 karma.conf.js 这样的:

frameworks: ['jasmine', 'requirejs'],
files: [
  { pattern: './test/**/*.spec.js', included: false },
  { pattern: './js/**/*.js', included: false},
  './test/test-main.js',
],

还有一个名为 test-main.js 的 require bootstrap 文件,它是这样配置的:

'use strict';

var allTestFiles = [];
var TEST_REGEXP = /(spec|test)\.js$/i;

// Get a list of all the test files to include
Object.keys(window.__karma__.files).forEach(function(file) {
  console.log(file);
  if (TEST_REGEXP.test(file)) {
    // Normalize paths to RequireJS module names.
    // If you require sub-dependencies of test files to be loaded as-is (requiring file extension)
    // then do not normalize the paths
    var normalizedTestModule = file.replace(/^\/base\/|\.js$/g, '');
    allTestFiles.push(file);
  }
});

require.config({
  // Karma serves files under /base, which is the basePath from your config file
  baseUrl: '/base/js',
  // dynamically load all test files
  deps: allTestFiles,
  // we have to kickoff jasmine, as it is asynchronous
  callback: window.__karma__.start
});

现在,当我启动测试 (grunt karma) 时,我收到了这个错误:

PhantomJS 1.9.8 (Linux 0.0.0) ERROR: Error{message: 'Module name "pegjs" has not been loaded yet for context: _. Use require([])

所以我尝试用这种方式在Karma加载的文件中包含pegjs karma.conf.js:

files: [
  { pattern: 'node_modules/pegjs/lib/**/*.js', included: true  },
  { pattern: './test/**/*.spec.js', included: false },
  { pattern: './js/**/*.js', included: false},
  './test/test-main.js'
],

当我这样做时,我仍然得到一个错误:

Error: Module name "utils/arrays" has not been loaded yet for context: _. Use require([])

查看pegjs模块内部,确实有一个arrays.js文件:

compiler/
compiler.js
grammar-error.js
parser.js
peg.js
utils/
  arrays.js
  classes.js
  objects.js

所以也尝试包含数组:

files: [
  { pattern: 'node_modules/pegjs/lib/utils/arrays.js', included: true },
  { pattern: 'node_modules/pegjs/lib/**/*.js', included: true  },
  { pattern: './test/**/*.spec.js', included: false },
  { pattern: './js/**/*.js', included: false},
  './test/test-main.js'
],

我得到:

ReferenceError: Can't find variable: module
at /blabla/node_modules/pegjs/lib/utils/arrays.js:108

因为:

108 module.exports = arrays;

所以,为了加载 npm 模块,我尝试以这种方式加载 bower 模块:

files: [
  { pattern: 'bower_components/pegjs/peg-0.9.0.js', included: true },
  { pattern: './test/**/*.spec.js', included: false },
  { pattern: './js/**/*.js', included: false},
  './test/test-main.js'
],

又来了:

PhantomJS 1.9.8 (Linux 0.0.0) ERROR: Error{message: 'Module name "pegjs" has not been loaded yet for context: _. Use require([])

还尝试不在 karma 生成的网页中包含 pegjs:

files: [
  { pattern: 'bower_components/pegjs/peg-0.9.0.js', included: false },
  { pattern: './test/**/*.spec.js', included: false },
  { pattern: './js/**/*.js', included: false},
  './test/test-main.js'
],

但它失败了:

PhantomJS 1.9.8 (Linux 0.0.0) ERROR: 'There is no timestamp for /base/bower_components/pegjs/peg-0.9.0!'

尝试将 bower_component 文件夹放入 js 文件夹,但没有成功。

所以我不知道从这里开始......在 Google 或这里找不到任何相关内容。这似乎是 requirejs/pegjs 因果报应的特定问题...欢迎任何想法。

根据丹的回答更新:

所以我在 parser.js:

中从同步需求切换到异步需求
define(['../bower_components/pegjs/peg-0.9.0'], function(PEG) {
  'use strict';

  var grammarFile = 'grammar.peg'

return {
  parse: function(fs, content, debug) {
    var grammar = fs.readFileSync(grammarFile, 'utf8').toString();
    // Build parser from grammar
    var parser = PEG.buildParser(grammar, { trace: debug });
    [...]

试图在 karma.conf.js:

中包含 pegjs bower 组件
{ pattern: 'bower_components/pegjs/peg-0.9.0.js', included: false },

或不包含:

{ pattern: 'bower_components/pegjs/peg-0.9.0.js', included: true },

但总是得到同样的错误:

Error: Script error for "/blabla/bower_components/pegjs/peg-0.9.0", needed by: /blabla/js/parser.js
http://requirejs.org/docs/errors.html#scripterror
at /blabla/node_modules/requirejs/require.js:140

是的,文件存在:

$ file /home/aa024149/share/logofrjs/bower_components/pegjs/peg-0.9.0.js 
/blabla/bower_components/pegjs/peg-0.9.0.js: ASCII text, with very long lines

UPDATE2:终于理解并找到了可以接受的解决方案。

听起来你正在通过 requirejs 加载 pegjs。如果是这种情况,则 pegjs 不应是包含的文件。在您的 karma.conf.js 中,您是否尝试过以下操作:

files: [
  { pattern: 'bower_components/pegjs/peg-0.9.0.js', included: false },
  { pattern: './test/**/*.spec.js', included: false },
  { pattern: './js/**/*.js', included: false},
  './test/test-main.js'
],

included 的值表示 karma 服务器生成的网页是否应该有该文件的脚本标签(参见 http://karma-runner.github.io/0.13/config/files.html)。所以你的 karma.config 的:

files: [
  { pattern: 'bower_components/pegjs/peg-0.9.0.js', included: true },
  { pattern: './test/**/*.spec.js', included: false },
  { pattern: './js/**/*.js', included: false},
  './test/test-main.js'
],

会导致karma生成一个head标签类似于:

的网页
<head>
  <script src="/base/bower_components/pegjs/peg-0.9.0.js"></script>
  <script src="/base/require.js"></script>
  <script src="/base/test/test-main.js"></script>
</head>

根据我的经验,我见过很多与此类似的行为,这些行为都是由于将我的文件标记为 included: true 而导致的。如果您尝试使用 requirejs 加载某个文件,请确保将其标记为 included: false.

我认为这标志着它是一项预处理工作,但我不完全确定为什么会产生如此大的不同。

据我所知,Karma 是一个测试框架,它将 运行 您在浏览器中的测试。

这不适合测试很多节点模块。

浏览器无法同步执行此操作:var PEG = require('pegjs')。这就是它要求您使用 require([]) 的原因,当 pegjs 完成加载时,您会传递一个要执行的回调。

使用 pegjs 的 bower 版本并确保在调用 require('pegjs') 之前加载它可能会有所帮助。这将确保已经为上下文 _ 加载了 pegjs(我认为是默认的 requirejs 上下文)。

它也无法使用 fs.readFileSync(grammarFile, 'utf8') 从文件系统加载文件,因此您必须以其他方式进行。你可以要求 Karma 将你的 peg 语法放在文件数组中,然后使用 requirejs text plugin.

加载它。

如果您正在测试的模块是针对 运行ning 在 node.js 而不是在浏览器中,那么它可能更适合使用不 运行 的测试框架浏览器中的代码,但 运行 将其放在节点中,因此您拥有所有可用的节点模块。如果你是针对浏览器的,我会重写它以更具体地针对浏览器。

所以在 dan 和 pieceOpiland 的各种回答和评论的帮助下,我终于找到了一种方法来做我想做的事。

首先,与许多 javascript 库一样,pegjs 有两种格式:npm 模块和 bower 模块。

Npm 模块用于为节点制作的脚本,并从命令行调用。 Bower 模块用于在浏览器中加载脚本。

我的第一个误解是 'require' 可以在节点和浏览器中模糊地工作。这是错误的。似乎要求模块使其在浏览器中工作的唯一方法是通过异步调用,如:

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

另一个误解是我可以在浏览器中加载 npm 模块。某种魔法会作用于我的页面加载的各种 npm 文件。这可能是可行的,但只能使用一些特殊工具,如 browserify。不做特殊改造,浏览器只能加载bower版本。此外,pegjs bower 模块的制作方式使得全局变量的定义如下:

var PEG = {
 ...
}

module.exports = PEG;

基本上,bower 模块将一个全局变量(实际上是几个全局变量)插入到顶级范围。

因此,我没有让我的客户端代码(浏览器和节点中的 运行 代码)加载模块,而是实际上在以下任一位置加载模块:

  1. main.js 通过对 npm 模块的同步要求,如下所示:var PEG = require('pegjs');
  2. main-browser.js 通过加载 bower pegjs 脚本时可用的全局变量(通过 <script> 标记)

这两个 'mains' 然后将 PEG 变量注入我的解析器函数。

为了 karma 工作,我只需要在生成的页面中包含 pegjs bower 模块(karma.conf.js 提取):

files: [
 { pattern: 'bower_components/pegjs/peg-0.9.0.js', included: true },
 { pattern: './test/**/*.spec.js', included: false },
 { pattern: './js/**/*.js', included: false},
 './test/test-main.js',
],