如何在多个grunt-browserify包中管理相对路径别名?

45

这篇文章有点长,但我需要通过代码示例来说明我的困惑。接下来我想了解以下问题的答案:

  1. 如何使用require('module')而不是require('../../src/module')require('./module')
  2. 如何在spec/specs.js中重用./index.js而不重复工作?(同时防止src/app.js运行,因为它是一个入口模块)。

我已经开始了几个基于浏览器的项目,并喜欢使用browserify和grunt。但每个项目都在开发/学习曲线的同一点死掉。当我将测试添加到混合中并且必须管理两个browserify捆绑包(app.jsspec/specs.js)时,整个系统就崩溃了。我会解释:

我使用grunt-browserify并设置了初始目录:

.
├── Gruntfile.js
├── index.js  (generated via grunt-browserify)      [1]
├── lib
│   ├── jquery
│   │   └── jquery.js                               [2]
│   └── jquery-ui
│       └── jquery-ui.js                            [3]
├── spec
│   ├── specs.js  (generated via grunt-browserify)  [4]
│   └── src
│       ├── spec_helper.js  (entry)
│       └── module_spec.js  (entry)
└── src
    ├── app.js  (entry)
    └── module.js
  1. 使用一个入口文件 (src/app.js) 并进行代码遍历以捆绑所有 所需的 模块。
  2. 使用 browserify-shim 别名 jquery
  3. 只是别名为 jquery-ui 而没有 shim (当你 var $ = require('jquery') 后需要)。
  4. 使用 spec/src 中的所有辅助和规范文件作为入口模块。

我会逐步介绍我的配置:

browserify: {
  dist: {
    files: {
      'index.js': ['src/app.js']
    }
  }
}

// in app.js
var MyModule = require('./module'); // <-- relative path required?!

开心

现在添加jQuery:

browserify: {
  options: {
    shim: {
      jquery: {
        path: 'lib/jquery/jquery.js',
        exports: '$'
      }
    },
    noParse: ['lib/**/*.js'],
    alias: [
      'lib/jquery-ui/jquery-ui.js:jquery-ui'
    ]
  },
  dist: {
    files: {
      'index.js': ['src/app.js']
    }
  }
}

// in app.js
var $ = require('jquery');
require('jquery-ui');
var MyModule = require('./module');

开心

现在加上规格:

options: {
  shim: {
    jquery: {
      path: 'lib/jquery/jquery.js',
      exports: '$'
    }
  },
  noParse: ['lib/**/*.js'],
  alias: [
    'lib/jquery-ui/jquery-ui.js:jquery-ui'
  ]
},
dist: {
  files: {
    'app.js': 'src/app.js'
  }
},
spec: {
  files: {
    'spec/specs.js': ['spec/src/**/*helper.js', 'spec/src/**/*spec.js']
  }
}

// in app.js
var $ = require('jquery');
require('jquery-ui');
var MyModule = require('./module');

// in spec/src/module_spec.js
describe("MyModule", function() {
  var MyModule = require('../../src/module'); // <-- This looks like butt!!!
});

悲伤

总结一下:我如何...

  1. 使用require('module')而不是require('../../src/module')require('./module')
  2. spec/specs.js中重用./index.js,避免重复工作?(同时防止src/app.js作为入口模块运行)。
4个回答

30

简单回答:

最简单的方法是使用browserify的paths选项。我已经成功地使用了几个月。我甚至制作了一个使用这个功能的入门套件:https://github.com/stample/gulp-browserify-react-phonegap-starter

var b = browserify('./app', {paths: ['./node_modules','./src/js']});

paths - 如果在普通的node_modules递归查找中找不到任何内容,则使用require.paths数组

如果您在中有一个文件,则不能随处写 require(“myModule”),而必须从任何其他源文件中写 require(“modulePath / myModule”)

已弃用选项?

似乎并不是这样!

Browserify模块解析算法反映了NodeJS中的解析算法。因此,Browserify的 paths 选项是NodeJS的 NODE_PATH env变量行为的镜像。 Browserify作者(substack)在此SO主题中声称, NODE_PATH 选项在NodeJS中已被弃用,因此它在Browserify中也已被弃用,并且可能会在下一个版本中删除。

我不同意这种说法。

请查阅NODE_PATH文档。文档中未提到该选项已被弃用。然而,仍有一条有趣的提醒符合substack的说法:

强烈建议将依赖项放置在本地的node_modules文件夹中。这样可以更快速、更可靠地加载它们。

此外,this question已经在2012年发布在邮件列表中。
Oliver Leics: is NODE_PATH deprecated? 
Ben Noordhuis (ex core NodeJS contributor): No. Why do you ask? 

如果在NodeJS解析算法中没有删除某些内容,我认为这些内容不会很快从Browserify中删除 :)

结论

您可以使用paths选项或像官方文档和Browserify作者建议的那样将代码放入node_modules中。

个人而言,我不喜欢将自己的代码放在node_modules中,因为我只是将整个文件夹保留在我的源代码控制之外。我现在已经使用paths选项几个月了,一点问题都没有,我的构建速度非常快。

将符号链接放在node_modules中的substack解决方案可能很方便,但不幸的是我们这里有使用Windows的开发人员...

然而,我认为有一种情况您不想使用paths选项:当您正在开发一个在NPM存储库上发布并将被其他应用程序所需的库时。您真的不希望这些库客户端必须设置特殊的构建配置,只是因为您想避免在库中出现相对路径问题。

另一个可能的选择是使用remapify

2
刚看到这个帖子,这个选项对于当前的browserify/grunt-browserify来说要好得多。关于在grunt-browserify中的外观示例(已验证),请参见此处:http://cl.ly/VXyx(注意路径位于browserify选项下)。 - David Kaneda
有其他人使用 Browserify 8.1.1 中的 paths 时遇到问题吗?我一直看到“找不到模块”的消息,已经回退到了 8.0.1。 - Cory

7
所有关于别名、opts.paths/$NODE_PATH的答案都不是很好,因为这种方法已经成为了node和browserify模块系统中被废弃的部分,所以它随时可能停止工作。
你应该学习node_modules算法如何工作,这样你就可以有效地组织你的代码,以便与嵌套的node_modules目录相匹配。
browserify手册中有一节涵盖了避免使用../../../../../../../..等相对路径问题。它可以概括为:
  • 将内部模块化代码放在node_modules/node_modules/app中,这样你就可以根据自己的喜好使用require('yourmodule')require('app/yourmodule')
  • 如果你开发的是非Windows平台,并且这是你想要的,你可以使用符号链接。
不要使用opts.path/$NODE_PATH。它会使你的项目:
  • 隐含依赖于非明显的配置或环境设置
  • 在node和浏览器中更难使其工作
  • 容易受到模块系统变化的影响,因为在node和browserify中,数组路径已被弃用

感谢您的见解。您知道为什么 opts.path 已被弃用吗?我不喜欢使用已弃用的解决方案,但我认为这个弃用选项比将代码放在 node_modules 中更方便。您知道它为什么被弃用了吗?有任何 GitHub 问题吗?我找不到任何信息。 - Sebastien Lorber
顺便提一下,在官方文档(http://nodejs.org/api/modules.html)中,我们可以看到:“为了使模块在node REPL中可用,将/usr/lib/node_modules文件夹添加到$NODE_PATH环境变量中也可能很有用。由于使用node_modules文件夹进行模块查找都是相对的,并且基于调用require()的文件的实际路径,因此包本身可以放在任何地方。”请告诉我们您在哪里看到它已被弃用,因为官方文档没有反映这一点。 - Sebastien Lorber
好的,我找到了一些关于你在这里说的内容的参考资料:http://nodejs.org/api/all.html#all_loading_from_the_global_folders。 "这些主要是出于历史原因。强烈建议您将依赖项放在本地的node_modules文件夹中。它们将加载更快,更可靠。" 这只是一个建议,但它实际上并没有说这个功能已经被弃用并可能被删除。根据这个ML讨论,它也没有被弃用:https://groups.google.com/forum/#!topic/nodejs/EENwg8GAJd8 - Sebastien Lorber
20
将应用程序代码放在 node_modules 中,我觉得非常奇怪。 :S - Dave
3
把你的项目代码移动到 node_modules 是一个非常糟糕的主意,以后只会导致问题的产生。记得不要使用 rm -rf ./node_modules 命令。 - airtonix
显示剩余2条评论

4
这些答案取决于你的项目如何设置,但也许这是一个好的起点。此外,你需要使用当前的grunt-browserify v2 beta才能使其正常工作(npm install grunt-browserify@2)。
1.
你可以使用aliasMapping为模块创建一些动态别名。为了清晰起见,让我们将所有模块移动到src/modules/。然后,aliasMapping配置可能如下所示:
options: {
  aliasMappings: {
    cwd: 'src',
    src: ['modules/**/*.js']
  }
}

假设您在src/modules/magic/stuff.js中有一个模块,那么无论执行require的.js文件位于何处,您都可以像这样进行要求:
var magicStuff = require('modules/magic/stuff.js');

2.

对于这个问题我不是很确定。您的项目结构显示了一个spec/index.js,但是您提到了spec/specs.js。它们应该是同一个文件吗?

无论如何,您在谈论什么重复工作?因为./index.js具有与spec/index.js不同的入口文件。如果您正在寻找一种方法将./index.js包含在specs/中,则可以在运行测试之前将其复制而不是从头构建它。


spec/index.jsspec/specs.js 是一个打字错误。已编辑问题。感谢您的回答。看起来事实是原始(旧版本)并没有设计做这个设置。我认为更好的问题是 这样的项目应该如何组织 许多教程不会捆绑生产脚本和测试脚本。添加类似 testem 的东西,难怪人们会感到困惑。我们需要一个元指南,以和谐地整合所有这些包。 - Sukima
1
可能的根本问题在于JS生态系统的多样性。它仍然很年轻。也许有一天,重复的软件包/工具将停止共存,其中之一将成为解决X问题的默认资源。就目前而言,在JS方面,至少有2/3个不同的库执行相同的操作。因此,对于像构建系统这样的东西来说,要完全中立并且能够很好地适用于所有可能的库组合是特别困难的。 - Joni Bekenstein
这个aliasMappings选项是特定于grunt-browserify还是它是对某些browserify隐藏选项的包装器?我试图使用opts.basedir选项获得相同的行为,但没有成功。 - eightyfive

1

我认为最好的方法是,正如Sebastien Lorber所指出的那样,在调用browserify时通过管道设置路径。

但是,在最新版本的browserify中(截至目前,即browserify@11.0.0),路径变量存储Browserify将用于其过程的唯一路径。因此,设置路径变量将排除例如...您的全局node文件夹,据我所知。因此,您需要一个类似于以下内容的Gulp任务:

gulp.task('reactBuild', function() {
  return gulp.src(newThemeJSX)
    .pipe(browserify({
        debug: true,
        extensions: ['.jsx', '.js', '.json'],
        transform: [reactify],
        paths: ['../base_folder/node_modules', '/usr/lib/node_modules']
    }))
    .pipe(gulp.dest(newThemeBuilt))
    .on('error', function(error) {
        console.log(error);
    });
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接