如何利用機器:與任務運行者一起提高工作效率

已發表: 2022-03-10
快速總結 ↬任務運行者是在大多數網絡和移動應用程序背後默默辛勤工作的英雄(或惡棍,取決於你的觀點)。 任務運行器通過大量開發任務的自動化提供價值,例如連接文件、啟動開發服務器和編譯代碼。 在本文中,我們將介紹 Grunt、Gulp、Webpack 和 npm 腳本。 我們還將提供一些示例來幫助您入門。 接近尾聲時,我將拋出一些簡單的勝利和技巧,以便將這篇文章中的想法集成到您的應用程序中。

任務執行者是在大多數網絡和移動應用程序背後默默辛勤工作的英雄(或惡棍,取決於你的觀點)。 任務運行器通過大量開發任務的自動化提供價值,例如連接文件、啟動開發服務器和編譯代碼。 在本文中,我們將介紹 Grunt、Gulp、Webpack 和 npm 腳本。 我們還將提供一些示例來幫助您入門。 接近尾聲時,我將拋出一些簡單的勝利和技巧,以便將這篇文章中的想法集成到您的應用程序中。

有一種觀點認為,任務運行器和 JavaScript 的總體進步使前端環境過於復雜。 我同意花一整天時間來調整構建腳本並不總是最好的利用你的時間,但是如果使用得當和適度,任務運行器會有一些好處。 這就是我們在本文中的目標,快速涵蓋最流行的任務運行器的基礎知識,並提供可靠的示例來激發您對這些工具如何適合您的工作流程的想像。

關於 SmashingMag 的進一步閱讀

  • 成為 Oh-My-ZSH 和 Z 的命令行高級用戶
  • PostCSS 簡介
  • 起床並與 Grunt 一起跑步
  • 使用 Gulp 構建

關於命令行的說明

任務運行器和構建工具主要是命令行工具。 在整篇文章中,我將假設在使用命令行方面具有相當水平的經驗和能力。 如果您了解如何使用cdlscpmv等常用命令,那麼在我們瀏覽各種示例時您應該沒問題。 如果您對使用這些命令感到不舒服,可以在 Smashing Magazine 上找到一篇很棒的介紹性文章。 讓我們從他們所有人的祖父開始:咕嚕聲。

跳躍後更多! 繼續往下看↓

咕嚕聲

Grunt 是第一個流行的基於 JavaScript 的任務運行器。 自 2012 年以來,我一直在以某種形式使用 Grunt。Grunt 背後的基本思想是您使用一個特殊的 JavaScript 文件Gruntfile.js來配置各種插件來完成任務。 它擁有龐大的插件生態系統,是一個非常成熟和穩定的工具。 Grunt 有一個很棒的網絡目錄,它索引了大多數插件(目前大約 5,500 個)。 Grunt 的簡單天才在於它結合了 JavaScript 和通用配置文件(如 makefile)的想法,這使得更多的開發人員可以在他們的項目中貢獻和使用 Grunt。 這也意味著 Grunt 可以與項目的其餘部分置於相同的版本控制系統下。

Grunt 經過實戰考驗且穩定。 在撰寫本文時,發布了 1.0.0 版本,這對 Grunt 團隊來說是一項巨大的成就。 因為 Grunt 在很大程度上配置了各種插件以協同工作,所以它很快就會變得混亂(即修改起來混亂和混亂)。 但是,只要稍加註意和組織(將任務分解為邏輯文件!),您就可以讓它為任何項目創造奇蹟。

在極少數情況下,插件無法完成您需要的任務,Grunt 提供有關如何編寫自己的插件的文檔。 創建自己的插件需要知道的只是 JavaScript 和 Grunt API。 您幾乎不必創建自己的插件,所以讓我們看看如何將 Grunt 與一個非常流行且有用的插件一起使用!

一個例子

grunt-example 目錄的屏幕截圖
我們的 Grunt 目錄長什麼樣(查看大圖)

讓我們看看 Grunt 是如何工作的。 在命令行中運行grunt將觸發 Grunt 命令行程序,該程序在目錄的根目錄中查找Gruntfile.jsGruntfile.js包含控制 Grunt 將做什麼的配置。 從這個意義上說, Gruntfile.js可以看作是廚師(即 Grunt,程序)遵循的一種食譜; 而且,就像任何好的食譜一樣, Gruntfile.js將包含許多食譜(即任務)。

我們將通過使用 Grunticon 插件為假設的 Web 應用程序生成圖標來讓 Grunt 完成這些步驟。 Grunticon 接收一個 SVG 目錄並吐出幾個資產:

  • 以 SVG 為 base-64 編碼的 CSS 文件作為背景圖像;
  • 一個 CSS 文件,帶有 PNG 版本的 SVG,base-64 編碼為背景圖像;
  • 一個 CSS 文件,它為每個圖標引用一個單獨的 PNG 文件。

這三個不同的文件代表了瀏覽器和移動設備的各種功能。 現代設備將接收高分辨率 SVG 作為單個請求(即單個 CSS 文件)。 不處理 SVG 但處理 base-64 編碼資產的瀏覽器將獲得 base-64 PNG 樣式表。 最後,任何不能處理這兩種情況的瀏覽器都會得到引用 PNG 的“傳統”樣式表。 所有這些都來自一個 SVG 目錄!

此任務的配置如下所示:

 module.exports = function(grunt) { grunt.config("grunticon", { icons: { files: [ { expand: true, cwd: 'grunticon/source', src: ["*.svg", ".png"], dest: 'dist/grunticon' } ], options: [ { colors: { "blue": "blue" } } ] } }); grunt.loadNpmTasks('grunt-grunticon'); };

讓我們來看看這裡的各個步驟:

  1. 你必須全局安裝 Grunt。
  2. 在項目的根目錄中創建Gruntfile.js文件。 最好還通過npm i grunt grunt-grunticon --save-dev將 Grunt 作為 npm 依賴項安裝在package.json文件中以及 Grunticon 中。
  3. 創建一個 SVG 目錄和一個目標目錄(構建的資產所在的位置)。
  4. 在 HTML 的head放置一個小腳本,它將確定要加載的圖標。

在運行 Grunticon 任務之前,您的目錄應該如下所示:

 |-- Gruntfile.js |-- grunticon | `-- source | `-- logo.svg `-- package.json
|-- Gruntfile.js |-- grunticon | `-- source | `-- logo.svg `-- package.json

一旦安裝並創建了這些東西,您就可以將上面的代碼片段複製到Gruntfile.js中。 然後,您應該能夠從命令行運行grunt grunticon並查看您的任務執行情況。

上面的代碼片段做了一些事情:

  • 在第 32 行向 Grunt 添加了一個名為grunticon的新config對象;
  • icons對像中填寫 Grunticon 的各種選項和參數;
  • 最後,通過loadNPMTasks Grunticon 插件。

這是您的目錄在 Grunticon 之後的樣子:

 |-- Gruntfile.js |-- dist | `-- grunticon | |-- grunticon.loader.js | |-- icons.data.png.css | |-- icons.data.svg.css | |-- icons.fallback.css | |-- png | | `-- logo.png | `-- preview.html |-- grunticon | `-- source | `-- logo.svg `-- package.json
|-- Gruntfile.js |-- dist | `-- grunticon | |-- grunticon.loader.js | |-- icons.data.png.css | |-- icons.data.svg.css | |-- icons.fallback.css | |-- png | | `-- logo.png | `-- preview.html |-- grunticon | `-- source | `-- logo.svg `-- package.json

你去 - 完成了! 在幾行配置和幾個軟件包安裝中,我們已經自動生成了我們的圖標資產! 希望這開始說明任務運行器的力量:可靠性、效率和可移植性。

Gulp:構建系統的樂高積木

Gulp 出現在 Grunt 之後的某個時候,並渴望成為一個構建工具,它不僅是配置,而且是實際代碼。 代碼優於配置背後的想法是代碼比修改無休止的配置文件更具表現力和靈活性。 Gulp 的障礙在於它需要比 Grunt 更多的技術知識。 您將需要熟悉 Node.js 流式 API 並能熟練地編寫基本的 JavaScript。

Gulp 使用 Node.js 流是它比 Grunt 更快的主要原因。 使用流意味著,Gulp 不使用文件系統作為文件轉換的“數據庫”,而是使用內存中的轉換。 有關流的更多信息,請查看 Node.js 流 API 文檔以及流手冊。

一個例子

Gulp 示例目錄的屏幕截圖
我們的 Gulp 目錄長什麼樣(查看大圖)

就像在 Grunt 部分中一樣,我們將通過一個簡單的示例讓 Gulp 逐步完成:將我們的 JavaScript 模塊連接到單個應用程序文件中。

運行 Gulp 與運行 Grunt 相同。 gulp命令行程序將在其運行的目錄中查找食譜食譜(即Gulpfile.js )。

限制每個頁面發出的請求數被認為是 Web 性能最佳實踐(尤其是在移動設備上)。 然而,如果功能被拆分為多個文件,那麼與其他開發人員協作會容易得多。 輸入任務運行器。 我們可以使用 Gulp 為我們的應用程序組合多個 JavaScript 文件,這樣移動客戶端就必須加載單個文件,而不是加載多個文件。

Gulp 擁有與 Grunt 相同的龐大的插件生態系統。 因此,為了使這項任務變得簡單,我們將依靠 gulp-concat 插件。 假設我們的項目結構如下所示:

 |-- dist | `-- app.js |-- gulpfile.js |-- package.json `-- src |-- bar.js `-- foo.js

兩個 JavaScript 文件在我們的src目錄中,我們希望將它們組合成一個文件app.js ,在我們的dist/目錄中。 我們可以使用以下 Gulp 任務來完成此任務。

 var gulp = require('gulp'); var concat = require('gulp-concat'); gulp.task('default', function() { return gulp.src('./src/*.js') .pipe(concat('app.js')) .pipe(gulp.dest('./dist/')); });

重要的位在gulp.task回調中。 在那裡,我們使用gulp.src API 來獲取src目錄中所有以.js結尾的文件。 gulp.src API 返回這些文件的流,然後我們可以(通過pipe API)將其傳遞給 gulp-concat 插件。 然後插件連接流中的所有文件並將其傳遞給gulp.dest函數。 gulp-dest函數只是將它接收到的輸入寫入磁盤。

您可以看到 Gulp 如何使用流為我們的任務提供“構建塊”或“鏈”。 典型的 Gulp 工作流程如下所示:

  1. 獲取特定類型的所有文件。
  2. 將這些文件傳遞給插件(concat!),或進行一些轉換。
  3. 將這些轉換後的文件傳遞到另一個塊(在我們的例子中, dest塊,它結束了我們的鏈)。

與 Grunt 示例一樣,只需從項目目錄的根目錄運行gulp即可觸發Gulpfile.js文件中定義的default任務。 此任務連接我們的文件,讓我們繼續開發我們的應用程序或網站。

網頁包

JavaScript 任務運行器俱樂部的最新成員是 Webpack。 Webpack 將自己稱為“模塊捆綁器”,這意味著它可以使用諸如 CommonJS 模式之類的模塊模式從多個單獨的文件中動態構建 JavaScript 代碼包。 Webpack 也有插件,稱為加載器。

Webpack 還很年輕,文檔也比較密集和混亂。 因此,在深入了解官方文檔之前,我建議將 Pete Hunt 的 Webpack 存儲庫作為一個很好的起點。 如果您不熟悉任務運行器或不精通 JavaScript,我也不推薦 Webpack。 撇開這些問題不談,它仍然是比 Grunt 和 Gulp 的一般廣泛性更具體的工具。 出於這個原因,許多人將 Webpack 與 Grunt 或 Gulp 一起使用,讓 Webpack 在打包模塊方面表現出色,讓 Grunt 或 Gulp 處理更通用的任務。

Webpack 最終讓我們為瀏覽器編寫 Node.js 風格的代碼,極大地提高了生產力,並通過模塊在我們的代碼中清晰地分離了關注點。 讓我們使用 Webpack 來實現與 Gulp 示例相同的結果,將多個 JavaScript 文件合併到一個應用程序文件中。

一個例子

webpack-example 目錄截圖
我們的 Webpack 目錄長什麼樣(查看大圖)

Webpack 經常與 Babel 一起使用,將 ES6 代碼轉換為 ES5。 將代碼從 ES6 轉換為 ES5 讓開發人員可以使用新興的 ES6 標準,同時將 ES5 提供給尚未完全支持 ES6 的瀏覽器或環境。 然而,在這個例子中,我們將專注於從 Gulp 示例中構建兩個文件的簡單包。 首先,我們需要安裝 Webpack 並創建一個配置文件webpack.config.js 。 這是我們的文件的樣子:

 module.exports = { entry: "./src/foo.js", output: { filename: "app.js", path: "./dist" } };

在這個例子中,我們將 Webpack 指向我們的src/foo.js文件以開始遍歷我們的依賴圖的工作。 我們還更新了foo.js文件,如下所示:

 //foo.js var bar = require("./bar"); var foo = function() { console.log('foo'); bar(); }; module.exports = foo;

我們更新了bar.js文件,如下所示:

 //bar.js var bar = function() { console.log('bar'); }; module.exports = bar;

這是一個非常基本的 CommonJS 示例。 您會注意到這些文件現在“導出”了一個函數。 從本質上講,CommonJS 和 Webpack 允許我們開始將代碼組織成獨立的模塊,這些模塊可以在整個應用程序中導入和導出。 Webpack 足夠聰明,可以遵循 import 和 export 關鍵字並將所有內容捆綁到一個文件dist/app.js中。 我們不再需要維護連接任務,而只需要遵守代碼結構即可。 好多了!

擴展

Webpack 與 Gulp 的相似之處在於“它只是 JavaScript”。 它可以通過其加載器系統擴展為執行其他任務運行器任務。 例如,您可以使用 css-loader 和 sass-loader 將 Sass 編譯為 CSS,甚至可以通過重載require CommonJS 模式在 JavaScript 中使用 Sass! 但是,我通常主張僅使用 Webpack 來構建 JavaScript 模塊,並使用另一種更通用的方法來運行任務(例如,Webpack 和 npm 腳本或 Webpack 和 Gulp 來處理其他所有事情)。

npm 腳本

npm 腳本是最新的時髦熱潮,這是有充分理由的。 正如我們在所有這些工具中看到的那樣,它們可能引入項目的依賴項數量最終可能會失控。 我看到的第一篇提倡將 npm 腳本作為構建過程的入口點的帖子是 James Halliday 寫的。 他的帖子完美地總結了 npm 腳本被忽視的力量(強調我的):

有一些花哨的工具可以在 JavaScript 項目上進行構建自動化,我從來沒有感覺到它們的吸引力,因為鮮為人知的npm run命令已經完全足夠我需要做的所有事情,同時保持非常小的配置佔用空間

最後一點你抓到了嗎? npm 腳本的主要吸引力在於它們具有“非常小的配置足跡”。 這是 npm 腳本開始流行的主要原因之一(遺憾的是,差不多四年後)。 使用 Grunt、Gulp 甚至 Webpack,人們最終開始淹沒在包裝二進製文件的插件中,並使項目中的依賴項數量增加一倍。

Keith Cirkel 有關於使用 npm 替換 Grunt 或 Gulp 的入門教程。 他為如何充分利用 npm 腳本的強大功能提供了藍圖,並且他介紹了一個必不可少的插件,Parallel Shell(以及許多其他類似的插件)。

一個例子

在我們關於 Grunt 的部分中,我們採用了流行的模塊 Grunticon 並在 Grunt 任務中創建了 SVG 圖標(帶有 PNG 後備)。 這曾經是我使用 npm 腳本的一個痛點。 有一段時間,我會為項目安裝 Grunt,只是為了使用 Grunticon。 我會在我的 npm 任務中向 Grunt “掏出”來實現任務運行程序的啟動(或者,正如我們在工作中開始稱它為構建工具 turducken)。 值得慶幸的是,Grunticon 背後的出色團隊 The Filament Group 發布了他們的工具 Grunticon-Lib 的獨立(即無 Grunt)版本。 所以,讓我們用它來創建一些帶有 npm 腳本的圖標吧!

這個例子比典型的 npm 腳本任務要高級一點。 典型的 npm 腳本任務是調用命令行工具,帶有適當的標誌或配置文件。 這是一個將我們的 Sass 編譯為 CSS 的更典型的任務:

 "sass": "node-sass src/scss/ -o dist/css",

看看它只是一條線,有各種選擇嗎? 不需要任務文件,不需要構建工具來啟動——只需npm run sass ,你的 Sass 現在就是 CSS。 npm 腳本的一個非常好的特性是如何將腳本任務鏈接在一起。 例如,假設我們想在 Sass 任務運行之前運行一些任務。 我們將創建一個新的腳本條目,如下所示:

 "presass": "echo 'before sass',

沒錯:npm 理解pre-前綴。 它還理解post-置前綴。 任何與另一個具有pre-post-前綴的腳本條目同名的腳本條目將在該條目之前或之後運行。

轉換我們的圖標需要一個實際的 Node.js 文件。 不過也不算太嚴重。 只需創建一個tasks目錄,然後創建一個名為grunticon.jsicons.js的新文件,或者任何對項目工作人員有意義的文件。 創建文件後,我們可以編寫一些 JavaScript 來啟動我們的 Grunticon 進程。

注意:所有這些示例都使用 ES6,所以我們將使用 babel-node 來運行我們的任務。 你可以輕鬆地使用 ES5 和 Node.js,如果這樣更舒服的話。

 import icons from "grunticon-lib"; import globby from "globby"; let files = globby.sync('src/icons/*'); let options = { colors: { "blue": "blue" } }; let icon = new icons(files, 'dist/icons', options); icon.process();

讓我們進入代碼並弄清楚發生了什麼。

  1. 我們import (即需要)兩個庫, grunticon-libglobby 。 Globby 是我最喜歡的工具之一,它使處理文件和 glob 變得如此簡單。 Globby 通過 Promise 支持增強了 Node.js Glob(通過./*.js選擇所有 JavaScript 文件)。 在本例中,我們使用它來獲取src/icons目錄中的所有文件。
  2. 一旦我們這樣做了,我們在options對像中設置一些選項,然後使用三個參數調用 Grunticon-Lib:
    • 圖標文件,
    • 目的地,
    • options.The 庫接管並咀嚼這些圖標,並最終在我們想要的目錄中創建 SVG 和 PNG 版本。
  3. 我們快完成了。 請記住,這是在一個單獨的文件中,我們需要添加一個“鉤子”來從我們的 npm 腳本中調用這個文件,例如: "icons": "babel-node tasks/icons.js"
  4. 現在我們可以運行npm run icons ,我們的圖標每次都會被創建。

npm 腳本提供與其他任務運行程序類似的功能和靈活性,而沒有插件債務。

此處涵蓋的任務運行者的細分

工具優點缺點
咕嚕聲不需要真正的編程知識此處介紹的最冗長的任務運行程序
吞嚥使用實際的 JavaScript 和流配置任務需要 JavaScript 知識
向項目添加代碼(可能有更多錯誤)
網頁包最好的模塊捆綁更通用的任務更難(例如,Sass 到 CSS)
npm 腳本與命令行工具直接交互。 如果沒有任務運行器,某些任務是不可能完成的。

一些輕鬆的勝利

所有這些示例和任務運行器可能看起來都讓人不知所措,所以讓我們分解一下。 首先,我希望您不要從本文中看出,您當前使用的任何任務運行器或構建系統都需要立即替換為此處提到的。 更換這樣的重要係統不應該沒有太多考慮。 這是我對升級現有系統的建議:逐步進行。

包裝腳本!

一種增量方法是考慮圍繞現有任務運行器編寫一些“包裝器”npm 腳本,以為實際使用的任務運行器之外的構建步驟提供通用詞彙表。 包裝腳本可以像這樣簡單:

 { "scripts": { "start": "gulp" } }

許多項目利用starttest npm 腳本塊來幫助新開發人員快速適應。 包裝腳本確實為您的任務運行程序構建鏈引入了另一層抽象,但我認為能夠圍繞 npm 原語(例如test )進行標準化是值得的。 npm 命令比單個工具具有更好的壽命。

撒上一個小 Webpack

如果您或您的團隊正在為您的 JavaScript 維護一個脆弱的“捆綁訂單”而感到痛苦,或者您正在尋求升級到 ES6,請考慮將 Webpack 引入您現有的任務運行系統的機會。 Webpack 很棒,因為您可以根據需要使用盡可能多或盡可能少的內容,但仍然可以從中獲得價值。 首先讓它捆綁你的應用程序代碼,然後將 babel-loader 添加到組合中。 Webpack 具有如此豐富的特性,以至於它可以在相當長的一段時間內適應幾乎任何添加或新特性。

通過 npm 腳本輕鬆使用 PostCSS

PostCSS 是一個很棒的插件集合,一旦編寫和預處理 CSS,就可以轉換和增強 CSS。 換句話說,它是一個後處理器。 使用 npm 腳本來利用 PostCSS 很容易。 假設我們有一個 Sass 腳本,就像我們之前的示例一樣:

 "sass": "node-sass src/scss/ -o dist/css",

我們可以使用 npm script 的lifecycle關鍵字來添加一個腳本以在 Sass 任務之後自動運行:

 "postsass": "postcss --use autoprefixer -c postcss.config.json dist/css/*.css -d dist/css",

每次運行 Sass 腳本時都會運行此腳本。 postcss-cli 包很棒,因為您可以在單獨的文件中指定配置。 請注意,在此示例中,我們添加了另一個腳本條目來完成新任務; 這是使用 npm 腳本時的常見模式。 您可以創建一個工作流來完成您的應用程序需要的所有各種任務。

結論

任務運行器可以解決實際問題。 我使用任務運行器來編譯 JavaScript 應用程序的不同版本,具體取決於目標是生產環境還是本地開發環境。 我還使用任務運行器來編譯 Handlebars 模板,將網站部署到生產環境並自動添加我的 Sass 中缺少的供應商前綴。 這些不是微不足道的任務,但是一旦將它們包裹在任務運行器中,它們就會變得毫不費力。

任務運行者不斷發展和變化。 我試圖涵蓋當前時代精神中最常用的那些。 然而,還有一些我什至沒有提到的,比如 Broccoli、Brunch 和 Harp。 請記住,這些只是工具:僅在解決特定問題時使用它們,而不是因為其他人都在使用它們。 快樂的任務運行!