使用 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
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 13
Summer

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

11年前 评论

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