砖模块和依赖
你是否是 JavaScript 新手,搞不清楚模块、模块加载器和模块打包器?或者你已经写了 JavaScript 一段时间,但是没法掌握模块的一些行话?你是否听过像 CommonJS、AMD、Browserify、SystemJS、Webpack、JSPM 等等术语,但是不理解为什么我们需要它们?
我将解释它们是什么,它们要解决什么问题,以及如何解决问题。
应用程序运行界面
在本文中,会用一个简单的应用程序来阐述模块的概念。这个应用程序要在浏览器上显示数组的和,它由四个函数和一个 index.html
文件组成。
函数的依赖示意图
main
函数计算数组中数字的和,然后把答案显示给 span#answer
。sum
函数依赖于两个函数:add
和 reduce
。add
函数做它名字所做的,把两个数相加。reduce
函数遍历数组,并且调用 iteratee
回调函数。
花点时间理解下面的代码。我会多次使用相同的函数。
0 - index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Modules</title>
</head>
<body>
<h1>
The Answer is
<span id="answer"></span>
</h1>
</body>
</html>
1 - main.js
var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
var answer = sum(values)
document.getElementById("answer").innerHTML = answer;
2 - sum.js
function sum(arr){
return reduce(arr, add);
}
3 - add.js
function add(a, b) {
return a + b;
}
4 - reduce.js
function reduce(arr, iteratee) {
var index = 0,
length = arr.length,
memo = arr[index];
for(index += 1; index < length; index += 1){
memo = iteratee(memo, arr[index])
}
return memo;
}
我们来看看如何将这些代码片段放在一起,来创建一个应用程序。
内嵌脚本就是在<script></script>
标记之间添加 JavaScript 代码。这是我开始学 JavaScript 时的做法。我相信大多数 JavaScript 开发者在其生命里至少这样做过一次。
这是开始的好方法。不需要操心外部文件或者依赖。但是这也导致了不可维护的代码,因为:
main
函数之前就有 add
、reduce
和 sum
函数。<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Modules</title>
</head>
<body>
<h1>
The Answer is
<span id="answer"></span>
</h1>
<script type="text/javascript">
function add(a, b) {
return a + b;
}
function reduce(arr, iteratee) {
var index = 0,
length = arr.length,
memo = arr[index];
for(index += 1; index < length; index += 1){
memo = iteratee(memo, arr[index])
}
return memo;
}
function sum(arr){
return reduce(arr, add);
}
/* Main Function */
var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
var answer = sum(values)
document.getElementById("answer").innerHTML = answer;
</script>
</body>
</html>
Script
标记链接外部 JavaScript 文件这是嵌入脚本的一个自然过渡。现在我们将大段的 JavaScript 分成更小的段,并用 <script src=“...”></script>
标记加载它们。
通过将文件分离到多个 JavaScript 文件,就可以重用代码了。我们不再需要在不同的网页之间复制和粘贴代码,只需要将文件用 script
标记就可以了。这种方法虽然更好,但是依然有如下问题:
add.js
、reduce.js
和 sum.js
文件。0 - index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Modules</title>
</head>
<body>
<h1>
The Answer is
<span id="answer"></span>
</h1>
<script type="text/javascript" src="./add.js"></script>
<script type="text/javascript" src="./reduce.js"></script>
<script type="text/javascript" src="./sum.js"></script>
<script type="text/javascript" src="./main.js"></script>
</body>
1 - add.js
//add.js
function add(a, b) {
return a + b;
}
2 - reduce.js
//reduce.js
function reduce(arr, iteratee) {
var index = 0,
length = arr.length,
memo = arr[index];
index += 1;
for(; index < length; index += 1){
memo = iteratee(memo, arr[index])
}
return memo;
}
3 - sum.js
//sum.js
function sum(arr){
return reduce(arr, add);
}
4 - main.js
// main.js
var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
var answer = sum(values)
document.getElementById("answer").innerHTML = answer;
通过使用模块对象和立即运行的函数表达式(IIFE),我们可以减少全局命名空间污染。在本方法中,我们只暴露了一个对象给全局作用域,该对象包含了应用程序中所需的所有方法和值。在本例中,我们只暴露了 myApp
对象给全局作用域。所有的函数将都被放到 myApp
对象中。
// 01 my-app.js
var myApp = {};
// 02 - add.js
(function(){
myApp.add = function(a, b) {
return a + b;
}
})();
// 03 - reduce.js
(function(){
myApp.reduce = function(arr, iteratee) {
var index = 0,
length = arr.length,
memo = arr[index];
index += 1;
for(; index < length; index += 1){
memo = iteratee(memo, arr[index])
}
return memo;
}
})();
// 04 - sum.js
(function(){
myApp.sum = function(arr){
return myApp.reduce(arr, myUtil.add);
}
})();
// 05 - main.js
(function(app){
var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
var answer = app.sum(values)
document.getElementById("answer").innerHTML = answer;
})(myApp);
// 06 - index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Modules</title>
</head>
<body>
<h1>
The Answer is
<span id="answer"></span>
</h1>
<script type="text/javascript" src="./my-app.js"></script>
<script type="text/javascript" src="./add.js"></script>
<script type="text/javascript" src="./reduce.js"></script>
<script type="text/javascript" src="./sum.js"></script>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
注意除了 my-app.js
之外,其它每个文件都被封装成 IIFE 格式。
(function(){ /*... your code goes here ...*/ })();
通过将每个文件封装为 IIFE,所有的局部变量就都待在函数作用域内。所以,函数中的所有变量将都会待在函数作用域内,而不会污染全局作用域。
通过将 add
、reduce
和 sum
函数附加在 myApp
对象上,从而对外暴露它们。并且我们可以像这样,通过引用 myApp
来访问这些函数:
myApp.add(1,2);
myApp.sum([1,2,3,4]);
myApp.reduce(add, value);
我们还可以通过 IIFE 参数,传递 myApp
全局对象,就像 main.js
文件中所示一样。通过将该对象作为参数传递给 IIFE,我们就可以为该对象选择一个较短的别名。而我们的代码会更简短点。
(function(obj){
// obj 是一个新的 veryLongNameOfGlobalObject
})(veryLongNameOfGloablObject);
这与前例相比有很大的提升了。很多流行的 JavaScript 都会用这种模式。比如 jQuery,它暴露一个全局对象 $
,所有 jQuery 函数都挂在 $
对象之下。
但是,这依然不算是完美的解决方案。这种方案依然会遇到上节相同的问题:
myApp.js
必须出现在所有其它文件之前,main.js
必须处在所有其它库文件之后。在 2009 年,出现了关于将 JavaScript 带到服务器端的讨论,因而 ServerJS 诞生了。之后,ServerJS 更名为 CommonJS。
CommonJS 并非一个 JavaScript 库,而是一个标准化组织,像 ECMA 或者 W3C 一样。ECMA 定义了 JavaScript 语言规范。W3C 定义了 JavaScript Web API,比如 DOM 和 DOM 事件。CommonJS 的目标是为 Web 服务器、桌面和命令行应用程序定义一套通用的 API。
CommonJS 还定义了模块 API 。因为在服务器应用程序中没有 HTML 页面,也没有 script 标记,所以就得有一些清晰的模块 API。模块需要暴露(export)给其它模块使用,并且是可访问的(import)。它的输出模块语法像这样的:
// add.js
module.exports = function add(a, b){
return a+b;
}
上述代码定义和输出了一个模块。代码保存在 add.js
文件中。
要使用或者导入 add
模块,需要 require
函数用文件名或者模块名为参数。如下的语法描述如何导入一个模块到代码中:
var add = require(‘./add’);
如果你曾经在 NodeJS 上写过代码,那么这种语法看起来会很熟悉。这是因为 NodeJS 实现了 CommonJS 风格的模块 API。
CommonJS 风格的模块定义的问题是,它不是异步的。当调用 var add=require(‘add’);
时,系统会暂停,直到模块准备好了。这意味着在所有模块正在加载时,这行代码会冻结浏览器。所以这可能不是定义浏览器端模块的最佳方式。
为了把服务器端用的模块语法转换给浏览器端用,CommonJS 提出了几种模块格式。其中之一,即 “Module/Transfer/C“,后来成为异步模块定义(AMD)。
AMD 的格式如下:
define([‘add’, ‘reduce’], function(add, reduce){
return function(){...};
});
define
函数(或者关键字)用依赖列表和一个回调函数做参数。回调函数的参数与数组中的依赖有相同的次序。这等于导入模块。而回调函数会返回一个值,该值就是输出的值。
CommonJS 和 AMD 解决了模块模式剩下的两个问题:依赖解析和全局作用域污染,现在我们只需要注意每个模块或者文件的依赖,并且不再有全局作用域污染。
AMD 可以把我们从浏览器应用程序中的 script 标记黑洞和全局污染中解救出来。那么,我们该如何使用它呢?这里 RequireJS 就可以帮助我们了。RequireJS 是一个 JavaScript 模块加载器。它可以帮助我们按需异步加载模块。
尽管 RequireJS 的名字中含有 require
,但是它的目标却并非要去支持 CommonJS 的 require
语法。有了 RequireJS,我们就可以编写 AMD 风格的模块。
在编写自己的应用程序之前,你将不得不从 RequireJS 网站 下载 require.js
文件。如下代码是用 RequireJS 编写的示例应用程序。
AMD 风格的示例应用程序
0 - index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Modules</title>
</head>
<body>
<h1>
The Answer is
<span id="answer"></span>
</h1>
<script data-main="main" src="require.js"></script>
</body>
</html>
1 - main.js
// main.js
define(['sum'], function(sum){
var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
var answer = sum(values)
document.getElementById("answer").innerHTML = answer;
})
2 - sum.js
// sum.js
define(['add', 'reduce'], function(add, reduce){
var sum = function(arr){
return reduce(arr, add);
};
return sum;
})
3 - add.js
// add.js
define([], function(){
var add = function(a, b){
return a + b;
};
return add;
});
4 - reduce.js
// reduce.js
define([], function(){
var reduce = function(arr, iteratee) {
var index = 0,
length = arr.length,
memo = arr[index];
index += 1;
for(; index < length; index += 1){
memo = iteratee(memo, arr[index])
}
return memo;
}
return reduce;
})
注意在 index.html
中只有一个 script 标记。
<script data-main="main" src="require.js"></script>
这个标记加载 require.js
库到页面,data-main
属性告诉 RequieJS 应用程序的入口点在哪里。默认情况下,它假定所有文件都有 .js
扩展名,所以省略 .js
文件扩展名是可以的。在 RequireJS 加载了 main.js
文件之后,就会加载该文件的依赖,以及依赖的依赖,等等。Chrome 浏览器的开发者工具会显示所有文件被以如下顺序加载:
浏览器加载 index.html
,index.html
又加载 require.js
。剩下的文件及其依赖都是由 require.js
负责加载。
RequireJS 和 AMD 解决了我们以前所遇到的所有问题。但是,它有带来了其它一些不怎么严重的问题:
define
函数内,代码就有一些额外的缩进。对于小文件来说,这不是啥问题,但是对于大的代码库来说,就可能是精神上的疲惫。因为以上原因,有些人就想用 CommonJS 语法来替换。但是,CommonJS 语法用于服务器和同步的,对吧?这时 Browserify 就来解救我们了!有了 Browserify,我们就可以在浏览器应用程序中使用 CommonJS 模块。Browserify 是一个模块打包器,它遍历代码的依赖树,将依赖树中的所有模块打包成一个文件。
RequireJS 是一个 JS 库,但是 Browserify 是一个命令行工具,需要 NodeJS 和 NPM 来按住那个它。如果系统中安装了 NodeJS,就可以用如下命令来安装 Browserify:
npm install -g browserify
下面我们来看看用 CommonJS 语法写的示例应用程序。
0 - index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Modules</title>
</head>
<body>
<h1>
The Answer is
<span id="answer"></span>
</h1>
<script src="bundle.js"></script>
</body>
</html>
1 - main.js
//main.js
var sum = require('./sum');
var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
var answer = sum(values)
document.getElementById("answer").innerHTML = answer;
2 - sum.js
//sum.js
var reduce = require('./reduce');
var add = require('./add');
module.exports = function(arr){
return reduce(arr, add);
};
3 - add.js
//add.js
module.exports = function add(a,b){
return a + b;
};
4 - reduce.js
//reduce.js
module.exports = function reduce(arr, iteratee) {
var index = 0,
length = arr.length,
memo = arr[index];
index += 1;
for(; index < length; index += 1){
memo = iteratee(memo, arr[index])
}
return memo;
};
你可能注意到,在 index.html
文件中,script 标记加载 bundle.js
,但是 bundle.js
文件在哪里?一旦我们执行了如下命令,Browserify 就会为我们生成这个文件:
$ brwoserify main.js -o bundle.js
Browserify 解析 main.js
中的 require()
函数调用,并遍历项目中的依赖树。然后将依赖树打包到一个文件中。
如下是Browserify 生成的 bundle.js
文件的代码:
function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
module.exports = function add(a,b){
return a + b;
};
},{}],2:[function(require,module,exports){
var sum = require('./sum');
var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
var answer = sum(values)
document.getElementById("answer").innerHTML = answer;
},{"./sum":4}],3:[function(require,module,exports){
module.exports = function reduce(arr, iteratee) {
var index = 0,
length = arr.length,
memo = arr[index];
index += 1;
for(; index < length; index += 1){
memo = iteratee(memo, arr[index])
}
return memo;
};
},{}],4:[function(require,module,exports){
var reduce = require('./reduce');
var add = require('./add');
module.exports = function(arr){
return reduce(arr, add);
};
},{"./add":1,"./reduce":3}]},{},[2]);
我们不必一行一行理解这个打包文件,只是要注意,所有熟悉的代码、main 文件及其所有依赖,都包含在这个文件中。
现在我们已经学习了全局模块对象、CommonJS 和 AMD 风格的模块,并且有很多库可以帮助我们要么用 CommonJS,要么用 AMD。但是,如果我们正编写一个模块,并要把它部署到互联网上该怎么办?我们到底需该用哪种风格写代码呢?
用三种不同的模块类型,即全局模块对象、CommonJS 和 AMD,都是可以的。但是我们就不得不维护三种不同的文件,用户就不得不识别他们正在下载的模块的类型。
通用模块定义 UMD(Universal Module Definition)是用来解决这个特殊问题的。本质上,UMD 是一套用来识别当前环境支持的模块风格的 if/else 语句。如下是用 UMD 风格编写的 sum
模块:
//sum.umd.js
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['add', 'reduce'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('add'), require('reduce'));
} else {
// Browser globals (root is window)
root.sum = factory(root.add, root.reduce);
}
}(this, function (add, reduce) {
// private methods
// exposed public methods
return function(arr) {
return reduce(arr, add);
}
}));
JavaScript 全局模块对象、CommonJS、AMD 和 UMD,太多选择了。现在也许你会问,下一个项目我该用哪一个呢?答案是一个都不用。
JavaScript 语言中并没有内置模块系统。这正是我们有如此多输入和输出模块的不同方式的原因。但是这种情况最近得到改变了。在 ES6 规范中,模块已经成为 JavaScript 的一部分。所以这个问题的答案是,如果想让项目不会过时,就得用 ES6 模块语法。
ES6 用 import
和 export
关键字来输入和输出模块。如下是用 ES6 模块语法编写的示例应用程序。
01 - main.js
// main.js
import sum from "./sum";
var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
var answer = sum(values);
document.getElementById("answer").innerHTML = answer
02 - sum.js
// sum.js
import add from './add';
import reduce from './reduce';
export default function sum(arr){
return reduce(arr, add);
}
03 - add.js
// add.js
export default function add(a,b){
return a + b;
}
04 - reduce.js
//reduce.js
export default function reduce(arr, iteratee) {
let index = 0,
length = arr.length,
memo = arr[index];
index += 1;
for(; index < length; index += 1){
memo = iteratee(memo, arr[index]);
}
return memo;
}
关于 ES6 模块有很多广告语:ES6 模块语法是简洁的;ES6 模块将统治 JavaScript 世界;ES6 模块是未来。。。但是不幸的是,有个问题,浏览器还没有为这个新语法做好准备。在本文编写时,只有 Chrome 浏览器支持 import 语句。即使到了大多数浏览器都支持 import
和 export
的时候,如果应用程序必须支持老的浏览器,我们就还会遇到一个问题。
幸运的是,现在已经有很多工具可以用了,这些工具让我们现在就可以用 ES6 模块语法。
Webpack 是一个模块打包器。就像 Browserify 一样,它会遍历依赖树,然后将其打包到一到多个文件。如果 Webpack 与 Browserify 完全相同,那么我们为什么依然需要另一个模块打包器呢?Webpack 可以处理CommonJS、AMD 和 ES6 模块,并且它带来了更大的灵活性和很酷的一些功能,比如:
require()
语法,不仅仅可以加载 JavaScript 文件,还可以加载 CSS、CoffeeScript、Sass、Less、HTML模板、图像,等等。下面我们来看看如何用 Webpack 创建示例应用程序。Webpack 需要一点引导工作和配置。
因为 Webpack 是 JavaScript 命令行工具,所以需要先安装上 NodeJS 和 NPM。装好 NPM 后,执行如下命令初始化项目:
$ mkdir project; cd project
$ npm init -y
$ npm install -D webpack webpack-dev-server
你需要为 wepack 写一个配置文件 webpack.config.js。文件中至少需要 entry 和 output 两个字段。
module.exports = {
entry: ‘./app/main.js’,
output: {
filename: ‘bundle.js’
}
}
打开 'package.json” 文件,在 'script’ 字段后添加如下行:
"scripts": {
"start": "webpack-dev-server -progress -colors",
"build": "webpack"
},
现在在 'project/app' 目录下添加所有 JavaScript 模块,在 'project' 目录下添加 index.html。
01 - index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Modules</title>
</head>
<body>
<h1>
The Answer is
<span id="answer"></span>
</h1>
<script src="bundle.js"></script>
</body>
</html>
02 - webpack.config.js
module.exports = {
entry: './app/main.js',
output: {
path: './dist',
filename: 'bundle.js'
}
}
03 - package.json
{
"name": "jsmodules",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"start": "webpack-dev-server --progress --colors",
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^1.12.14",
"webpack-dev-server": "^1.14.1"
}
}
04 - app-add.js
// app/add.js
module.exports = function add(a,b){
return a + b;
};
05 - app-reduce.js
// app/reduce.js
module.exports = function reduce(arr, iteratee) {
var index = 0,
length = arr.length,
memo = arr[index];
index += 1;
for(; index < length; index += 1){
memo = iteratee(memo, arr[index])
}
return memo;
};
06 - app-sum.js
// app/sum.js
define(['./reduce', './add'], function(reduce, add){
sum = function(arr){
return reduce(arr, add);
}
return sum;
});
07 - app-main.js
// app/main.js
var sum = require('./sum');
var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
var answer = sum(values)
document.getElementById("answer").innerHTML = answer;
注意 add.js
和 reduce.js
是用 CommonJS 风格写的,而 sum.js
是用 AMD 风格写的。 Webpack 默认是可以处理 CommonJS 和 AMD。如果你用 ES6 模块,就需要安装和配置 'babel loader'。
所有文件准备好,就可以运行你的应用程序了。
$ npm start
打开浏览器,把 URL 指向 .
webpack dev server in action
此时,你可以打开你喜欢的编辑器编辑代码。保存文件时,浏览器会自动刷新以显示修改后的结果。
这里你可能会注意到一件事情,就是找不到 dist/bundle.js
文件。这是因为 Webpack Dev Server 会创建打包文件,但是不会写入到文件系统中,而是放在内存中。
如果要部署,就得创建打包文件。可以键入如下命令,创建 bundle.js
文件:
$ npm run build
如果有兴趣学习更多的 Webpack 技巧,请参考 Webpack 文档页。
将一个大的 JavaScript 库包含进来,只是为了用它函数中的少数几个,你是否有这样的经历?Rollup 是另一个 JavaScript ES6 模块打包器。与 Browserify 和 Webpak 不同,rollup 值包含在项目中用到的代码。如果有大模块,带有很多函数,但是你只是用到少数几个,rollup 只会将需要的函数包含到打包文件中,从而显著减少打包文件大小。
Rollup 可以被用作为命令行工具。如果有 NodeJS 和 NPM,那么就可以用如下命令安装 rollup:
$ npm install -g rollup
Rollup 可以与任何类型的模块风格一起工作。但是,推荐使用 ES6 模块风格,这样就可以利用 tree-shaking 功能。如下是用 ES6 编写的示例应用程序代码:
01 - add.js
let add = (a,b) => a + b;
let sub = (a,b) => a - b;
export { add, sub };
02 - reduce.js
// reduce.js
export default (arr, iteratee) => {
let index = 0,
length = arr.length,
memo = arr[index];
index += 1;
for(; index < length; index += 1){
memo = iteratee(memo, arr[index]);
}
return memo;
}
03 - sum.js
// sum.js
import { add } from './add';
import reduce from './reduce';
export default (arr) => reduce(arr, add);
04 - main.js
// main.js
import sum from "./sum";
var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
var answer = sum(values);
document.getElementById("answer").innerHTML = answer;
注意,在 add
模块中,我引入了另一个函数 sub()
。但是该函数在应用程序中并没有用到。
现在我们用 rollup 将这些代码打包:
$ rollup main.js -o bundle.js
这会生成像如下的 bundle.js
文件:
let add = (a,b) => a + b;
var reduce = (arr, iteratee) => {
let index = 0,
length = arr.length,
memo = arr[index];
index += 1;
for(; index < length; index += 1){
memo = iteratee(memo, arr[index]);
}
return memo;
}
var sum = (arr) => reduce(arr, add);
var values = [ 1, 2, 4, 5, 6, 7, 8, 9 ];
var answer = sum(values);
document.getElementById("answer").innerHTML = answer;
这里我们可以看到 sub()
函数并没有包含在这个打包文件中。
SystemJS 是一个通用的模块加载器,它能在浏览器或者 NodeJS 上动态加载模块,并且支持 CommonJS、AMD、全局模块对象和 ES6 模块。通过使用插件,它不仅可以加载 JavaScript,还可以加载 CoffeeScript 和 TypeScript。
SystemJS 的另一个优点是,它建立在 ES6 模块加载器之上,所以它的语法和 API 在将来很可能是语言的一部分,这会让我们的代码更不会过时。
要异步输入一个模块,可以用如下语法:
System.import(‘module-name’);
然后我们可以用配置 API 来配置 SystemJS 的行为:
System.config({
transplier: ‘babel’,
baseURL: ‘/app’
});
上面的配置会让 SystemJS 使用 babel 作为 ES6 模块的编译器,并且从 /app
目录加载模块。
随着现代 JavaScript 应用程序变得越来越大,越来越复杂,开发工作流也是如此。所以我们不仅仅模块加载器,还得去寻找开发服务器、生产的模块打包器以及第三方模块的包管理器。
JSPM 是 JavaScript 开发工具的瑞士军刀,它是既是包管理器,又是模块加载器,又是模块打包器。
现代 JavaScript 开发很少只是需要自己的模块,绝大部分时候,我们还需要第三方模块。使用 JSPM,我们可以使用如下的命令,从 NPM 或者 Github 安装第三方模块:
jspm install npm:package-name or github:package/name
上述命令会从 npm
或者 github
下载包,并将包安装到 jspm_packages
目录。
在开发模式下,我们可以使用 jspm-server
。像 Webpack Dev Server 一样,它会检测代码改变,重新加载浏览器来显示改变。与 Webpack Dev Server 不同的是,jspm-server 用的是 SystemJS 模块加载器。所以,每次它检测了文件的改变时,不会将所有文件读取来打包,而是只加载页面所需要的模块。
在部署时,肯定要打包代码。JSPM 带有打包器,可以用如下命令对代码打包:
jspm bundle main.js bundle.js
在幕后,JSPM 用 rollup 作为它的打包器。
我希望本文给了足够的信息来理解 JavaScript 模块的词汇。现在你也许会问,下一个项目我应该用什么呢?不幸的是,我回答不了这个问题。现在你有能力开始自己的探索。希望本文能让你更容易理解我提到的有关工具的文档和文章。
本文所有的代码示例都可以在 这个 Github 仓库中找到。如有任何疑问,请在下面留言。
扫码关注w3ctech微信公众号
共收到0条回复