cssmagic / blog

CSS魔法 - 博客
http://blog.cssmagic.net/
2.81k stars 274 forks source link

[译] [PJA] [504] AMD 规范 #35

Open cssmagic opened 11 years ago

cssmagic commented 11 years ago

[译] [PJA] [504] AMD 规范

AMD

AMD 规范

On the client side, there is often a need to load modules asynchronously at runtime in order to avoid the need for the client to download the entire codebase every time the app is loaded. Imagine you have an app like twitter, where users can post messages or status updates. The core of the application is the messaging feature. However, you also have a large profile editing module that allows users to customize the look of their profile pages.

在客户端,经常会有一种需求,在运行时异步加载模块,以避免每次应用启动时都需要把完整的代码库下载到客户端。想像一下你有一个类似 twitter 的应用,用户可以在上面发布消息或更新状态。这个应用程序的核心功能就是消息系统。不过,你还建立了一个庞大的账号编辑模块,允许用户自定义个人主页的外观。

Users will generally update the look of their profiles a few times per year, so the entire profile editing module (all 50,000 lines of it) goes completely unused 99% of the time. What you need is a way to defer the loading of the profile editor until the user actually enters edit mode. You could just make it a separate page, but then the user has to endure a page refresh, when maybe all they wanted to do was change their profile image. It would be a much better experience to keep this all on one page, with no new page load.

一般来说用户只会偶尔更新一下他们的个人主页,因此整个账号编辑模块(差不多五万行代码)在 99% 的情况下完全用不到。于是你渴望有一种方法,可以延迟账号编辑器的加载——直到用户真的进入编辑模式时才加载。当然你也可以简单地把它设为一个单独的页面,不过这样的话,用户就不得不忍受一次页面刷新(跳转),而此时他们想做的可能仅仅是更新一下头像。避免页面跳转,让所有功能在一个页面内搞定,这种方式会带来更好的体验。

The module pattern doesn't solve this problem. CommonJS modules (like those used by Node) are not asynchronous. In the future, JavaScript will have a native module system that works in the browser (see “Harmony Modules”), but it's very young technology that may not be widely implemented in all major browsers for the foreseeable future.

上一节介绍过的“模块模式”并不能解决这个问题。CommonJS 模块规范(比如 Node 所采用的模块系统)也不是异步的。在未来,JavaScript 将会有一个工作在浏览器端的原生的模块系统(参见《Harmony 的模块机制》一节),但它还是一项非常年轻的技术,在可预见的未来,都不太可能在所有主流浏览器中普遍实现。

Asynchronous Module Definition (AMD) is an interim solution to the problem. It works by wrapping the module inside a function called define(). The call signature looks like this:

异步模块定义 规范(AMD)是为这个问题而生的一个过渡性的解决方案。它的工作方式是把模块包裹进一个叫做 define() 的函数中。它的调用语法看起来是这样的:

define([moduleId,] dependencies, definitionFunction);

The moduleId parameter is a string that will identify the module, however, this parameter has fallen out of favor because changes in the application or module structure can necessitate a refactor, and there really is no need for an ID in the first place. If you leave it out and begin your define call with the dependency list, you'll create a more adaptable anonymous module:

那个 moduleId 参数是一个字符串,它用于标识这个模块。不过这个参数现在已经失宠了,因为对应用程序或模块结构的修改会不可避免地导致重构,我们真的不需要先给模块定好一个 ID。如果你忽略它,直接用依赖列表(即 dependencies 参数)开始你的 define 调用,那么你将创建一个适应性更强的 匿名模块

define(['ch05/amd1', 'ch05/amd2'],
  function myModule(amd1, amd2) {
    var testResults = {
        test1: amd1.test(),
        test2: amd2.test()
      },

      // Define a public API for your module:
      // 为你的模块定义一个公开的 API:
      api = {
        testResults: function () {
          return testResults;
        }
      };

    return api;
  });

To kick it off, call require(). You specify dependencies similar to define():

要启动这个模块,需要调用 require()。你可以指定依赖关系,类似于 define() 的用法:

require(['ch05-amd'], function (amd) {
  var results = amd.testResults();

  test('AMD with Require.js', function () {
    equal(results.test1, true,
      'First dependency loaded correctly.');

    equal(results.test2, true,
      'Second dependency loaded correctly.');
  });
});

[$]

Use anonymous modules wherever possible in order to avoid refactors.

只要有可能,就应该使用匿名模块,以避免重构。

The problem with this approach is that if you define your module this way, it can only be used with an AMD loader, such as Require.js or Curl.js (two popular AMD loaders). However, it is possible to get the best of both AMD and module pattern modules. Simply create your module as you normally would, at the end of the wrapping function, add this:

这种方式所带来的问题是,如果你像这样来定义你的模块,模块就只能通过一个 AMD 加载器(比如目前流行的 Require.js 和 Curl.js)来调用。不过,让你的模块同时享受 AMD 和模块模式这两种方式的益处也是可能的。像往常一样(用模块模式)创建你的模块,然后再在包裹函数的结尾加上这个:

if (typeof define === 'function') {
  define([], function () {
    return api;
  });
}

That way, it will be possible to load your module asynchronously if you want to, but your module will still function properly if it's loaded with a simple script tag, or compiled together with a bunch of other modules. This is the pattern that jQuery uses to add AMD loader support. The only trouble with this pattern is that dependency timing is a little more complicated. You'll need to ensure that your dependencies have loaded before you try to use them.

这样就可以实现这种效果:当你想用 AMD 的时候,你可以异步加载这个模块;而当你用普通的 script 标签来加载模块或把一堆模块编译到一起时,你的模块也可以正常工作。这种方式带来的唯一麻烦就是,处理依赖关系有一点困难。在使用模块之前,你需要确保依赖条件都已经加载好了。

Plugins

插件

Loader plugins are an AMD mechanism that allow you to load non-JavaScript resources, such as templates, css, etc... Require.js supplies a text! plugin that you can use to load your HTML templates. To use a plugin, simply prefix the file path with the plugin name:

加载器插件是 AMD 的一项机制,它允许你加载非 JavaScript 资源,比如模板、CSS 等等。Require.js 提供了一个 text! 插件,通过它可以加载 HTML 模板。如果你想使用一个插件,只需要简单地在文件路径前面加上插件名就可以了:

'use strict';
require(['ch05/mymodule.js', 'text!ch05/mymodule.html'],
    function (myModule, view) {
  var container = document.body,
    css = 'ch05/mymodule.css';

  myModule.render(container, view, css);

  test('AMD Plugins', function () {
    equal($('#mymodule').text(), 'Hello, world!',
      'Plugin loading works.');
  });
});

Here's what mymodule.js looks like:

那么 mymodule.js 看起来是这样的:

define(function () {
  'use strict';
  var api = {
    render: function render(container, view, css) {
      loadCss('ch05/mymodule.css');

      $(view).text('Hello, world!')
        .appendTo(container);
    }
  };

  return api;
});

And the mymodule.html template:

还有 mymodule.html 模板是这样:

<div id="mymodule"></div>

The stylesheet is simple:

那个样式表也很简单:

#mymodule {
  font-size: 2em;
  color: green;
}

Note that the css is not loaded as a plugin. Instead, the url is assigned to a variable and passed into the .render() method for manual loading. The loadCSS() function looks like this:

请留意 CSS 文件并没有以插件的方式加载进来。而是将其 URL 赋值给一个变量,并传进 .render() 方法以便手工加载。那个 loadCSS() 函数差不多是这样的:

function loadCss(url) {
  $('<link>', {
    type: 'text/css',
    rel: 'stylesheet',
    href: url,
  }).appendTo('head');
}

This obviously isn't an ideal solution, but as of this writing, there is no standard recommended css! plugin for Require.js. There is a css! plugin for Curl.js, and you might want to try Xstyle. Use them the same way you define the HTML template.

这显然不是一个完美的解决方案,但直到下笔的此刻,Require.js 还没有发布官方的 css! 插件。Curl.js 倒是有一个 css! 插件,此外你还可以试试 Xstyle。你可以像加载 HTML 模板那样去使用它们。(译注:Xstyle 是一个 CSS 工具集,可以用做 AMD 加载器的 CSS 插件。)

AMD has a couple of serious drawbacks. First, it requires you to include a boilerplate wrapper function for every module, and second, it forces you to either compile your whole application in a compile step, or asynchronously load every single module on the client side, which, in spite of advertising to the contrary, could actually slow down the load and execution of your scripts due to simultaneous download limits and connection latency.

AMD 其实也存在一些严重的缺陷。首先,它需要你为每个模块包上一层公式化的包裹函数;其次,它强迫你要么在一个编译步骤中把你的整个应用程序编译,要么你就不得不在客户端异步地加载每个模块——这与它所宣扬的卖点背道而驰,由于并行下载的限制以及网络延迟的关系,这实际上会拖慢脚本的加载和执行。

I recommend the precompile solution over the asynchronous load solution, and as long as you're doing that anyway, you may as well be using the simplified CommonJS syntax and a tool like Browserify. More on that soon.

相对于异步加载的方案,我更倾向于预编译的方案。但如果你正在尝试前者,那不妨使用 CommonJS 的简化语法,并且使用一个像 Browserify 这样的工具。我们稍后会继续这个话题。


© Creative Commons BY-NC-ND 4.0   |   我要订阅   |   我要打赏