gruntjs - NPM vs. Bower vs. Browserify vs. Gulp vs. Grunt vs. Webpack
(6)
什麼是webpack&webpack-dev-server? 官方文檔說它是一個模塊捆綁器,但對我來說它只是一個任務運行器。 有什麼不同?
webpack-dev-server 是一個實時重新加載的Web服務器, Webpack 開發人員使用它來獲得他們所做的即時反饋。 它應該只在開發期間使用。
該項目深受 nof5 單元測試工具的啟發。
顧名思義,Webpack
將為網絡創建一個
包裝
年齡。
軟件包將被最小化,並合併為一個文件(我們仍然生活在HTTP 1.1時代)。
Webpack
具有組合資源(JavaScript,CSS,圖像)並將其註入的神奇功能:
<script src="assets/bundle.js"></script>
。
它也可以稱為 模塊捆綁器, 因為它必須了解模塊依賴關係,以及如何獲取依賴關係並將它們捆綁在一起。
你會在哪裡使用browserify? 我們不能對node / ES6導入做同樣的事情嗎?
您可以在使用 Webpack 的完全相同的任務上使用 Browserify 。 - Webpack 更緊湊。
請注意, Webpack2 中的 ES6模塊加載器功能 正在使用 System.import ,而不是單個瀏覽器本身支持的。
什麼時候你會在npm +插件上使用gulp / grunt?
你可以 forget Gulp,Grunt,Brokoli,Brunch和Bower 。 直接使用npm命令行腳本,你可以在這里為 Gulp 消除這些額外的包:
var gulp = require('gulp'),
minifyCSS = require('gulp-minify-css'),
sass = require('gulp-sass'),
browserify = require('gulp-browserify'),
uglify = require('gulp-uglify'),
rename = require('gulp-rename'),
jshint = require('gulp-jshint'),
jshintStyle = require('jshint-stylish'),
replace = require('gulp-replace'),
notify = require('gulp-notify'),
在為項目創建配置文件時,您可以使用 Gulp 和 Grunt 配置文件生成器。 這樣您就不需要安裝 Yeoman 或類似工具。
我正在嘗試總結我對最流行的JavaScript包管理器,捆綁包和任務運行器的了解。 如果我錯了,請糾正我:
-
npm
&bower
是包管理員。 他們只是下載依賴項,不知道如何自己構建項目。 他們知道的是在獲取所有依賴項後調用webpack
/webpack
/grunt
。 -
bower
就像是npm
,但構建了扁平的依賴樹(與npm
不同,它遞歸地執行)。 含義npm
獲取每個依賴項的依賴項(可能會獲取相同的幾次),而bower
期望您手動包含子依賴項。 有時bower
和npm
分別用於前端和後端(因為每兆字節可能在前端很重要)。 -
grunt
和gulp
是任務運行器,可以自動化所有可以自動化的東西(即編譯CSS / Sass,優化圖像,製作捆綁並縮小/轉換它)。 -
grunt
vs.gradle
(就像maven
vs.gradle
或配置與代碼)。 Grunt基於配置單獨的獨立任務,每個任務打開/處理/關閉文件。 Gulp需要的代碼量較少,並且基於節點流,這使得它可以構建管道鏈(無需重新打開同一個文件)並使其更快。 -
webpack
(webpack-dev-server
) - 對我而言,它是一個熱門重新加載更改的任務運行器,可以讓你忘記所有JS / CSS觀察者。 -
npm
/bower
+ plugins可以替換任務運行者。 他們的能力經常交叉,所以如果你需要在npm
+插件上使用gulp
/grunt
,會有不同的含義。 但是任務運行者肯定更適合複雜的任務(例如“在每個構建創建捆綁包,從ES6轉換到ES5,在所有瀏覽器模擬器上運行它,製作屏幕截圖並通過ftp部署到dropbox”)。 -
browserify
允許為瀏覽器打包節點模塊。browserify
vsnode
的require
實際上是 AMD vs CommonJS 。
問題:
-
什麼是
webpack
&webpack-dev-server
? 官方文檔說它是一個模塊捆綁器,但對我來說它只是一個任務運行器。 有什麼不同? -
你會在哪裡使用
browserify
? 我們不能對node / ES6導入做同樣的事情嗎? -
什麼時候你會在
npm
+插件上使用gulp
/grunt
? - 當您需要使用組合時,請提供示例
Webpack和Browserify
Webpack和Browserify幾乎完成相同的工作,即 處理您的代碼以在目標環境中使用 (主要是瀏覽器,但您可以定位其他環境,如Node)。 這種處理的結果是一個或多個 捆綁 - 適合目標環境的彙編腳本。
例如,假設您編寫了一個分為模塊的ES6代碼,並希望能夠在瀏覽器中運行它。 如果這些模塊是節點模塊,瀏覽器將無法理解它們,因為它們僅存在於節點環境中。 ES6模塊也不適用於IE11等舊版瀏覽器。 此外,您可能已經使用了瀏覽器尚未實現的實驗語言功能(ES下一個提議),因此運行此類腳本只會引發錯誤。 像Webpack和Browserify這樣的工具通過 將這些代碼轉換為能夠執行的表單瀏覽器來 解決這些問題。 最重要的是,它們可以對這些捆綁包應用各種各樣的優化。
但是,Webpack和Browserify在許多方面有所不同,Webpack默認提供了許多工具(例如代碼分割),而Browserify只能在下載插件後才能執行此操作,但 使用兩者導致非常相似的結果 。 歸結為個人偏好(Webpack更具時尚性)。 順便說一句,Webpack不是一個任務運行器,它只是文件的處理器(它通過所謂的加載器和插件處理它們),它可以由任務運行器運行(以及其他方式)。
Webpack Dev Server
Webpack Dev Server為Browsersync提供了類似的解決方案 - 一個開發服務器,您可以在其中快速部署應用程序,並立即驗證您的開發進度,開發服務器自動刷新瀏覽器代碼更改甚至將更改的代碼傳播到瀏覽器無需重新加載所謂的熱模塊更換。
任務運行器與NPM腳本
我一直在使用Gulp的簡潔和簡單的任務寫作,但後來發現我根本不需要Gulp和Grunt。 我所需要的一切都可以使用NPM腳本通過其API運行第三方工具。 在Gulp,Grunt或NPM腳本之間進行選擇取決於團隊的品味和經驗。
雖然Gulp或Grunt中的任務很容易閱讀,即使對於不太熟悉JS的人來說,它也是另一種需要和學習的工具,我個人更喜歡縮小我的依賴關係並使事情變得簡單。 另一方面,使用NPM腳本和運行第三方工具(例如,節點腳本配置和運行 rimraf 以進行清理)的(可運行的JS)腳本的組合替換這些任務可能更具挑戰性。 但在大多數情況下, 這三者在結果方面是平等的。
例子
至於這些例子,我建議你看看這個
React啟動項目
,它展示了一個包含整個構建和部署過程的NPM和JS腳本的完美組合。
您可以在根文件夾的package.json中找到名為
scripts
的屬性中的NPM
scripts
。
你會遇到像
babel-node tools/run start
這樣的命令。
Babel-node是一個CLI工具(不適合生產使用),它首先編譯ES6文件
tools/run
(位於
tools
run.js文件) - 基本上是一個運行器實用程序。
這個運行器將一個函數作為參數並執行它,在這種情況下是
start
- 另一個實用程序(start.js)負責捆綁源文件(客戶端和服務器)並啟動應用程序和開發服務器(開發服務器將是可能是Webpack Dev Server或Browsersync)。
更準確地說,start.js創建客戶端和服務器端捆綁包,啟動快速服務器並在成功啟動後進入瀏覽器同步,在編寫本文時看起來像這樣(請參閱 反應啟動項目 以獲取最新代碼)。
const bs = Browsersync.create();
bs.init({
...(DEBUG ? {} : { notify: false, ui: false }),
proxy: {
target: host,
middleware: [wpMiddleware, ...hotMiddlewares],
},
// no need to watch '*.js' here, webpack will take care of it for us,
// including full page reloads if HMR won't work
files: ['build/content/**/*.*'],
}, resolve)
重要的部分是
proxy.target
,它們設置了他們想要代理的服務器地址,可以是
http:// localhost:3000
,而Browsersync啟動服務器監聽
http:// localhost:3001
,其中生成的資產被提供具有自動變化檢測和熱模塊更換功能。
正如您所看到的,還有另一個配置屬性
files
包含單獨的文件或模式瀏覽器同步監視更改並重新加載瀏覽器(如果有的話),但正如評論所說,Webpack負責用HMR自己觀看js源,所以他們在那里合作。
現在我沒有任何Grunt或Gulp配置的等效示例,但是使用Gulp(和Grunt有些相似),您可以在gulpfile.js中編寫單獨的任務
gulp.task('bundle', function() {
// bundling source files with some gulp plugins like gulp-webpack maybe
});
gulp.task('start', function() {
// starting server and stuff
});
你在哪裡做的幾乎和初學者工具箱一樣,這次是使用任務運行器,它為你解決了一些問題,但在學習使用過程中提出了自己的問題和一些困難,正如我所說,你擁有的依賴性越多,就越容易出錯。 這就是我想擺脫這些工具的原因。
關於npm的一個小註釋:npm3嘗試以平面方式安裝依賴項
https://docs.npmjs.com/how-npm-works/npm3#npm-v3-dependency-resolution
好吧, 他們都有一些相似之處,他們以不同和相似的方式為你做同樣的事情,我將它們劃分為 3個主要組 ,如下所示:
1)模塊捆綁器
webpack和browserify作為流行的,像任務運行器一樣工作但具有更大的靈活性,因為它會將所有內容捆綁在一起作為您的設置,因此您可以將結果指向bundle.js,例如在一個文件中包括CSS和Javascript,更多細節,請看下面的詳細信息:
的WebPack
webpack是現代JavaScript應用程序的模塊捆綁器。 當webpack處理您的應用程序時,它會遞歸地構建一個包含應用程序所需的每個模塊的依賴關係圖,然後將所有這些模塊打包成少量的包 - 通常只有一個 - 由瀏覽器加載。
它具有令人難以置信的可配置性,但要開始使用,您只需要了解四個核心概念:入口,輸出,加載器和插件。
本文檔旨在對這些概念進行高級概述,同時提供詳細概念特定用例的鏈接。
更多 here
browserify
Browserify是一個開發工具,它允許我們編寫node.js樣式的模塊,這些模塊可以在瀏覽器中進行編譯。 就像節點一樣,我們將模塊編寫在單獨的文件中,使用module.exports和exports變量導出外部方法和屬性。 我們甚至可以使用require函數來要求其他模塊,如果省略它將解析到node_modules目錄中的模塊的相對路徑。
更多 here
2)任務跑步者
gulp和grunt是任務運行者,基本上是他們所做的,創建任務並隨時運行它們,例如你安裝一個插件來縮小你的CSS,然後每次運行它來進行縮小,更多關於每個的細節:
吞
gulp.js是Fractal Innovations和GitHub上的開源社區的開源JavaScript工具包,用作前端Web開發中的流構建系統。 它是一個基於Node.js和Node Package Manager(npm)構建的任務運行器,用於自動化Web開發中涉及的耗時和重複性任務,如縮小,連接,緩存清除,單元測試,linting,優化等.gulp使用一種代碼優化配置方法,用於定義其任務,並依賴於其小型,單一用途的插件來實現它們。 gulp生態系統有1000多個這樣的插件可供選擇。
更多 here
咕嚕
Grunt是一個JavaScript任務運行器,一個用於自動執行常用任務的工具,如縮小,編譯,單元測試,linting等。它使用命令行界面來運行在文件中定義的自定義任務(稱為Gruntfile) 。 Grunt由Ben Alman創建,用Node.js編寫。 它通過npm分發。 目前,Grunt生態系統中有超過五千個插件可供使用。
更多 here
3)包管理員
包管理器,他們所做的是管理你的應用程序所需的插件,並使用package.json通過github等為你安裝,非常方便更新模塊,安裝它們並共享你的應用程序,更多細節:
NPM
npm是JavaScript編程語言的包管理器。 它是JavaScript運行時環境Node.js的默認包管理器。 它由命令行客戶端(也稱為npm)和公共包的在線數據庫(稱為npm註冊表)組成。 通過客戶端訪問註冊表,可以通過npm網站瀏覽和搜索可用的包。
更多 NPM
亭子
Bower可以管理包含HTML,CSS,JavaScript,字體甚至圖像文件的組件。 Bower不會連接或縮小代碼或執行任何其他操作 - 它只是安裝所需的軟件包及其依賴項的正確版本。 首先,Bower通過從各地獲取和安裝包來工作,負責打獵,查找,下載和保存您正在尋找的東西。 Bower在清單文件bower.json中跟踪這些包。
更多 Bower
和最新的軟件包管理器不應該錯過,它在實際工作環境中比較年輕和快速,而我之前大多使用的是npm,為了重新安裝模塊,它會對node_modules文件夾進行雙重檢查以檢查模塊是否存在,似乎安裝模塊花費的時間更少:
紗
Yarn是您的代碼的包管理器。 它允許您與世界各地的其他開發人員一起使用和共享代碼。 紗線可快速,安全,可靠地完成,因此您無需擔心。
Yarn允許您使用其他開發人員的解決方案來解決不同的問題,從而使您更容易開發軟件。 如果您遇到問題,可以報告問題或回饋問題,並且在問題得到解決後,您可以使用Yarn將其保持最新狀態。
代碼通過稱為包(有時稱為模塊)的東西共享。 包中包含所有共享的代碼以及描述包的package.json文件。
更多 here
2018年10月更新
如果您仍然不確定前端開發,可以在這裡快速查看一個優秀的資源。
https://github.com/kamranahmedse/developer-roadmap
2018年6月更新
如果你從一開始就不去那裡學習現代JavaScript很難。 如果您是新來者,請記得查看這篇優秀的文章,以便更好地了解。
https://medium.com/the-node-js-collection/modern-javascript-explained-for-dinosaurs-f695e9747b70
2017年7月更新
最近我發現了一個非常全面的Grab團隊指南,介紹如何在2017年進行前端開發。您可以查看以下內容。
https://github.com/grab/front-end-guide
我也一直在尋找這個,因為那裡有很多工具,每個工具都有不同的方面讓我們受益。
社區分為
Browserify, Webpack, jspm, Grunt and Gulp
。
你可能也聽說過
Yeoman or Slush
。
這不是一個真正的問題,對於每個試圖理解明確前進道路的人來說,這只會令人困惑。
無論如何,我想貢獻一些東西。
1.包管理器
軟件包管理器簡化了項目依賴項的安裝和更新,這些庫是如下的庫:
jQuery, Bootstrap
等 - 您網站上使用的所有內容都不是由您編寫的。
瀏覽所有圖書館網站,下載和解壓縮檔案,將文件複製到項目中 - 所有這些都被終端中的一些命令所取代。
-
NPM
代表:Node JS package manager
可以幫助您管理軟件所依賴的所有庫。 您可以在名為package.json
的文件中定義您的需求,並在命令行中運行npm install
...然後BANG,您的軟件包已下載並可供使用。 可以用於front-end and back-end
庫。 -
Bower
:對於前端包管理,這個概念與NPM相同。 所有庫都存儲在名為bower.json
的文件中,然後在命令行中運行bower install
。
Bower
和NPM
之間的最大區別在於NPM執行嵌套依賴樹,而Bower需要平面依賴樹,如下所示。
project root
[node_modules] // default directory for dependencies
-> dependency A
-> dependency B
[node_modules]
-> dependency A
-> dependency C
[node_modules]
-> dependency B
[node_modules]
-> dependency A
-> dependency D
project root
[bower_components] // default directory for dependencies
-> dependency A
-> dependency B // needs A
-> dependency C // needs B and D
-> dependency D
有關
npm 3 Duplication and Deduplication
更新,請打開文檔了解更多詳細信息。
-
Yarn
:最近由Facebook
published 的JavaScript
新包管理器,與NPM
相比具有更多優勢。 使用Yarn,您仍然可以使用NPM
和Bower
註冊表來獲取包。 如果您之前已安裝過軟件包,則yarn
會創建一個緩存副本,以便於offline package installs
。 -
jspm
:是SystemJS
通用模塊加載器的包管理器,構建於動態ES6
模塊加載器之上。 它不是一個全新的包管理器,它有自己的一套規則,而是在現有的包源之上工作。 開箱即用,它適用於GitHub
和npm
。 由於大多數基於Bower
的軟件包都基於GitHub
,我們也可以使用jspm
安裝這些軟件包。 它有一個註冊表,列出了大多數常用的前端軟件包,以便於安裝。
查看
Bower
和jspm
之間的不同: Package Manager:Bower vs jspm
2.模塊加載器/捆綁
任何規模的大多數項目都將在許多文件之間分配代碼。
您可以只使用單個
<script>
標記包含每個文件,但是,
<script>
建立新的http連接,對於小文件 - 這是模塊化的目標 - 設置連接的時間可能比傳輸時間長得多數據。
下載腳本時,頁面上不能更改任何內容。
- 下載時間的問題可以通過將一組簡單模塊連接成一個文件並將其縮小來解決。
例如
<head>
<title>Wagon</title>
<script src=“build/wagon-bundle.js”></script>
</head>
- 然而,性能是以犧牲靈活性為代價的。 如果您的模塊具有相互依賴性,那麼這種缺乏靈活性可能會成為一個障礙。
例如
<head>
<title>Skateboard</title>
<script src=“connectors/axle.js”></script>
<script src=“frames/board.js”></script>
<!-- skateboard-wheel and ball-bearing both depend on abstract-rolling-thing -->
<script src=“rolling-things/abstract-rolling-thing.js”></script>
<script src=“rolling-things/wheels/skateboard-wheel.js”></script>
<!-- but if skateboard-wheel also depends on ball-bearing -->
<!-- then having this script tag here could cause a problem -->
<script src=“rolling-things/ball-bearing.js”></script>
<!-- connect wheels to axle and axle to frame -->
<script src=“vehicles/skateboard/our-sk8bd-init.js”></script>
</head>
計算機可以做得更好,這就是為什麼你應該使用工具自動將所有內容捆綁到一個文件中。
然後我們聽說過
RequireJS
,
Browserify
,
Webpack
和
SystemJS
-
RequireJS
:是一個JavaScript
文件和模塊加載器。 它針對瀏覽器內使用進行了優化,但可以在其他JavaScript環境中使用,例如Node
。
例如: myModule.js
// package/lib is a dependency we require
define(["package/lib"], function (lib) {
// behavior for our module
function foo() {
lib.log( "hello world!" );
}
// export (expose) foo to other modules as foobar
return {
foobar: foo
}
});
在
main.js
,我們可以將
myModule.js
作為依賴項導入並使用它。
require(["package/myModule"], function(myModule) {
myModule.foobar();
});
然後在我們的
HTML
,我們可以參考
RequireJS
使用。
<script src=“app/require.js” data-main=“main.js” ></script>
閱讀有關
CommonJS
和AMD
更多信息,以便輕鬆了解。 CommonJS,AMD和RequireJS之間的關係?
-
Browserify
:開始允許在瀏覽器中使用CommonJS
格式的模塊。 因此,Browserify
不像模塊捆綁器那樣是一個模塊加載器:Browserify
完全是一個構建時工具,產生一組代碼,然後可以在客戶端加載。
從安裝了node&npm的構建計算機開始,獲取包:
npm install -g –save-dev browserify
用
CommonJS
格式編寫模塊
//entry-point.js
var foo = require('../foo.js');
console.log(foo(4));
當快樂時,發出捆綁命令:
browserify entry-point.js -o bundle-name.js
Browserify以遞歸方式查找入口點的所有依賴項,並將它們組合到一個文件中:
<script src=”bundle-name.js”></script>
-
Webpack
:它將所有靜態資產(包括JavaScript
,圖像,CSS等)捆綁到一個文件中。 它還使您能夠通過不同類型的加載器處理文件。 您可以使用CommonJS
或AMD
模塊語法編寫JavaScript
。 它以一種從根本上更加綜合和自以為是的方式攻擊構建問題。 在Browserify
您可以使用Gulp/Grunt
以及一長串轉換和插件來完成工作。Webpack
提供了足夠的電源,您通常根本不需要Grunt
或Gulp
。
基本用法不僅僅是簡單的。 像Browserify一樣安裝Webpack:
npm install -g –save-dev webpack
並將命令傳遞給入口點和輸出文件:
webpack ./entry-point.js bundle-name.js
-
SystemJS
:是一個模塊加載器, 可以在運行時以 當今使用的 任何流行格式 (CommonJS, UMD, AMD, ES6
) 導入模塊 。 它建立在ES6
模塊加載器polyfill之上,並且足夠智能,可以檢測正在使用的格式並適當地處理它。SystemJS
還可以使用插件來轉換ES6代碼(使用Babel
或Traceur
)或其他語言,如TypeScript
和CoffeeScript
。
想知道什麼是
node module
以及為什麼它不適合在瀏覽器中使用。更有用的文章:
為什麼選擇
jspm
和SystemJS
?
ES6
模塊化的主要目標之一是使安裝和使用互聯網上任何地方的任何Javascript庫非常簡單(Github
,npm
等)。 只需要兩件事:
- 用於安裝庫的單個命令
- 一行代碼導入庫並使用它
所以使用
jspm
,你可以做到。
- 使用以下命令安裝庫:
jspm install jquery
- 使用單行代碼導入庫,無需在HTML文件內部進行外部引用。
display.js
var $ = require('jquery'); $('body').append("I've imported jQuery!");
然後在導入模塊之前在
System.config({ ... })
配置這些內容。 通常在運行jspm init
,會出現一個名為config.js
的文件。要運行這些腳本,我們需要在HTML頁面上加載
system.js
和config.js
。 之後,我們將使用SystemJS
模塊加載器加載display.js
文件。的index.html
<script src="jspm_packages/system.js"></script> <script src="config.js"></script> <script> System.import("scripts/display.js"); </script>
注意:當Angular 2應用它時,你也可以使用
npm
和Webpack
。 由於jspm
是為了與SystemJS
集成而SystemJS
,它可以在現有的npm
源代碼之上npm
,因此您的答案取決於您。
3.任務選手
任務運行器和構建工具主要是命令行工具。 為什麼我們需要使用它們:一句話: 自動化 。 在執行重複性任務(例如 縮小,編譯,單元測試,linting)時 ,您需要做的工作越少,以前需要花費很多時間才能完成命令行甚至手動操作。
-
Grunt
:您可以為開發環境創建自動化,以預處理代碼或使用配置文件創建構建腳本,並且處理複雜任務似乎非常困難。 在過去幾年流行。
Grunt
中的每個任務都是一系列不同的插件配置,只需一個接一個地以嚴格獨立和順序的方式執行。
grunt.initConfig({
clean: {
src: ['build/app.js', 'build/vendor.js']
},
copy: {
files: [{
src: 'build/app.js',
dest: 'build/dist/app.js'
}]
}
concat: {
'build/app.js': ['build/vendors.js', 'build/app.js']
}
// ... other task configurations ...
});
grunt.registerTask('build', ['clean', 'bower', 'browserify', 'concat', 'copy']);
-
Gulp
:自動化就像Grunt
一樣,但是你可以用流編寫JavaScript
,而不是配置,就像它是節點應用程序一樣。 喜歡這些天。
這是一個
Gulp
示例任務聲明。
//import the necessary gulp plugins
var gulp = require('gulp');
var sass = require('gulp-sass');
var minifyCss = require('gulp-minify-css');
var rename = require('gulp-rename');
//declare the task
gulp.task('sass', function(done) {
gulp.src('./scss/ionic.app.scss')
.pipe(sass())
.pipe(gulp.dest('./www/css/'))
.pipe(minifyCss({
keepSpecialComments: 0
}))
.pipe(rename({ extname: '.min.css' }))
.pipe(gulp.dest('./www/css/'))
.on('end', done);
});
查看更多: https://medium.com/@preslavrachev/gulp-vs-grunt-why-one-why-the-other-f5d3b398edc4#.fte0nahri : https://medium.com/@preslavrachev/gulp-vs-grunt-why-one-why-the-other-f5d3b398edc4#.fte0nahri
4.腳手架工具
-
Slush and Yeoman
:你可以用它們創建入門項目。 例如,您計劃使用HTML和SCSS構建原型,然後手動創建一些文件夾,如scss,css,img,fonts。 你可以安裝yeoman
並運行一個簡單的腳本。 那麼一切都在這裡給你。
here 找到更多。
npm install -g yo
npm install --global generator-h5bp
yo h5bp
查看更多: https://www.quora.com/What-are-the-differences-between-NPM-Bower-Grunt-Gulp-Webpack-Browserify-Slush-Yeoman-and-Express : https://www.quora.com/What-are-the-differences-between-NPM-Bower-Grunt-Gulp-Webpack-Browserify-Slush-Yeoman-and-Express
我的答案與問題的內容並不完全匹配,但當我在谷歌上搜索這些知識時,我總是在頂部看到這個問題,所以我決定總結一下。 希望你們發現它有用。