require
引用其他模块Stream
阅读此篇,大概可以较好使用 browserify,以及将其用在合适的地方
此外,文中带 删除线 的内容,因相对的内容过时,阅读意义不大,可简单跳过
PS:知乎上删除线无法显示,可通过 此链接 阅读原文
以 nums.js,demo.js,build 文件做大概分析
var uniq = require('uniq'); // uniq 为 npm 依赖包
var nums = [ 5, 2, 1, 3, 2, 5, 4, 2, 0, 1 ];
module.exports = nums
const nums = require('./nums')
console.log(nums)
const browserify = require('browserify')
const fs = require('fs')
browserify(['./src/demo'])
.bundle()
.pipe(fs.createWriteStream('./build/demo.js'))
通过 detective 进行依赖查找,后落地为以下文件
(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})()({
1:[function(require,module,exports){
"use strict"
// 为避免内容过长,此部分略去
module.exports = unique
},{}],
2:[function(require,module,exports){
const nums = require('./nums')
console.log(nums)
},{"./nums":3}],
3:[function(require,module,exports){
var uniq = require('uniq');
var nums = [ 5, 2, 1, 3, 2, 5, 4, 2, 0, 1 ];
module.exports = nums
},{"uniq":1}],
},
{},
[2]);
上面的编译后文件,顶部的压缩代码,来源于 browser-pack
// modules are defined as an array
// [ module function, map of requireuires ]
//
// map of requireuires is short require name -> numeric require
//
// anything defined in a previous bundle is accessed via the
// orig method which is the requireuire for previous bundles
(function() {
function outer(modules, cache, entry) {
// Save the require from previous bundle to this closure if any
var previousRequire = typeof require == "function" && require;
function newRequire(name, jumped){
if(!cache[name]) {
if(!modules[name]) {
// if we cannot find the module within our internal map or
// cache jump to the current global require ie. the last bundle
// that was added to the page.
var currentRequire = typeof require == "function" && require;
if (!jumped && currentRequire) return currentRequire(name, true);
// If there are other bundles on this page the require from the
// previous one is saved to 'previousRequire'. Repeat this as
// many times as there are bundles until the module is found or
// we exhaust the require chain.
if (previousRequire) return previousRequire(name, true);
var err = new Error('Cannot find module \'' + name + '\'');
err.code = 'MODULE_NOT_FOUND';
throw err;
}
var m = cache[name] = {exports:{}};
modules[name][0].call(m.exports, function(x){
var id = modules[name][1][x];
return newRequire(id ? id : x);
},m,m.exports,outer,modules,cache,entry);
}
return cache[name].exports;
}
for(var i=0;i<entry.length;i++) newRequire(entry[i]);
// Override the current require with this new one
return newRequire;
}
return outer;
})()
上方的编译代码,即相当于:
// step 1
newRequire(2)
// step 2
(function(require,module,exports){
const nums = require('./nums')
console.log(nums)
}).call({}, function(x){
// name is 2
// x is './nums'
// id is 3
var id = modules[name][1][x];
return newRequire(id ? id : x);
// 至于说,为什么这里要增加 outer, modules, cache, entry 这样的无用参数
// 可参考:https://github.com/browserify/browser-pack/issues/82
// 简单理解为:有其他用途
}, { exports:{} }, {}, outer, modules, cache, entry)
// step 3, 4, 5, etc...
以此类推,通过 newRequire
以及相应的 modules
编号,达到代码执行的目的。
const browserify = require('browserify')
const fs = require('fs');
// 指定入口即可
['./src/demo-1', './src/demo-2'].forEach(f => {
browserify(f).bundle().pipe(fs.createWriteStream(f.replace('./src', './build') + '.js'))
})
同 browserify 第一个参数,files
如果 entries
/ files
为 stream,需要指定 basedir
来让 browserify 可以处理内容中的相对路径
默认为 .
即当前脚本运行目录
数组,通过模块名或文件路径指定需要打包到bundle中的其他模块
browserify('demo-4', {
basedir: './src',
require: ['./demo-3-module-2'],
})
适用于一些全局的处理,又没有模块依赖的内容
例如 ./demo-3-module-2
内容:
window.demo3Func = function (n) {
return n + 1
}
通过 参数 opts.require
方式引入,那么所有被打包的文件,都会有此部分代码
debug
为 true
时,会将 sourcemap 添加到包的末尾
默认为 false
,即如果 require
的模块不存在时,会报错;如果设置为false
,即忽略报错
一个数组,跳过数组中每个文件的所有 require
和全局解析
适用于jquery或threejs等巨型、无需解析的库,避免解析耗时过长
{
noParse: ['jquery']
}
一个数组,用于内容的相应转换。例如使用 uglifyify
browserify(f, {
transform: ['babelify'], // babel 配置在 .babelrc 中指定
})
// or
browserify(f, {
transform: [['babelify', { presets: ['@babel/preset-env'] }]],
})
数组的元素可以为字符串,或者数组(该数组第一项为使用的transform组件,第二项为该组件配置项)。
下方 plugin 等,同理。
一个数组,用于过滤不需要做 transform
的 transform 控件
browserify(f, {
transform: [['babelify', { presets: ['@babel/preset-env'] }]],
ignoreTransform: ['babelify'],
})
也就不会进行 babelify
transform
这个参数没什么作用,其实如果不想进行转换,不把它放入 transform
内就好,不需要多此一举在 transform
中添加,又在 ignoreTransform
定义不进行转换
插件数组。主要用于一些更高级的插件配置,增强 browserify 功能。
详见:plugins 或者下方一些示例
参数可为字符串或数组。默认是 ['.js', '.json']
,可以补充 .ts
, .jsx
等等
一个目录数组,用于在查找未使用相对路径引用的模块时浏览搜索,可以是绝对的或相对于basedir。调用browserify命令时,等效设置 NODE_PATH
环境变量
browserify('./src/demo', {
basedir: './',
transform: [['babelify', { presets: ['@babel/preset-env'] }]],
paths: ['src'], // ./src 下的模块引用都不需要使用相对路径引用
})
browserify('./demo', {
basedir: './src',
transform: [['babelify', { presets: ['@babel/preset-env'] }]],
paths: ['test'], // ./src/test 下的模块引用都不需要使用相对路径引用
})
例如:原来 require('./a')
可以直接写为 require('a')
没什么作用的参数,要么不传递,要么传递 false
目前看 browserify index.js:616 只会在 builtIns
为数组时,会将 basedir
设置为 /
可能对 sourcemap 有一点影响,其他没什么作用
布尔值,默认为 false
,参考上方的分析代码,对应模块会被标记为数字 id,例如: ./nums: 0
如果设置为 true,不会转换为 id,而是以绝对路径形式展示。例如:"./nums":"/Users/xxx/xxx/xxx/browserify-demo/src/nums.js"
。官网文档描述其对于保留生成包的原始路径很有用,但是如果在生产环境下,需要设置为 false
,否则可能会暴露一些信息
// beep.js
var shout = require('./shout.js');
module.exports = function beep() {
console.log(shout('beep'));
}
// shout.js
module.exports = function shout(str) {
return str.toUpperCase() + '!';
};
// build.js
var fs = require('fs');
var browserify = require('browserify');
var b = browserify('./src/beep.js', { standalone: 'beep-boop' });
b.bundle().pipe(fs.createWriteStream('./build/demo-5.js'));
如果使用 standalone: 'beep-boop'
最终打包出来的内容,就不是 browser-pack
做的包裹,而是
这个 umd/template 做的包裹,主要用来处理 requireJS 类似的调用方式,包括在全局下增加 变量。例如上方最终在全局下增加的 beepBoop
(驼峰) 变量。
所以作为 standalone(“独立”)的模块,就目前9012年来说,没有什么意义。
参考:Standalone Browserify Builds
文档不全,没什么用途,不要使用。需要搭配 prelude
参数(文档未描述)。参见:
如果在项目中 package.json 中配置 browser 字段
"browser": {
"ccc": "./src/ccc.js"
},
在代码源文件中,require('ccc')
会自动被处理成 require('./src/ccc.js')
功能和 paths 类似,但是其主要用来替换原有的模块,而不是 alias
作用
而优先级方面,paths
的设置更高。不过,在使用上需要尽量避免paths
和 browserField
设置相同模块的情况,以免造成一些歧义和不可控的现象
为 false
时,将忽略 package.json 中这个字段
设置要使用的内置函数列表,默认情况下为 lib/builtins.js
可为 false
、array
、object
如果设置为 false,不会进行任何 Node 相关内容的设置
如果设置为 array
,可以设置为 lib/builtins.js
对应的 key name,例如: ['assert', 'buffer']
代表只将此两部分作为内置内容
如果设置为 Object
,将直接替换掉默认的 lib/builtins.js
,而采用用户的配置
例如:
console.log(__dirname)
console.log(process.env)
默认为 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 模块,浏览器端运行会直接报错
默认为 false
设置为 true
时,创建一个在 Node 中运行的bundle,不使用浏览器版本的依赖项。与传递 {bare:true,browserField:false}
相同。这个参数,一般也用不上,如果在 node 运行,也便不需要 browserify
默认为 true
,只在 bare
为 false
时作用。例如:
console.log(__dirname)
console.log(process.env)
会进行模块扫描,上方 __dirname
和 process.env
的设置,是先通过检测,后设置,不设置其他多余内容。但是这样,检测的时间会长一些
如果将其设置为 false
,类似 bare
设置为 true
,不会进行 __dirname
和 process.env
的设置
默认为 false
,即不直接设置所有 Node 相关的内容,而是通过 detectGlobals=true
按需设置
如果设置为 true
,会始终插入 Node 相关内容,而不做相应模块分析检测。提高了效率,但是打出来的包,内容也更大。但是detectGlobals
必须为 true
才能工作
其他一些作用的需要条件详见:globalTr
会被作为 opts.vars
传递给 insert-module-globals
格式可参考 defaultVars
{
detectGlobals: true,
insertGlobalVars: {
forTest: function () {
return '1111';
}
},
}
注意:detectGlobals
需要为 true
,这样才能检测文件内的 forTest 变量,并做相应设置
默认为 true
,代表内置的 process
、buffer
是否可以设置进去
例如:
{
detectGlobals: true,
insertGlobal: true,
bundleExternal: false,
insertGlobalVars: {
xxx: function () {
return '1111';
}
},
}
即使 detectGlobals
, insertGlobal
都为 true
,也不会进行 process
和 buffer
的设置
同 opts.entries
参数
同 opts.require
参数
这三个方法,都是用来将 打包文件内的某个/某几个模块 移除编译内容,参数可为 string
、array
三个方法的区别,文档也没说清(browserify 文档太过简略)。大概如下:
const $ = require('jquery')
$('body').css('background', 'red')
// ignore
({
1:[function(require,module,exports){
},{}],
2:[function(require,module,exports){
const $ = require('jquery')
$('body').css('background', 'red')
},{"jquery":1}],
}, {},[2]);
// exclude
({
1:[function(require,module,exports){
const $ = require('jquery')
$('body').css('background', 'red')
},{"jquery":undefined}]
}, {},[1]);
// external
({
1:[function(require,module,exports){
const $ = require('jquery')
$('body').css('background', 'red')
},{"jquery":"jquery"}]
},{},[1]);
理解大概是:
ignore
代表忽略,如果内部引用了,会将其模块作为作为空模块处理,模块的位置还在exclude
会将该模块直接移除,并且如果 require
了的话,值为 undefined
external
会将该模块移除,但是对应的模块引入,还是会在运行时进行 require(name)
的形式,由全局的 require 进行其他模块依赖引入这么看,也只有 external
具备一定的实用性
同 opts.transform
b.transform('babelify', { presets: ['@babel/preset-env'] })
// 同
browserify('./src/demo', {
transform: [['babelify', { presets: ['@babel/preset-env'] }]],
})
同 opts.plugin
将内容以及内容内部的 reuqire
内容,一并打包进一个文件内
创建了一个可读流,用于 pipe 进可写流文件。例如:
b.bundle().pipe(fs.createWriteStream('./build/demo-7.js'))
callback
为可选项,参数为 err, buf
,buf
为文件 buffer
内容,因此也就可以基于 buf
内容进行一些其他处理
一个属性,使用 labeled-stream-splicer,个人简单理解为将内容拆分为不同的分段,通过流的方式进行传递
一般来讲,如果不是写插件之类东西,单纯使用 browserify 层面上来说,用不到
对应的,browserify 内置了一些 label
'record' - save inputs to play back later on subsequent bundle() calls
'deps' - module-deps
'json' - adds module.exports= to the beginning of json files
'unbom' - remove byte-order markers
'unshebang' - remove #! labels on the first line
'syntax' - check for syntax errors
'sort' - sort the dependencies for deterministic bundles
'dedupe' - remove duplicate source contents
'label' - apply integer labels to files
'emit-deps' - emit 'dep' event
'debug' - apply source maps
'pack' - browser-pack
'wrap' - apply final wrapping, require= and a newline and semicolon
可以通过 b.pipeline.get(label)
的方式获取,并对其进行相应的处理
将流恢复到 bundle()
前的状态,主要用于需要多次 bundle()
的场景
实际每次 bundle()
调用后,reset()
都会自动执行,所以这个方法在实际使用过程中,可能也没有太大的用处
var b = browserify('./src/beep.js', {
debug: true,
commondir: false,
builtins: [],
});
b
.transform('uglifyify', { global: true })
.bundle().pipe(fs.createWriteStream('./build/demo-6.js'));
setTimeout(() => {
b.bundle().pipe(fs.createWriteStream('./build/demo-7.js'));
}, 2000)
上方 打包出来 demo-6、demo-7 内容是完全一致的
更多的工具,可见 awesome-browserify#tools,此处取一部分代表性内容
启动 http 服务器,进行相应 browserify 打包
budo ./beep.js --live --open
如果 index.html 内,引用 <script src="./beep.js"></script>
,最终启动的服务便是这个 index.html,以及实时打包的 http://127.0.0.1:9966/beep.js
文件
内容类似如下:
<script type="text/javascript" src="/budo/livereload.js" async="" defer=""></script>
<script src="beep.js"></script>
给 process.env
添加环境变量替换
b.transform(envify({
// 将 process.env 其他字段设置为 undefined
_: 'purge',
// process.env.NODE_ENV 会被自动替换为 'development',而不是在运行时获取 process.env.NODE_ENV 值
NODE_ENV: 'development'
}))
因为 browserify 只处理文件相关依赖引入,不处理文件的 es6 转换,因此如果需要使用 es6、es7 语法,需要经过 babelify 进行转换
['./src/demo-1', './src/demo-2'].forEach(f => {
browserify(f)
.transform('babelify', { presets: ['@babel/preset-env'] })
.bundle()
.pipe(fs.createWriteStream(f.replace('./src', './build') + '.js'))
})
因为 browserify 只处理文件相关依赖引入,如果想要使用 typescript 编写浏览器端代码,需要进行相应转换
但是原则上,其实也可以通过 gulp-babel 的方式进行处理,因为:
或者,通过 gulp-typescript 进行处理,理由同上
代码丑化
['./src/demo-1', './src/demo-2'].forEach(f => {
browserify(f)
.transform('uglifyify', { global: true })
.bundle()
.pipe(fs.createWriteStream(f.replace('./src', './build') + '.js'))
})
b.plugin('tinyify', {
env: {
PUBLIC_PATH: 'https://mywebsite.surge.sh/'
}
})
以下用到的插件的整合版本
b
.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'))
检测改动,自动编译
作为插件:
const b = browserify('./demo-2', {
basedir: 'src',
debug: true,
paths: ['./'],
fullPaths: true,
plugin: [['watchify', {
delay: 100,
ignoreWatch: ['**/node_modules/**'],
poll: false
}]]
})
b.on('update', bundle)
bundle()
function bundle(x) {
console.log(x)
b
.transform('babelify', { presets: ['@babel/preset-env'] })
.transform('uglifyify', { global: true, sourceMap: true })
.bundle()
.on('error', console.error)
.pipe(fs.createWriteStream('./build/demo-5.js'))
}
或者
const b = watchify(browserify('./demo-2', {
basedir: 'src',
debug: true,
paths: ['./'],
fullPaths: true,
}), {
delay: 100,
ignoreWatch: ['**/node_modules/**'],
poll: false
})
b.on('update', bundle)
bundle()
function bundle(x) {
console.log(x)
b
.transform('babelify', { presets: ['@babel/preset-env'] })
.transform('uglifyify', { global: true, sourceMap: true })
.bundle()
.on('error', console.error)
.pipe(fs.createWriteStream('./build/demo-5.js'))
}
附:Fast browserify builds with watchify
如其名 css-modulesify,使用它可以在 js 中 require
css 内容
使用 brfs,可以达到 js 中 require
html 类似的效果
var html = fs.readFileSync(__dirname + '/robot.html', 'utf8');
// 最终转换为
var html = "<b>beep boop</b>\n";
browserify 本身是基于流,效率比较高。热更新的用处不大,而且根据 README.md内作者描述,此插件还是存在不少问题
拆包:将 x、y 共用部分,打包进 common.js
,有一定的实用性
browserify([ './files/x.js', './files/y.js' ])
.plugin('factor-bundle', { outputs: [ 'bundle/x.js', 'bundle/y.js' ] })
.bundle().pipe(fs.createWriteStream('bundle/common.js'))
require
引用处理的能力// a module
window.lib.someFunc = xxx
// b module
window.lib.someFunc()
// 打包
gulp.src(['a.js', 'b.js'])
require
完成。与 node 模块编写方式一致,此外,入口文件只需要一个。// 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 做的部分介绍,相关配置、插件其实已经过时而没有太大存在和深究的意义
此外,
bare, builtins, detectGlobals, insertGlobals, insertGlobalVars
等等配置都应该移除gulp-browserify
已经停止维护,像其他的一些 browserify 工具,也都很少再有更新