cherryvenus

task runner提高项目效率

原文链接: www.smashingmagazine.com

task runner提高项目效率

任务运行器是英雄(或者恶棍,基于你的观点它是个啥),在大多数网站和手机应用的后台默默地运行。通过类似于合并文件,加速开发服务器和编译代码此类的大量的开发任务,任务运行器体现它的价值。在这篇文章中,我们会涵盖 Grunt, Gulp, Webpack 以及 npm scripts。我们也会给每一个工具都提供一些列子,来帮助你了解它们。在本文末尾,为了帮助你们将本文的观点融入你们的应用中,我会给出一些简单快速开始的方法和建议。

有一种情绪,关于任务运行器,或者一般来说javascript的发展已经让前端前景变得更加复杂了。我同意一个观点,就是将一整天的时候都耗在构建脚本上,这不是时间的最佳利用,但是当适度地使用任务运行器,能够带来不少好处。这就是我们在本篇文章中的目标,是快速涵盖一些最受欢迎的任务运行器,同时提供一些实例,以此来激发你的想象,如何将这些工具运用到你的工流中。

命令行上的注释

任务运行器和构建工具主要通过命令行工具来实现。在整篇文章中,我会假定读者有一定关于命令行的经验和能力的水平。如果你知道如何使用一些常用命令,像 cd, ls, cpmv,那么我们的各种例子你都可以理解。如果你对用这些命令很不在行,在 Smashing Magazine上有一篇很赞的文章入门篇1。让我们从他们中的祖先grunt开始说吧。

Grunt

Grunt是第一个受欢迎的js任务运行器。从2012年开始在以某些形式上,我一直在使用grunt。Grunt的基本理念就是,在一个特殊的JavaScript文件,即Gruntfile.js,配置不同的插件,以用来完成任务。这是一个巨大的插件生态系统,并且是十分稳定且成熟的工具。Grunt有一个很赞的网络目录这罗列出主要的插件(现在大约5500个)。Grunt的简单机智的地方在于,他是通过JavaScript组合以及通用配置文件的方法(就像生成文件),这允许更多的开发者贡献,并且在他们的项目中使用Grunt。这也意味着Grunt可以用相同版本的控制系统放在其余的项目中。

Grunt久经测试因此是很稳定的。在编写的时候,version 1.0.0发布了,这是对Grunt团队来说是一个巨大的成功。因为Grunt配置大量的插件,让他们互相配合工作,这很快地纠缠在一起(i.e 凌乱且困惑地修改)。然而,有一点细心和组织(将任务分成好几个逻辑文件) ,你可以在任何项目上创造奇迹。

在极少的案例中,你会找不到你需要的插件,grunt提供如何编写自己的插件的文档。对于如何创造自己的插件,你所需要知道,只有JavaScript和Grunt API。你可能根本不需要自己创建插件,因此让我们看看如何使用grunt和一些十分受欢迎又有用的插件!

来个例子

grunt-example目录的截图

Grunt目录看上去是这样的(查看大版本)

现在来看看Grunt实际上是如何工作的。在命令行运行grunt会触发Grunt命令程序在根目录寻找Gruntfile.jsGruntfile.js包含Grunt任务命令的配置。从这层意思上说Gruntfile.js可以被当做一本食谱,让厨师(i.e. Grunt,程序)跟着书中步奏走。并且,就像任何一本好的食谱,Gruntfile.js会包含许多配方(i.e. 任务)。

通过 Grunticon插件来自动生成图标给假设的网页应用,我们要将Grunt,完成任务。Grunticon提取svg的文件夹,并将此分成好几份资源:

  • 一个css文件用base-64加密的SVG作为背景图片;
  • 一个css文件用PNG版本base-64加密的SVG作为背景图片;
  • 一个css文件用来为每个图标对应的一个独立的Png文件。

这3种不同的文件代表了不同性能的浏览器和手机设备。可以接受高分辨路的现代设备会接受SVG的单独的请求(i.e. 单独的CSS文件)。浏览器不处理SVG但是处理base64加密的资源,这样它可以接受base64的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文件.将Grunt作为npm依赖,配置在package.json文件 ,然后通过npm i grunt grunt-grunticon --save-dev安装Grunticon,这是最好的方法.
  3. 创建SVG文件夹和一个目标文件夹(创建的assets生成的地方).
  4. 在html页面上的head放一小段脚本(script), 这会决定加载哪一个图标.

在运行Grunticon这个任务之前,你需要确保你的目录看上去像这样的:

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

一旦这些都安装创建了,那么你就可以复制上面的代码片段到Gruntfile.js之中。你接下来就能在命令行运行grunt grunticon并且看你的任务是怎么执行的。

上方的代码片段做了一些事:

  • 在Grunt32行中,添加一个新的config对象,名为grunticon
  • 为在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

就这样了 —— 完成!在几行配置和几个包的安装之下,我们自动生成了需要的图标资源!希望,这解释了任务运行器的强大;可靠,高效,可复用性。

Gulp: 搭建系统的乐高积木

Gulp在Grunt之后出现,Gulp目标是创建一种,不完全是配置也有真正代码的工具。代码优于配置,这背后的想法是,修改代码比无休止的配置文件要更生动和灵活。Gulp的障碍是,这比Grunt需要有更多的技术知识。你需要了解Node.js streaming API并且可以很熟练地写基础js。

因为Gulp使用Node.js streams,所以他比grunt要快很多。不同于使用文件系统作为文件转化的“数据库”,使用streams意味着Gulp使用了虚拟存储转化文件。欲知更多stream的信息,查看Node.js streams API 文档,搭配stream手册

一个例子

Gulp文件目录的截图

gulp文件目录就像这样(查看大版本)

就像在Grunt的章节一样,通过一个直观的例子来讲解Gulp:将js模块合并到一个单独的应用文件。

Gulp和Grunt的运行方式是一样的。在即将运行的文件夹中,gulp命令行程序会寻找“食谱配方”(i.e. Gulpfile.js

限制每张页面的请求量是web性能最好的尝试(特别是在手机上)。如果功能被分成好几个文件,与其他开发者合作就会简单很多。 进入任务运行器。用Gulp为程序合并众多文件,这样手机端就只需要加载一个文件,而不是许多文件。

Gulp和grunt一样有一个大规模的插件生态系统。因此,为了简化任务,我们将会依靠gulp-concat插件。项目的系统结构就像下面这样:

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

src目录下有2个JavaScript文件,我们想将他们合并成一个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返回这些文件的流,之后我们可以传输流到gulp-concat插件中(通过 pipe API)。接着插件将流中所有的文件都串联起来传输到gulp.dest这个方法中。 gulp-dest方法只将它接收到流的写入磁盘。

你可以看到Gulp如何利用流来为任务“搭积木”或者说“串联”。一个典型的Gulp工作流看上去像下面这样:

  1. 获得某一类型的所有文件.
  2. 将这些文件传到插件(concat!),或者做些转换。
  3. 将这些转换的文件放入另一个块中(在例子中,dest 块是结束串联的地方)。

正如在Grunt的例子中,只是简单地运行下项目根目录中的gulp,会定位 Gulpfile.js 文件中定义default任务。这个任务,合并我们的文件,以此优化我们的app或者网站。

Webpack

最新加入JavaScript任务运行器大军的是Webpack。Webpack定义自己为“模块打包”,也就意味着这个可以动态的创建一个来JavaScript代码的打包,这些代码可以来自不同的模块模式,比如CommonJS模式。Webpack也有插件,在这里称之为loaders

Webpack还是相对稚嫩的,并且拥有密密麻麻,令人疑惑的文档。因此,我建议大家把Pete Hunt的Webpack仓库作为切入点,然后投入官方文档的怀抱。如果你是任务运行器的新手或者对JavaScript并不熟练,我同样不建议用Webpack。撇开这些问题,webpack依然是一个比广泛的grunt和gulp更为具体的工具。许多人用webpack搭配Grunt或 Gulp,来解决问题,让webpack做他擅长的模块搭建,然后让grunt或者gulp做更加通用的任务。

Webpack根本上是让我们为浏览器写Node.js类型的代码,一个极佳上手提高生产力的方法并且通过模块让我们的代买变得简洁分块明确。让我们来做一个和Gulp中一样的例子来了解webpack吧——将多个JavaScript文件合并到一个app文件中。

一个例子

webpack-example例子目录的截图

我们的webpack项目的目录看上去就是这样的(查看大版本](https://media-mediatemple.netdna-ssl.com/wp-content/uploads/2016/05/03-webpack-example-opt.png))

webpack经常和Babel一起搭配使用,将ES6格式翻译成ES5的。将ES6转译成ES5,让开发者可以用ES6标准的同时也可以为浏览器或者还完全不支持ES6的环境提供ES5。然而在这个例子中,我们会集中在打包两个文件,也就是来自Gulp的例子。开始前,我们需要安装webpack和创建一个配置文件webpack.config.js。下面就是我们文件:

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

在这个例子中,我们制定src/foo.js为webpack的初始文件,来展开依赖关系的工作。我们也更新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的例子。你会发现这些文件现在“export”一个function。本质上来说,CommonJS和Webpack允许组织代码变为独立的模块,这样就可以在整个应用中引入或导出他们。Webpack足够聪明,他可以通过引入和导出的关键词,然后把所有的都打包到dist/app.js文件中。我们不再需要去维护一个串联的任务,只需要简单地将结构写入代码之中。好多啦!

扩展

webpack近似于Gulp,因为“这只是javascript”。这可以通过他的载入系统,扩展做其他的任务运行器。举个例子,你可以用css-loadersass-loader来编译Sass到CSS,甚至可以在javascript中用Sass,通过超载require的CommonJS模式!然而,我建议单独使用webpack来搭建模块,然后用其他的工具做更加通用的方法(比如Webpack和npm scripts,或者, Webpack和 Gulp 来解决其他一切问题)。

npm Scripts

npm scripts是最新的时髦热潮,它有很好的理由。我们看到所有的这些工具,有大量的依赖需要导入到项目中,这有可能会失控。我看到的倡议使用npm scripts 作为搭建网站的一个过程的文章是由James Halliday写的。 他的文章完美地总结了npm scripts隐藏的力量(重视我)。

有一些很棒的工具来做这些JavaScript项目的自动化,我从来没有感受到他们的魅力,因为鲜为人知的npm run命令已经完美地,为一切我需要做的做好了准备,只需要维护了一个非常小的配置文件

你有没有get到最后一点?npm scripts最主要的吸引力就是他有一个“非常小的配置文件”。这就是其中一个原因,为什么npm scripts开始追上其他工具了(几乎4年了,伤心)。用Grunt, Gulp 甚至Webpack,容易会没落在插件中,那些在项目中包含二进制和双倍依赖的插件。

Keith Cirkel有go-to tutorial讲述如何使用npm来替代Grunt和Gulp,他引入了一个必要的插件Parallel Shell(和一个host of others和它差不多)。

一个例子

在关于Grunt的模块中,我们在Grunt任务中用了受欢迎的模块Grunticon创建了SVG图标(和返回PNG)。这曾经是nmp scripts,对于我来说的痛点。有一段时间我会在项目中保持使用Grunt,只是为了用Grunticon。在我的npm任务重,我会表面上使用Grunt来获得任务运行器开始(或者我们称之在工作中,一个构建工具的火鸦)。谢天谢地,The Filament Group,一个Gruntion背后的很赞的团队,发布了一个独立(i.e 不需要Grunt)的版本,Grunticon-Lib。因此让我们通过npm script来创建图标吧!

这个例子要比一个典型的npm script任务要更先进一些。一个典型的npm script任务被称为命令行的工具,有适当的标记和配置文件。这里有一个更典型的将Sass编译为CSS的任务:

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

看这只有带不同选项的一行命令?不需要任务文件,不需要搭建工具加速——只需要在命令行打npm run sass,Sass就变成css啦。npm scripts的一个很好的特点就是你如何将任务脚本串联在一起。比如,我们想在Sass任务运行前运行一些任务。我们会创建一个新脚本入口,就像这样:

`"presass": "echo 'before sass',`

说的没错:npm识别pre-前缀。他也识别post-前缀。任何脚本入口都有带pre- or post-前缀的相同脚本名字,会在这个入口之前或之后运行这脚本。

转换图标需要一个实参nodejs的文件。虽然,这不是很重要。只是需要创建一个tasks目录,并创建一个名为grunticon.js或者icons.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 (i.e. require)两个库,grunticon-libglobby.Globby是我最爱的工具之一,他让一堆文件之间的工作变得简单。 Globby 承诺支持强化了 Node.js Glob(通过 ./*.js 筛选所有的javascript文件)。在这个案例中,我们用它来从src/icons目录下获取所有文件。

2 一旦我们这么做了,我们需要设置一些选项在一个‘options’的对象中,然后调用Grunticon-Lib和3个参数

  • 图标文件,
  • 目标目录,
  • 选项。

库接过处理这些图标,然后生成SVG和PNG版本在我们想要的目录下。

  1. 差不多快完成啦。记住这是在一个不同的文件中,我们需要添加一个链接来调用来自npm script的文件,就像这样"icons": "babel-node tasks/icons.js"
  2. 现在我们可以运行npm run icons,每次运行,图标都会生成。

npm scripts提供了和其他任务管理器相同层级的力量和灵活度,而没有插件烦恼。

分析提到的任运运行器

工具 支持 反对 Grunt 真正的javascript和流的配置任务 需要javascript的知识 添加一些代码到项目中(潜在更多的bug) Webpack 最好的模块构建器 对于更多一般的任务更难(比如。Sass变为Css) npm scripts 和命令行直接链接 没有任务构建器一些任务不可能完成。

快速上手

这里所有的例子和任务运行器的案例看上去让你压力山大。因此让我们分开消化。首先我们并不希望你从本文拿走任意一个任务构建器或者搭建你正在用的系统,立刻替换成本文提到的一个。替换像这样重要的系统,不应该不假思索。这里是我的一些升级现有系统的一些建议:一步步来吧。

封装脚本!

一个的方法是在你现有任务构建器周围写一些“封装”npm scripts来提供一个通用的词来构建步骤,在实际项目构建器中使用的步骤。一个封装脚本可以向下面这么简单:

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

许多项目利用 starttest npm script代码块来帮助许多新手开发者快速适应新环境。一个分装脚本确实引入了另一个抽象层到你的项目构建器的链中,然而我认为这能够标准化npm源代码(e.g. test)。npm命令比一个单独的工具有更强的生命力。

在Webpack放一些

如果你或者你的团队觉得维护这个脆弱的有一堆顺序的javascript力不从心,或者你想升级到ES6,考虑一个机会引入webpack到现有任务运行系统吧。webpack十分棒,你可以随意的用需要的部分然后依然从中获取价值。开始仅仅用来捆绑你的应用代码,然后添加babel-loader来引入不同的。webpack有如此深的特性,这将能够容纳任何的添加或者之后的一些新属性。

和npm Scripts简单地使用PostCSS

PostCSS是一个极佳的插件集合,能够转换和加强CSS,一旦写入和预处理,换句话说这是一个预处理器。用npmscript处理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 script的时候。你可以建立一个工作流来完成所有你应用需要的不同的任务。

结论

任务运行器可以解决现实问题。我用任务运行器编译一个javascript应用中不同的文件,依赖于目标是否生产或者是本地开发。我曾经用任务运行器编译Handlebars模板,部署网站到正式运行已经动态添加Sass中遗漏的前缀。这些都不是琐碎的任务,但是一旦他们被任务运行器包括,他们就变成了不需要费力的。

任务运行器在不断进化和改变,我尝试在当前的时代精神下涵盖用的最多的任务运行器到本文。然而,还有一些我没有提到,比如BroccoliBrunchHarp。记住他们仅仅是工具:仅仅因为他们能解决一个特定的问题才用他们,不是因为每个人都在用他们。快乐的任务!

在本文中: Task Runner:任务运行器 Easy Wins:快速开始的方法