总结一下写grunt脚本的思路

今天终于写个接近最终版的脚本,简单总结一下思路。
先确定我都要干什么,总结如下:

【cssmin】压缩css

【uglify】压缩js

【cssmin】合并css到style.css

【uglify】合并js库/框架文件到lib.js

【uglify】合并自定义js到script.js

【copy】复制需要处理的html到目标地点(单页面项目只涉及到一个文件)

【usemin】然后处理目标文件

【filerev】处理目标文件版本号问题(对合并后的制定文件,现在只写了js和css)

 

弄清思路后一步步操作,这个脚本总算是正常工作了。

 

和一些说明有所不同的是,我这里没有用concat、jshint、clean。

concat是被cssmin和uglify的功能替代掉了,不写也可以合并。

jshint没用主要是因为对自己代码不是特别有自信,用了以后感觉要花好多时间调试…

clean是真心用不上。

 

参考文章:

http://ericnishio.com/blog/compile-less-files-with-grunt
http://www.xuanfengge.com/npm-installation-did-not-succeed-and-github-will-not-open-solutions.html
https://www.npmjs.org/package/grunt-html-build
https://www.npmjs.org/package/grunt-contrib-concat
https://www.npmjs.org/package/grunt-contrib-less
https://www.npmjs.org/package/grunt-contrib-watch

 

grunt-usemin(中文文档)

grunt-usemin的作用

usemin用来生成优化后(合并,压缩)的脚本,然后以此替换html文件里面的javascript, css和其他脚本的引用

使用方式

npm install grunt-usemin --save-dev

任务

usemin通过两个内置的任务来完成上述的功能,分别是useminPrepare和usemin,同时还需要其它一些优化工具(cssmin,concat等)来完成优化工作。usemin通过动态的为这些优化工具插件生成子任务的方式来实现整个的目标。

usemin的两个内置的任务

  • useminPrepare 准备配置文件。配置文件是根据结构化的文件(如html)里面的块声明来生成的。最终把这些应用替换成优化后的文件引用。在这个过程中,为每个优化的步骤生成了很多的名为generated的子任务,这些优化的步骤每步都是一个grunt插件,下面将会列举出来。
  • usemin 把结构化文件(html)的块声明里面的文件引用替换。如果那些脚本文件有打过版本声明的,将会用版本声明的文件应用来替换。这个个过程会直接修改结构化文件(如html)的内容。

你必须手动的install和load构建过程中需要的依赖插件 基本例子:

grunt.registerTask('build', [
    'useminPrepare',
    'concat:generated',
    'cssmin:generated',
    'uglify:generated',
    'filerev',
    'usemin'
    ]);

useminPrepare任务

useminPrepare 在之前配置基础上,生成新的配置文件,在这个新的配置文件里定义了文件整个处理过程。默认情况下js文件会做concat,uglify处理,css文件会做concat,cssmin处理。

结构化文件(html)的块声明

<!-- build:<type>(alternate search path) <path> -->
    ... HTML Markup, list of script / link tags.
    <!-- endbuild -->
  • type 文件类型 可以是css,js,或者其他自定义类型(后面会讲到)。如果都不是构建脚本会忽略。这个引用块只会在开发环境出现,最后的build成功后不会出现。
  • alternate search path (可选的)默认情况下那些脚本文件的查找都是相对当前处理的文件的。
  • path 优化后文件的输出目录。
<!-- build:js js/app.js -->
    <script src="js/app.js"></script>
    <script src="js/controllers/thing-controller.js"></script>
    <script src="js/models/thing-model.js"></script>
    <script src="js/views/thing-view.js"></script>
    <!-- endbuild -->

处理流程

整个处理流程分很多步骤:每个步骤处理完后,useminPrepare就会修改配置文件,确保整个流程正确的执行。

默认的处理流程是合并后压缩。除此之后,在合并压缩之后可以定义一个后置的处理函数来做一个额外的工作,来修改配置文件。

下面看一个例子,使用了默认的处理流程。

下面是block声明
<!-- build:js js/app.js -->
<script src="js/app.js"></script>
<script src="js/controllers/thing-controller.js"></script>
<script src="js/models/thing-model.js"></script>
<script src="js/views/thing-view.js"></script>
<!-- endbuild -->

处理后的生成的配置文件如下:

{
    concat:
    generated: {
    files: [
    {
    dest: '.tmp/concat/js/app.js',
    src: [
    'app/js/app.js',
    'app/js/controllers/thing-controller.js',
    'app/js/models/thing-model.js',
    'app/js/views/thing-view.js'
    ]
    }
    ]
    }
    },
    uglify: {
    generated: {
    files: [
    {
    dest: 'dist/js/app.js',
    src: [ '.tmp/concat/js/app.js' ]
    }
    ]
    }
    }
    }

指南

默认情况下usemin会把当前处理的文件作为‘根文件系统’,所有其他的相对路径,都是参照此路径。这个方式也同样适用绝对路径。如果需要改变‘根文件系统’,你需要在option中从定义root属性(下面会提到)

concat/cssmin/uglify这些任务的配置是useminPrepare生成的,没有必要专门为这些任务定义配置文件了。

options

dest string类型,默认为null 处理后的文件的基准输出路径。 staging string类型,默认.tmp 临时文件的输出路径(上面的例子提到过) root string|array类型,默认null 处理文件路径时的参考的’根文件系统’ flow object类型 默认{ steps: { js: [‘concat’, ‘uglifyjs’], css: [‘concat’, ‘cssmin’] }, post: {} } 通这个选项能在每个目标之前或者所有目标之前配置处理流程。你也能单独的配置steps属性或者post属性。

例如,为html目标改变js文件的处理流程:

useminPrepare: {
    html: 'index.html',
    options: {
    flow: {
    html: {
    steps: {
    js: ['uglifyjs']
    },
    post: {}
    }
    }
    }
    }

为全部目标改变js文件的处理流程:

useminPrepare: {
    html: 'index.html',
    options: {
    flow: {
    steps: {
    js: ['uglifyjs']
    },
    post: {}
    }
    }
    }

通过post属性,自定义处理配置文件

useminPrepare: {
    html: 'index.html',
    options: {
    flow: {
    steps: {
    js: ['uglifyjs']
    },
    post: {
    js: [{
    name: 'uglifyjs',
    createConfig: function (context, block) {
    var generated = context.options.generated;
    generated.options = {
    foo: 'bar'
    };
    }
    }]
    }
    }
    }
    }

自定义处理步骤和后置处理器

自定义处理步骤和后置处理器有两个属性 + name string类型 在name指定相应的处理步骤上生效 + createConfig 函数 有两个参数 context block 返回配置对象

context conetxt对象包含了当前step/post-processor运行的一些信息,作为主力流程中的一步,必须要有处理输入的相关目录和文件的配置,同时也必须有输出相关的文件和目录。

context属性: + inDir 输入文件的目录 + inFiles 输入文件 + outDir 输出文件的目录 + outFiles 输出文件 + last 是否是处理步骤的最后一步 + options 此处理步骤的一些可选项

block 例子说明:

<!-- build:js scripts/site.js -->
    <script src="foo.js"></script>
    <script src="bar.js"></script>
    <script src="baz.js"></script>
    <!-- endbuild -->

被解析为block对象如下:

var block = {
    type: 'js',
    dest: 'scripts/site.js',
    src: [
    'foo.js',
    'bar.js',
    'baz.js'
    ],
    raw: [
    '    <!-- build:js scripts/site.js -->',
    '    <script src="foo.js"></script>',
    '    <script src="bar.js"></script>',
    '    <script src="baz.js"></script>',
    '    <!-- endbuild -->'
    ]
    };

usemin任务

usemin任务会做两件事 + 首先会把block替换为一行,这个引用指向之前流程生成的优化后的脚本文件。 + 之后如果找到打过版本的文件,就会用打过版本的文件引用替换当前的引用。

脚本文件的搜索

默认情况下,usemin会使用一个grunt-filerev插件map对象, 这个对象是grunt.filerev.summary,如果没找到就会到磁盘去检索,这样会需要更多的时间。

使用options.revmap会提供一个地图对象,供usemin任务查找

说明

当usemin尝试使用打过版本的文件替换当前引用(也就是之前提到的第2步操作时),就会收集脚本的所有搜索目录。最终生成一个目录的树,并尝试找到打过版本的文件。搜索目录默认是结构化文件(html)的目录,可以通过options重新指定。

例子1: dist/html/index.html有如下内容

<link rel="stylesheet" href="styles/main.css">
    <img src="../images/test.png">

默认情况下就会在dist/html/下搜索打过版本的文件。 + style/main.css将会在dist/html/styles搜索那么版本文件,例如:dist/html/style/main.1234.css就匹配,最终会替换。 + images/test.png将会在 dist/iamges搜索。

换成如下内容

<link rel="stylesheet" href="/styles/main.css">
    <img src="/images/test.png">

搜索的路径仍然不变是上面的dist/html/stylesdist/html/images,现在假如我们的脚本是在另一个目录dist/assets下,我们可以配置options,使得最终的搜索路径在dist/assets下面,例如可能就会有这样的结果:dist/assets/images/test.875487.pngdist/assets/styles/main.98090.css

options

assetsDirs array 类型 脚本搜索的目录 patterns object 类型 用户自定义替换。加入要把js文件里面的所有image.png替换为打过版本后的文件,就可以采用如下定义方式:

usemin: {
    js: '*.js',
    options: {
    assetsDirs: 'images',
    patterns: {
    js: [
    [/(image\.png)/, 'Replacing reference to image.png']
    ]
    }
    }
    }

patterns的key必须出现目标targets中,每个pattern有四个参数,最后两个是可选的,第一个参数表示那些引用是要替换的blockReplacements object 类型 默认值{ css: function (block) { … }, js: function (block) { … } } 由此可以控制替换的具体细节,返回值为最终替换的字符串。 例如:

usemin: {
    html: index.html,
    options: {
    blockReplacements: {
    less: function (block) {
    return '<link rel="stylesheet" href="' + block.dest + '">';
    }
    }
    }
    }
    //less必须和block的type相匹配.

revmap object 类型 声明版本的文件匹配,之后在版本文件的查找替换时是一个依据。 例如:

{
    "foo.png": "foo.1234.png"
    }

小结

useminPrepare任务必须声明输入,临时目录和输出路径。最终输出正确的配置文件,确保正确的文件处理流程。usemin任务,只是处理最终的输出文件。所有的脚本文件必须输出到最后的输出目录中。

useminPrepare 为整个构建过程,生成正确的配置文件,确保每步流程的处理顺序和期望的一致。在block声明的文件,有的是绝对路径,有的是相对路径,路径的文件引用是在搜索路径下(input)查找, 默认情况下就是正在文件本身所在的路径。

usemin 把images,scripts,css…的这些引用,替换为打过版本的文件引用。

例子

app
    |
    +- assets
    |  +- js
    |     +- index.js
    |     +- rank.js
    |  +- css
    |     +- layout.css
    +- index.html
    +- dist

index.html的内容

<!-- build:css(.) /assets/css/layout.min.css -->
    <link rel="stylesheet" href="/assets/css/layout.css"/>
    <!-- endbuild-->
    <!--build:js(.) /assets/js/app.min.js -->
    <script type="text/javascript" src="/assets/js/rank.js"></script>
    <script type="text/javascript" src="/assets/js/index.js"></script>
    <!-- endbuild -->

Gruntfile.js

/**
    * Created by Administrator on 2014/7/15.
    */
    module.exports = function(grunt){
    grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    clean: {
    build: ['.tmp/'],
    release: ['dist/']
    },
    jshint: {
    all: ['assets/**/*.js']
    },
    copy: {
    html: {
    files: [
    {src: ["index.html"], dest: "dist/"}
    ]
    }
    },
    useminPrepare: {
    html: ['dist/index.html']
    },
    filerev: {
    options: {
    length: 8
    },
    generated: {
    files:[
    {src: "dist/assets/**/*.min.js"},
    {src: "dist/assets/**/*.min.css"}
    ]
    }
    },
    usemin: {
    options: {
    assetsDirs: ['dist/']
    },
    html: 'dist/index.html'
    }
    });
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-usemin');
    grunt.loadNpmTasks('grunt-filerev');
    grunt.registerTask('build', [
    'clean',
    'jshint',
    'copy',
    'useminPrepare',
    'concat:generated',
    'cssmin:generated',
    'uglify:generated',
    'filerev',
    'usemin',
    'clean:build'
    ]);
    }