7

JavaScript を多用する Web アプリケーションを開発しています。JavaScript がなければ、アプリケーション全体が役に立たないほど重いのです。私は現在、モジュールローダーとしてrequirejsを使用しており、本番環境でr.jsJSを単一のファイルに最適化するためのツールを使用しています。

現在、本番環境では、私のマークアップは次のようになっています。

<script src="/js/require.js"></script>
<script>
    require.config({
       // blah blah blah
    });

    require(['editor']); // Bootstrap the JavaScript code.
</script>

ただし、これは JavaScript を非同期的にロードするため、JavaScript がロードされるまでページは使用できませんがレンダリングされたままになります。要点がわかりません。代わりに、JavaScript を同期的にロードしたいと思います。

<script src="/js/bundle.js"></script><!-- combine require.js, config and editor.js -->

このようにして、ページがレンダリングされると、使用可能になります。最近のすべてのブラウザーが並列読み込みをサポートしていることを読んだことがあります。これにより、並列ダウンロードをブロックするため、このアプローチを回避することを提案するインターネット上のアドバイスのほとんどは時代遅れであると信じています。

まだ;

  1. AMD ( Asynchronous Module Definition) は、これが requirejs の使用方法ではないことを示唆しています。
  2. 開発中は、単一の縮小されたファイルではなく、結合されていないファイルを複数のスクリプト タグとして挿入したいと考えています。

    <script src="/js/require.js"></script>
    <script>/* require.config(...); */</script>
    <script src="/js/editor-dep-1.js"></script>
    <script src="/js/editor-dep-2.js"></script>
    <script src="/js/editor.js"></script>
    

    ... それでも、これは requirejs では非常に扱いにくいようです (r.js を使用して偽のビルドを作成し、の依存関係のリストを取得します)、それは間違っているeditor.jsように感じます。

したがって、私の質問は次のとおりです。

  1. synchronous<script />のアドバイスが時代遅れになるのを避けるのは正しいですか?
  2. このようにrequirejs/AMDを使用するのは間違っていると思いますか?
  3. 私が見逃した代替のテクニック/アプローチ/ツール/パターンはありますか?
4

4 に答える 4

3

短い答え: はい、それは間違っています。まず、require.js を使用してすべての依存関係を読み込みます。次に、すべての依存関係が読み込まれたら、読み込んだすべてのものに依存するコードを実行します。

あなたのページがrequireでラップされたコードが実行されるまで使用できない場合、問題はrequireではなくあなたのページです:代わりに、最小限のページを作成し、まだロード中であることを示し、他に何も(目に見える)何もありません(cssを使用してください)display:noneたとえば、JS が終了するまで使用してはならない要素について)、require が完了し、コードが必要なすべての UI/UX をセットアップした場合にのみ、実際の機能ページ要素を有効化/表示します。

于 2013-06-08T14:31:17.040 に答える
2

私はljfranklin のアドバイスを受けて、RequireJS を完全に廃止することにしました。個人的には、AMD のやり方はすべて間違っていると思います。CommonJS (同期動作を伴う) が進むべき道です。しかし、それは別の議論のためです。

私が調べたことの 1 つは、Browserifyへの移行ですが、開発中の各コンパイル (すべてのファイルをスキャンしてrequire()呼び出しを追跡するため) に時間がかかりすぎて、受け入れられるとは思えませんでした。

最終的に、私は独自のオーダーメイドのソリューションを展開しました。基本的には Browserify ですが、代わりに、Browserify にそれ自体を理解させるのではなく、すべての依存関係を指定する必要があります。これは、コンパイルが 30 秒ではなく、ほんの数秒であることを意味します。

それが TL;DRです。以下に、私がそれをどのように行ったかについて詳しく説明します。長々とすみません。これが誰かの役に立てば幸いです...または少なくとも誰かにインスピレーションを与えてください!


まず、JavaScript ファイルがあります。これらは CommonJS 風に書かれてexportsおり、「グローバル」変数として使用できないという制限があります (module.exports代わりに使用する必要があります)。例えば:

var anotherModule = require('./another-module');

module.exports.foo = function () {
    console.log(anotherModule.saySomething());
};

次に、構成ファイルで依存関係の順序どおりのリストを指定します (注意js/support.jsしてください。後で保存されます)。

{
  "js": [
    "js/support.js",
    "js/jquery.js",
    "js/jquery-ui.js",
    "js/handlebars.js",
    // ...
    "js/editor/manager.js",
    "js/editor.js"
  ]
}

次に、コンパイル プロセスで、(js/ディレクトリ内の) すべての JavaScript ファイルをフォームにマップします。

define('/path/to/js_file.js', function (require, module) {
    // The contents of the JavaScript file
});

ただし、これは元の JavaScript ファイルに対して完全に透過的です。define以下では、requireなどのすべてのサポートを提供しているためmodule、元の JavaScript ファイルに対して.

私はうなり声を使用してマッピングを行います。最初にファイルをディレクトリにコピーしbuild(オリジナルをいじらないように)、次にファイルを書き換えます。

// files were previous in public/js/*, move to build/js/*
grunt.initConfig({
    copy: {
      dist: {
        files: [{
          expand: true,
          cwd: 'public',
          src: '**/*',
          dest: 'build/'
        }]
      }
    }
});

grunt.loadNpmTasks('grunt-contrib-copy');

grunt.registerTask('buildjs', function () {
    var path = require('path');

    grunt.file.expand('build/**/*.js').forEach(function (file) {
      grunt.file.copy(file, file, {
        process: function (contents, folder) {
          return 'define(\'' + folder + '\', function (require, module) {\n' + contents + '\n});'
        },
        noProcess: 'build/js/support.js'
      });
    });
});

各ファイルをラップ/js/support.jsする関数を定義するfileがあります。40 行未満でandのdefine()サポートが追加されるので、ここで魔法が起こります。module.exportsrequire()

(function () {
    var cache = {};

    this.define = function (path, func) {
        func(function (module) {
            var other = module.split('/');
            var curr = path.split('/');
            var target;

            other.push(other.pop() + '.js');
            curr.pop();

            while (other.length) {
                var next = other.shift();

                switch (next) {
                case '.':
                break;
                case '..':
                    curr.pop();
                break;
                default:
                    curr.push(next);
                }
            }

            target = curr.join('/');

            if (!cache[target]) {
                throw new Error(target + ' required by ' + path + ' before it is defined.');
            } else {
                return cache[target].exports;
            }
        }, cache[path] = {
            exports: {}
        });
    };
}.call(this));

次に、開発中に、構成ファイル内の各ファイルを文字通り繰り返し処理し、個別の<script />タグとして出力します。すべてが同期し、縮小されたものはなく、すべてが迅速です。

{{#iter scripts}}<script src="{{this}}"></script>
{{/iter}}

これは私に与えます;

<script src="js/support.js"></script>
<script src="js/jquery.js"></script>
<script src="js/jquery-ui.js"></script>
<script src="js/handlebars.js"></script>
<!-- ... -->
<script src="js/editor/manager.js"></script>
<script src="js/editor.js"></script>

本番環境では、 UglifyJsを使用して JS ファイルを縮小および結合します。技術的には、UglifyJ のラッパーを使用します。ミニファイア

grunt.registerTask('compilejs', function () {
    var minifier = require('mini-fier').create();

    if (config.production) {
      var async = this.async();
      var files = bundles.js || [];

      minifier.js({
        srcPath: __dirname + '/build/',
        filesIn: files,
        destination: __dirname + '/build/js/all.js'
      }).on('error', function () {
        console.log(arguments);
        async(false);
      }).on('complete', function () {
        async();
      });
    }
});

... 次に、アプリケーション コードで、scripts(ビューに出力するスクリプトを格納するために使用する変数) を、['/build/js/all.js']実際のファイルの配列ではなく、単に に変更します。それは私にシングルを与えます

<script src="/js/all.js"></script> 

...出力。同期、縮小、適度に高速。

于 2014-05-02T10:57:27.223 に答える
2

ゲームでは少し遅れていますが、それがこのトピックに関する私の意見です。

はい、違います。AMD は、プロジェクトに「構文ノイズ」を追加しますが、メリットはありません。

必要な場合にのみモジュールを段階的にロードするように設計されています。これは意図的なものですが、大規模なプロジェクトでは問題になります。アプリケーションをブートストラップするだけで 2 秒以上かかるアプリケーションをいくつか見てきました。これは、モジュールがクライアントで解析された後にのみ、requirejs が追加の依存関係を要求できるためです。したがって、開発者ツールのネットワーク タブに滝のような画像が表示されます。

より良いアプローチは、同期モジュール スタイル (CommonJS や今後の ES6 モジュールなど) を使用し、アプリケーションをチャンクに分割することです。その後、これらのチャンクはオンデマンドでのみロードできます。webpackは、コード分割に関しては素晴らしい仕事をしています(ただし、browserify もそれをサポートするように構成できます)。

通常、次のような通常の要件を実行します。

var a = require("a");
var b = require("b");
var c = require("c");

次に、モジュールが特定の場合にのみ必要であると判断した場合は、次のように記述します。

// Creates a new chunk
require.ensure(["d"], function () { // will be called after d has been requested
    var d = require("d");
});

dモジュールが必要で、またはで必要eとされない場合、それは 2 番目のチャンクにのみ含まれます。webpack はすべてのチャンクを出力フォルダーにエクスポートし、実行時に自律的にロードします。これらのことに対処する必要はありません。コードを非同期的にロードしたいときはいつでも(またはbundle- / promise -loader を)使用する必要があります。eabcrequire.ensure

このアプローチは、エントリ バンドルを小さく保ちながら、ブートストラップを高速化します。


requirejs の唯一の利点は、開発のセットアップが非常に簡単だということです。スクリプト タグとして requirejs を追加し、小さな構成を作成するだけで準備完了です。

しかし、本番環境でコードをチャンクに分割する戦略が必要なため、これは少し近視眼的です。そのため、クライアントに送信する前にサーバーでコードを前処理する必要がなくなるとは思いません。

于 2015-04-10T08:56:23.527 に答える