LoginSignup
5
5

More than 5 years have passed since last update.

brackets/atomのエクステンションの仕組み

Posted at

本記事はNode.js Advent Calendar 201518日目の記事です。

atomやbracketsなど、WWWの技術で作られたアプリケーションの中にはエクステンションを入れて拡張できるものがいろいろ存在します。
これらのアプリケーションは、どのようにしてエクステンションの仕組みを実現しているのでしょうか。

bracketsとatom(electron)を例にとって調べてみました。

bracketsはRequireJSベース

bracketsは結論からいうとRequireJSベースのようです。以下詳しく説明します。

エクステンションの作り方

ざっくりですが、まずはエクステンション自身の作り方を紹介します。

main.js

下記は、wikiに示されているHello World!のサンプルです。

エクステンションは、必ずmain.jsに本体を記述します。

/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define, $, brackets, window */

/** Simple extension that adds a "File > Hello World" menu item */
define(function (require, exports, module) {
    "use strict";

    var CommandManager = brackets.getModule("command/CommandManager"),
        Menus          = brackets.getModule("command/Menus");


    // Function to run when the menu item is clicked
    function handleHelloWorld() {
        window.alert("Hello, world!");
    }


    // First, register a command - a UI-less object associating an id to a handler
    var MY_COMMAND_ID = "helloworld.sayhello";   // package-style naming to avoid collisions
    CommandManager.register("Hello World", MY_COMMAND_ID, handleHelloWorld);

    // Then create a menu item bound to the command
    // The label of the menu item is the name we gave the command (see above)
    var menu = Menus.getMenu(Menus.AppMenuBar.FILE_MENU);
    menu.addMenuItem(MY_COMMAND_ID);

    // We could also add a key binding at the same time:
    //menu.addMenuItem(MY_COMMAND_ID, "Ctrl-Alt-W");
    // (Note: "Ctrl" is automatically mapped to "Cmd" on Mac)
});

これは、ファイルメニューに項目を追加して、それを選択すると「Hello World」のアラートを表示するというものです。

defineでエクステンションを定義しているようです。
エクステンション本体の関数はfunction (require, exports, module)となっています。
これらの記法はRequireJSのsimplified CommonJS wrapperですね。

グローバル変数には、$, brackets, windowが宣言されているようです。
require("text!templates/panel.html")のようにして用意したテンプレートを読み込んだりできます。

package.json

エクステンションのメタ情報は、package.jsonに記述します。
下記はGist Managerというエクステンションから持ってきました。

{
    "name": "fezvrasta.gist-manager",
    "title": "Gist Manager",
    "description": "Create and view Github Gists within Brackets. (View > Show Gists Manager)",
    "keywords": ["gist", "GitHub", "snippets"],
    "homepage": "https://github.com/FezVrasta/gist-manager",
    "version": "0.1.1",
    "author": "Fez Vrasta <info@mywebexpression.com> (http://www.mywebexpression.com)",
    "license": "MIT",
    "engines": {
        "brackets": ">=0.24.0"
    }
}

エクステンション読み込み機構

下記のモジュールにエクステンションの読み込み処理が記述されています。

ExtensionLoader.js(169)
// Read optional requirejs-config.json
var promise = _mergeConfig(extensionConfig).then(function (mergedConfig) {
        // Create new RequireJS context and load extension entry point
        var extensionRequire = brackets.libRequire.config(mergedConfig),
                extensionRequireDeferred = new $.Deferred();

        contexts[name] = extensionRequire;
        extensionRequire([entryPoint], extensionRequireDeferred.resolve, extensionRequireDeferred.reject);

        return extensionRequireDeferred.promise();
}).then(function (module) {

extensionRequireでエクステンションのエントリポイント(main.js)を読み込んでいます。
extensionRequireの元になっているbrackets.libRequireは以下のように定義されています:

Global.js(114)
// Loading extensions requires creating new require.js contexts, which
// requires access to the global 'require' object that always gets hidden
// by the 'require' in the AMD wrapper. We store this in the brackets
// object here so that the ExtensionLoader doesn't have to have access to
// the global object.
global.brackets.libRequire = global.require;

つまり、エクステンションの読み込み機構はrequire.jsそのものだったということですね。

atom(electron)はNode.jsベース

ブラウザプロセスでも普通にrequireできる!

Electronではなぜかブラウザプロセス上からでも動的にrequireできます。
例えば以下のコードをグローバルに宣言してみます:

global.loadModule = function (name) {
  return require(name);
};

ElectronアプリケーションのDeveloper Toolsのコンソールで以下のコードを実行すると、正しくモジュールがロードできます。

loadModule("util"); // -> Object {}

という事は、パッケージの読み込みやパッケージ内の依存関係の解決もできますね。

まとめ

RequireJSって動的なパスでモジュールを読み込めるんですね。
WebpackやBrowserifyのようにあらかじめコンパイルして依存関係を解決しなくてもいいという点が勉強になりました。

atomはnode.jsと同じ感覚で依存関係を記述できる点がすばらしいです。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5