使用Gulp按顺序连接脚本

197

比如说,你正在使用Backbone或其它框架来开发一个项目,并且需要按照一定的顺序加载脚本文件,例如需要在加载backbone.js之前先加载underscore.js

那么如何使它们按照正确的顺序合并到一起呢?

// JS concat, strip debugging and minify
gulp.task('scripts', function() {
    gulp.src(['./source/js/*.js', './source/js/**/*.js'])
    .pipe(concat('script.js'))
    .pipe(stripDebug())
    .pipe(uglify())
    .pipe(gulp.dest('./build/js/'));
});

我在source/index.html中脚本的顺序是正确的,但由于文件按字母顺序组织,gulp会在backbone.js之后连接underscore.js,而且source/index.html中脚本的顺序并不重要,它只看目录中的文件。

那么有人对此有什么想法吗?

我最好的建议是将供应商脚本重命名为123以使它们具有正确的顺序,但我不确定是否喜欢这种方法。

随着我的了解越来越多,我发现Browserify是一个很好的解决方案,虽然一开始可能会有些麻烦,但它非常棒。


5
我使用Browserify来打包现在的代码。个人认为它有一些学习曲线,一开始我遇到了一些麻烦,但用gulp browserify很棒!这种方式使你的代码可以模块化,你可以在shim中处理顺序,因此在使用Browserify时不需要进行串联操作。 - Michael Joseph Aubry
谢谢!实际上我看到了一篇很棒的文章https://medium.com/@dickeyxxx/best-practices-for-building-angular-js-apps-266c1a4a6917?source=email-7f7d7c98862f-1405262869336-note_published,它指出对于`Angular`模块,您不需要使用`browserify`,简单的连接就可以工作,而且顺序并不重要 :) - Dmitri Zaitsev
不错,我会出于好奇心去阅读它。我正在使用backbone.js而不是angular.js,所以在我的情况下,看起来我需要坚持使用browserify。 - Michael Joseph Aubry
我正在使用bower来管理我的客户端依赖项,并且它与gulp很好地配合使用。main-bower-files可以用来获取所有源的列表,然后您可以将它们连接并压缩。 - Codebling
尝试使用gulp-order包。 - Anton Putau
显示剩余3条评论
17个回答

206

最近我在构建我的AngularJS应用程序时也遇到了类似的Grunt问题。这是我发布的问题

我最终做的是显式地按顺序列出Grunt配置中的文件。配置文件将如下所示:

[
  '/path/to/app.js',
  '/path/to/mymodule/mymodule.js',
  '/path/to/mymodule/mymodule/*.js'
]

Grunt 能够找出哪些文件是重复的并将其排除在构建过程之外。同样的技术也适用于 Gulp。


74
顺便提一下,这也适用于 Gulp,很好地发挥作用。Gulp 也不会重复文件。 - OverZealous
1
酷毙了,这两个杰作真是太棒了。我终于成功设置了我的gulp.js文件,按照自己的意愿编写了一些HTML代码,保存文件后,一个使用最佳框架和良好实践构建的网站就诞生了。而且更新也很容易,如果你还没有使用其中任何一个,那么你需要尝试一下! - Michael Joseph Aubry
1
是的!我最近开始使用 Grunt,它很棒。不再将 JavaScript 应用程序嵌入后端框架中了。 - Chad Johnson
3
在我的尝试中,Gulp 在复制文件时出现了重复的情况,但后来我发现在 gulp 和文件系统中大小写不同,因此要注意这一点!使用完全相同的大小写,gulp 就不会再复制相同的文件了。 - Chris
2
手动排序在一个严肃的项目中是一场噩梦。有专门的文件排序器 - 适用于AngularJS和其他框架。 - zhekaus
显示剩余4条评论

138

如果您需要让一些文件在一堆文件之后出现,另一件有帮助的事情就是从您的全局匹配中排除特定的文件,例如:

[
  '/src/**/!(foobar)*.js', // all files that end in .js EXCEPT foobar*.js
  '/src/js/foobar.js',
]
您可以将此与指定需要首先处理的文件相结合,如Chad Johnson的答案中所述。

啊,我之前看到了你的帖子,它帮助我将代码注入到我的“src”和“build”中。我看到你使用了那个语法,但最终我删除了那部分,因为我不确定我需要它的原因,现在明白了。 - Michael Joseph Aubry
哦,好的,你的观点让我想到了,这不会使foobar.js最后加载吗?难道不应该反过来吗?['./source/js/*.js', './source/js/**/underscore.js', './source/js/**/!(underscore)*.js'] - Michael Joseph Aubry
是的,这只是额外的帮助。当您需要或希望核心应用程序代码在其他所有内容加载完成后再加载时,它非常有用。如果您事先包含了特定文件,则没有理由使用它(!(foobar))。 - OverZealous
对于一个 AngularJS 应用程序,我的模块定义位于没有破折号的文件中,这个 Gulp glob 模式可以工作;['src/app/**/!(*-)*.js', 'src/app/**/*.js'] - Sam T

18

我使用了gulp-order插件,但并不总是成功的,你可以看到我的stackoverflow帖子 gulp-order node module with merged streams。在浏览Gulp文档时,我发现了streamque模块,它对于指定顺序(在我的情况下是拼接)非常有效。 https://github.com/gulpjs/gulp/blob/master/docs/recipes/using-multiple-sources-in-one-task.md

下面是我使用它的示例。

var gulp         = require('gulp');
var concat       = require('gulp-concat');
var handleErrors = require('../util/handleErrors');
var streamqueue  = require('streamqueue');

gulp.task('scripts', function() {
    return streamqueue({ objectMode: true },
        gulp.src('./public/angular/config/*.js'),
        gulp.src('./public/angular/services/**/*.js'),
        gulp.src('./public/angular/modules/**/*.js'),
        gulp.src('./public/angular/primitives/**/*.js'),
        gulp.src('./public/js/**/*.js')
    )
        .pipe(concat('app.js'))
        .pipe(gulp.dest('./public/build/js'))
        .on('error', handleErrors);
});

请参阅stream-series。它不需要您指定对象模式,并且可以与gulp-inject一起使用。请查看我的回答。 - Codebling
似乎有一半的Gulp插件根本不能保持一致性(就像你所指出的顺序),这非常遗憾,因为Gulp的架构概念是很壮观的,只是太多人实现和维护他们的插件不好。我认为底层的Node模块更有用,所以感谢你提供的解决方案!它工作得很好! - Jimmy Hoffa
1
streamqueue和event-stream对我来说都不起作用,但merge2按预期工作 https://www.npmjs.com/package/merge2 - Alexander Shutau

14

使用gulp-useref,您可以按照声明顺序将在索引文件中声明的每个脚本连接起来。

https://www.npmjs.com/package/gulp-useref

var $ = require('gulp-load-plugins')();
gulp.task('jsbuild', function () {
  var assets = $.useref.assets({searchPath: '{.tmp,app}'});
  return gulp.src('app/**/*.html')
    .pipe(assets)
    .pipe($.if('*.js', $.uglify({preserveComments: 'some'})))
    .pipe(gulp.dest('dist'))
    .pipe($.size({title: 'html'}));
});

在HTML中,您需要声明要生成的构建文件的名称,就像这样:

<!-- build:js js/main.min.js -->
    <script src="js/vendor/vendor.js"></script>
    <script src="js/modules/test.js"></script>
    <script src="js/main.js"></script>
在您的构建目录中,您将拥有对main.min.js的引用,其中包含vendor.js、test.js和main.js。

2
这太完美了!我讨厌需要定义顺序的答案!你知道吗?顺序已经在HTML文件中了。完美的解决方案。 - Ali Ok

6

sort-stream也可用于确保使用gulp.src指定文件的特定顺序。下面是一个示例代码,始终将backbone.js放在最后处理:

var gulp = require('gulp');
var sort = require('sort-stream');
gulp.task('scripts', function() {
gulp.src(['./source/js/*.js', './source/js/**/*.js'])
  .pipe(sort(function(a, b){
    aScore = a.path.match(/backbone.js$/) ? 1 : 0;
    bScore = b.path.match(/backbone.js$/) ? 1 : 0;
    return aScore - bScore;
  }))
  .pipe(concat('script.js'))
  .pipe(stripDebug())
  .pipe(uglify())
  .pipe(gulp.dest('./build/js/'));
});

我希望这个模块能够工作,因为它似乎是对我来说最简单的答案,但在我的情况下,我有编号的文件名和一个非常简单的比较函数,所以这并不起作用。 - Jeremy John
@JeremyJohn 你应该可以使用 return a.path.localeCompare(b.path, undefined, { numeric: true }) 进行排序。 - MiniGod

5

我只是在文件名前面添加数字:

0_normalize.scss
1_tikitaka.scss
main.scss

没有任何问题地在gulp中工作。


1
是的,我觉得这样会更容易一些。我的意思是,如果你要为生产环境编译所有文件,那么在开发过程中你给文件命名是没有任何影响的。 - Michael Joseph Aubry
2
我刚发现这个不正确。尝试使用1_xx、2_xx、10_xx、11_xx。至少在Windows下,它将是1_xx、10_xx、11_xx、2_xx。 - dbinott
19
就我个人而言,我非常不同意“在开发中给文件命名无关紧要”的说法。所有的开发都应该首先以人为本,而不是以计算机为本。将文件更改以匹配您的构建工具会违背使用构建工具的初衷。应该更改您的构建以匹配您的项目,而不是相反。 - Jon Hieb
2
@JonHieb 从某种程度上说,给你的文件加上数字前缀也有助于下一个开发人员了解每个文件的依赖关系,对吧?如果我打开一个文件夹并看到1_foo.js、2_bar.js和3_baz.js,我会按顺序打开这些文件,并从1_foo.js开始阅读代码。 - sqram
1
我发现gulp.src是异步运行的,这意味着在处理目录中有更多文件的情况下,它不会始终正常工作。 - Jeremy John
你不应该通过黑客手段来完成文件名的操作。这样做会导致未来的更新和升级变得混乱无序。虽然这看起来是一个简单的解决方案,但实际上你正在为未来的自己设置一个陷阱。 - Luciano Bargmann

5

我将我的脚本按照从Bower中获取的每个包以及我的应用程序自己的脚本放在不同的文件夹中进行组织。既然您要在某个地方列出这些脚本的顺序,为什么不在gulp文件中直接列出它们呢?对于您项目上的新开发人员来说,所有脚本的端点都在此处列出非常好。您可以使用gulp-add-src实现此功能:

gulpfile.js

var gulp = require('gulp'),
    less = require('gulp-less'),
    minifyCSS = require('gulp-minify-css'),
    uglify = require('gulp-uglify'),
    concat = require('gulp-concat'),
    addsrc = require('gulp-add-src'),
    sourcemaps = require('gulp-sourcemaps');

// CSS & Less
gulp.task('css', function(){
    gulp.src('less/all.less')
        .pipe(sourcemaps.init())
        .pipe(less())
        .pipe(minifyCSS())
        .pipe(sourcemaps.write('source-maps'))
        .pipe(gulp.dest('public/css'));
});

// JS
gulp.task('js', function() {
    gulp.src('resources/assets/bower/jquery/dist/jquery.js')
    .pipe(addsrc.append('resources/assets/bower/bootstrap/dist/js/bootstrap.js'))
    .pipe(addsrc.append('resources/assets/bower/blahblah/dist/js/blah.js'))
    .pipe(addsrc.append('resources/assets/js/my-script.js'))
    .pipe(sourcemaps.init())
    .pipe(concat('all.js'))
    .pipe(uglify())
    .pipe(sourcemaps.write('source-maps'))
    .pipe(gulp.dest('public/js'));
});

gulp.task('default',['css','js']);

注意: 为了演示订单,添加了jQuery和Bootstrap。由于它们被广泛使用且浏览器可能已经从其他站点缓存了它们,因此最好使用CDN。


3

merge2 看起来是目前唯一工作正常且得到维护的有序流合并工具。

2020年更新

接口总是在变化,一些库变得无法使用或包含漏洞,或者它们的依赖项包含多年未修复的漏洞。对于文本文件操作,最好使用自定义的NodeJS脚本和流行的库,如 globbyfs-extra 以及其他不带Gulp、Grunt等包装器的库。

import globby from 'globby';
import fs from 'fs-extra';

async function bundleScripts() {
    const rootPaths = await globby('./source/js/*.js');
    const otherPaths = (await globby('./source/**/*.js'))
        .filter(f => !rootFiles.includes(f));
    const paths = rootPaths.concat(otherPaths);

    const files = Promise.all(
        paths.map(
            // Returns a Promise
            path => fs.readFile(path, {encoding: 'utf8'})
        )
    );

    let bundle = files.join('\n');
    bundle = uglify(bundle);
    bundle = whatever(bundle);
    bundle = bundle.replace(/\/\*.*?\*\//g, '');

    await fs.outputFile('./build/js/script.js', bundle, {encoding: 'utf8'});
}

bundleScripts.then(() => console.log('done');

3
尝试使用stream-series。它的工作方式类似于merge-stream/event-stream.merge(), 但是它不会交错,而是将内容追加到末尾。与streamqueue不同,它不需要你指定对象模式,因此代码更简洁。
var series = require('stream-series');

gulp.task('minifyInOrder', function() {
    return series(gulp.src('vendor/*'),gulp.src('extra'),gulp.src('house/*'))
        .pipe(concat('a.js'))
        .pipe(uglify())
        .pipe(gulp.dest('dest'))
});

1

另一种方法是使用专门为此问题创建的Gulp插件。https://www.npmjs.com/package/gulp-ng-module-sort

它允许您通过添加.pipe(ngModuleSort())来对脚本进行排序,如下所示:

var ngModuleSort = require('gulp-ng-module-sort');
var concat = require('gulp-concat');

gulp.task('angular-scripts', function() {
    return gulp.src('./src/app/**/*.js')
        .pipe(ngModuleSort())
        .pipe(concat('angularAppScripts.js))
        .pipe(gulp.dest('./dist/));
});

假设一个目录约定为:
|——— src/
|   |——— app/
|       |——— module1/
|           |——— sub-module1/
|               |——— sub-module1.js
|           |——— module1.js
|       |——— module2/
|           |——— sub-module2/
|               |——— sub-module2.js
|           |——— sub-module3/
|               |——— sub-module3.js
|           |——— module2.js
|   |——— app.js

希望这有所帮助!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接