Làm cách nào để bạn tự động thu nhỏ Javascript cho các ứng dụng web Java của mình?


122

Tôi muốn biết cách bạn thích tự động hóa việc thu nhỏ Javascript cho các ứng dụng web Java của mình. Dưới đây là một số khía cạnh tôi đặc biệt quan tâm:

  • tích hợp như thế nào? Nó có phải là một phần của công cụ xây dựng của bạn, một bộ lọc servlet, một chương trình độc lập xử lý sau tệp WAR hay thứ gì khác không?
  • dễ bật và tắt không? Việc thử và gỡ lỗi một tập lệnh được thu nhỏ sẽ rất đơn giản, nhưng nó cũng rất hữu ích cho một nhà phát triển để có thể kiểm tra rằng việc thu nhỏ không làm hỏng bất cứ điều gì.
  • Nó có hoạt động minh bạch không , hay nó có bất kỳ tác dụng phụ nào (ngoài những tác dụng phụ vốn có trong quá trình thu nhỏ) mà tôi phải xem xét trong công việc hàng ngày của mình?
  • Mà minifier nó sử dụng không?
  • có thiếu bất kỳ tính năng nào mà bạn có thể nghĩ đến không?
  • Bạn thích nó chỗ nào?
  • Bạn không thích điều gì ở nó?

Điều này chủ yếu sẽ phục vụ như một tài liệu tham khảo cho các dự án tương lai của tôi (và hy vọng các SOer khác cũng sẽ tìm thấy nó nhiều thông tin), vì vậy tất cả các loại công cụ đều thú vị.

(Lưu ý rằng đây không phải là câu hỏi về bộ thu nhỏ nào tốt nhất . Chúng tôi đã có rất nhiều thứ xung quanh rồi.)


điều này trông thực sự thú vị, tôi đã không nghe nói về nó. Tất cả các công cụ tôi đã tìm thấy trong một tìm kiếm nhanh đều là các công cụ thủ công chạy một lần. Thật tuyệt nếu có một ổ cắm cho kiến ​​hoặc maven. Hy vọng rằng ai đó có một câu trả lời tốt.
Jay

Và có vẻ như ai đó đã làm - hãy kiểm tra câu trả lời của dfa: stackoverflow.com/questions/1379856/…
gustafc

Câu trả lời:


65

Giảm kích thước-maven và maven yui nén không chơi đẹp với ES6 tính năng cho tôi tính đến thời điểm nhận xét này
DPM

13

Chúng tôi đang sử dụng tác vụ Ant để thu nhỏ các tệp js bằng YUICompressor trong quá trình xây dựng sản xuất và đưa kết quả vào một thư mục riêng biệt. Sau đó, chúng tôi tải các tệp đó lên máy chủ web. Bạn có thể tìm thấy một số ví dụ điển hình về tích hợp YUI + Ant trong blog này .

Đây là một ví dụ:

<target name="js.minify" depends="js.preprocess">
    <apply executable="java" parallel="false">
        <fileset dir="." includes="foo.js, bar.js"/>
        <arg line="-jar"/>
        <arg path="yuicompressor.jar"/>
        <srcfile/>
        <arg line="-o"/>
        <mapper type="glob" from="*.js" to="*-min.js"/>
        <targetfile/>
    </apply>
</target>

2
Đoạn trích; đẹp. Bạn có nhắm mục tiêu lại script srctrên các bản dựng dành cho nhà phát triển hay bạn chỉ sao chép các tệp không được rút gọn vào thư mục nén / js?
gustafc

Chỉ dành cho sản xuất tải lên các tệp nén thay vì các tệp gốc trong public_html / js. Điều tốt là không có mã hóa hoặc thay đổi đường dẫn giữa cục bộ và sản xuất, điều tồi tệ là bạn phải thực hiện một số thao tác tải lên và ghi đè theo cách thủ công (Tôi chắc rằng nó có thể được tự động hóa, nhưng đối với chúng tôi thì điều đó không đáng, tải lên một vài tập tin js thỉnh thoảng không phải là vấn đề quá lớn).
serg

Tôi đã sử dụng mã của bạn nhưng nó tạo tệp được rút gọn ở gốc của dự án của tôi, tôi đã đặt <fileset dir="${generatedScriptsDir}" includes="**/*.js"/>nhưng nó không hoạt động. Làm cách nào để tạo tệp trong tệp ${generatedScriptsDir}?
Vadorequest

hãy thử thêm thuộc tính 'dir' vào thẻ 'apply'. đảm bảo rằng '$ {
createdScriptsDir

12

Tôi nghĩ rằng một trong những công cụ tốt nhất và phù hợp cho công việc là wro4j Hãy xem https://github.com/wro4j/wro4j

Nó làm mọi thứ bạn cần:

  • Giữ các tài nguyên web của dự án (js & css) được tổ chức tốt
  • Hợp nhất và thu nhỏ chúng tại thời điểm chạy (sử dụng bộ lọc đơn giản) hoặc thời gian xây dựng (sử dụng plugin maven)
  • Mã nguồn mở và miễn phí: Được phát hành theo giấy phép Apache 2.0
  • một số công cụ thu nhỏ được wro4j hỗ trợ: JsMin, máy nén Google Closure, YUI, v.v.
  • Rất dễ sử dụng. Hỗ trợ bộ lọc Servlet, Java thuần túy hoặc cấu hình mùa xuân
  • Javascript và CSS Meta Frameworks Hỗ trợ: CoffeeScript, Less, Sass, v.v.
  • Xác thực: JSLint, CSSLint, v.v.

Có thể chạy trong chế độ gỡ lỗi cũng như sản xuất. Chỉ cần chỉ định tất cả các tệp mà nó sẽ xử lý / xử lý trước và nó thực hiện phần còn lại.

Bạn có thể chỉ cần bao gồm tài nguyên được hợp nhất, rút ​​gọn và nén như sau:

<script type="text/javascript" src="wro/all.js"></script>

2
Có vẻ như thực sự là một công cụ tiện lợi. Cảm ơn vì đã cập nhật!
gustafc

Nó có thêm phiên bản cho các tệp tài nguyên để buộc làm mới ở phía máy khách không? Tôi không thể tìm thấy bất kỳ tài liệu nào về tính năng này.
Qiang

Điều duy nhất tôi thực sự nhớ trong wro4j là tiền tố css.
inafalcao

Có thể phân phát nội dung tĩnh (được tạo bởi wromáy chủ Ứng dụng) từ apachemáy chủ web không?
HybrisHelp

8

Tôi đã viết macro ant cho trình biên dịch Google Closure và máy nén Yahoo và đưa tệp này vào các dự án web khác nhau.

<?xml version="1.0" encoding="UTF-8"?>
<!-- CSS and JS minifier. -->
<!DOCTYPE project>
<project name="minifier" basedir=".">

  <property name="gc" value="compiler-r1592.jar" />
  <property name="yc" value="yuicompressor-2.4.6.jar" />

  <!-- Compress single js with Google Closure compiler -->
  <macrodef name="gc-js">
    <attribute name="dir" />
    <attribute name="src" />
    <sequential>
      <java jar="${gc}" fork="true">
        <!--
        - - compilation_level WHITESPACE_ONLY | SIMPLE_OPTIMIZATIONS | ADVANCED_OPTIMIZATIONS
        Specifies the compilation level to use. Default: SIMPLE_OPTIMIZATIONS
        - - warning_level QUIET | DEFAULT | VERBOSE
        Specifies the warning level to use.
        -->
        <arg line="--js=@{dir}/@{src}.js" />
        <arg line="--js_output_file=@{dir}/@{src}-min-gc.js" />
      </java>
    </sequential>
  </macrodef>

  <!-- Compress single js with Yahoo compressor -->
  <macrodef name="yc-js">
    <attribute name="dir" />
    <attribute name="src" />
    <sequential>
      <java jar="${yc}" fork="true">
        <arg value="@{dir}/@{src}.js" />
        <arg line="-o" />
        <arg value="@{dir}/@{src}-min-yc.js" />
      </java>
    </sequential>
  </macrodef>

  <!-- Compress all js in directory with Yahoo compressor -->
  <macrodef name="yc-js-all">
    <attribute name="dir" />
    <sequential>
      <apply executable="java" parallel="false">
        <fileset dir="@{dir}" includes="*.js" excludes="*-min*.js" />
        <arg line="-jar" />
        <arg path="${yc}" />
        <srcfile />
        <arg line="-o" />
        <mapper type="glob" from="*.js" to="@{dir}/*-min-yc.js" />
        <targetfile />
      </apply>
    </sequential>
  </macrodef>

  <!-- Compress all css in directory with Yahoo compressor -->
  <macrodef name="yc-css-all">
    <attribute name="dir" default="${build.css.dir}" />
    <sequential>
      <apply executable="java" parallel="false">
        <fileset dir="@{dir}" includes="*.css" excludes="*-min*.css" />
        <arg line="-jar" />
        <arg path="${yc}" />
        <arg line="-v --line-break 0" />
        <srcfile />
        <arg line="-o" />
        <mapper type="glob" from="*.css" to="@{dir}/*-min.css" />
        <targetfile />
      </apply>
    </sequential>
  </macrodef>
</project>
  • Tích hợp: <import file="build-minifier.xml" />trong build.xml của bạn, sau đó gọi các tác vụ kiến ​​thông thường:<gc-js dir="${build.js.dir}" src="prototype" /> <yc-js-all dir="${build.js.dir}" />

  • Lựa chọn hai trình thu nhỏ: trình biên dịch Google Closure và trình nén Yahoo, bạn nên tải chúng xuống theo cách thủ công và đặt gần tệp xml

  • Bộ thu nhỏ bỏ qua các tệp đã được nén (kết thúc bằng -min*)

  • Thông thường, tôi tạo ba phiên bản script: không nén (ví dụ prototype.js) để gỡ lỗi, nén bằng trình biên dịch đóng ( prototype-min-gc.js) cho máy chủ sản xuất, nén bằng Yahoo ( prototype-min-yc.js) để khắc phục sự cố vì trình biên dịch đóng sử dụng tối ưu hóa rủi ro và đôi khi tạo ra tệp nén không hợp lệ và máy nén Yahoo an toàn hơn

  • Máy nén Yahoo có thể thu nhỏ tất cả các tệp trong một dir với một macro duy nhất, trình biên dịch C Close không thể


8

Tôi đã thử hai cách:

  1. sử dụng bộ lọc servlet. Khi ở chế độ sản xuất, bộ lọc được kích hoạt và nó nén mọi dữ liệu liên kết với URL như * .css hoặc * .js
  2. sử dụng maven và yuicompressor-maven-plugin ; nén là hoàn hảo không tantum, (khi lắp ráp chiến tranh sản xuất )

Tất nhiên giải pháp thứ hai tốt hơn vì nó không tiêu tốn tài nguyên trong thời gian chạy (ứng dụng web của tôi đang sử dụng công cụ ứng dụng google) và nó không làm phức tạp mã ứng dụng của bạn. Vì vậy, giả sử trường hợp sau này trong các câu trả lời sau:

Nó tích hợp như thế nào? Nó có phải là một phần của công cụ xây dựng của bạn, một bộ lọc servlet, một chương trình độc lập xử lý sau tệp WAR hay thứ gì khác không?

sử dụng maven

Có dễ bật và tắt không? Việc thử và gỡ lỗi một tập lệnh được thu nhỏ sẽ rất đơn giản, nhưng nó cũng rất hữu ích cho một nhà phát triển để có thể kiểm tra rằng việc thu nhỏ không làm hỏng bất cứ điều gì.

bạn chỉ kích hoạt nó khi lắp ráp cuộc chiến cuối cùng; trong chế độ phát triển, bạn sẽ thấy phiên bản không nén của tài nguyên

Nó có hoạt động minh bạch không, hay nó có bất kỳ tác dụng phụ nào (ngoài những tác dụng phụ vốn có trong quá trình thu nhỏ) mà tôi phải xem xét trong công việc hàng ngày của mình?

chắc chắn rồi

Nó sử dụng bộ thu nhỏ nào?

Máy nén YUI

Nó có thiếu bất kỳ tính năng nào mà bạn có thể nghĩ đến không?

không, nó rất đầy đủ và dễ sử dụng

Bạn thích nó chỗ nào?

nó được tích hợp với công cụ yêu thích của tôi (maven) và plugin nằm trong kho lưu trữ trung tâm (một công dân maven tốt)


Plugin Maven - tốt. Quá xấu các dự án hiện tại của tôi tất cả sử dụng kiến :)
gustafc

bạn có thể tạo mục tiêu xây dựng "tệp chiến tranh sản xuất", bằng cách sử dụng nhiệm vụ kiến ​​YUI
dfa

4

Tôi nghĩ bạn cần một thư viện nén, ví dụ như thẻ Granule.

http://code.google.com/p/granule/

Nó gzip và kết hợp các javascrip được bọc bởi thẻ g: nén bằng cách sử dụng các phương pháp khác nhau, cũng có tác vụ Ant

mẫu mã là:

<g: nén>
  <script type = "text / javascript" src = "common.js" />
  <script type = "text / javascript" src = "close / goog / base.js" />
  <script>
       goog.require ('goog.dom');
       goog.require ('goog.date');
       goog.require ('goog.ui.DatePicker');
  </script>
  <script type = "text / javascript">
      var dp = new goog.ui.DatePicker ();
      dp.render (document.getElementById ('datepicker'));
  </script>
</ g: nén>
...


Vâng, điều đó trông khá tiện lợi.
gustafc

3

Tôi thực sự ngạc nhiên khi không có ai đề cập đến JAWR - https://jawr.github.io

Nó khá hoàn thiện và hỗ trợ tất cả các tính năng tiêu chuẩn được mong đợi và hơn thế nữa. Đây là cách nó chống lại các tiêu chí xuất sắc của OP.

Nó tích hợp như thế nào? Nó có phải là một phần của công cụ xây dựng của bạn, một bộ lọc servlet, một chương trình độc lập xử lý sau tệp WAR hay thứ gì khác không?

Ban đầu nó thực hiện việc xử lý / nâng cao khi khởi động ứng dụng và việc phân phát dựa trên một servlet . Bắt đầu với 3.x, họ đã thêm hỗ trợ tích hợp tại thời điểm xây dựng .

Hỗ trợ cho JSP và Khuôn mặt được cung cấp thông qua thư viện thẻ JSP tùy chỉnh để nhập tài nguyên đã xử lý. Ngoài ra, một trình tải tài nguyên JS được triển khai hỗ trợ tải tài nguyên từ các trang HTML tĩnh .

Có dễ bật và tắt không? Việc thử và gỡ lỗi một tập lệnh được thu nhỏ sẽ rất đơn giản, nhưng nó cũng rất hữu ích cho một nhà phát triển để có thể kiểm tra rằng việc thu nhỏ không làm hỏng bất cứ điều gì.

Một debug=ontùy chọn có sẵn để sử dụng trước khi khởi động ứng dụng và một GETthông số tùy chỉnh có thể được chỉ định theo yêu cầu riêng lẻ trong quá trình sản xuất để chuyển đổi chế độ gỡ lỗi một cách chọn lọc trong thời gian chạy cho yêu cầu đã nêu.

Nó sử dụng bộ thu nhỏ nào?

Đối với JS, nó hỗ trợ YUI Compressor và JSMin, đối với CSS thì tôi không chắc.

Nó có thiếu bất kỳ tính năng nào mà bạn có thể nghĩ đến không?

SASShỗ trợ đến trong tâm trí. Điều đó nói rằng, nó hỗ trợ LESS.


2

Dự án của chúng tôi đã xử lý theo một số cách nhưng chúng tôi vẫn tiếp tục sử dụng YUI Compressor qua các lần lặp khác nhau.

Ban đầu, chúng tôi có một servlet xử lý nén cho JavaScript lần đầu tiên tệp cụ thể đó được truy cập; sau đó nó đã được lưu vào bộ nhớ đệm. Chúng tôi đã có sẵn một hệ thống để xử lý các tệp thuộc tính tùy chỉnh nên chúng tôi chỉ cần cập nhật tệp cấu hình của mình để hỗ trợ bật hoặc tắt máy nén tùy thuộc vào môi trường chúng tôi đang làm việc.

Giờ đây, các môi trường phát triển không bao giờ sử dụng JavaScript nén cho mục đích gỡ lỗi. Thay vào đó, chúng tôi xử lý nén trong quá trình xây dựng của mình khi xuất ứng dụng của chúng tôi sang tệp WAR.

Khách hàng của chúng tôi chưa bao giờ nêu quan ngại về việc nén và các nhà phát triển không nhận thấy điều đó cho đến khi họ quyết định gỡ lỗi JavaScript. Vì vậy, tôi muốn nói rằng nó khá minh bạch với các mặt ảnh hưởng tối thiểu, nếu có.


Bạn sử dụng máy nén YUI từ quá trình xây dựng của mình như thế nào? Plugin Maven hay thứ gì khác?
gustafc

1
Xin lỗi, chúng tôi sử dụng Ant hiện tại. Đây là một liên kết hữu ích cho Nhiệm vụ Kiến: blog.gomilko.com/2007/11/29/yui-compression-tool-as-ant-task
doomspork

1

Điều này phù hợp với tôi: https://bitbucket.org/m6_russell_francis/yui-compressor-ant-task/wiki/Home

<!-- minimize all static *.css & *.js content -->
<target name="static-content-minify">

    <taskdef name="yuicompressor"
             classname="com.metrosix.yuicompressor.anttask.YuiCompressorTask">
        <classpath>
            <pathelement location="${jar.yui.compressor}"/>
            <pathelement location="${jar.yui.anttask.compressor}" />
        </classpath>
    </taskdef>

    <yuicompressor todir="${build.static.content.min}" charset="utf-8" 
        preserveallsemicolons="true" munge="true" >
        <fileset dir="${src.static.content}">
            <include name="**/*.css"/>
            <include name="**/*.js"/>
        </fileset>
    </yuicompressor>
</target>

Tôi nhận được $ {} jar.yui.compressor từ search.maven.org: search.maven.org/...
Xếp Tarzan

1

Tôi đang viết một khuôn khổ để quản lý nội dung web, được gọi là humpty . Nó nhằm mục đích đơn giản hơn và hiện đại hơn so với hàm hoặc wro4j bằng cách sử dụng WebJars và ServiceLoaders.

Nó tích hợp như thế nào? Nó có phải là một phần của công cụ xây dựng của bạn, một bộ lọc servlet, một chương trình độc lập xử lý sau tệp WAR hay thứ gì khác không?

Trong quá trình phát triển, một servlet xử lý các tài sản khi cần thiết. Sau đó, các nội dung sẽ được biên dịch trước trước khi sản xuất và được đặt trong một thư mục công khai, để phần duy nhất được sử dụng tạo ra các nội dung chính xác trong HTML.

Có dễ bật và tắt không? Việc thử và gỡ lỗi một tập lệnh được thu nhỏ sẽ rất đơn giản, nhưng nó cũng rất hữu ích cho một nhà phát triển để có thể kiểm tra rằng việc thu nhỏ không làm hỏng bất cứ điều gì.

Điều đó sẽ được thực hiện bằng cách chuyển đổi giữa các phương thức phát triển và sản xuất.

Nó có hoạt động minh bạch không, hay nó có bất kỳ tác dụng phụ nào (ngoài những tác dụng phụ vốn có trong quá trình thu nhỏ) mà tôi phải xem xét trong công việc hàng ngày của mình?

Tôi tin rằng nó là minh bạch, nhưng thực sự ủng hộ việc sử dụng WebJars.

Nó sử dụng bộ thu nhỏ nào?

Cho dù bạn sử dụng plugin nào trên classpath của mình. Hiện đang tìm cách viết một plugin cho Trình biên dịch đóng cửa của Google.

Nó có thiếu bất kỳ tính năng nào mà bạn có thể nghĩ đến không?

Vẫn là bản phát hành trước, mặc dù tôi đang sử dụng nó trong sản xuất. Plugin maven vẫn cần nhiều công việc.

Bạn thích nó chỗ nào?

Sự đơn giản của việc chỉ thêm một phụ thuộc để định cấu hình khung

Bạn không thích điều gì ở nó?

Nó là con tôi, tôi yêu nó tất cả;)


1

Thực sự trễ đến bữa tiệc ở đây, nhưng có thể thấy điều này có thể giúp ai đó vẫn đang tìm kiếm một câu trả lời khác:

Sau khi cố gắng sử dụng YUI Compressor, tôi thất vọng vì nó không tương thích với các phiên bản mới hơn của jQuery và Prism (hai thư viện JS chính của bên thứ ba mà tôi cần cho dự án của mình mà tôi muốn nén thành một tệp). Vì vậy, tôi quyết định sử dụng Terser , là một nhánh của Uglify-JS hỗ trợ ES6 +. Tôi đã không thể làm cho nó chạy trực tiếp bằng cách sử dụng <exec>tác vụ, nhưng sử dụng phương pháp dòng lệnh Windows hoạt động cho Win 10, ít nhất (không nói rằng nó không thể hoạt động khác, nhưng đây là một công việc rất dễ dàng). Không cần thêm bất kỳ thứ gì khác vào biến hệ thống Đường dẫn (vì Node.JS thường được thêm vào trong quá trình cài đặt). Đầu tiên tôi sử dụng <concat>tác vụ ANT để tạo một tệp lớn, không nén. Sử dụng <fileset>vì nó sẽ bảo toàn thứ tự (dù sao thì điều đó cũng quan trọng).

<concat destfile="${js-big-file}" encoding="UTF-8" outputencoding="UTF-8" fixlastline="true">
   <filelist refid="js-input-filelist"/>
</concat>

Sau đó, sử dụng <exec>tác vụ để chạy bất kỳ chương trình NPM nào, chẳng hạn như Terser. Các Apache của nhãn hiệu trang trên nhiệm vụ này cho thấy đây là Windows workaround cho chạy .bat file, nhưng nó thực sự cho phép bạn chạy chỉ là về bất kỳ ứng dụng dòng lệnh (thậm chí cả những <exec>bí ẩn không thể tìm cách khác).

<exec executable="cmd">
   <arg value="/c"/>
   <arg value="terser"/>
   <arg value="${js-big-file}" />
   <arg value="-o" />
   <arg value="${smaller-js-file}"/>  
</exec>

Tích hợp? Nó là một phần của tập lệnh xây dựng ANT (một plugin Bộ công cụ mở DITA để hỗ trợ JavaScript tùy chỉnh, trong số những thứ khác - không phải ứng dụng Web Java, mà là sử dụng Java để tạo đầu ra HTML5), vì vậy việc tích hợp không nhiều hơn là thêm những nhiệm vụ cho một mục tiêu mới (có thêm mã liên quan đến việc đặt giá trị mặc định và kiểm tra các thông số đầu vào!).

Dễ dàng bật / tắt? Trong trường hợp của tôi, tôi có một tham số tôi chuyển cho ANT Build để bao gồm việc xây dựng và thu nhỏ tệp JS. Vì vậy, có, nó chỉ thực hiện mục tiêu này nếu tôi đặt tham số thành 'Có'. Đó là một điều khá dễ dàng để thiết lập trong một bản dựng ANT.

Trong suốt Cho đến nay, nó dường như không ảnh hưởng đến bất kỳ tệp JS nào mà tôi đang đưa vào. Một số trong số đó là của riêng tôi (và tôi không phải là chuyên gia JS, bởi bất kỳ phương tiện nào) và một số, như tôi đã đề cập, là các thư viện JS phổ biến.

Minifier Terser, nhưng bạn có thể sử dụng bất kỳ đầu vào dòng lệnh nào được thu nhỏ với phương thức này.

Thiếu tính năng? Terser chỉ hoạt động với JavaScript. Nếu tôi muốn làm điều tương tự cho các tệp CSS của mình (mà tôi làm), tôi sử dụng YUI Compressor.

Like That nó là một dự án hiện đang hoạt động và có sự hỗ trợ tốt. Thêm vào đó, việc triển khai hiện tại (chỉ gọi nó qua <exec>mục tiêu ANT ) cho phép tôi hoán đổi các bộ thu nhỏ nếu tôi cần sử dụng thứ gì đó khác trên đường.

Không thích Điều đó nó yêu cầu Node.JS. Không có gì chống lại Node.JS, phiền bạn, chỉ là dự án cụ thể này không cần nó. Tôi muốn sử dụng tệp .jar Java như YUI Compressor cho việc này (tôi có thể dễ dàng phân phối tệp đó với một plugin nếu tôi cần).


Những người đến sau cũng được chào đón! Tôi đồng ý rằng thật khó chịu khi có một dự án phụ thuộc vào hai môi trường lập trình khác nhau (Java + Node). Tuy nhiên, không có gì ngạc nhiên khi hầu hết các công việc Javascript xảy ra trong cộng đồng Node, vì vậy không có nhiều việc phải làm và Terser dường như có rất nhiều động lực trong những ngày này. Cảm ơn sự đóng góp của bạn!
gustafc
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.