【js&css文件压缩】jsMin文件压缩及服务器配置 -3

上一次我们已经配好了nginx服务器,使其能够运行lua脚本,同时能够用lua脚本进行js文件和css文件的合并,已经css文件的压缩。但是那个脚本并不能进行js合并后的文件的压缩。这也是我们需要解决的一个问题。由于作者我的lua脚本编写能力不是很高,只能看得懂部分的内容,不过这样就足够了,可以通过修改icombo.lua文件,来达到js文件压缩的目的。但是js是按照何种标准进行压缩的呢?于是我搜索到了一篇介绍压缩js的文章,同时它附带了一个c语言程序.可以给我们提供文件的压缩。这就是[jsMin],(http://www.crockford.com/javascript/jsmin.html)你可以点击这个链接去查看详细内容。好了话不多说,来开始添加新的功能吧。

配置安装前提:
1. 阅读 上一篇文章,服务器的基本配置。
2. 添加lua的posix模块(注:上期未介绍)
3. 下载jsMin脚本,下载地址

一:linux服务器下载jsMin脚本

wget https://github.com/douglascrockford/JSMin/archive/master.zip
unzip master
cd JSMIN-master
gcc  -o jsmin jsmin.c 
mv jsmin /usr/bin

二:验证jsmin是否具有压缩js功能

cd /opt/nginx/html/js
ll 
-rw-r--r--. 1 root root 566808 Apr 15 16:01 backbone.js
-rw-r--r--. 1 root root 507676 Apr 15 16:00 underscore.js
jsmin < backbone.js > backbone.min.js
jsmin < backbone.js > underscore.min.js

这里需要强调几点知识。

  1. 管道流 < 表示input流
  2. 管道流 > 表示output流
  3. 两个文件名不能一直,否则会导致最后文件为空字节

我们查看一下压缩后文件的大小。

ll
-rw-r--r--. 1 root root 35186 Apr 17 19:19 backbone.js
-rw-r--r--. 1 root root 20012 Apr 17 19:20 backbone.min.js
-rw-r--r--. 1 root root 54539 Apr 17 19:20 underscore.js
-rw-r--r--. 1 root root 25662 Apr 17 19:21 underscore.min.js

大概压缩了一半左右的内容,之后我们可以去试一试这两个文件是否还能有效。

三: 修改上次说的icombo.lua文件

cd /opt/nginx/scripts/lua/icombo
vi icombo.lua

icombo.lua修改后的文件如下

-------------------------------
-- iCombo version 0.2.4 beta
-------------------------------
-------- explode string -------
function explode(s, delimiter)
    result = {}
    for match in (s..delimiter):gmatch("(.-)"..delimiter) do
        table.insert(result, match)
    end
    return result
end
-------- get client ip --------
function getClientIp()
        IP = ngx.req.get_headers()["X-Real-IP"]
        if IP == nil then
            IP  = ngx.var.remote_addr 
        end
        if IP == nil then
            IP  = "unknown"
        end
        return IP
end
-------- table find --------
function tableFind(t, e)
    for _, v in ipairs(t) do
        if v == e then
          return true
        end
    end
    return nil
end
-------- read file --------
function fileRead(filename)
    local f = io.open(filename, "r")
    if f == nil then
       return
    end
    local t = f:read("*all")
    f:close()
    return t
end
-------- write file --------
function fileWrite(filename, content)
    local f = io.open(filename, "w")
    if f == nil then
       return nil
    end
    local t = f:write(content)
    f:close()
    return true
end
-------- get file extension --------
function getFileExt(filename)
    return filename:match(".+%.(%w+)$")
end
-------- check value empty --------
function empty(val)
    if val == nil or val == '' then
        return true
    end
    return 
end
-------- remove css comments ---------
function removeCssComents(out)
        out = out:gsub("/%*.-%*/", "")
                 :gsub('\n%s', "")
                 :gsub('^%s*', "")
                 :gsub('\n$', "")
                 :gsub('%s*{', "{")
                 :gsub(';%s*', ";")
                 :gsub(';\n', ";")
                 :gsub('{%s*', "{")
                 :gsub(';}', "}")
                 :gsub('}%s*', "}")
                 :gsub('}\r\n', "}")
                 :gsub('}\n', "}")
                 :gsub('\r\n}', "}")
                 :gsub(',%s*', ",")
                 :gsub(':%s', ":")
                 :gsub('%s>%s', ">")
                 :gsub('#([0-f])%1([0-f])%2([0-f])%3([\\s;\\}])', "#%1%2%3%4")
        return out

end
-------- delete cache file -----------
function delFile(posix, path)
    for name in posix.files(path) do
        if name ~= "." and name ~= ".." then
            posix.unlink(path..name)
        end
    end
end
-------- log execute_time ---------
function logTime()
    local exec_time = string .format("%.3f", ngx.now() - ngx.req.start_time())
    ngx.log(ngx.CRIT, exec_time);
end
-------- log -----------
function log(data, status)
    status = status or 400
    ngx.log(ngx.CRIT, data)
    if status ~= 0 then
        ngx.exit(status)
    end
end

-------- main --------
local file_lists  = ngx.var.arg_f
if file_lists == nil then
    log("param f is empty")
end

local types            = {["js"]="application/x-javascript", ["css"]="text/css"}
local posix            = require 'posix'
local root             = ngx.var.document_root..'/'
local file_dir         = root
local req_url          = ngx.var.host..ngx.var.uri..file_lists
local uris             = explode(file_lists, ",")
local size             = table.getn(uris)
local cache_dir        = ""
local out              = ""
local ext              = ""
local file_out         = ""
local file_info        = 0
local last_modify      = 0
local http_last_modify = 0
local count            = 0

-- only allow js css
ext = getFileExt(uris[1])
if types[ext] == nil then
    log("extension is error")
end

-- max files
local max_files = ngx.var.max_files
if not empty(max_files) and size > tonumber(max_files) then
    log("exceed max files")
end

-- set file directory
if ext == "css" then
    if ngx.var.css_dir ~= nil then
        file_dir = root..ngx.var.css_dir
    end
elseif ngx.var.js_dir ~= nil then
    file_dir = root..ngx.var.js_dir
end

if ngx.var.cache_dir ~= nil then
    cache_dir = ngx.var.cache_dir.."/"
else
    log("cache_dir is empty")
end

-- delete cache file
if ngx.var.arg_c ~= nil and ngx.var.admin_ip ~= nil then
    local admin_ip = explode(ngx.var.admin_ip, ",");
    local ip       = getClientIp()
    if (tableFind(admin_ip, ip)) ~= nil then
        delFile(posix, cache_dir)
        ngx.header.content_type  = "text/html"
        ngx.say('success')
        ngx.exit(200)
    end
end

-- get files last modify time
for i = 1, size, 1 do 
    file_info = posix.stat(file_dir..uris[i])
    if file_info == nil then
        log(uris[i].." file not found")
    end
    if file_info["type"] ~= "regular" or getFileExt(uris[i]) ~= ext then
        log(uris[i].." extension is error")
    end
    if file_info["mtime"] > last_modify then
        last_modify = file_info["mtime"]
    end
end

ngx.header.content_type  = types[ext]
http_last_modify         = ngx.http_time(last_modify)

local md5_req_url  = ngx.md5(req_url)
local combo_file   = cache_dir..md5_req_url..".combo"
local icombo_sub   = '/icombo_sub/'..md5_req_url..".combo"
local combo_modify = posix.stat(combo_file, "mtime") 
-- cache nil
if nil == combo_modify then
    combo_modify = 0
end

local up_cache = last_modify > combo_modify
-- cache valid
if not up_cache then
    return ngx.exec(icombo_sub)
end

-- cache expire or nil
local bom           = string.char(0xEF, 0xBB, 0xBF)
local dir_name      = ""
local css_path_auto = ngx.var.css_path_auto
local t_out    = {}
for i = 1, size, 1 do 
    file_out = fileRead(file_dir..uris[i])
    if file_out == nil then
        log(uris[i].." read fail")
    elseif file_out == "" then
        -- no cache file
        if 0 == combo_modify then
           log(uris[i].." content is empty")
        else
            log(uris[i].." read old cache file", 0)
            return ngx.exec(icombo_sub)
        end        
    end
    if file_out:sub(1,3) == bom then
        file_out = file_out:sub(4)
    end
    -- css_path_auto
    dir_name = posix.dirname(uris[i])
    if not empty(css_path_auto) and dir_name ~= nil then
        -- handle ../
        file_out, count = ngx.re.gsub(file_out, '../'..css_path_auto, './'..css_path_auto, 'i');
        -- handle ./
        file_out, count = ngx.re.gsub(file_out, '(?<!/)'..css_path_auto, dir_name..'/'..css_path_auto, 'i');
    end
    t_out[i] = file_out
end

-- large string use table is fast
out   = table.concat(t_out, "\r\n")
t_out = nil

if ext == "css" then
    -- remove css comment
    local css_trim = ngx.var.css_trim
    if css_trim ~= nil and css_trim:lower() == "on" then
        out = removeCssComents(out)
    end
    -- replace images path
    local css_replace = ngx.var.css_replace
    if not empty(css_replace) then
        local replaces  = explode(css_replace, "|")
        size            = table.getn(replaces)
        for i = 1, size, 1 do
            local single = explode(replaces[i], ",")
            out, count   = ngx.re.gsub(out, single[1], single[2], 'i')
        end
    end

end


 -- minify js file  修改地方开始
if ext == "js" then
-- 将js合并的字符串写入一个文件格式中
    local res = fileWrite(combo_file, out)
    if res == nil then

     log('cache write fail, please check cache_dir permitted access')

    else
--  运行命令行程序 将文件以流的形式传入
        local minifycmd = '/usr/bin/jsmin < '..combo_file
--  返回一个文件类型
        local t = io.popen(minifycmd)
--  读取文件的所有内容 至out变量中
        out  =  t:read("*all")

    end

end
-- 修改地方结束

local res = fileWrite(combo_file, out)
    if res == nil then
     log('cache write fail, please check cache_dir permitted access')
    end

posix.utime(combo_file, last_modify)
return ngx.exec(icombo_sub)

基本上是添加了一个判断是否传入的是js脚本,然后通过命令行的方式将js文件进行压缩。

四:添加lua的posix模块
这里介绍posix模块,它主要是可以让lua支持文件的读与写操作,这里刚好我们需要进行这类操作,因此该模块必不可少.

1.安装lua_posix模块

# wget http://git.alpinelinux.org/cgit/luaposix/snapshot/luaposix-5.1.8.tar.bz2
# tar jxvf luaposix-5.1.8.tar.bz2
# cd luaposix-5.1.8
# vi Makefile  -- 打开 Makefile 文件 修改LUAINC
  LUAINC= /usr/local/include/
# make CC=gcc
# make install CC=gcc
  1. 创建一个软连接
ln -s /usr/local/lib/lua/5.1/posix /usr/lib/lua/5.1

注意:坐着我尝试下一个最新的posix版本的模块,遗憾的是icombo.lua脚本并不支持我这么做。于是参照的是icombo作者提供的posix版本。

五:测试压缩是否有效
1. 开启nginx服务器

servicce nginx start

2.输入测试地址

http://你的ip地址/icombo/?f=css/screen.css
http://你的ip地址/icombo/?f=js/backbone.js,js/underscore.js

这样我们添加的服务就完成了。来看看效果图吧
这里写图片描述

终于这块内容结束了,于是作者简单的修改了下人家的东西,做了一个满足自己的功能。当然有些地方需要进行优化,如缓存文件的放置等等。经过作者的测试,发现使用minify的处理方式更加消耗cpu性能,但是它的压缩方式比较彻底,压缩比要好于使用nginx的压缩。这原因就在于我们没有优化吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
由Douglas Crockford创建的JSMin是一个筛选程序,用于JavaScript文件中删除注释和不必要的空格。这个程序通常能够使文件大小减半,从而节省下载时间。 压缩包里包含了用C写的源码,及可执行程序 由于看到三条评论说没有用,在里我必须说明一下,下面那三个白痴肯定没学过c语言,更没用过c编译好的.exe文件。 以下是c里的源码大家可以先看觉得可用再下 #include <stdlib.h> #include <stdio.h> static int theA; static int theB; static int theLookahead = EOF; /* isAlphanum -- return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character. */ static int isAlphanum(int c) { return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' || c > 126); } /* get -- return the next character from stdin. Watch out for lookahead. If the character is a control character, translate it to a space or linefeed. */ static int get() { int c = theLookahead; theLookahead = EOF; if (c == EOF) { c = getc(stdin); } if (c >= ' ' || c == '\n' || c == EOF) { return c; } if (c == '\r') { return '\n'; } return ' '; } /* peek -- get the next character without getting it. */ static int peek() { theLookahead = get(); return theLookahead; } /* next -- get the next character, excluding comments. peek() is used to see if a '/' is followed by a '/' or '*'. */ static int next() { int c = get(); if (c == '/') { switch (peek()) { case '/': for (;;) { c = get(); if (c <= '\n') { return c; } } case '*': get(); for (;;) { switch (get()) { case '*': if (peek() == '/') { get(); return ' '; } break; case EOF: fprintf(stderr, "Error: JSMIN Unterminated comment.\n"); exit(1); } } default: return c; } } return c; } /* action -- do something! What you do is determined by the argument: 1 Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B. (Delete A). 3 Get the next B. (Delete B). action treats a string as a single character. Wow! action recognizes a regular expression if it is preceded by ( or , or =. */ static void action(int d) { switch (d) { case 1: putc(theA, stdout); case 2: theA = theB; if (theA == '\'' || theA == '"') { for (;;) { putc(theA, stdout); theA = get(); if (theA == theB) { break; } if (theA == '\\') { putc(theA, stdout); theA = get(); } if (theA == EOF) { fprintf(stderr, "Error: JSMIN unterminated string literal."); exit(1); } } } case 3: theB = next(); if (theB == '/' && (theA == '(' || theA == ',' || theA == '=' || theA == ':' || theA == '[' || theA == '!' || theA == '&' || theA == '|' || theA == '?' || theA == '{' || theA == '}' || theA == ';' || theA == '\n')) { putc(theA, stdout); putc(theB, stdout); for (;;) { theA = get(); if (theA == '[') { for (;;) { putc(theA, stdout); theA = get(); if (theA == ']') { break; } if (theA == '\\') { putc(theA, stdout); theA = get(); } if (theA == EOF) { fprintf(stderr, "Error: JSMIN unterminated set in Regular Expression literal.\n"); exit(1); } } } else if (theA == '/') { break; } else if (theA =='\\') { putc(theA, stdout); theA = get(); } if (theA == EOF) { fprintf(stderr, "Error: JSMIN unterminated Regular Expression literal.\n"); exit(1); } putc(theA, stdout); } theB = next(); } } } /* jsmin -- Copy the input to the output, deleting the characters which are insignificant to JavaScript. Comments will be removed. Tabs will be replaced with spaces. Carriage returns will be replaced with linefeeds. Most spaces and linefeeds will be removed. */ static void jsmin() { theA = '\n'; action(3); while (theA != EOF) { switch (theA) { case ' ': if (isAlphanum(theB)) { action(1); } else { action(2); } break; case '\n': switch (theB) { case '{': case '[': case '(': case '+': case '-': action(1); break; case ' ': action(3); break; default: if (isAlphanum(theB)) { action(1); } else { action(2); } } break; default: switch (theB) { case ' ': if (isAlphanum(theA)) { action(1); break; } action(3); break; case '\n': switch (theA) { case '}': case ']': case ')': case '+': case '-': case '"': case '\'': action(1); break; default: if (isAlphanum(theA)) { action(1); } else { action(3); } } break; default: action(1); break; } } } } /* main -- Output any command line arguments as comments and then minify the input. */ extern int main(int argc, char* argv[]) { int i; for (i = 1; i < argc; i += 1) { fprintf(stdout, "// %s\n", argv[i]); } jsmin(); return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值