ホームページ > 記事 > ウェブフロントエンド > jQuery_jquery を使用した Browserify プログラミングのスーパー ガイド
はじめに
1. 手動で
以前、私は新しい Web プロジェクトを開始し、jQuery を使用しようと考えました。ブラウザを開き、jQuery の公式 Web サイトを見つけて、目を引く「jQuery をダウンロード」ボタンをクリックしてダウンロードしました。 .js ファイルを作成し、それをプロジェクト ディレクトリにドロップします。使用する必要がある場合は、3f1c4e4b6b16bbbd69b2ee476dc4f83a を使用して次のように導入します。
<script src="path/to/jquery.js"></script>
2. バウアー
その後、Bower のようなパッケージ管理ツールを使い始めました。したがって、プロセスは次のようになります。まずコマンド ラインを開き、bower を使用して jQuery をインストールします。
bower install jquery
<script src="bower_components/jquery/dist/jquery.js"></script>
3.npm&ブラウザ
今、私にはおそらく次のような新しい選択肢があります:npm を使用して、コマンド ラインから jQuery をインストールします。
npm install jquery
var $ = require("jquery");
Browserifyはその名の通り、本来サーバー側にあるNodeやnpmをブラウザ側でも利用できるようにします。
明らかに、上記のプロセスはまだ終わっていません。次のステップは Browserify の作業です (上記のコードが配置されているファイルの名前が main.js であると仮定します)。
browserify main.js -o bundle.js
<script src="bundle.js"></script>
ちょっと待って、なぜ前よりも複雑になったのですか?
CommonJS スタイルのモジュールと依存関係の管理
Browserify は、JavaScript パッケージを簡単に参照できるようにするだけのツールではありません。その主な機能は、JavaScript モジュールと依存関係の管理です。 (これが教師の主な仕事です)
モジュールや依存関係の管理に関しては、RequireJSや国産のSea.jsなど、すでに優れた作品が存在します。そして今、Browserify は私たちに新しい選択肢を与えてくれました。
Browserify は Node のモジュール システムを参照し、他のモジュールを導入するために require() を使用し、モジュールをエクスポートするために module.exports を使用することに同意します。私の意見では、Browserify は、「ランタイム」モジュール ローダーの提供には重点を置かず、プリコンパイルに重点を置いているという点で、RequireJS や Sea.js とは異なります。プリコンパイルには追加のプロセスが必要になりますが、それに応じて、パッケージ化の層を追加するために特定のルールに従う必要はなくなります。したがって、比較すると、Browserify が提供する編成方法はより簡潔で、CommonJS 仕様との整合性が高くなります。
インストールと使用
コマンドラインフォーム
Browserify 自体も npm であり、npm を通じてインストールされます:
npm install -g browserify
browserify entry.js -o bundle.js
<script src="bundle.js"></script>
ガルプフォーム
npm install browserify --save-dev
次は gulpfile.js の部分です。ここに簡単な例を示します:
var gulp = require("gulp"); var browserify = require("browserify"); var sourcemaps = require("gulp-sourcemaps"); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); gulp.task("browserify", function () { var b = browserify({ entries: "./javascripts/src/main.js", debug: true }); return b.bundle() .pipe(source("bundle.js")) .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) .pipe(sourcemaps.write(".")) .pipe(gulp.dest("./javascripts/dist")); });
可以看到,Browserify是独立的,我们需要直接使用它的API,并将它加入到Gulp的任务中。
在上面的代码中,debug: true是告知Browserify在运行同时生成内联sourcemap用于调试。引入gulp-sourcemaps并设置loadMaps: true是为了读取上一步得到的内联sourcemap,并将其转写为一个单独的sourcemap文件。vinyl-source-stream用于将Browserify的bundle()的输出转换为Gulp可用的vinyl(一种虚拟文件格式)流。vinyl-buffer用于将vinyl流转化为buffered vinyl文件(gulp-sourcemaps及大部分Gulp插件都需要这种格式)。
这样配置好之后,直接运行gulp browserify就可以得到结果了,可能像这样:
如果你的代码比较多,可能像上图这样一次编译需要1s以上,这是比较慢的。这种时候,推荐使用watchify。它可以在你修改文件后,只重新编译需要的部分(而不是Browserify原本的全部编译),这样,只有第一次编译会花些时间,此后的即时变更刷新则十分迅速。
有关更多Browserify + Gulp的示例,请参考Gulp Recipes。
特性及简要原理
使用Browserify来组织JavaScript,有什么要注意的地方吗?
要回答这个问题,我们先看看Browserify到底做了什么。下面是一个比较详细的例子。
项目内现在用到2个.js文件,它们存在依赖关系,其内容分别是:
name.js
module.exports = "aya";
main.js
var name = require("./name"); console.log("Hello! " + name);
然后对main.js运行Browserify,得到的bundle.js的文件内容是这样的:
bundle.js
(function e(t, n, r) { // ... })({ 1: [function (require, module, exports) { var name = require("./name"); console.log("Hello! " + name); }, {"./name": 2}], 2: [function (require, module, exports) { module.exports = "aya"; }, {}] }, {}, [1]) //# sourceMappingURL=bundle.js.map
请先忽略掉省略号里的部分。然后,它的结构就清晰多了。可以看到,整体是一个立即执行的函数(IIFE),该函数接收了3个参数。其中第1个参数比较复杂,第2、3个参数在这里分别是{}和[1]。
模块map
第1个参数是一个Object,它的每一个key都是数字,作为模块的id,每一个数字key对应的值是长度为2的数组。可以看出,前面的main.js中的代码,被function(require, module, exports){}这样的结构包装了起来,然后作为了key1数组里的第一个元素。类似的,name.js中的代码,也被包装,对应到key2。
数组的第2个元素,是另一个map对应,它表示的是模块的依赖。main.js在key1,它依赖name.js,所以它的数组的第二个元素是{"./name": 2}。而在key2的name.js,它没有依赖,因此其数组第二个元素是空Object{}。
因此,这第1个复杂的参数,携带了所有模块的源码及其依赖关系,所以叫做模块map。
包装
前面提到,原有的文件中的代码,被包装了起来。为什么要这样包装呢?
因为,浏览器原生环境中,并没有require()。所以,需要用代码去实现它(RequireJS和Sea.js也做了这件事)。这个包装函数提供的3个参数,require、module、exports,正是由Browserify实现了特定功能的3个关键字。
缓存
第2个参数几乎总是空的{}。它如果有的话,也是一个模块map,表示本次编译之前被加载进来的来自于其他地方的内容。现阶段,让我们忽略它吧。
入口模块
第3个参数是一个数组,指定的是作为入口的模块id。前面的例子中,main.js是入口模块,它的id是1,所以这里的数组就是
[1]。数组说明其实还可以有多个入口,比如运行多个测试用例的场景,但相对来说,多入口的情况还是比较少的。
实现功能
还记得前面忽略掉的省略号里的代码吗?这部分代码将解析前面所说的3个参数,然后让一切运行起来。这段代码是一个函数,来自于browser-pack项目的prelude.js。令人意外的是,它并不复杂,而且写有丰富的注释,很推荐你自行阅读。
所以,到底要注意什么?
到这里,你已经看过了Browserify是如何工作的。是时候回到前面的问题了。首先,在每个文件内,不再需要自行包装。
你可能已经很习惯类似下面这样的写法:
;(function(){ // Your code here. }());
但你已经了解到,Browserify的编译会将你的代码封装在局部作用域内,所以,你不再需要自己做这个事情,像这样会更好:
// Your code here.
类似的,如果你想用"use strict";启用严格模式,直接写在外面就可以了,这表示在某个文件的代码范围内启用严格模式。
其次,保持局部变量风格。我们很习惯通过window.jQuery和window.$这样的全局变量来访问jQuery这样的库,但如果使用Browserify,它们都应只作为局部变量:
var $ = require("jquery"); $("#alice").text("Hello!");
这里的$就只存在于这个文件的代码范围内(独立的作用域)。如果你在另一个文件内要使用jQuery,需要按照同样的方式去require()。
然而,新的问题又来了,既然jQuery变成了这种局部变量的形式,那我们熟悉的各种jQuery插件要如何使用呢?
browserify-shim
你一定熟悉这样的jQuery插件使用方式:
<script src="jquery.js"></script> <script src="jquery.plugin.js"></script> <script> // Now the jQuery plugin is available. </script>
很多jQuery插件是这样做的:默认window.jQuery存在,然后取这个全局变量,把自己添加到jQuery中。显然,这在Browserify的组织方式里是没法用的。
为了让这样的“不兼容Browserify”(其实是不兼容CommonJS)的JavaScript模块(如插件)也能为Browserify所用,于是有了browserify-shim。
下面,以jQuery插件jquery.pep.js为例,请看browserify-shim的使用方法。
使用示例
安装browserify-shim:
npm install browserify-shim --save-dev
然后在package.json中做如下配置:
"browserify": { "transform": [ "browserify-shim" ] }, "browser": { "jquery.pep" : "./vendor/jquery.pep.js" }, "browserify-shim": { "jquery.pep" : { "depends": ["jquery:jQuery"] } }
最后是.js中的代码:
var $ = require("jquery"); require("jquery.pep"); $(".move-box").pep();
完成!到此,经过Browserify编译后,将可以正常运行这个jQuery插件。
这是一个怎样的过程呢?
在本例中,jQuery使用的是npm里的,而jquery.pep.js使用的是一个自己下载的文件(它与很多jQuery插件一样,还没有发布到npm)。查看jquery.pep.js源码,注意到它用了这样的包装:
;(function ( $, window, undefined ) { // ... }(jQuery, window));
可以看出,它默认当前环境中已存在一个变量jQuery(如果不存在,则报错)。package.json中的"depends": ["jquery:jQuery"]是为它添加依赖声明,前一个jquery表示require("jquery"),后一个jQuery则表示将其命名为jQuery(赋值语句)。这样,插件代码运行的时候就可以正常找到jQuery变量,然后将它自己添加到jQuery中。
实际上,browserify-shim的配置并不容易。针对代码包装(尽管都不兼容CommonJS,但也存在多种情况)及使用场景的不同,browserify-shim有不同的解决方案,本文在此只介绍到这。
关于配置的更多说明,请参照browserify-shim官方文档。更多参考可以查看browserify shim recipes。此外,如果你觉得browserify-shim有些难以理解或者对它的原理也有兴趣,推荐阅读这篇Stack Overflow上的回答。
当然,对于已经处理了CommonJS兼容的库或插件(比如已经发布到npm),browserify-shim是不需要的。
其实还有的更多transform
在前面browserify-shim的例子中,"browserify": {"transform": [ "browserify-shim" ]}其实是Browserify的配置。可以看出,browserify-shim只是Browserify的其中一种transform。在它之外,还有很多的transform可用,分别应对不同的需求,使Browserify的体系更为完善。
比如,还记得本文引言里的Bower吗?debowerify可以让通过Bower安装的包也可以用require()引用。npm和bower同为包管理工具,Browserify表示你们都是我的翅膀。
一点提示
Browserify是静态分析编译工具,因此不支持动态require()。例如,下面这样是不可以的:
var lang = "zh_cn"; var i18n = require("./" + lang);
文档资料
有关Browserify更详细的说明文档,请看browserify-handbook。
结语
我觉得Browserify很有趣,它用了这样一个名字,让你觉得它好像只是一个Node的浏览器端转化工具。为此,它还完成了Node中大部分核心库的浏览器端实现。但实际上,它走到了更远的地方,并在JavaScript模块化开发这个重要的领域中,创立了一个全新的体系。
喜欢CommonJS的简洁风格?请尝试Browserify!