为了账号安全,请及时绑定邮箱和手机立即绑定

使用 makefile 压缩 JavaScript 代码

标签:
JavaScript

一、makefile 初探

1. 什么是 make指令 和 makefile?
make 指令就像它的名字一样 ,用于制作某个文件(make filename),或者根据 makefile target 自动化编译、打包、生成一个文件(可执行文件或压缩文件)。
例如,我们想根据 a.txt 和 b.txt 文件合成 output.txt 文件,可以书写如下 makefile 文件:

output.txt: a.txt b.txt
	@# 根据 a.txt b.txt 文件合成 output.txt 文件
	@cat a.txt b.txt > output.txt
复制代码
# 制作 output.txt 文件
make output.txt

通常我们用 make 指令构建 c/c++ 项目,当然,我们也可以用 make 指令构建 go 语言项目、java项目及 node.js 项目。
例如,我们用 make 指令编译 hello.cpp 文件

#include <iostream>
using namespace std;
int main(){
   cout << "Hello World!";
   return 0;
}

makefile 文件:

hellocpp:	hello.o
	echo "开始编译"
	g++ -o hello hello.o
	rm -f hello.o
	echo "编译结束"

执行 make 指令

# 使用 makefile 执行 hello.cpp
make

# 执行生成的 hello文件
./hello
> Hello World!

没玩过 makefile 的同学肯定以为 makefile 和 shell 脚本很像,没错,makefile 确实可以当做 shell 脚本使用(bushi),接下来我就简单介绍一下 makefile 的基本规则和常见写法。
2、makefile 的结构
makefile 文件是由一系列的规则(rules) 组成的,每条规则的写法如下:

<target> : <prerequisites> 
[tab]  <commands>

其中,冒号前面的部分表示目标(target),表示执行的动作。目标可以是一个文件名(如上文中的 output.txt),也可以是多个文件名,中间用空格隔开。目标除了是文件名,也可以是操作名,这种在 makefile 中,叫伪目标(phony target),用 .PHONY 声明目标操作。

如果我们想执行 make clean 操作,但是恰好目录中有一个 clean 的文件,那么我们在执行 make clean
的时候,是不会触发 clean 操作的,这个时候我们就要用 .PHONY 声明伪目标

.PHONY clean
clean: 
	rm -f *.o

我们在 makefile 中一般有一些约定俗成的目标,如:

  • make all:编译所有文件
  • make install:安装编译好的应用程序
  • make clean:清理应用程序,可执行文件,目标文件等。

冒号后面的部分表示前置条件(prerequisites),之间用空格分隔。声明的目标指定了前置条件,如果没有该前置条件匹配的文件,那么就要先生成该前置条件所需要的文件,才能执行目标。
命令(commands)表示如何构建目标文件,每个命令前必须以 tab 开头,可以和 prerequisites 写在一行,不过要用分号做分隔。
前置条件和命令为非必填项,不过其中一个没写另一个就必须要写。
makefile 里主要包含了五个东西:

  • 变量的定义
  • 显式规则:根据上文的 target-prerequisites-commands 的书写方式,就是显示规则
  • 隐晦规则:由于 makefile 具有自动推到的功能,所以隐晦规则可以让我们粗糙的书写makefile。用makefile
  • 内置的变量和函数编写的规则就是隐晦规则。
  • 文件指示:可以用 include 指令嵌套引入 makefile
  • 注释

变量的定义一般都是字符串,和C语言中的宏比较类似,所以我们有的时候也管makefile中的变量称作宏。
和 vue 的差值模板以及 shell、php 的变量一样,我们一般用${VARIABLE} 或 $(VARIABLE) 的方式去使用一个变量。
在 makefile 中,变量有四种声明方式:

VARIABLE = value # 惰性赋值,在执行时扩展,可以递归扩展

VARIABLE := value # 立即赋值

VARIABLE ?= value # 只有在该变量为空时才设置值

VARIABLE += value # 将值追加到变量的尾端

这四种赋值方式的具体区别可以查看 Stack Overflow,因为这四种赋值方式的区别不是本文讨论的重点,所以我就不过多赘述。
除了用户声明的变量外,makefile 中还有内置变量和自动变量。
内置变量分为两类,作为程序名称的变量(如CC),包含程序参数的变量(如CFLAGS)。关于makefile 所有的内置变量可以查看官方文档。
自动变量的值与当前的规则有关。
一般常用的有以下几个:

  • $@:当前目标
  • $?:比目标更新的前置条件
  • $<:第一个前置条件
  • $*:与通配符匹配的部分
  • $^:所有前置条件
  • KaTeX parse error: Expected 'EOF', got '和' at position 5: (@D)和̲(@F):$@ 的目录名和文件名
  • KaTeX parse error: Expected 'EOF', got '和' at position 5: (<D)和̲(<F):$< 的目录名和文件名

@指代的是当前的目标文件,如下面这个例子中,@ 指代的是当前的目标文件,如下面这个例子中,@指代的是当前的目标文件,如下面这个例子中,@ 就表示 a.txt 和 b.txt 两个目标文件的文件名;

# 下面两种写法等价
# 写法一
a.txt b.txt: 
    touch $@
# 写法二
a.txt:
    touch a.txt
b.txt:
    touch b.txt

<表示第一个前置条件,如下面这个例子中,< 表示第一个前置条件,如下面这个例子中,<表示第一个前置条件,如下面这个例子中,< 就表示 b.txt

# 下面两种写法等价
# 写法一
a.txt: b.txt c.txt
    cp $< $@ 
# 写法二
a.txt: b.txt c.txt
    cp b.txt a.txt 

除了内置变量和自动变量,makefile 还可以使用内置函数,使用方法和变量一样。官方文档总共列举了总共14种函数,常用的主要有以下几种:

  • shell,shell 函数可以执行shell 命令,个人觉得和 shell 里的管道作用很像。例如 dir:=$(shell pwd)
  • subst,用于文本替换,用法如下:$(subst from,to,text)
  • patsubst,patsubst 函数用于模式匹配的替换,主要用于替换通配符。用法为 $(patsubst pattern,replacement,text) 。例如 $(patsubst %.c,%.o,a.c.c b.c) 可以将 a.c.c和 b.c 替换成 a.c.o 和 b.o。
  • wildcard,wildcard 函数可以用空格分格所有匹配此格式的文件列表。例如,$(wildcard .c)可以获取工作目录下的所有的.c*文件列表。

makefile 还有一些其他的语法需要值得注意:
回声:@
正常情况下,make 在执行的过程中会打印每条 command,包括注释,这种现象在 makefile 中叫做回声。如果不想打印回声的话,可以用 @ 操作符来关闭回声。如:

@# 关闭注释
test:
	@echo "编译中。。。"
	@npm run dev

通配符(wildcard)
make 的通配符和 shell 一样,主要有*、?。
模式匹配
make 的模式匹配主要的操作符是%,允许对文件名进行类似正则的匹配
注释
makefile 的注释和 shell 脚本l 一样,都是 #
循环和判断指令
makefile的循环判断指令和 shell 脚本一样,主要有以下几种:

  • ifeq (if eqaul)指令。它包含两个参数,用逗号分隔并用圆括号包围。变量替换在两个参数上执行,然后进行比较。如果两个参数匹配,则遵循 ifeq后面的命令行;否则会被忽略。
  • ifneq (if not eqaul)指令。它包含两个参数,用逗号分隔并用圆括号包围。变量替换在两个参数上执行,然后进行比较。如果两个参数不匹配,则遵循ifneq后面的makefile行;否则会被忽略。
  • ifdef (if defined)指令。它包含单个参数。如果给定的参数为真,则条件成立。
  • ifndef (if not defined)指令。它包含单个参数。如果给定的参数为假,则条件成立。
  • else 指令。
  • endif 指令结束的语句,每个 if 条件必须以 endif 结尾。
  • for-in-do-done,循环

include 指令
include 指令可以引入其他的 makefile 文件。语法如下
include

# 文件名可以包含 shell 格式的文件名匹配。额外的空格是允许的,并且在行的开始处被忽略,但不允许使用制表符 tab(\t)

-include <filename>
# 无论include过程中出现什么错误,都不要报错继续执行。上面那条指令若是找不到include的目标文件,会报错

override 指令
如果想要重新赋值一个变量,则要使用 override 指令。如
override VARIABLE = value

二、使用 make 构建 JavaScript 代码

前面已经简单介绍了下 make 的用法及 makefile 的一些规则,接下来我们讲一讲如何用 make 压缩 JavaScript 代码。
废话不多说,直接上代码:

src_files := $(shell find src -name '*.js')
dist_files := $(patsubst src/%.js, dist/%.min.js, $(src_files))

node_modules: package.json package-lock.json
	@npm i uglifyjs 

$(dist_files): $(src_files)
	@rm -rf dist
	@mkdir dist
	@npx uglifyjs $^ -cmo $@

all: node_modules $(dist_files) 

.PHONY: all

为了方便测试,我们用零宽空格测试文件是否压缩成功。
在根目录下新建 src 目录,并新建 app.js 文件,文件内容为如下代码:
​​​​​​​​​​​​​​​​​​a​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

虽然在编辑器中,只显示一个字符a,但是该字符串的长度却是 221,文件大小是 601 字节。这是由零宽字符导致的。
那么什么是零宽字符(zero-width space)呢?
用多个字节表示的字符称之为宽字符,我们常见的 Unicode 编码就是宽字符的一种实现,但是宽字符并不一定是 Unicode。
零宽字符,顾名思义,就是宽度为0的字符。零宽字符在浏览器等环境是不可见的,但却是真是存在的,获取字符长度时也占有长度。常用于防止爬虫,数据隐写,也可以用于 DOS 攻击。
以下为浏览器中常见的特殊字符:

零宽空格(zero-width space, ZWSP)用于可能需要换行处。
    Unicode: U+200B  HTML: &#8203;
零宽不连字 (zero-width non-joiner,ZWNJ)放在电子文本的两个字符之间,抑制本来会发生的连字,而是以这两个字符原本的字形来绘制。
    Unicode: U+200C  HTML: &#8204;
零宽连字(zero-width joiner,ZWJ)是一个控制字符,放在某些需要复杂排版语言(如阿拉伯语、印地语)的两个字符之间,使得这两个本不会发生连字的字符产生了连字效果。
    Unicode: U+200D  HTML: &#8205;
左至右符号(Left-to-right mark,LRM)是一种控制字符,用于计算机的双向文稿排版中。
    Unicode: U+200E  HTML: &lrm; &#x200E; 或&#8206;
右至左符号(Right-to-left mark,RLM)是一种控制字符,用于计算机的双向文稿排版中。
    Unicode: U+200F  HTML: &rlm; &#x200F; 或&#8207;
字节顺序标记(byte-order mark,BOM)常被用来当做标示文件是以UTF-8、UTF-16或UTF-32编码的标记。
    Unicode: U+FEFF

知道了零宽字符的含义后,我们来测试 JavaScript 的压缩功能。
在终端输入 make all 指令,打开生成的 dist 目录,查看 app.min.js 文件,发现大小被压缩到 2个字节了。
上面只是简单列举了用 makefile 构建前端项目的例子,在实际开发中,我们可以使用 make -j 开启多线程来提升我们的构建速度,但是在现代的前端构建程序上,如 webpack、rollup,我们也可以使用多进程提升我们的打包速度(如 thread-loader)。所以这里建议大家,在实际的开发场景中,最好用同构的代码来构建我们的项目。
代码晚些时间会长传至 Github。

作者:上沅同学
链接:https://juejin.cn/post/6957594245083430943
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消