BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

环境特定的构建工具:Grunt、Gulp和Broccoli

| 作者 孙镜涛 关注 2 他的粉丝 发布于 2014年3月14日. 估计阅读时间: 15 分钟 | ArchSummit北京2018 共同探讨机器学习、信息安全、微服务治理的关键点

项目的开发、分期和产品版本之间可能会有巨大的改变,这是我们需要基于环境以及特定目标信息改变资源(脚本、样式、模板)、生成标记或者其他内容路径的原因之一。很幸运的是,在Grunt、Gulp和Broccolli生态系统中已经有很多构建工具能够帮助我们完成这些工作。不久之前来自于Google Chrome开发者关系团队的工程师Addy Osmani对解决该问题的三种方式(字符串替代、条件注释和模板变量)做了对比

最简单的选项—字符串/正则表达式替换

对于环境特定的输出,最简单的选项是使用字符串替换,通过一个简单的字符串或者正则表达式找出并移除/替代HTML文件中的块。构建文件中可能会有很多目标设置,其中的每一个都能够用任意内容替换字符串。

例如,在Grunt中使用grunt-string-replace的一个非常基础的例子可能是在prod构建的时候将字符串source.js替换为build.js,如下所示:

module.exports = function(grunt) {
  grunt.initConfig({
    'string-replace': {
      prod: {
        src: './app/**/*.html',
        dest: './dist/',
        options: {
          replacements: [{
            pattern: 'source.js',
            replacement: 'build.js'
          }]
        }
      }
    }
  });
  grunt.loadNpmTasks('grunt-string-replace');
  grunt.registerTask('default', ['string-replace']);
};

grunt-replace 也能很好的满足这种场景)

一般情况下,文本替换的方式需要少量配置,像Stephen Sawchuk(Yeoman和Bower的一位贡献者,他认为对于自己的设置而言其他的替代方案过于复杂)这样的开发人员会使用这种方式。从这个gist中你可以找到他在项目中是如何使用字符串替换的。

如果你并没有使用Grunt,那么等价方案还有gulp-replacebroccoli-replace

对于简单的场景而言,字符串替换能够很好地完成工作,例如需要更新或者删除多个文件中的一组字符串。但是如果你有大量需要替换的内容,同时需要在源文件和构建文件之间来回跳转以便于算出到底需要替换哪些内容时,这种方式就会变得难以维护。

对于这种场景更好的解决方案是在源文件中就地定义替代品。也就是下面的选项。

条件注释

环境条件注释的基本思想是:在构建文件中通过最少的配置、使用HTML注释语法定义将一个字符串替代为目标内容所需要的逻辑和信息(例如路径)。这种方案的可读性比较好,同时也能够很容易地在每一个文件中实现。

下面是一些可选方案:

Grunt:

Gulp:

该列表前三个选项的注释语法在可读性和冗余度方面有所差异,但是它们都能有效地实现同样的结果。所有的选项都能够毫不费力地输出目标/环境特定的块,首先让我们来比较一下targethtml和preprocess:

一个grunt-targethtml 示例 (Dev vs. Production)

Dev:
<!--(if target dev)><!-->
 <link rel="stylesheet" href="dev.css" />
<!--<!(endif)-->
<!--(if target dev)><!-->
  <script src="dev.js"></script>
  <script>
    var less = { env:'development' };
  </script>
<!--<!(endif)-->
Production:
<!--(if target prod)><!-->
  <link rel="stylesheet" href="release.css">
<!--<!(endif)-->
<!--(if target prod)><!-->
   <script src="release.js"></script>
<!--<!(endif)-->

同样的grunt-processhtml

Dev:
<!-- @if NODE_ENV='production' -->
 <link rel="stylesheet" href="dev.css">
<!-- @endif -->
<!-- @if NODE_ENV='production' -->
<script src="dev.js"></script>
<script>
  var less = { env:'development' };
</script>
<!-- @endif -->

Production:
<!-- @if NODE_ENV='production' -->
<link rel="stylesheet" href="release.css">
<!-- @endif -->
<!-- @if NODE_ENV='production' -->
<script src="release.js"></script>
<!-- @endif -->

正如我们所看到的,按照注释的形式指定逻辑相当直接,这种方式已经被大量地使用,并没有太多的抱怨。

使用条件注释的一个主要缺点是:标记文件中可能会包含大量与构建相关的逻辑。虽然有一些开发人员不喜欢这样,但是这些任务提供了可读性流。这也意味着我们没必要跳回Gruntfile中查看它到底正在干什么。

之前列出了3个Grunt任务,那么grunt-processhtml怎么样呢?它有一点专业,你可以使用模板变量以及其他的一些不错的技巧包含完全不同的文件。

例如,在下面的例子中只有当“dist”目标运行的时候才会将class修改为“production”:

<!-- build:[class]:dist production -->
<html class="debug_mode">
<!-- /build -->

或者使用任意一个文件替换目标中的一个块:

<!-- build:include:dev dev/content.html -->
This will be replaced by the content of dev/content.html
<!-- /build -->

或者根据目标删除一个块:

<!-- build:remove -->
<p>This will be removed when any processhtml target is done</p>
<!-- /build -->
<!-- build:remove:dist -->
<p>But this one only when doing processhtml:dist target</p>
<!-- /build -->

如果确定需要配置代码注释块格式的能力,那么可以查看 grunt-devcode ,它支持这些。下面是一些示例配置:

Gruntfile:
Devcode:
    {
      options :
      {
        html: true,        // html files parsing?
        js: true,          // javascript files parsing?
        css: true,         // css files parsing?
        clean: true,
        block: {
          open: 'condition', // open code block
          close: 'conditionend' // close code block
        },
        dest: 'dist'
      },
Markup:
<!-- condition: !production -->
  <li class="right">
    <a href="#settings" data-toggle="tab">
      Settings
    </a>
  </li>
<!-- conditionend -->

如果你想利用占位符的方式实现,那么可以借助于模板变量。

HTML中的模板变量

构建时模板允许我们将源文件中定义的占位符字符串(例如<%- title %>)替换为构建文件(例如Gruntfile)中定义的与特定目标相关的数据(字符串、对象等任何你需要的内容)。与条件注释相比,使用这种方式时你注入的数据并不是在目标文件本身中指定的。这种类型的选项有:

Grunt:

Gulp:

grunt-template基本上是对grunt.template.process()的包装。同时它主要针对的是基于目标的目标字符串操作,你可以通过data 对象根据dev/prod目标改变路径或者内容。下面就是一个这样的例子:

index.html.tpl

Gruntfile.js
module.exports = function(grunt) {
    grunt.initConfig({
        'template': {
            'dev': {
                'options': {
                    'data': {
                        'title': 'I <3 JS in dev'
                    }
                },
                'files': {
                    'dev/index.html': ['src/index.html.tpl']
                }
            },
            'prod': {
                'options': {
                    'data': {
                        'title': 'I <3 JS in prod'
                    }
                },
                'files': {
                    'dist/index.html': ['src/index.html.tpl']
                }
            }
        }
    });
    grunt.loadNpmTasks('grunt-template');
    grunt.registerTask('default', [
        'template:prod'
    ]);
    grunt.registerTask('dev', [
        'template:dev'
    ]);
};

grunt-include-replace 做的事情比较相似,只是模板字符串的格式不同(<title>@@title</title>)。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

test by Shui Arthur

test

刚好我也才写了一个简单对比的文章 by 胡 波

可以看看 这里

这是里源码

推荐使用gulp,grunt复杂任务之后的代码真的很难看

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

2 讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT