使用 Gulp 来实现 Laravel 4 的 Assets 管理

The Problem

需要有一个自动化工具来管理 Assets (css,js,img etc..) , 具备以下功能

  • 支持编译 scss, coffee script, less 等语言;
  • 合并文件的能力, 把页面上所有的 css 和 js 各自合并为一个文件, 将极大提高网页的加载速度;
  • 压缩 css 和 js 的能力, 减少文件大小, 注意这里的压缩不只是去除空格和换行;
  • 支持 CDN 加速, 需要对每次修改的文件加版本号, 这是最有效的, 也是最合理的客户端样式更新方法;
  • 开发要足够简单;
  • 部署要足够简单;

有两个工具作为候选, 一个是 Grunt, 一个是 Gulp, 最后选择 Gulp , 因为其易用性, 并且是个新奇玩意, 此项目在 web 开发的开源世界中非常受欢迎, 虽然出现的时间只有短短的一年 (第一个提交 contra authored on Jul 4, 2013).

安装和配置 gulp.js

gulp.js 依赖于 nodejs, 如果你使用 Homestead 作为开发环境的话, 这些东西已经都装好了, 如果你还没有安装 Homestead 请看这里 Laravel 4 的 Homestead 开发环境部署 .

配置 package.json

在项目根目录下, 创建 package.json 并填入以下内容:

{
  "devDependencies": {
    "del": "^0.1.2",
    "gulp": "^3.8.7",
    "gulp-asset-manifest": "0.0.4",
    "gulp-autoprefixer": "0.0.10",
    "gulp-concat": "^2.3.4",
    "gulp-minify-css": "^0.3.7",
    "gulp-rev": "^1.1.0",
    "gulp-sass": "^0.7.3",
    "gulp-uglify": "^1.0.0"
  }
}

关于里面每一个 package 的说明见以下注释:

{
  "devDependencies": {
    "del": "^0.1.2",            // 删除文件
    "gulp": "^3.8.7",           // 主程序
    "gulp-asset-manifest": "0.0.4", // 压缩 css 
    "gulp-autoprefixer": "0.0.10",  // 自动给 css3 属性加浏览器前缀, 如: `-webkit-`
    "gulp-concat": "^2.3.4",        // 文件合并
    "gulp-minify-css": "^0.3.7",    // // 压缩 css 
    "gulp-rev": "^1.1.0",           // 给文件加版本号, 如 `script-$version$.js`
    "gulp-sass": "^0.7.3",      // 编译 scss
    "gulp-uglify": "^1.0.0"     // 压缩 js
  }
}

保存后在根目录下运行以下命令下载 package

npm install

Gulpfile.js

在根目录下创建文件 Gulpfile.js , 填入以下内容:

var gulp = require('gulp');
var sass = require('gulp-sass');
var autoprefixer = require('gulp-autoprefixer');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');
var rev = require('gulp-rev');
var del = require('del');
var filename = require('gulp-asset-manifest');
var minifycss = require('gulp-minify-css');

// Paths to your asset files
var paths = {
    frontend: {
        scripts: [
            'app/assets/js/jquery.min.js',
            'app/assets/js/bootstrap.min.js',
            'app/assets/js/moment.min.js',
            'app/assets/js/zh-cn.min.js',
            'app/assets/js/emojify.min.js',
            'app/assets/js/jquery.scrollUp.js',
            'app/assets/js/jquery.pjax.js',
            'app/assets/js/nprogress.js',
            'app/assets/js/jquery.autosize.min.js',
            'app/assets/js/prism.js',
            'app/assets/js/jquery.textcomplete.js',
            'app/assets/js/emoji.js',
            'app/assets/js/ekko-lightbox.js',
            'app/assets/js/main.js',
        ],
        styles: [
            'app/assets/css/bootstrap.min.css',
            'app/assets/css/font-awesome.min.css',
            'app/assets/css/main.css',
            'app/assets/css/markdown.css',
            'app/assets/css/nprogress.css',
            'app/assets/css/prism.css',
        ]
    }
}

// CSS task
gulp.task('css', function() {

    // Convert scss first
    gulp.src('app/assets/sass/**/*.scss')
        .pipe(sass())
        .pipe(autoprefixer('last 10 version'))
        .pipe(gulp.dest('app/assets/css'));

    // Cleanup old assets
    del(['public/assets/css/styles-*.css'], function (err) {});

    // Prefix, compress and concat the CSS assets
    // Afterwards add the MD5 hash to the filename
    gulp.src(paths.frontend.styles)
        .pipe(concat('styles.css'))
        .pipe(rev())
        .pipe(filename({ bundleName: 'frontend.styles' })) // This will create/update the assets.json file
        .pipe(minifycss())
        .pipe(gulp.dest('public/assets/css'));
});

// JavaScript task
gulp.task('js', function() {
    // Cleanup old assets
    del(['public/assets/js/scripts-*.js'], function (err) {});

    // Concat and uglify the JavaScript assets
    // Afterwards add the MD5 hash to the filename
    gulp.src(paths.frontend.scripts)
        .pipe(concat('scripts.js'))
        .pipe(uglify())
        .pipe(rev())
        .pipe(filename({ bundleName: 'frontend.scripts' })) // This will create/update the assets.json file
        .pipe(gulp.dest('public/assets/js'));
});

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

gulp.task('watch', function(){
    gulp.watch('app/assets/sass/**/*.scss', ['css']);
    gulp.watch('app/assets/css/**/*.css', ['css']);
    gulp.watch('app/assets/js/**/*.js', ['js']);
});

// The default task (called when you run `gulp` from cli)
gulp.task('default', ['build', 'watch']);

关于上面代码的一些解释:


// 第一部分是引入 package
var gulp = require('gulp');
... 中间省略一堆
var minifycss = require('gulp-minify-css');

// 手动来设置这些文件, 这样的话会我们可以控制其合并文件时候
// 的顺序, 以后要加入某个 js 或者 css 的时候都在此添加
var paths = {
    frontend: {
        scripts: [
            'app/assets/js/jquery.min.js',
            ... 中间省略一堆
            'app/assets/js/main.js',
        ],
        styles: [
            'app/assets/css/bootstrap.min.css',
            ... 中间省略一堆
            'app/assets/css/prism.css',
        ]
    }
}

// CSS task
gulp.task('css', function() {

    // 先编译 scss 
    gulp.src('app/assets/sass/**/*.scss')
        .pipe(sass())
        .pipe(autoprefixer('last 10 version'))
        .pipe(gulp.dest('app/assets/css'));

    // 清除之前的文件
    del(['public/assets/css/styles-*.css'], function (err) {});

    gulp.src(paths.frontend.styles)     // 处理scc 文件
        .pipe(concat('styles.css'))     // 合并
        .pipe(rev())                    // 加版本号
        .pipe(filename({ bundleName: 'frontend.styles' })) // 生成 asset_manifest.json 文件
        .pipe(minifycss())              // 压缩
        .pipe(gulp.dest('public/assets/css'));  // 存放到 `public/assets/css` 文件夹
});

// JavaScript task
gulp.task('js', function() {
    // 清除之前的文件
    del(['public/assets/js/scripts-*.js'], function (err) {});

    gulp.src(paths.frontend.scripts)    // 处理 js 文件列表
        .pipe(concat('scripts.js'))     // 合并
        .pipe(uglify())                 // 压缩  
        .pipe(rev())                    // 加版本号
        .pipe(filename({ bundleName: 'frontend.scripts' })) // 生成 asset_manifest.json 文件
        .pipe(gulp.dest('public/assets/js'));   // 存放到 `public/assets/js` 文件夹
});

// 设置 build 任务, 此任务调用上面定义的 css 和  js 任务
gulp.task('build', ['css', 'js']);

// 设置 build 任务, 方便开发, css 和 js 文件一修改, 立刻进行重新编译
gulp.task('watch', function(){
    gulp.watch('app/assets/sass/**/*.scss', ['css']);
    gulp.watch('app/assets/css/**/*.css', ['css']);
    gulp.watch('app/assets/js/**/*.js', ['js']);
});

// 默认命令行运行 `gulp` 的时候开始执行 build 和 watch 任务
gulp.task('default', ['build', 'watch']);

asset_manifest.json 文件

保存完上面文件后, 当我们在命令行下运行

gulp build

后, 就会在 public/assets/jspublic/assets/css 生成最终会在模版里面使用的文件, 文件名:

  • styles-$version$.css
  • scripts-$version$.js

其中 $version$ 是变量, 每一次文件修改的时候, 都会不一样, 类似于 scripts-39eb8a9a.jsstyles-7c717f38.css.

问题: $version$ 会随着文件修改而变化, 模版里该怎么来引用他们?

asset_manifest.json 文件的作用就是为了解决这个问题, 每一次文件一修改, 作为各种操作后, gulp-asset-manifest 插件会把修改后的文件名存放到此文件里面, 类似于这样:

{
    "frontend.scripts":[
        "scripts-39eb8a9a.js"
    ],
    "frontend.styles":[
        "styles-7c717f38.css"
    ]
}

那么接下需要做的事情就是写一个 php 脚本来读取并解析此文件, 获取最终处理好的 css 和 js 文件名, 并在模版里面引用.

Laravel4 的 asset-manager Package

asset-manager 正是做了上面最后谈到的 一个 php 脚本 所做的事, 原始的项目在这里 modbase/asset-manager , 可惜只支持 Laravel4.1, 原作者已经不再维护, 只能自己 folk 一份, 并做了些许修改 summerblue/asset-manager .

Composer 安装 asset-manager

使用 repository 来安装, 在 composer.json 里面加入:

    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/summerblue/asset-manager"
        }
    ], 

在 require 节点下添加

"summerblue/asset-manager": "0.2.*"

修改后如下图:

然后

composer update

修改 app/config/app.php, 在 providers 数组里面添加:

'Modbase\AssetManager\AssetManagerServiceProvider'

至此部署完成, 接下来讲讲怎么使用.

一般使用

在修改 css 和 js 之前, 先在项目根目录下命令行执行 gulp :

➜  phphub git:(master) ✗ gulp
[07:45:45] Using gulpfile ~/Projects/phphub.org/phphub/Gulpfile.js
[07:45:45] Starting 'css'...
[07:45:45] Finished 'css' after 12 ms
[07:45:45] Starting 'js'...
[07:45:45] Finished 'js' after 10 ms
[07:45:45] Starting 'build'...
[07:45:45] Finished 'build' after 5.17 μs
[07:45:45] Starting 'watch'...
[07:45:45] Finished 'watch' after 14 ms
[07:45:45] Starting 'default'...
[07:45:45] Finished 'default' after 5 μs
[07:45:47] Starting 'css'...
[07:45:47] Finished 'css' after 4.12 ms
[07:45:47] Starting 'css'...
[07:45:47] Finished 'css' after 4.13 ms

请让此命令行一直保持执行着, 因为 TA 正在监控着你的文件修改.

需要添加 css 和 js 文件的话, 请到 Gulpfile.js 里面的 paths 选项下添加, 并重启 gulp 的监控.

添加 .gitignore 文件

修改根目录下的 . gitignore 文件, 添加这几行:

public/assets/css/*
public/assets/js/*
asset_manifest.json

这些是 gulp 的产生物, 不需要入版本.

生产环境下的部署

生产环境下, 每一次修改 assets 的话, 都需要执行此命令:

gulp build

如果是使用 envoy 进行远程部署的话, 只需要添加多一个 task :

@task('assets:publish')
    cd /var/www/phphub
    git pull origin master
    gulp build
@endtask

允许本地命令行下运行一条命令进行部署:

envoy run assets:publish

关于 Envoy 请见这里 Laravel Envoy 优雅的 SSH 远程任务执行工具.


:sun_with_face: :sun_with_face: :sun_with_face: :sunflower: :sunglasses:

-- EOF --


欢迎关注 LaravelTips, 这是一个专注于为 Laravel 开发者服务, 致力于帮助开发者更好的掌握 Laravel 框架, 提升开发效率的微信公众号.

摈弃世俗浮躁,追求技术精湛
本帖已被设为精华帖!
Summer
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 13
Summer

@yzdel200 大部分能找到的, 不是弃用了就是很久没有更新. gulp 是比较合理的方式.

9年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!