Gulp:最大化站点速度的Web开发人员的秘密武器

本文概述

我们许多人必须处理生产中使用的基于Web的项目, 这些项目可为公众提供各种服务。处理此类项目时, 能够快速构建和部署我们的代码很重要。快速执行操作通常会导致错误, 尤其是在过程重复的情况下, 因此, 最好的方法是尽可能地使该过程自动化。

Gulp:最大化站点速度的Web开发人员的秘密武器

我的开发者同行:没有任何借口向你的浏览器提供垃圾邮件。

鸣叫

在本文中, 我们将研究一种可以成为实现自动化的工具的一部分。该工具是一个名为Gulp.js的npm软件包。为了熟悉本文中使用的基本Gulp.js术语, 请参阅以前由我们的srcmini开发人员之一Antonios Minas在博客上发布的”使用Gulp进行JavaScript自动化简介”。我们假定对npm环境有基本的了解, 因为在本博文中广泛使用它来安装软件包。

服务前端资源

在继续之前, 让我们退后几步, 以概述Gulp.js可以为我们解决的问题。许多基于Web的项目都具有提供给客户端的前端JavaScript文件, 以便为网页提供各种功能。通常, 还会为客户提供一组CSS样式表。有时, 在查看网站或Web应用程序的源代码时, 我们可以看到如下代码:

<link href="css/main.css" rel="stylesheet">
<link href="css/custom.css" rel="stylesheet">
<script src="js/jquery.min.js"></script>
<script src="js/site.js"></script>
<script src="js/module1.js"></script>
<script src="js/module2.js"></script>

此代码存在一些问题。它引用了两个单独的CSS样式表和四个单独的JavaScript文件。这意味着服务器必须向服务器发出总共六个请求, 并且每个请求必须在页面准备就绪之前分别加载资源。对于HTTP / 2来说, 这并不是什么大问题, 因为HTTP / 2引入了并行性和标头压缩, 但这仍然是一个问题。由于增加了加载文件的时间, 因此增加了加载此页面所需的总流量, 并降低了用户体验的质量。在使用HTTP 1.1的情况下, 它还会占用网络并减少可用请求通道的数量。最好将CSS和JavaScript文件合并为一个捆绑包。这样, 总共将只有两个请求。提供这些文件的缩小版本也很不错, 这些缩小版本通常比原始文件要小得多。如果缓存了任何资源, 我们的Web应用程序也可能会中断, 并且客户端将收到过时的版本。

超载

解决其中一些问题的一种原始方法是使用文本编辑器将每种类型的资源手动组合到包中, 然后通过诸如http://jscompress.com/的压缩程序服务运行结果。事实证明, 在开发过程中连续进行非常繁琐。一个轻微但可疑的改进是使用GitHub上可用的软件包之一托管我们自己的Minifier服务器。然后, 我们可以执行与以下操作类似的操作:

<script src="min/f=js/site.js, js/module1.js"></script>

这将为我们的客户端提供缩小的文件, 但不能解决缓存问题。这也将导致服务器上的额外负载, 因为我们的服务器实际上必须在每次请求时重复连接并缩小所有源文件。

使用Gulp.js自动化

当然, 我们可以做的比这两种方法都好。我们真正想要的是使捆绑自动化并将其包括在项目的构建阶段中。我们希望最终获得已精简且可以使用的预制资源捆绑包。我们还希望强制客户在每次请求时都接收我们捆绑资源的最新版本, 但是如果可能的话, 我们仍然希望利用缓存。对我们来说幸运的是, Gulp.js可以解决这个问题。在本文的其余部分, 我们将构建一个利用Gulp.js的功能来串联和缩小文件的解决方案。当有更新时, 我们还将使用插件来破坏缓存。

在示例中, 我们将创建以下目录和文件结构:

public/
|- build/
   |- js/
      |- bundle-{hash}.js
   |- css/
      |- stylesheet-{hash}.css
assets/
|- js/
   |- vendor/
   |- jquery.js
   |- site.js
   |- module1.js
   |- module2.js
|- css/
   |- main.css
   |- custom.css
gulpfile.js
package.json

npm使Node.js项目中的程序包管理变得极乐。 Gulp利用npm的简单包装方法来提供模块化且功能强大的插件, 从而提供了极大的可扩展性。

在gulpfile.js文件中, 我们将定义Gulp将为我们执行的任务。 npm使用package.json定义应用程序的程序包并跟踪将要安装的依赖项。公共目录是应配置为面向网络的目录。资源目录是我们存储源文件的位置。要在项目中使用Gulp, 我们需要通过npm安装它, 并将其另存为该项目的开发人员依赖项。我们还将要从Gulp的concat插件开始, 这将使我们可以将多个文件连接为一个。

要安装这两项, 我们将运行以下命令:

npm install --save-dev gulp gulp-concat

接下来, 我们将要开始编写gulpfile.js的内容。

var gulp = require('gulp');
var concat = require('gulp-concat');
 
gulp.task('pack-js', function () {    
    return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js'])
        .pipe(concat('bundle.js'))
        .pipe(gulp.dest('public/build/js'));
});
 
gulp.task('pack-css', function () {    
    return gulp.src(['assets/css/main.css', 'assets/css/custom.css'])
        .pipe(concat('stylesheet.css'))
        .pipe(gulp.dest('public/build/css'));
});
 
gulp.task('default', ['pack-js', 'pack-css']);

在这里, 我们正在加载gulp库及其concat插件。然后, 我们定义三个任务。

加载gulp库及其concat插件

第一个任务(pack-js)定义了将多个JavaScript源文件压缩为一个包的过程。我们列出了源文件, 这些文件将按照指定的顺序进行glob, 读取和连接。我们将其通过管道传输到concat插件中, 以获得一个名为bundle.js的最终文件。最后, 我们告诉gulp将文件写入public / build / js。

第二项任务(pack-css)与上述任务相同, 但适用于CSS样式表。它告诉Gulp将连接的输出作为stylesheet.css存储在public / build / css中。

第三个任务(默认)是Gulp在不带参数的情况下运行的任务。在第二个参数中, 我们传递运行默认任务时要执行的其他任务的列表。

让我们使用通常使用的任何源代码编辑器将此代码粘贴到gulpfile.js中, 然后将文件保存到应用程序根目录中。

接下来, 我们将打开命令行并运行:

gulp

如果在运行此命令后查看文件, 我们将找到两个新文件:public / build / js / bundle.js和public / build / css / stylesheet.css。它们是源文件的串联, 解决了部分原始问题。但是, 它们没有被缩小, 并且还没有缓存破坏。让我们添加自动缩小功能。

优化构建资源

我们将需要两个新的插件。要添加它们, 我们将运行以下命令:

npm install --save-dev gulp-clean-css gulp-minify

第一个插件用于最小化CSS, 第二个插件用于最小化JavaScript。第一个使用clean-css包, 第二个使用UglifyJS2包。我们将首先在gulpfile.js中加载这两个软件包:

var minify = require('gulp-minify');
var cleanCss = require('gulp-clean-css');

然后, 在将输出写入磁盘之前, 我们将需要在任务中使用它们:

.pipe(minify())
.pipe(cleanCss())

gulpfile.js现在应如下所示:

var gulp = require('gulp');
var concat = require('gulp-concat');
var minify = require('gulp-minify');
var cleanCss = require('gulp-clean-css');
 
gulp.task('pack-js', function () {    
    return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js'])
        .pipe(concat('bundle.js'))
        .pipe(minify())
        .pipe(gulp.dest('public/build/js'));
});
 
gulp.task('pack-css', function () {    
    return gulp.src(['assets/css/main.css', 'assets/css/custom.css'])
        .pipe(concat('stylesheet.css'))
        .pipe(cleanCss())
   .pipe(gulp.dest('public/build/css'));
});
 
gulp.task('default', ['pack-js', 'pack-css']);

让我们再次大吃一惊。我们将看到文件stylesheet.css以缩小格式保存, 而文件bundle.js仍按原样保存。我们将注意到, 我们现在还有bundle-min.js, 它已缩小。我们只需要缩小的文件, 并且希望将其保存为bundle.js, 因此我们将使用其他参数修改代码:

.pipe(minify({
    ext:{
        min:'.js'
    }, noSource: true
}))

根据gulp-minify插件文档(https://www.npmjs.com/package/gulp-minify), 这将为缩小版本设置所需的名称, 并告诉插件不要创建包含原始源代码的版本。如果我们删除构建目录的内容并再次从命令行运行gulp, 最终将只有两个缩小的文件。我们刚刚完成了构建过程的最小化阶段。

缓存清除

接下来, 我们将要添加缓存清除功能, 并且需要为此安装一个插件:

npm install --save-dev gulp-rev

并在我们的gulp文件中要求它:

var rev = require('gulp-rev');

使用插件有点棘手。我们必须首先通过插件通过管道传递缩小的输出。然后, 将结果写入磁盘后, 我们必须再次调用该插件。插件会重命名文件, 以便使用唯一的哈希标记它们, 并且还会创建清单文件。清单文件是一个映射, 我们的应用程序可以使用该清单来确定我们应该在HTML代码中引用的最新文件名。修改gulp文件后, 它应该最终看起来像这样:

var gulp = require('gulp');
var concat = require('gulp-concat');
var minify = require('gulp-minify');
var cleanCss = require('gulp-clean-css');
var rev = require('gulp-rev');
 
gulp.task('pack-js', function () {    
    return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js'])
        .pipe(concat('bundle.js'))
        .pipe(minify({
            ext:{
                min:'.js'
            }, noSource: true
        }))
        .pipe(rev())
        .pipe(gulp.dest('public/build/js'))
        .pipe(rev.manifest())
        .pipe(gulp.dest('public/build'));
});
 
gulp.task('pack-css', function () {
    return gulp.src(['assets/css/main.css', 'assets/css/custom.css'])
        .pipe(concat('stylesheet.css'))
        .pipe(cleanCss())
        .pipe(rev())
            .pipe(gulp.dest('public/build/css'))
        .pipe(rev.manifest())
        .pipe(gulp.dest('public/build'));
});
 
gulp.task('default', ['pack-js', 'pack-css']);

有了适当的缓存清除功能, 你就可以花很长的时间来处理JS和CSS文件, 并在需要时仍用新版本可靠地替换它们。

让我们删除构建目录的内容, 然后再次运行gulp。我们将发现, 我们现在有两个文件, 每个文件名上都带有标签, 并且manifest.json保存到public / build。如果打开清单文件, 我们将看到它仅引用了我们的缩小文件和带标签文件之一。发生的情况是, 每个任务都写入一个单独的清单文件, 并且其中一个最终覆盖了另一个。我们将需要使用其他参数来修改任务, 这些参数将告诉它们寻找现有清单文件, 并将新数据合并到该清单文件中(如果存在)。语法有点复杂, 所以让我们看一下代码的外观, 然后再遍历它:

var gulp = require('gulp');
var concat = require('gulp-concat');
var minify = require('gulp-minify');
var cleanCss = require('gulp-clean-css');
var rev = require('gulp-rev');
 
gulp.task('pack-js', function () {
    return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js'])
        .pipe(concat('bundle.js'))
        .pipe(minify({
            ext:{
                min:'.js'
            }, noSource: true
        }))
        .pipe(rev())
        .pipe(gulp.dest('public/build/js'))
        .pipe(rev.manifest('public/build/rev-manifest.json', {
            merge: true
        }))
        .pipe(gulp.dest(''));
    });
 
gulp.task('pack-css', function () {    
    return gulp.src(['assets/css/main.css', 'assets/css/custom.css'])
        .pipe(concat('stylesheet.css'))
        .pipe(cleanCss())
        .pipe(rev())
        .pipe(gulp.dest('public/build/css'))
        .pipe(rev.manifest('public/build/rev-manifest.json', {
            merge: true
        }))
        .pipe(gulp.dest(''));
});
 
gulp.task('default', ['pack-js', 'pack-css']);

我们首先将输出传递给rev.manifest()。这将创建标记的文件, 而不是我们之前拥有的文件。我们正在提供rev-manifest.json的所需路径, 并告诉rev.manifest()合并到现有文件(如果存在)中。然后, 我们告诉gulp将清单写入当前目录, 此时该目录将是public / build。路径问题是由于存在一个错误, 该错误在GitHub上有更详细的讨论。

现在, 我们具有自动缩小功能, 标记文件和清单文件。所有这些将使我们能够更快地将文件交付给用户, 并在进行修改时破坏其缓存。但是, 仅存在两个问题。

第一个问题是, 如果我们对源文件进行任何修改, 我们将获得新标记的文件, 但是旧文件也将保留在那里。我们需要某种方式来自动删除旧的缩小文件。让我们使用一个允许我们删除文件的插件来解决此问题:

npm install --save-dev del

我们将在代码中要求它, 并定义两个新任务, 每种类型的源文件一个:

var del = require('del');
 
gulp.task('clean-js', function () {
    return del([
        'public/build/js/*.js'
    ]);
});
 
gulp.task('clean-css', function () {
    return del([
        'public/build/css/*.css'
    ]);
});

然后, 我们将确保新任务在我们的两个主要任务之前完成运行:

gulp.task('pack-js', ['clean-js'], function () {
gulp.task('pack-css', ['clean-css'], function () {

如果我们在修改后再次运行gulp, 我们将只有最新的压缩文件。

第二个问题是我们不想每次进行更改时都保持正常运行。为了解决这个问题, 我们将需要定义一个观察者任务:

gulp.task('watch', function() {
 gulp.watch('assets/js/**/*.js', ['pack-js']);
 gulp.watch('assets/css/**/*.css', ['pack-css']);
});

我们还将更改默认任务的定义:

gulp.task('default', ['watch']);

如果现在从命令行运行gulp, 我们将发现它不再在调用时生成任何内容。这是因为它现在调用监视程序任务, 该任务将监视我们的源文件是否有任何更改, 并且仅在检测到更改时才进行构建。如果尝试更改任何源文件, 然后再次查看控制台, 我们将看到pack-js和pack-css任务及其依赖项自动运行。

现在, 我们要做的就是在我们的应用程序中加载manifest.json文件, 并从中获取带标签的文件名。我们如何执行此操作取决于我们特定的后端语言和技术堆栈, 并且实现起来非常琐碎, 因此我们将不对其进行详细介绍。但是, 通常的想法是, 我们可以将清单加载到数组或对象中, 然后定义一个辅助函数, 该函数将允许我们以类似于以下方式的方式从模板中调用版本化资源:

gulp(‘bundle.js’)

一旦这样做, 我们将不必再担心文件名中的标记更改, 并且我们将能够专注于编写高质量的代码。

可以在此GitHub存储库中找到本文的最终源代码以及一些示例资源。

总结

在本文中, 我们介绍了如何在构建过程中实现基于Gulp的自动化。我希望这对你有所帮助, 并允许你在自己的应用程序中开发更复杂的构建过程。

请记住, Gulp只是可用于此目的的工具之一, 还有许多其他工具, 例如Grunt, Browserify和Webpack。它们的目的和可以解决的问题范围各不相同。有些可以解决Gulp无法解决的问题, 例如将JavaScript模块与可以按需加载的依赖项绑定在一起。这称为”代码拆分”, 它是对在每个页面上将程序的所有部分都提供一个大文件的想法的改进。这些工具非常复杂, 但将来可能会涉及到。在下一篇文章中, 我们将介绍如何自动化应用程序的部署。

相关:深入研究:构建基于流的任务自动化工具

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?