大概介绍
- 浏览器端的前端打包工具
- 主要用于在浏览器中使用 npm 包,最终会转换为 commonJS (require) 类似方式,在浏览器使用
- 方便模块细分,每个模块自成,通过
require
引用其他模块 - 基于流
Stream
- 旧时代产物,尽管也能勉强处理 css(CSS bundlers),html(brfs),但是不太友好,且年久失修
阅读此篇,大概可以较好使用 browserify,以及将其用在合适的地方
此外,文中带 删除线 的内容,因相对的内容过时,阅读意义不大,可简单跳过
大概分析
以 nums.js,demo.js,build 文件做大概分析
nums.js
1 | var uniq = require('uniq'); // uniq 为 npm 依赖包 |
demo.js
1 | const nums = require('./nums') |
build
1 | const browserify = require('browserify') |
build 后文件
通过 detective 进行依赖查找,后落地为以下文件
1 | (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({ |
build 后文件运转方式
上面的编译后文件,顶部的压缩代码,来源于 browser-pack
1 | // modules are defined as an array |
上方的编译代码,即相当于:
1 | // step 1 |
以此类推,通过 newRequire
以及相应的 modules
编号,达到代码执行的目的。
多入口文件编译
1 | const browserify = require('browserify') |
配置
entries
同 browserify 第一个参数,files
basedir
如果 entries
/ files
为 stream,需要指定 basedir
来让 browserify 可以处理内容中的相对路径
默认为 .
即当前脚本运行目录
require
数组,通过模块名或文件路径指定需要打包到bundle中的其他模块
1 | browserify('demo-4', { |
适用于一些全局的处理,又没有模块依赖的内容
例如 ./demo-3-module-2
内容:
1 | window.demo3Func = function (n) { |
通过 参数 opts.require
方式引入,那么所有被打包的文件,都会有此部分代码
debug
debug
为 true
时,会将 sourcemap 添加到包的末尾
ignoreMissing
默认为 false
,即如果 require
的模块不存在时,会报错;如果设置为false
,即忽略报错
noParse
一个数组,跳过数组中每个文件的所有 require
和全局解析
适用于jquery或threejs等巨型、无需解析的库,避免解析耗时过长
1 | { |
transform
一个数组,用于内容的相应转换。例如使用 uglifyify
1 | browserify(f, { |
数组的元素可以为字符串,或者数组(该数组第一项为使用的transform组件,第二项为该组件配置项)。
下方 plugin 等,同理。
ignoreTransform
一个数组,用于过滤不需要做 transform
的 transform 控件
1 | browserify(f, { |
也就不会进行 babelify
transform
这个参数没什么作用,其实如果不想进行转换,不把它放入 transform
内就好,不需要多此一举在 transform
中添加,又在 ignoreTransform
定义不进行转换
plugin
插件数组。主要用于一些更高级的插件配置,增强 browserify 功能。
详见:plugins 或者下方一些示例
extensions
参数可为字符串或数组。默认是 ['.js', '.json']
,可以补充 .ts
, .jsx
等等
paths
一个目录数组,用于在查找未使用相对路径引用的模块时浏览搜索,可以是绝对的或相对于basedir。调用browserify命令时,等效设置 NODE_PATH
环境变量
1 | browserify('./src/demo', { |
例如:原来 require('./a')
可以直接写为 require('a')
commondir
没什么作用的参数,要么不传递,要么传递 false
目前看 browserify index.js:616 只会在 builtIns
为数组时,会将 basedir
设置为 /
可能对 sourcemap 有一点影响,其他没什么作用
fullPaths
布尔值,默认为 false
,参考上方的分析代码,对应模块会被标记为数字 id,例如: ./nums: 0
如果设置为 true,不会转换为 id,而是以绝对路径形式展示。例如:"./nums":"/Users/xxx/xxx/xxx/browserify-demo/src/nums.js"
。官网文档描述其对于保留生成包的原始路径很有用,但是如果在生产环境下,需要设置为 false
,否则可能会暴露一些信息
standalone
1 | // beep.js |
如果使用 standalone: 'beep-boop'
最终打包出来的内容,就不是 browser-pack
做的包裹,而是
这个 umd/template 做的包裹,主要用来处理 requireJS 类似的调用方式,包括在全局下增加 变量。例如上方最终在全局下增加的 beepBoop
(驼峰) 变量。
所以作为 standalone(“独立”)的模块,就目前9012年来说,没有什么意义。
参考:Standalone Browserify Builds
externalRequireName
文档不全,没什么用途,不要使用。需要搭配 prelude
参数(文档未描述)。参见:
browserField
如果在项目中 package.json 中配置 browser 字段
1 | "browser": { |
在代码源文件中,require('ccc')
会自动被处理成 require('./src/ccc.js')
功能和 paths 类似,但是其主要用来替换原有的模块,而不是 alias
作用
而优先级方面,paths
的设置更高。不过,在使用上需要尽量避免paths
和 browserField
设置相同模块的情况,以免造成一些歧义和不可控的现象
为 false
时,将忽略 package.json 中这个字段
builtins
设置要使用的内置函数列表,默认情况下为 lib/builtins.js
可为 false
、array
、object
如果设置为 false,不会进行任何 Node 相关内容的设置
如果设置为 array
,可以设置为 lib/builtins.js
对应的 key name,例如: ['assert', 'buffer']
代表只将此两部分作为内置内容
如果设置为 Object
,将直接替换掉默认的 lib/builtins.js
,而采用用户的配置
bare
例如:
1 | console.log(__dirname) |
默认为 false
,会将 __dirname
、process.env
设置为浏览器可运行的内容 (包含 builtins
)。process.env
会被设置为 node-process
设置为 true
时,同 builtins = false, commondir = false
。创建一个不包含 Node builtins 的bundle,并且不设置除 __dirname
和 __filename
之外的全局 Node 变量。即 process.env
还是 process.env
。而这样的处理,如果模块内有使用相关 Node 模块,浏览器端运行会直接报错
node
默认为 false
设置为 true
时,创建一个在 Node 中运行的bundle,不使用浏览器版本的依赖项。与传递 {bare:true,browserField:false}
相同。这个参数,一般也用不上,如果在 node 运行,也便不需要 browserify
detectGlobals
默认为 true
,只在 bare
为 false
时作用。例如:
1 | console.log(__dirname) |
会进行模块扫描,上方 __dirname
和 process.env
的设置,是先通过检测,后设置,不设置其他多余内容。但是这样,检测的时间会长一些
如果将其设置为 false
,类似 bare
设置为 true
,不会进行 __dirname
和 process.env
的设置
insertGlobals
默认为 false
,即不直接设置所有 Node 相关的内容,而是通过 detectGlobals=true
按需设置
如果设置为 true
,会始终插入 Node 相关内容,而不做相应模块分析检测。提高了效率,但是打出来的包,内容也更大。但是detectGlobals
必须为 true
才能工作
其他一些作用的需要条件详见:globalTr
insertGlobalVars
会被作为 opts.vars
传递给 insert-module-globals
格式可参考 defaultVars
1 | { |
注意:detectGlobals
需要为 true
,这样才能检测文件内的 forTest 变量,并做相应设置
bundleExternal
默认为 true
,代表内置的 process
、buffer
是否可以设置进去
例如:
1 | { |
即使 detectGlobals
, insertGlobal
都为 true
,也不会进行 process
和 buffer
的设置
方法
b.add(file, opts)
同 opts.entries
参数
b.require(file, opts)
同 opts.require
参数
b.ignore(file)、b.external(file)、b.exclude(file)
这三个方法,都是用来将 打包文件内的某个/某几个模块 移除编译内容,参数可为 string
、array
三个方法的区别,文档也没说清(browserify 文档太过简略)。大概如下:
1 | const $ = require('jquery') |
理解大概是:
ignore
代表忽略,如果内部引用了,会将其模块作为作为空模块处理,模块的位置还在exclude
会将该模块直接移除,并且如果require
了的话,值为undefined
external
会将该模块移除,但是对应的模块引入,还是会在运行时进行require(name)
的形式,由全局的 require 进行其他模块依赖引入
这么看,也只有 external
具备一定的实用性
b.transform(tr, opts={})
同 opts.transform
1 | b.transform('babelify', { presets: ['@babel/preset-env'] }) |
b.plugin(plugin, opts)
同 opts.plugin
b.bundle(cb)
将内容以及内容内部的 reuqire
内容,一并打包进一个文件内
创建了一个可读流,用于 pipe 进可写流文件。例如:
b.bundle().pipe(fs.createWriteStream('./build/demo-7.js'))
callback
为可选项,参数为 err, buf
,buf
为文件 buffer
内容,因此也就可以基于 buf
内容进行一些其他处理
b.pipeline
一个属性,使用 labeled-stream-splicer,个人简单理解为将内容拆分为不同的分段,通过流的方式进行传递
一般来讲,如果不是写插件之类东西,单纯使用 browserify 层面上来说,用不到
对应的,browserify 内置了一些 label
1 | 'record' - save inputs to play back later on subsequent bundle() calls |
可以通过 b.pipeline.get(label)
的方式获取,并对其进行相应的处理
b.reset(opts)
将流恢复到 bundle()
前的状态,主要用于需要多次 bundle()
的场景
实际每次 bundle()
调用后,reset()
都会自动执行,所以这个方法在实际使用过程中,可能也没有太大的用处
1 | var b = browserify('./src/beep.js', { |
上方 打包出来 demo-6、demo-7 内容是完全一致的
其他工具
更多的工具,可见 awesome-browserify#tools,此处取一部分代表性内容
budo
启动 http 服务器,进行相应 browserify 打包
1 | budo ./beep.js --live --open |
如果 index.html 内,引用 <script src="./beep.js"></script>
,最终启动的服务便是这个 index.html,以及实时打包的 http://127.0.0.1:9966/beep.js
文件
内容类似如下:
1 | <script type="text/javascript" src="/budo/livereload.js" async="" defer=""></script> |
envify
给 process.env
添加环境变量替换
1 | b.transform(envify({ |
babelify
因为 browserify 只处理文件相关依赖引入,不处理文件的 es6 转换,因此如果需要使用 es6、es7 语法,需要经过 babelify 进行转换
1 | ['./src/demo-1', './src/demo-2'].forEach(f => { |
tsify
因为 browserify 只处理文件相关依赖引入,如果想要使用 typescript 编写浏览器端代码,需要进行相应转换
但是原则上,其实也可以通过 gulp-babel 的方式进行处理,因为:
- babel 7 支持了 typescript 的转换
- gulp-babel 也是基于流
或者,通过 gulp-typescript 进行处理,理由同上
uglifyify
代码丑化
1 | ['./src/demo-1', './src/demo-2'].forEach(f => { |
tinyify
1 | b.plugin('tinyify', { |
以下用到的插件的整合版本1
2
3
4
5
6
7
8
9b
.transform('unassertify', { global: true })
.transform('envify', { global: true })
.transform('uglifyify', { global: true })
.plugin('common-shakeify')
.plugin('browser-pack-flat/plugin')
.bundle()
.pipe(require('minify-stream')({ sourceMap: false }))
.pipe(fs.createWriteStream('./output.js'))
watchify
检测改动,自动编译
作为插件:
1 | const b = browserify('./demo-2', { |
或者
1 | const b = watchify(browserify('./demo-2', { |
附:Fast browserify builds with watchify
css-modulesify
如其名 css-modulesify,使用它可以在 js 中 require
css 内容
brfs
使用 brfs,可以达到 js 中 require
html 类似的效果
1 | var html = fs.readFileSync(__dirname + '/robot.html', 'utf8'); |
browserify-hmr
browserify 本身是基于流,效率比较高。热更新的用处不大,而且根据 README.md 内作者描述,此插件还是存在不少问题
factor-bundle
拆包:将 x、y 共用部分,打包进 common.js
,有一定的实用性
1 | browserify([ './files/x.js', './files/y.js' ]) |
和 gulp 对比
gulp 缺陷
- gulp 没有相关
require
引用处理的能力 如果单纯只用 gulp,相应模块之间的拆分,只能通过全局变量的方式进行管理,相对来说比较混乱。例如:
1
2
3
4
5
6
7
8// a module
window.lib.someFunc = xxx
// b module
window.lib.someFunc()
// 打包
gulp.src(['a.js', 'b.js'])
browserify 优势与缺陷
优势
而如果用 browserify,因为模块之间的引用,通过
require
完成。与 node 模块编写方式一致,此外,入口文件只需要一个。1
2
3
4
5
6
7
8
9
10
11
// a module
exports.someFunc = xxx
// b module
const { someFunc } = require('./a')
someFunc()
browserify(['./b'])
.bundle()
.pipe(fs.createWriteStream('./build/b.js'))
缺陷
- 以
js
作为入口文件,缺乏css
、html
等处理能力
一起使用
而因为他们都是基于流的处理,因此可以通过流相关的工具,例如 vinyl-source-stream
、through2
进行相应转换,来达到共用的目的
结语
基于其与 gulp 的比较,个人认为 最好的方式 是在 gulp 中集成 browserify 功能(将 browserify 作为 gulp 的一个扩展),只用于 require
相关处理
这样,可以结合双方的优点,并且避免了双方的缺陷
而像 babel、typescript 相关转换以及 sourcemap、minify 等等功能,交给 gulp 相关插件
gulp 对应的 browserify 插件:gulp-bro
代码也比较简单,仅仅是对于流进行了相应转换 gulp-bro 源码
最后,此文对 browserify 做的部分介绍,相关配置、插件其实已经过时而没有太大存在和深究的意义
此外,
- browserify 的配置很多,而且很多都重复功能
- browserify 基本上也只有浏览器前端才会需要使用,也就没必要用到太多的无用配置。例如:
bare, builtins, detectGlobals, insertGlobals, insertGlobalVars
等等配置都应该移除 - 原
gulp-browserify
已经停止维护,像其他的一些 browserify 工具,也都很少再有更新