2013-06-08 26 views
7

Tôi đang phát triển một ứng dụng web nặng trên JavaScript; nặng như trong, không có JavaScript, toàn bộ ứng dụng là vô dụng. Tôi hiện đang sử dụng requirejs làm trình tải mô-đun của tôi và công cụ r.js để tối ưu hóa JS của tôi thành một tệp duy nhất trong quá trình sản xuất.Có sai khi sử dụng requirejs (AMD) theo cách đồng bộ không?

Hiện tại, trong quá trình sản xuất đánh dấu của tôi trông giống như thế này;

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

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

Tuy nhiên, điều này tải JavaScript không đồng bộ, khiến trang này hiển thị mặc dù không sử dụng được cho đến khi JavaScript được tải; Tôi không thấy điểm. Thay vào đó, tôi muốn tải JavaScript đồng bộ như vậy;

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

Bằng cách này, khi trang được hiển thị, nó có thể sử dụng được. Tôi đã đọc rằng tất cả các trình duyệt hiện đại đều hỗ trợ parallel loading, điều này khiến tôi tin rằng hầu hết các lời khuyên trên Internet đều gợi ý tránh phương pháp này vì nó chặn tải xuống song song đã lỗi thời.

Tuy nhiên;

  1. AMD (Không đồng bộ Định nghĩa mô-đun) gợi ý rằng đây không phải là cách requirejs nên được sử dụng.
  2. Trong quá trình phát triển, tôi muốn chèn các tệp không được kết hợp dưới dạng nhiều thẻ tập lệnh, thay vì tệp được rút gọn đơn lẻ;

    <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> 
    

    ... nhưng điều này dường như rất khó sử dụng trong requirejs (Sử dụng r.js để sản xuất một giả xây dựng, để có được một danh sách các sự phụ thuộc của editor.js), nó cảm thấy sai.

(Các) câu hỏi của tôi như sau;

  1. Tôi có quyền tránh được đồng bộ <script /> Lời khuyên của bạn đã lỗi thời?
  2. Việc sử dụng requirejs/AMD theo cách này có sai không?
  3. Có kỹ thuật/phương pháp/phương pháp/mẫu thay thế nào tôi đã bỏ lỡ không?
+0

Bạn có thể xem xét việc thêm giọng nói của mình vào chủ đề này mà tôi đã bắt đầu trong danh sách gửi thư. https://groups.google.com/forum/?fromgroups#!topic/requirejs/nT8bPgHf9Vg –

Trả lời

2

Tôi quyết định thực hiện ljfranklin's advice và hoàn toàn không cần dùng đến RequireJS. Cá nhân tôi nghĩ rằng AMD đang làm tất cả sai, và CommonJS (với hành vi đồng bộ của nó) là con đường để đi; nhưng đó là một cuộc thảo luận khác.

Một điều tôi xem là di chuyển đến Browserify, nhưng trong việc phát triển mỗi biên dịch (vì nó quét tất cả các tệp của bạn và truy tìm các cuộc gọi require()) mất quá lâu để tôi cho là chấp nhận được.

Cuối cùng, tôi đã triển khai giải pháp riêng biệt của riêng mình. Đó là về cơ bản Trình duyệt, nhưng thay vào đó, nó yêu cầu bạn chỉ định tất cả các phụ thuộc của bạn, thay vì để Browserify tự tìm ra. Nó có nghĩa là biên dịch chỉ là một vài giây thay vì 30 giây.

Đó là TL; DR. Dưới đây, tôi đi vào chi tiết như thế nào tôi đã làm nó. Xin lỗi cho chiều dài. Hy vọng điều này sẽ giúp một ai đó ... hoặc ít nhất là cung cấp cho ai đó một số cảm hứng!


Thứ nhất, tôi có tệp JavaScript của mình. Chúng được viết chung là CommonJS, với giới hạn là exports không có sẵn dưới dạng biến "chung" (bạn phải sử dụng module.exports thay thế). ví dụ:

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

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

Sau đó, tôi xác định trong trật tự danh sách phụ thuộc trong một tập tin cấu hình (lưu ý js/support.js, nó tiết kiệm ngày sau):

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

Sau đó, trong quá trình biên soạn, tôi ánh xạ tất cả các tệp JavaScript của tôi (trong thư mục js/) vào biểu mẫu;

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

Đây là hoàn toàn hoàn toàn trong tệp JavaScript gốc; bên dưới, chúng tôi cung cấp tất cả hỗ trợ cho define, requiremodule v.v., chẳng hạn như, đối với tệp JavaScript ban đầu nó chỉ hoạt động.

Tôi thực hiện ánh xạ bằng cách sử dụng grunt; đầu tiên để sao chép các tập tin vào một thư mục build (vì vậy tôi không gây rối với bản gốc) và sau đó để viết lại tập tin.

// 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' 
     }); 
    }); 
}); 

Tôi có một tệp /js/support.js, xác định hàm define() tôi bao gồm từng tệp; đây là nơi ma thuật xảy ra, vì nó thêm hỗ trợ cho module.exportsrequire() trong ít hơn 40 dòng!

(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)); 

Sau đó, trong phát triển, tôi theo nghĩa đen lặp qua mỗi tập tin trong tập tin cấu hình và đầu ra nó như là một <script /> thẻ riêng biệt; mọi thứ đồng bộ, không có gì được rút gọn, mọi thứ nhanh chóng.

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

Điều này mang lại cho tôi;

<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> 

Trong sản xuất, tôi rút gọn và kết hợp các tệp JS bằng cách sử dụng UglifyJs. Vâng, về mặt kỹ thuật tôi sử dụng một wrapper xung quanh UglifyJs; mini-fier.

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(); 
     }); 
    } 
}); 

... sau đó trong mã ứng dụng, tôi thay đổi scripts (biến tôi sử dụng để chứa các kịch bản để sản lượng trong giao diện), để chỉ có ['/build/js/all.js'], chứ không phải là mảng các tập tin thực tế. Điều đó mang lại cho tôi một kết quả

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

... đầu tiên. Đồng bộ, rút ​​gọn, hợp lý nhanh chóng.

3

Câu trả lời ngắn gọn: có, điều đó là sai. Bạn sử dụng require.js để tải đầu tiên tất cả các phụ thuộc của bạn, và sau đó một khi tất cả chúng được nạp, bạn chạy mã phụ thuộc vào tất cả những thứ bạn đã nạp.

Nếu trang của bạn không sử dụng được cho đến khi mã yêu cầu của bạn chạy, vấn đề không bắt buộc, nhưng trang của bạn: thay vào đó, tạo một trang tối thiểu và cho biết trang vẫn đang tải, không có gì khác (hiển thị) trên nó (sử dụng css display:none trên các phần tử không nên được sử dụng cho đến khi JS kết thúc, ví dụ) và cho phép/chỉ hiển thị các phần tử trang chức năng thực khi yêu cầu và mã của bạn đã thiết lập tất cả UI/UX cần thiết.

2

Dành ít phút để suy nghĩ về lý do bạn sử dụng requirejs ngay từ đầu. Nó giúp quản lý các phụ thuộc của bạn, tránh một danh sách dài các thẻ tập lệnh phải đúng thứ tự đúng. Bạn có thể tranh luận điều này chỉ trở nên không thể quản lý được khi một số lượng lớn các tập lệnh được tham gia.

Thứ hai, nó tải tập lệnh không đồng bộ. Một lần nữa, với một số lượng lớn các kịch bản này có thể làm giảm đáng kể thời gian tải, nhưng lợi ích là nhỏ hơn khi một số lượng nhỏ các kịch bản được sử dụng.

Nếu ứng dụng của bạn chỉ sử dụng một vài tệp javascript, bạn có thể quyết định rằng chi phí thiết lập requirejs đúng cách không đáng để thử. Những lợi ích của requirejs chỉ trở nên rõ ràng khi một số lượng lớn các kịch bản có liên quan. Nếu bạn thấy mình muốn sử dụng một khuôn khổ theo cách cảm thấy "sai", nó giúp lùi lại và hỏi bạn có cần sử dụng khung công tác không.

Edit:

Để giải quyết vấn đề của bạn với RequireJS, bước đầu thiết lập khu vực nội dung chính của bạn để display: none, hoặc tốt hơn là hiển thị một hình ảnh động tải spinner. Sau đó, ở cuối tập tin RequireJS chính của bạn chỉ đơn giản là mờ dần trong khu vực nội dung.

2

Hơi muộn một chút trong trò chơi, nhưng đó là ý kiến ​​của tôi về chủ đề này:

Vâng, điều đó sai. AMD thêm "tiếng ồn cú pháp" vào dự án của bạn mà không thêm bất kỳ lợi ích nào.

Nó đã được thiết kế để tải mô-đun từng bước chỉ khi cần. Trong khi điều này là có ý tốt, nó sẽ trở thành một vấn đề trong các dự án lớn. Tôi đã nhìn thấy một số ứng dụng, yêu cầu 2 giây trở lên chỉ để khởi động ứng dụng. Đó là bởi vì requirejs chỉ có thể yêu cầu phụ thuộc bổ sung sau mô-đun đã được phân tích cú pháp trên máy khách. Vì vậy, bạn sẽ nhận được một hình ảnh giống như thác nước trong tab mạng của các công cụ phát triển của bạn.

Cách tiếp cận tốt hơn là sử dụng kiểu mô-đun đồng bộ (chẳng hạn như CommonJS hoặc mô-đun ES6 sắp tới) và chia ứng dụng thành khối. Sau đó, các khối này có thể được tải chỉ theo yêu cầu. webpack đang thực hiện một công việc tuyệt vời khi nói đến (mặc dù browserify can be configured to support it too).

Thông thường, bạn làm bình thường của bạn yêu cầu, chẳng hạn như:

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

Sau đó, khi bạn quyết định rằng một mô-đun chỉ được yêu cầu trong một số trường hợp, bạn viết:

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

Nếu d đòi hỏi một mô-đun ee không được yêu cầu bởi a, b hoặc c, sau đó nó sẽ chỉ được đưa vào đoạn thứ hai. webpack xuất tất cả các đoạn vào thư mục đầu ra và tải chúng tự động vào thời gian chạy. Bạn không phải đối phó với những điều này.Bạn chỉ cần sử dụng require.ensure (hoặc bundle-/promise -loader) bất cứ khi nào bạn muốn tải mã không đồng bộ.

Cách tiếp cận này mang lại khả năng khởi động nhanh trong khi vẫn giữ gói nhập nhỏ.


Lợi thế duy nhất tôi thấy với requirejs là thiết lập phát triển khá dễ dàng. Bạn chỉ cần thêm requirejs làm thẻ script, tạo một cấu hình nhỏ và bạn đã sẵn sàng.

Nhưng imho hơi bị nhìn thấy một chút, vì bạn cần một chiến lược để tách mã của bạn thành các phần trong quá trình sản xuất. Đó là lý do tại sao tôi không nghĩ rằng tiền xử lý mã của bạn trên máy chủ trước khi gửi nó cho khách hàng sẽ biến mất.