Total Pageviews

Monday 29 August 2016

jspm & SystemJS 教程

前端开发上,要解决的问题能简单分为两个阶段:
  1. 开发阶段
  2. 部署阶段
其中开发阶段要解决:
  1. 第三方包安装、使用、依赖关系的维护
  2. 自有代码的依赖关系维护及使用
先来聊聊开发阶段的解决方案。

包管理器

最初在 jQuery 站点上,文档可能是这样写的:
  1. 下载 jquery.min.js 文件
  2. 保存到 js 目录
  3. 在 HTML 文件中使用 script 标签引用 jquery
因为 jQuery 不依赖其它库,所以相对来说,上面的操作还算简单。
但如果碰上有依赖关系的,比如 Bootstrap 依赖于 jQuery,我们可能就需要分开下载 Bootstrap 与 jQuery。好在这一类第三方库通常都在下载文件中打包好依赖了。但这样又有一个问题,如果另一个库也同样打包一个 jQuery,并且版本与 Bootstrap 里打包的不一致呢。可以想像,这样的情况并不少见,我们的开发目录最终容易失控 – 添加包很容易,删除就难了。另外,手工来做这件事,效率太低。
包管理器的意义就在这里。它封装了细节,自动化处理我们的需求。我们只需要提问题,它们提供答案:
  1. 我要使用 jQuery – 好,bower install jquery
  2. 我要使用 Bootstrap – 好, bower install bootstrap,顺便会把依赖 jQuery 一起安装了
  3. 我想了想,还是删除 Bootstrap 吧 – 好, bower uninstall bootstrap
包管理器会维护一个依赖清单,个中关系一目了然。
当然,以上只是用 bower 举例,市面上同类产品还非常多,比如 duojs,本文的主角 jspm 也是一个,甚至 npm 都算。

加载器

包管理器解决了我们管理各种模块的需求。接下来,我们要利用这些模块来开发,那么就会碰上如何使用这些模块的问题了。
目前 ES6 模块的标准还没在浏览器中得到完全落实,过渡期间我们有许多规范或不规范的模块:
  1. CommonJS
  2. AMD
  3. ES6 Modules
  4. 命名空间方式定义
  5. 其它
如果只使用单一规范,比如针对 AMD,我们可能会用 RequireJS;ES6 的模块,我们可能会用到 ES6 Module Loader Polyfill;CommonJS 规范的模块,我们可能用 SystemJS – 它同样可用于加载 AMD/ES6 模块。

CSS 加载器??

上面提及的加载器,通常是针对 JavaScript 模块的,CSS 并没有严格意义的模块,那它怎么管理?我们的包管理器当然会连着包的 CSS 文件一同管理。那我们该如何使用这些模块中的 CSS 呢?举 SystemJS 来说,我们可以通过它的插件执行 import 命令动态插入 CSS。打包的时候,SystemJS 默认会把整个 CSS 文件打包入 JS 文件中。当然,我们也可以借助 bower 与 gulp.js 及 gulp.js 的 wiredep 插件 这样的组合实现在页面上「主动」插入 link 标签 – 但这需要搭配 gulp.js 等工具。
走完开发阶段,我们来看看部署阶段要解决的几个明显问题:
  1. CSS 文件合并、压缩等
  2. JavaScript 文件合并、压缩、混淆等
不过,还是先正式介绍 jspm 与 SystemJS 的用法。

jspm

如前所说的,jspm 是一个浏览器端包管理器。

安装 jspm

npm install jspm -g

初始化目录

在安装完 jspm 后,我们在命令行下就有一个 jspm 命令可用。
创建一个目录,执行 jspm init 即可在该目录下初始化开发环境:
Package.json file does not exist, create it? [yes]: 
Would you like jspm to prefix the jspm package.json properties under jspm? [yes]: 
Enter server baseURL (public folder path) [.]: 
Enter jspm packages folder [./jspm_packages]: 
Enter config file path [./config.js]: 
Configuration file config.js doesn't exist, create it? [yes]:
Enter client baseURL (public folder URL) [/]: 
Which ES6 transpiler would you like to use, Traceur or Babel? [traceur]:
如果你用过 yeoman 一类工具,对这类提示应该非常熟悉。

安装第三方库

比如要安装 jQuery:
jspm install jquery
这条命令会从 github:components/jquery 上读取下载。
还可以从 npm 上下载安装:
jspm install npm:jquery

创建 HTML 文件

创建一个 index.html 文件如下:
 <!doctype html>
  <script src="jspm_packages/system.js"></script>
  <script src="config.js"></script>
  <script>
    System.import('app');
  </script>
首先我们需要引用 jspm_packages/system.js,这个是 jspm 提供的万用加载器。之后是 config.js 文件,我们安装的各种包、依赖等信息都在这个文件中维护,之后我们用全局的 System.import 执行 index.html 同一目录下的 app.js 文件。
在 app.js 文件中,我们使用 ES6 语法:
import $ from 'jquery';
$(function() {
  console.log($);
});
假定我们要在 index.html 中使用 Bootstrap,那么先通过 jspm 安装:
jspm install bootstrap
然后把 app.js 文件修改如下:
import bootstrap from 'bootstrap';

$(function() {
    console.log($);
});
我们并没有 import jQuery,这是因为 jspm 维护有 bootstrap 的依赖,会自动加载 jQuery,不需要我们再手动 import
import bootstrap from 'bootstrap' 一行是加载了 Bootstrap 的 js 模块。那么,Bootstrap 的 CSS 部分如何加载呢?我们需要用到 jspm 的 CSS 插件
首先,安装 jspm 的 css 插件:
jspm install css
然后在 app.js 中添加一行:
import 'bootstrap/css/bootstrap.css!';
! 表示这会经过插件处理。
这时如果在本地服务器上打开 index.html 文件,借助浏览器的开发者工具查看:
[resp_image id=’16028′ caption=” ]
Wow,请求有点多 – 但这只是开发阶段。

打包 JavaScript

我们终于说到 JavaScript 的打包了。
jspm 里,js 文件的打包非常简单,举上面的例子说,如果我们只有一个 js 入口的话,则执行:
jspm bundle-sfx app build.js --minify
就可以将所有需要的 js 文件包括 CSS 文件打包到一个 build.js 文件中。
之后修改 index.html 文件中 script 部分如下:
<!--    <script src="jspm_packages/system.js"></script>
    <script src="config.js"></script>
    <script>
        System.import('app');
    </script>-->
    <script src="build.js"></script>
这时打开 index.html 页面,就只剩下 index.html 与 build.js 两个请求了。

打包 CSS

在上在一个步骤中,我们把 CSS 文件连着一起打包进了 js 中,这可能并不是多数人想要的结果。
我们可以通过定义 config.js 文件改变这种行为。
打开 config.js 文件,添加 seperateCSS: true
System.config({
  "baseURL": "/",
  "separateCSS": true
});
再次执行 jspm bundle-sfx app build.js --minify,会在 index.html 同级目录下生成一个 build.js 与 build.css,在 index.html 中引用 build.css 文件即可。
对比 gulp.js 或 grunt.js 等工具,jspm 给我的体验非常好,解决了开发阶段、部署阶段的几个重要难题,目前只有 ember-cli 这样的环境能给我同样的感受.