转载

使用 jspm 把握 JavaScript 模块的未来

JavaScript 封装横跨两个世界:客户端和系统/服务器端。客户端 JavaScript 依赖项管理和封包长久以来一直是一个 Web 开发痛点,目前有各种零散解决方案在使用中。撰写本文时,仍然是标准预览版的 ECMAScript 6 (ES6) 尝试从这些解决方案中吸取最好的想法,并将它们合并到语言中。

jspm 是一种渐进式框架,它使得为 JavaScript 使用 ES6 依赖项处理语法成为可能。jspm 结合:

  • (通过 Babel ),支持使用未来的语言功能
  • SystemJS ,一个可了解在用的若干封装形态的模块抽象层

jspm 位于 Babel 和 SystemJS 之上,以一致的语法处理它们,通过 npm 和 GitHub 等注册表提供自动化的模块解析。该框架还包括对 CSS 加载和一个开发服务器的支持。

本教程对 jspm 进行了实用的介绍,并概述了该框架可为您的项目提供什么。对于上下文,我首先从一个通过 Node Package Manager (npm) 进行服务器端依赖项管理的简单示例入手。然后您将了解 jspm 如何在浏览器端提供可以比拟的易用性和其他优势。参见部分,获取样例代码。

npm local

您可能熟悉 Node.js 及其对 JavaScript 作为浏览器的免费通用语言的应用。您可以使用 Node 运行 JavaScript 文件,但 Node 中几乎相关的一切都通过 模块 (也称为 依赖项 )发生,您必须将其包含在内。npm 使得在全局系统和本地项目中安装和控制服务器端依赖项变得非常容易。

如果您尚未在您的系统上安装 Node.js 和 npm,请立即 安装 它们。安装后,您的全局路径上有两个命令: nodenpm

您的项目将要创建一个命令行应用程序,其中包含一个旋转器(一个告诉用户处理正在进行中、请稍候的动画图标)。这项功能(以前属于计算密集型功能)为了便于日常使用而进行了简化,如今以 node 模块形式存在。

通过运行 npm install module-name -g ,将 node 模块安装到全局上下文中,但对于该应用程序,您 不会 希望上下文是全局的。而是希望为项目明确定义依赖项,并将其包含在项目中。这样一来,您便可以通过版本控制签出项目,并在项目内安装依赖项,还可以查看其中列出的依赖项。

因此使用 package.json(一个项目中控制 npm 的文件)保持所有内容一目了然地包含在内。清单 1 显示了针对您的应用程序的一个简单 package.json。将此文件放在您的项目中一个新文件夹的 root 中。

清单 1. 包含依赖项的 package.json

{     "name": "jspm-intro",     "preferGlobal": true,     "version": "1.0.0",     "author": "user",     "description": "JSPM Intro - Demo App",     "license": "",     "engines": {         "node": ">=0.10"     },     "scripts": {         "go": "node app-cl.js"     },     "dependencies": {         "cli-spinner": "0.2.2"     } }

请注意,package.json 中的 dependencies 条目引用了 cli-spinner 模块,为其提供了一个版本号。(大部分 JavaScript 包会为您提供其 npm 页面的最新版本 — 例如 cli-spinner 页面。)您还可以通过从命令行运行 npm install package-name 来获得最新版本。

要安装依赖项,请从命令行运行 npm install 。观察 npm 下载依赖项并将它们放入 /node_modules。

另请注意,在清单 1 中, scripts 包含一个 go 命令。在 scripts 属性中,您可以在已定义 npm 上下文内运行任意命令。因此依赖项可用于 node app-cl.js 命令。(在 npm run-script 命令执行期间 /node_modules 目录被放到路径上。)

清单 2 展示了一个简单但令人深刻的 app-cl.js 程序。

清单 2. Node 应用程序中需要并使用了一个模块

"use strict"; var Spinner = require('cli-spinner').Spinner;  var spinner = new Spinner('This is gonna blow your mind... %s'); spinner.setSpinnerString('|/-//');  spinner.start();  setTimeout(function() {     spinner.stop();s }, 3000);

转到命令行并运行 npm run-script go 。可以看到旋转器在旋转,这表明依赖项使用是正常的。

用于 Web 应用程序的 jspm

假设您在编写一个 Web 应用程序,并想要包含 jQuery 。如果您有 npm,那么可以按照安装 spinner 模块的相同方法来安装 JQuery 模块:将 "jquery": "2.2.0" 添加到 package.json 的 dependencies 部分。现在执行该添加,然后通过运行 npm install 将 jQuery 放入 /node_modules。但是,不管您如何安装 jQuery,都需要想办法将 jQuery 模块放入您的 Web 应用程序中。

下面假设您的 Web 应用程序有一个 index.html 页面,您要从该页面使用 jQuery。一种简单的方法就是创建 index.html 文件,并通过一个 <script> 标记包含 node_modules/jquery/dist 中的 jQuery 文件。清单 3 展示了一个该方法示例。(在演示应用程序中,该文件是 index-script.html;参见部分。)

清单 3. 包含 <script> 标记的典型旧式 index.html

<!doctype html>  <html lang="en"> <head>   <meta charset="utf-8">    <title>JSPM Intro</title>   <meta name="description" content="JSPM Intro">   <meta name="author" content="MTyson"> </head>  <body>   <script src="node_modules/jquery/dist/jquery.js"></script>   <script src="app.js"></script> </body> </html>

下面假设在 app.js 中,您想将一些内容附加到文档主体。可以使用 &("body").append("<div>Ground control to major Tom.</div>"); ,因为您已经包含了 jQuery 文件,因而可以访问 $ 全局变量。

该方法虽然有效,但有诸多缺陷,特别在某个方法变得更复杂并依赖于更复杂的模块时。您需要用 HTML 写出各种(可能深入嵌套的)依赖项。此外,您必须将 include 语句与程序包的使用分开。该方法还需要在全局命名空间中公开模块。

您不会想用以前的这种简单的全局变量方法。而是想用模块化的 include 语句,该语句类似于 Java™ JARs 和 import 语句,可使用类似于 Java world 的 Maven 或 Gradle 的依赖项管理器对其进行管理。

您可以在 ES6 中编写 include 语句,以各种格式导入程序包,包括 AMD、CommonJS、ES6 和旧式全局封装。

有许多实现模块化的语法方法可供使用, CommonJS 最近占据主导地位。npm 包使用了 CommonJS。 Browserify 等工具可将 npm 包转化为浏览器可使用的包。

jspm 支持 CommonJS,但更进一步通过 SystemJS 支持未来的 ES6 include 语法。SystemJS 是一种 meta-import 兼容层。(在 ES6 中, System 对象将是浏览器中的一个原生对象,这是 SystemJS 获取其名称的方式。)在使用 SystemJS 时,可以在 ES6 中编写 include 语句,以各种格式导入程序包,包括 AMD、CommonJS、ES6 和旧式全局封装。

下面您将了解如何使用 jspm 包含 jQuery — 具体地讲,将了解如何使用 jspm 将依赖项连接到 app.js 文件。

安装 jspm

  1. 将 jspm 添加到您的项目中。您可以手动编码,将其添加到 package.json 中的依赖项中,但我想向您展示另一种方法。转到命令行并键入 npm install jspm --save-dev 。该命令将 jspm 包添加到 package.json 中的 devDependencies 字段。 devDependencies 与依赖项的工作原理一样,但仅用于开发工具,而不是运行涉及的应用程序。jspm 仅用于您的内部版本,因此 dependencies 中不需要它。
  2. 启用 jspm 命令。同样不用全局安装 jspm,而是使用 npm 保持项目结构一目了然。将 "jspm": "jspm" 添加到 scripts 字段,然后放入命令提示符中,并运行 npm run-script jspm init
  3. 接受 jspm 问题的所有默认设置。jspm 会为您创建一个 config.js 文件(该文件主要创建了一个依赖关系图),更新 package.json 并创建一个 jspm_modules 目录。

package.json jspm 条目

您可以在 package.json 中看到,在安装 jspm 后添加了一个 jspm 对象。该对象中嵌套了新的 devDependencies ,其功能类似于其他 npm 依赖项,但仅用于 jspm。jspm 让所有内容保持一目了然。请注意,jspm 依赖项语法稍有不同:程序包名称带有 npm 前缀,如 "npm:babel-core@^5.8.24" 中所示。使用该前缀是因为 jspm 支持多个 注册表 ,npm 是其中之一。(另一个内置注册表是 GitHub。jspm 的注册表系统是可扩展的,因此您可以定义新的注册表终结点 — 例如,为了使用某个特定于组织的注册表。) "npm:babel-core@^5.8.24" include 告诉 jspm 从 npm 获取 babel-core 包。jspm 使用 Babel 将 ES6 语法转译为当今的浏览器可以理解的 JavaScript。

加入 jQuery。在命令行中,运行 npm run-script jspm install jquery 。在 package.json 中找到新条目: "jquery": "npm:jquery@^2.2.0" 。您可以看到相同的 名称注册表模块名称版本要求 模式。jspm 维护着一个具有通用要求(比如 jQuery)的小注册表;它通过这种方式知道要从 jquery 转换到 npm:jquery 。对于不那么通用的模块,在命令行中安装模块时,一定要指定注册表和模块。

jspm 为您准备好了资源;下面我们准备了解如何使用它们。

与其将 app.js 文件包含在 index.html 中,您现在就可以将其作为模块导入。但您必须首先包含两个支持文件:jspm 为您创建的 config.js 文件和 system.js 文件(即 SystemJS)。我们将使用 ES6 语法包含依赖项,SystemJS 支持您跨各种程序包实现此目的。

清单 4 展示的 index.html 文件使用了 jspm 生成的内部版本。不用管额外的 include 语句;待会儿您将看到如何将代码捆绑投入生产。

清单 4. 使用 jspm 生产的内部版本的 index.html

<!doctype html>  <html lang="en"> <head>   <meta charset="utf-8">    <title>JSPM Intro</title>   <meta name="description" content="JSPM Intro">   <meta name="author" content="MTyson">    <head>     <script src="jspm_packages/system.js"></script>     <script src="config.js"></script>     <script>       System.import('./app');     </script>   </head> </head>  <body> </body> </html>

创建一个包含以下内容的 app.js 文件:

import $ from 'jquery';  &("body").append("<div>Ground control to major Tom.</div>");

注意 import $ from 'jquery'; 导入。这是一个 指定导入 。您可以采用几种不同的方式使用 ES6 语法来要求一个程序包。在本例中,我们导入了默认模块,使用了经典的 $ 符号。

导出函数

让我们来更深入地查看一下 ES6 语法。在 ES6 中,一个模块可以定义多个导出。因此在您的 app.js 文件中,可以创建一个导出函数:

import & from 'jquery'; export function init(){       &("body").append("<div>Good morning!.</div>");       &("body").append("<div>How are you?.</div>");

在 index.html 文件中,可以利用清单 5 中所示的导出。请注意,我使用了一个 .then(...) 约定语句(类似于 AMD 语法)来定义一个回调函数,因为模块是异步加载的。

清单 5. 使用一个导出模块

<script>       System.import('./app').then(function(app){         app.init();       });     </script>

面向 CSS 和其他资源的 Loader 插件

假设您有一些 CSS 文件想要合并到应用程序中。首先从命令行运行 npm run jspm install css=npm:jspm-loader-css ,安装一个名为 css 的 jspm CSS Loader。

使用以下内容创建一个简单的 app.css 文件:

div {   background-color: red; }

接下来您可以通过将以下代码行添加到 app.css 来将 CSS 导入您的 JavaScriptimport './app.css!'; 。请注意,CSS 也是在评估时(即脚本在浏览器中运行时)加载的,因此您可以巧妙地按需加载 CSS 模块。

可使用 npm run jspm install scss=sass 为 CSS 的预处理器 SASS 实现同样的结果。参见与 SystemJS 和 jspm 兼容的 loader 插件的 完整列表 。

使用 jspm 开发服务器进行测试

如要测试该应用程序,需要想办法将文件托管在一台服务器中,因为浏览器无法从本地磁盘加载程序包。您可以使用 NGINX 或 Apache 或 Express 服务器(哪一个都行)。但因为您在运行 jspm,所以可以安装并使用 jspm 开发服务器。

作为一个实用工具,jspm 开发服务器更倾向于全局安装。全局安装对于此依赖项很有意义,因为在另一个环境中,比如暂存、测试或生产环境中,应用程序可能会由特定服务器托管。运行 npm install -g jspm-server 。在项目根处键入 jspm-server 。等待 jspm 弹出一个浏览器窗口并将您导向 http://127.0.0.1:8080/,应用程序在此处运行。

请注意,jspm 开发服务器支持请求代理。因此,如果您有一个后端服务要集成,可在开发期间将某些 URI 路径(例如 /api )映射到后端。

捆绑投入生产

HTTP/2 和捆绑

目前,您仍然需要捆绑包来实现最佳性能。但 ES6 语法被设计为在 HTTP/2 上下文内有效,HTTP/2 会在协议级别处理多个文件加载优化。

您可以捆绑依赖关系树以便投入生产。如果转到命令行并运行 npm run jspm bundle app --inject ,jspm 会基于 app.js 入口点提取整个依赖图,深入到某个 build.js 文件。然后在系统遇到 include 语句时,您可以一举加载所有内容,而不是异步加载它们。

那时您可以像这样包含 build.js 而非 app.js: <script src="build.js"></script> 。不过您仍然会需要 system.js 和 config.js。

结束语

使用 jspm 的一个好处在于,未来浏览器本身会支持 SystemJS 语法,而且您的应用程序将准备就绪。您可以利用使用顺应未来的语法的各种现有捆绑包,并在此过程中利用 CSS inclusion 和开发服务器等构建工具。尽管 SystemJS 可能会在很大程度上被原生浏览器支持所取代,jspm 的封装和捆绑特性仍会有用且有必要。

下载

描述 名字 大小
Sample code jspm-intro.zip 99KB
原文  http://www.ibm.com/developerworks/cn/web/wa-use-jspm-javascript-modules/index.html?ca=drs-
正文到此结束
Loading...