如何利用机器:与任务运行者一起提高工作效率

已发表: 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。 请记住,这些只是工具:仅在解决特定问题时使用它们,而不是因为其他人都在使用它们。 快乐的任务运行!