2014-04-10

Grunt.js 入門講義

●● Grunt 入門   林新德 編 ( shinder.lin@gmail.com ) 2014.4 

Grunt ( http://gruntjs.com/ )
名稱來自於 grunt work (單調的工作),它的功能是使用設定來執行常做的動作,例如最小化 JavaScript CSS 檔等。


Grunt 0.3 版時提供常用的功能,設定檔使用 grunt.js。到 0.4 版時做了些調整,使用外掛的架構,設定檔為 Gruntfile.js


本講義以 Windows 7 為作業系統為例,各軟體的安裝皆在 Windows 7 底下安裝,請注意使用的軟體版本,例如 NodeJS 32-bit 64-bit 之分。
Windows Command Prompt 使用 PowerShell
範例專案 shinder_maze.zip shinder_circle_menu.zip  (** blog 不提供專案包 zip 檔 )

Web server,使用 Python3 內建的簡易 web server ( 非必要 )
Command prompt,到專案目錄使用下式啟動 server
> python -m http.server [埠號]
使用「Ctrl + C」停止 server

在安裝 Grunt 之前需先安裝 Git NodeJS

1. NodeJS ( http://nodejs.org/ )
直接從官網按「 INSTALL 」,下載 msi 檔並安裝。

2. Git
安裝 Git for Windows ( http://git-scm.com/download/win )
安裝過程中請勾選「Run Git from the Windows Command Prompt


安裝 grunt-cli
> npm install -g grunt-cli


安裝 Grunt 起始功能(非必要)
> npm install -g grunt-init

安裝 Grunt 起始功能的樣版 ( grunt-init-XXX templates )
官方維護的 templates ( http://gruntjs.com/project-scaffolding#installing-templates )
以安裝 gruntplugin template 為例:
> git clone git://github.com/gruntjs/grunt-init-gruntplugin.git C:\Users\shinder\.grunt-init\gruntplugin
> git clone git://github.com/gruntjs/grunt-init-gruntplugin.git $env:home\.grunt-init\gruntplugin


建立新目錄成為專案目錄,手動的話自行建立 package.json Gruntfile.js

使用 Grunt 起始功能 ( 在專案目錄 )
> grunt-init gruntplugin

在專案目錄安裝 Grunt 外掛 ( 產生 package.json Gruntfile.js 之後 )
下式命令列將會安裝 package.json 裡所設定的外掛
> npm install


手動安裝 Grunt 外掛
> npm install grunt --save-dev
> npm install grunt-contrib-clean --save-dev
> npm install grunt-contrib-jshint --save-dev
> npm install grunt-contrib-concat --save-dev

> npm install grunt-contrib-watch --save-dev
> npm install grunt-contrib-uglify --save-dev
> npm install grunt-contrib-cssmin --save-dev
> npm install grunt-contrib-htmlmin --save-dev
> npm install grunt-contrib-connect --save-dev

> npm install grunt-usemin --save-dev


Gruntfile.js 的架構
1. The "wrapper" function
2. Project and task configuration
3. Loading Grunt plugins and tasks
4. Custom tasks
module.exports = function (grunt) {
    grunt.initConfig({
        cssmin: {
            combine: {
                files: {
                    'dist/css/style.css': ['src/css/normalize.css','src/css/main.css']
                }
            }
        },
        uglify: {
            jssrcTask: {
                files: {
                    'dist/js/main.min.js': 'src/js/*.js'
                }
            }
        }
    });
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-cssmin');

    grunt.registerTask('default', ['cssmin', 'uglify']);
};


外掛原則上可以分成兩類:開發過程的工具、發佈的工具
發佈的工具,以下將介紹:
  grunt-contrib-concat
  grunt-contrib-uglify
  grunt-contrib-cssmin
  grunt-contrib-copy
  grunt-usemin
開發過程的工具,以下將介紹:
  grunt-contrib-jshint
  grunt-contrib-connect
  grunt-contrib-watch
  

grunt-contrib-concat 用法範例:
concat: {
    jslibTask: {
        src: ['src/assets/lib/*.js'],
        dest: 'dist/assets/lib/lib.js'
    }
}

concat: {
    basic_and_extras: {
        files: {
            'dist/basic.js': ['src/main.js'],
            'dist/with_extras.js': ['src/main.js', 'src/extras.js']
        }
    }
}


grunt-contrib-uglify 用法範例:
uglify: {
    jssrcTask: {
        files: {
            'dist/assets/js/main.min.js': 'src/assets/js/*.js',
            'dest/output.min.js': ['src/input1.js', 'src/input2.js']
        }
    }
}

uglify: {
    options: {
        mangle: {
            except: ['jQuery', 'Backbone']
        }
    },
    my_target: {
        files: {
            'dest/output.min.js': ['src/input.js']
        }
    }
}


grunt-contrib-cssmin 用法範例:
cssmin: {
    combine: {
        files: {
            'dist/css/style.css': ['src/css/normalize.css','src/css/main.css']
        }
    }
}

cssmin: {
    add_banner: {
        options: {
            banner: '/* My minified css file */'
        },
        files: {
            'path/to/output.css': ['path/to/**/*.css']
        }
    }
}


grunt-contrib-copy 用法範例:
copy: {
    task0: {
        files: [{
            src: 'src/shadow_maze.htm',
            dest: 'dist/shadow_maze.htm'
        }]
    }
}

copy: {
    main: {
        expand: true,
        cwd: 'src/',  // 相對於 cwd 目錄, current working directory
        src: '**',
        dest: 'dest/',
        flatten: true,
        filter: 'isFile'
    }
}


grunt-usemin 用法範例:


1. 修改 HTML
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta id="viewport" name="viewport" content="">
<meta name="author" content="Shinder Lin">
<title>Shinder studio : Shadow Maze</title>

<!-- build:css assets/css/style.css -->
<link rel="stylesheet" type="text/css" href="assets/css/normalize.css" />
<link rel="stylesheet" type="text/css" href="assets/css/main.css" />
<!-- endbuild -->

</head>
<body>
<div id="wrapper">
<div id="container"></div>
</div>

<!-- build:js assets/lib/lib.js -->
<script type="text/javascript" src="assets/lib/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="assets/lib/detectmobilebrowser.js"></script>
<script type="text/javascript" src="assets/lib/kinetic-v4.7.4.min.js"></script>
<script type="text/javascript" src="assets/lib/qrcode.min.js"></script>
<!-- endbuild -->

<!-- build:js assets/js/main.min.js -->
<script type="text/javascript" src="assets/js/shinder.classes.js"></script>
<script type="text/javascript" src="assets/js/shinder.main.js"></script>
<!-- endbuild -->

</body>
</html>

2. Gruntfile.js
module.exports = function (grunt) {
    grunt.initConfig({
        copy: {
            task0: {
                files: [
                    { src: 'src/shadow_maze.htm',
                        dest: 'dist/shadow_maze.htm'}
                ]
            }
        },
        useminPrepare: {
            html: 'src/shadow_maze.htm',
            options: {
                dest: 'dist'
            }
        },
        usemin: {
            html: 'dist/shadow_maze.htm'
        }
    });
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-usemin');
    grunt.registerTask('default', [
        'copy',
        'useminPrepare',
        'usemin'
    ]);
};

3. pre-run 測試

4. 複製圖片目錄,並將 useminPrepare 所需的工作加入。
module.exports = function (grunt) {
    grunt.initConfig({
        copy: {
            task0: {
                files: [
                    { src: 'src/shadow_maze.htm',
                        dest: 'dist/shadow_maze.htm'},
                    { expand: true,
                        cwd: 'src/',
                        src: ['assets/images/**'],
                        dest: 'dist'}
                ]
            }
        },
        useminPrepare: {
            html: 'src/shadow_maze.htm',
            options: {
                dest: 'dist'
            }
        },
        usemin: {
            html: 'dist/shadow_maze.htm'
        }
    });
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-usemin');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.registerTask('default', [
        'copy',
        'useminPrepare',
        'concat',
        'uglify',
        'cssmin',
        'usemin'
    ]);
};


● ●
grunt-contrib-jshint 用法範例: ( 參考 http://www.jshint.com/docs/options/ )
jshint: {
    options: {
        curly: true,  // if, for 單一 statement 也要有大括號包裹
        eqeqeq: true,  // 使用 === 和 !==
        immed: true,  // 暱名函式即時呼叫檢查, 需 ( ) 包裹
        latedef: true,  // 變數在宣告之前不能使用
        newcap: true,  // 建構函式的第一個字母需大寫
        noarg: true,  // 禁用 arguments.caller 和 arguments.callee
        sub: true,  // 允許使用 obj['name'] 中括號表示法 (不顯示警示)
        undef: true,  // 禁用未宣告的變數
        unused: true,  // 提示沒有使用的變數
        boss: true,  // 條件式可以使用設定 =
        eqnull: true,  // 允許使用 ==null
        browser: true  // 在瀏覽器底下使用 (可以使用瀏覽器裡的預設物件)
    },
    jsTest: {
        src: ['src/assets/js/*.js']
    }
}


grunt-contrib-connect 用法範例:
connect: {
    server: {
        options: {
            port: 9001,
            base: 'src',
            keepalive: true
        }
    }
}

使用「Ctrl + C」停止 server

使用多個伺服器時,應使用多個 command prompt 視窗執行。
connect: {
    site1: {
        options: {
            port: 9002,
            base: 'src',
            keepalive: true
        }
    },
    site2: {
        options: {
            port: 9003,
            base: 'dist',
            keepalive: true
        }
    }
}


grunt-contrib-watch 用法範例:
watch: {
    scripts: {
        files: ['src/assets/js/*.js'],
        tasks: ['jshint']
    }
}

watch: {
    gruntfile: {
        files: 'Gruntfile.js',
        tasks: ['jshint:gruntfile'],
    },
    src: {
        files: ['lib/*.js', 'css/**/*.scss', '!lib/dontwatch.js'],
        tasks: ['default'],
    },
    test: {
        files: '<%= jshint.test.src %>',
        tasks: ['jshint:test', 'qunit'],
    },
}

使用 grunt-contrib-watch LiveReload
watch: {
    options: {
        livereload: true
    },
    scripts: {
        files: ['src/js/main.js', 'src/css/style.css'],
        tasks: ['jshint']
    }
}

HTML 檔掛 livereload.js
<script src="http://localhost:35729/livereload.js"></script>

也可以使用瀏覽器的外掛,但使用 livereload.js 比較不會發生連接出狀況。

沒有留言:

FB 留言