在单个JavaScript代码库中使用Browserify的多种风格/目标

Multiple flavors / targets with Browserify in single JavaScript codebase

本文关键字:风格 目标 Browserify JavaScript 单个 代码      更新时间:2023-09-26

我正在研究多个浏览器扩展/附加组件,这些扩展/附加组件通常需要至少在Chrome和Firefox中工作(有时也可以在Safari

中工作(。

最大的问题是保持干燥,另一方面保持源头清洁。从概念上讲,该项目通常具有以下部分:

  • Chrome 的背景脚本
  • 火狐浏览器的背景脚本
  • 通用后台代码
  • 适用于 Chrome 的内容脚本
  • 火狐浏览器的内容脚本
  • 常用内容脚本代码
  • 其他脚本(例如:选项页面(。

为了减少代码重复,我有一个用于浏览器和预处理的内容脚本它(删除其他特定于浏览器的部分(在构建过程中。

不幸的是,这使得内容脚本非常长和丑陋(并且难以 lint(。

我想在我的项目中基本上将浏览器用于整个JS代码。仍然要做到这一点,我需要一个解决方案来处理这种流程:

浏览器特定的入口脚本 ->跨浏览器代码 ->浏览器特定的低级代码。

我会想象这种层次结构:

- Entry scripts
  - Browser A
  - Browser B 
  - ...
- Common code 
- Low-level code
  - Browser A
  - Browser B
  - ...

因此,例如,在构建过程中,我希望 Browserify 为浏览器 A 获取入口脚本,然后将其与通用代码捆绑在一起,并且仅与浏览器 A 的低级代码捆绑在一起。这是在没有公共代码中进行这种切换的情况下完成的:

if(isBrowserA()) {
   var lowLevelModule = require("../lowLevel/browserA/module");
} else {
   var lowLevelModule = require("../lowLevel/browserB/module");
}

我希望 Browserify 的构建过程能够为我做到这一点——根据目标替换低级代码的"根路径"。

使用 package.json 破解它是行不通的,因为我需要灵活数量的目标(甚至可能更深的依赖树(。

尝试使用 factor-bundle 或 partition-bundle Browserify 插件。它们都有助于将代码拆分为不同的入口文件和通用模块文件。分区捆绑包还包括支持异步加载不同捆绑包的脚本。

一种可能的方法是使用固定路径从代码中删除if/else require()调用。

var lowLevelModule = require("../lowLevel/module")

然后为每个浏览器运行单独的构建,使用 browserify 使用 expose 更改每个构建的路径解析为的内容。

因此,在gulpfile.js中(例如,browserify API 是重要的位 - 您可以使用 -r-x 标志从 shell 中对浏览器化执行相同的操作,使用 : 来分隔 require/expose 值(,为每个浏览器运行一次构建,每次传入不同的 --browser= arg。

var browserify = require('browserify')
var gulp = require('gulp')
var gutil = require('gulp-util')
var source = require('vinyl-source-stream')
var browser = gutil.env.browser // browserA, or browserB, or...
// You might want to configure paths up-front separately, just 
// hardcoding below for brevity.
gulp.task('bundle-app', function() {
  var b = browserify('./entry/' + browser + '/module', {detectGlobals: false})
  b.require('./path/to/lowLevel/' + browser + '/module', {expose: '../lowLevel/module'})
  return b.bundle()
    .pipe(source('app.js'))
    .pipe(gulp.dest('./build'))
})

对于常见依赖项,您可以将它们捆绑到外部文件中,并将external调用添加到特定于浏览器的捆绑包中:

var commonModules = ['module1', 'module2']
gulp.task('bundle-common', function() {
  var b = browserify({detectGlobals: false})
  commonModules.forEach(function(module) {
    b.require(module)
  })
  return b.bundle()
    .pipe(source('common.js'))
    .pipe(gulp.dest('./build'))
})
gulp.task('bundle-app', function() {
  var b = browserify('./entry/' + browser + '/module', {detectGlobals: false})
  commonModules.forEach(function(module) {
    b.external(module)
  })
  b.require('./path/to/lowLevel/' + browser + '/module', {expose: '../lowLevel/module'})
  return b.bundle()
    .pipe(source('app.js'))
    .pipe(gulp.dest('./build'))
})

为了方便起见,我通常将构建链放在package.json脚本中:

"scripts": {
  "build": "gulp bundle-common && gulp bundle-app --browser=browserA && gulp bundle-app --browser=browserB"
}

最后:

npm run build

这不是 Browserify 支持开箱即用的东西,除非,但理论上如果你编写一个自定义源代码转换,你可以实现你想要的。

作为替代方案,RaptorJS优化器提供了与您正在寻找的开箱即用完全相同的功能。(免责声明:我是这个工具的作者,它是我们在eBay用于所有Node.js应用程序的工具(RaptorJS优化器允许您根据优化期间启用的一组任意标志将一个模块重新映射到另一个模块。我们在 eBay 使用此功能有条件地为不同的 Web 浏览器、设备、实验组等发送不同的代码。有关该功能的更多详细信息,请参阅:

  • https://github.com/raptorjs/optimizer-require#conditional-remap
  • https://github.com/raptorjs/optimizer#conditional-dependencies

仅供参考,RaptorJS优化器支持Browserify的所有功能,并且它支持非JS依赖项,异步加载,条件依赖项,动态需求等。它仍然像Browserify一样非常模块化,可以通过插件进行扩展,以教它如何处理新的依赖类型。与 Webpack 不同,RaptorJS 优化器不会重载 CommonJS 模块加载系统,因此代码仍然允许在 Node.js 和 Web 浏览器中运行。我们在eBay(和其他公司(的RaptorJS优化器上取得了很大的成功,所以我鼓励你去看看。