前端构建:Source Maps详解
一、前言
当使用CoffeeScript、ClojureScript编写前端脚本时,当使用Less、Sacc编写样式规则时,是否觉得调试时无法准确找到源码位置呢?当使用jquery.min.js等经压缩后的工具库时,是否觉得连调试的门都不不知道在哪呢?
针对上述问题,google为我们提供了Source Maps这一解决方案,以下内容为对Source Maps的学习记录,以便日后查阅。
由于篇幅较长,特设目录一坨!
二、示例
首先我们使用ClojureScript写一段递归函数becomeGeek
(ns sample)
(defn becomeGeek [progress]
(.log js/console progress)
(if (> progress)
(becomeGeek (+ progress))))
预编译后得到如下js
sample.becomeGeek = (function becomeGeek(process){
console.log(process);
if((() > process)){
return becomeGeek.call(null,(() + process));
} else {
return null;
}
});
当需要调试时我们的处境就是看着JS代码修改ClojureScript代码,对于这个becomeGeek函数来说没多大困难,但对于整个工程来说难度不亚于看着二进制中间码来修改Java代码哦。下面我们通过lein+cljsbuild插件来生成source maps从而解决上述问题!
project.clj配置信息
(defproject sample "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/clojurescript "0.0-2411"
:exclusions [org.apache.ant/ant]]
[compojure "1.1.6"]]
:plugins [[lein-cljsbuild "1.0.4"]]
:cljsbuild {
:builds [{:id "main"
:source-paths ["src-cljs"]
:compiler {:output-to "js/main.js"
:output-dir "out"
:optimizations :none
:source-map true}}]})
执行lein命令
$ lein cljsbulid once
然后我们开启Chrome的devTools中js和css的source maps功能即可像在VS上调试C#一样爽快了。

在sample.cljs文件中设置断点,然后调用sample.becomeGeek调试即可!
Chrome的devTools:

FF的devTools:

三、Source Maps方案详解
我想大家现在已经感受到Source Maps的威力了,有了它我们就可以安心的使用JS的超集语言(ClojureScript、CoffeeScript和TypeScript等),也可安心调试jquery.min.js等经过压缩混淆的库代码了。
1. 方案结构
Source Maps不仅仅是一个.map后缀的文件,而是由浏览器、.map文件生成器和.map文件组成的一套技术方案。
.map文件,其实是一个关系映射文件,用于存放源码和编译后代码的文件、行号、列号和变量名的映射关系;
.map文件生成器,每种预处理器(Lessc、Closure、cljsc等)都可通过可选项设置如何生成.map文件;
浏览器,Chrome和FF均提供Source Maps支持(IE11依然不支持),浏览器实质上提供的是.map文件解析引擎,根据.map文件内容加载源文件和在调试模式中关联源码和编译后代码。
另外编译后代码最后一行会追加一行指向.map文件语句,指向的方式有http uri scheme 和 data uri scheme两种。
http uri scheme,格式为 //# sourceMappingURL=sample.js.map?rel=1420853090118
data uri scheme,就是通过对.map文件进行base64编码,然后编译后代码最后一行以data uri scheme的形式引入.map文件内容,格式为 //# sourceMappingURL=data:application/json;base64,Asdi.......
2. 支持的浏览器及启用方式
Chrome,devTools的Settings中开启JS和CSS的Source Maps功能。

FF,默认已经开启JS和CSS的Source Maps功能。
3. 生成器
下面将介绍Lessc、GC(Google Closure Compiler)、UglifyJS、ClojureScript和CoffeeScript
Less的生成器为lessc,通过可选项 --source-map 开启生成.map文件的功能,并通过如 --source-map-rootpath 等可选项配置.map文件的相关信息。具体请查看《前端构建:Less入了个门》
GC,作为JS的编译器,不但提供去除空白、注释等功能,还会对代码进行语法分析并优化代码(函数内联、变量常量化、局部变量和属性名替换等)
a = new Object => a = {}
a = new Array => a = []
if (a) b() => a && b()
return * ; => return ;
GC提供三种调用方式,分别为网页版、网络API版和独立应用程序。由于GC使用Java编写,因此我们需要安装JRE。(若不想安装JRE那么可参考@赵劼通过IKVM.NET来将clojure-compiler.jar转码为.Net版)然后通过下面的命令生成.map文件:
$ java -jar compiler.jar --js sample.js --create_source_map ./sample-map --js_output_file sample.min.js
UglifyJS,由于jQuery改用UglifyJS作为其预编译工具令其声名远播,通过下面的命令生成.map文件:
$ uglifyjs sample.js -o sample.min.js --source-map sample.min.map
ClojureScript,我们可以通过第二节的方式生成.map文件。
CoffeeScript,通过下面的命令生成.map文件:
coffee -c sample.min.js sample.js -m
4. map文件详解
到这里大家已经可以得心应手地使用Source Maps了,接下来的内容是为想再深入理解.map文件内容和Source Maps实现原理的朋友准备的。内容主要来自@阮一峰的《Javascript Source Map 详解》
4.1. map文件格式
以第二节生成的.map文件为例
{"version":,
"file":"/C:/lein/myapp/out/sample.js",
"sources":["sample.cljs?rel=1420853090124"],
"sourceRoot":"","mappings":
";AAAA;;AAEA,oBAAA,pBAAMA,yCAAYC;AAAlB,AACC,AAAMC,YAAWD;;AACjB,GAAI,CAAA,QAAOA;AACV,OAACE,qBAAW,CAAA,MAAKF;;AADlB",
"names":["sample/becomeGeek", "process", "js/console", "becomeGeek"]}
{Number} version,Source map的版本,目前为3;
{String} file ,编译后的文件路径;
{Array.<String>} sources ,源码文件路径数组;
{String} sourceRoot ,源码文件的所在目录;
{Array.<String>} names ,源码中的所有变量名和属性名;
{String} mappings ,记录源码与编译后代码的位置信息。
4.2. mappings属性
首先mapping属性值分为三层含义
①以分号(;)标识编译后代码的每一行,即是分号间隔的内容代表编译后代码的一行;
②以逗号(,)标识编译后代码该行中的每一个映射位置,即是逗号间隔的内容代表一个映射位置;
③以5组VLQ编码字段标识源码和编译后代码的具体映射信息。从左至右每组表示如下:
第1组,表示对应编译后代码的第几列;
第2组,表示源码所属文件在sources数组中的索引值;
第3组,表示对应源码的第几行;
第4组,表示对应源码的第几列;
第5组,表示在names数组中的索引值,若没有则可省略。
注意:每组VLQ编码字段有0~N个VLQ编码字符组成,如qCAAUH。
4.3. VLQ编码
VLQ编码最早用于MIDI文件,后来被多种格式采用。它的特点就是可以非常精简地表示很大的数值。
VLQ编码是变长的。如果(整)数值在-15到+15之间(含两个端点),用一个字符表示;超出这个范围,就需要用多个字符表示。并且规定每6bit标识一个字符。
Continuation
| Sign
| |
V V
101011
第一位(Continuation位)表示当前6个bit是否为当前编码段的最后一节,1表示不是,0表示是。
最后一位(Sign位),当该节为当前编码段的第一节时,表示符号1为负号,0为正号;若不是第一节则表示数值位。
下面对16进行VLQ编码,
1. 将16转换为二进制10000;
2. 在最右边补充符号位(Sign位)得到100000;
3. 从最右边开始以5bit为一组对其进行分段,分段后不足5bit的在前面补0,得到00001、00000;
4. 倒序得到00000、00001;
5. 为每一段添加连续位(Continuation位)得到100000、000001;
6. 对每段进行Base64编码,得到gB。(下图为Base64编码字符集)

四、注意
通过Chrome和FF下devTools的network面板我们可以看到浏览器加载了.map文件和源代码文件,现在问题来了,那么在生产环境当中用户访问网页时岂不会多加载两个开发环境使用的文件吗?
其实浏览器默认情况下(不打开devTools时)是不会加载.map文件和源代码文件的,所以大家可以放心了。假如你还是怕用户误操作打开了devTools,那么就在打包发布时不生成.map文件就好了!
五、总结
之前尝试过CoffeeScript,但由于编码速度虽然提高不少,但调试效率却降低更多(without source maps之痛),导致最终回归JS的怀抱了。现在我们终于可以安心使用CoffeeScript咯!
尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/4208566.html ^_^肥仔John
六、参考
Source Map Revision 3 Proposal
前端构建:Source Maps详解的更多相关文章
- Sentry(v20.12.1) K8S 云原生架构探索,SENTRY FOR JAVASCRIPT Source Maps 详解
系列 Sentry-Go SDK 中文实践指南 一起来刷 Sentry For Go 官方文档之 Enriching Events Snuba:Sentry 新的搜索基础设施(基于 ClickHous ...
- JAVA Eclipse使用Maven构建web项目详解(SSM框架)
tips: 启动项目后,welcome-file的链接即为测试用例 部署maven web项目 Eclipse使用Maven构建web项目详解 pom.xml添加webapp依赖: <depen ...
- eclipse里面构建maven项目详解(转载)
本文来源于:http://my.oschina.net/u/1540325/blog/548530 eclipse里面构建maven项目详解 1 环境安装及分配 Maven是基于项目对象模 ...
- 前端技术之_CSS详解第一天
前端技术之_CSS详解第一天 一html部分 略.... 二.列表 列表有3种 2.1 无序列表 无序列表,用来表示一个列表的语义,并且每个项目和每个项目之间,是不分先后的. ul就是英语unorde ...
- 前端技术之_CSS详解第二天
前端技术之_CSS详解第二天 1.css基础选择器 html负责结构,css负责样式,js负责行为. css写在head标签里面,容器style标签. 先写选择器,然后写大括号,大括号里面是样式. & ...
- 前端技术之_CSS详解第三天
前端技术之_CSS详解第三天 二.权重问题深入 2.1 同一个标签,携带了多个类名,有冲突: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 ...
- 前端技术之_CSS详解第四天
前端技术之_CSS详解第四天 一.第三天的小总结 盒模型box model,什么是盒子? 所有的标签都是盒子.无论是div.span.a都是盒子.图片.表单元素一律看做文本. 盒模型有哪些组成: wi ...
- 前端技术之_CSS详解第五天
前端技术之_CSS详解第五天 一.行高和字号 1.1 行高 CSS中,所有的行,都有行高.盒模型的padding,绝对不是直接作用在文字上的,而是作用在“行”上的. <!DOCTYPE html ...
- 前端技术之_CSS详解第六天--完结
前端技术之_CSS详解第六天--完结 一.复习第五天的知识 a标签的伪类4个: a:link 没有被点击过的链接 a:visited 访问过的链接 a:hover 悬停 a:active 按下鼠标不松 ...
随机推荐
- MVC使用ASP.NET Identity 2.0实现用户身份安全相关功能,比如通过短信或邮件发送安全码,账户锁定等
本文体验在MVC中使用ASP.NET Identity 2.0,体验与用户身份安全有关的功能: →install-package Microsoft.AspNet.Identity.Samples - ...
- 你眼中的async/await是什么样的?
又到了周末的code review环节,这次code review发现了一个对async/await的理解问题.让我们直奔主题: var foodsSearch = new FoodSearchSer ...
- 控件UI性能调优 -- SizeChanged不是万能的
简介 我们在之前的“UWP控件开发——用NuGet包装自己的控件“一文中曾提到XAML的布局系统 和平时使用上的一些问题(重写Measure/Arrange还是使用SizeChanged?),这篇博文 ...
- nw.js如何处理拖放操作
nw.js如何处理拖放操作 其实拖放(drag-drop)操作是Html5的功能,不是nw.js的内置API,那么我们采用Html5应用一般的处理方法就可以了. 首先我们看一下一个正常的页面,直接拖放 ...
- clearTimeout消除闪动
需求:当鼠标放到父级菜单上面的时候,显示下方的子菜单.鼠标从子菜单或者父级菜单上面移开的时候,子菜单要收起来.最终效果如下: PS:这样需求很常见,最常见的做法是li元素下面再嵌套一个Ul元素来包含子 ...
- [ZigBee] 15、Zigbee协议栈应用(一)——Zigbee协议栈介绍及简单例子(长文,OSAL及Zigbee入门知识)
1.Zigbee协议栈简介 协议是一系列的通信标准,通信双方需要按照这一标准进行正常的数据发射和接收.协议栈是协议的具体实现形式,通俗讲协议栈就是协议和用户之间的一个接口,开发人员通过使用协议栈来使用 ...
- ehcache2拾遗之cache持久化
问题描述 应用在使用过程中会需要重启等,但是如果ehcache随着应用一起重启,那么刚重启的时候就会出现大量的miss,需要一定的访问量来重建缓存,如果缓存能够持久化,重启之后可以复用将会有助于缓解重 ...
- struts2学习笔记之五:表单数据收集的几种方式
方法一:struts2对ModelDriven模式的支持(模型驱动模式) Struts2可以采用类似于Struts1中的ActionForm方式收集数据,这样方式叫ModelDriven模式 Acti ...
- getattribute()与getparameter()的区别
1.它们取到的值不同.getAttribute取到的是对象(object),而getParameter取到的是String. 2.数据传递路劲不同.request.getParameter方法传递的数 ...
- Atitit 通过调用gui接口杀掉360杀毒 360卫士 qq保镖等难以结束的进程(javac# php )
Atitit 通过调用gui接口杀掉360杀毒 360卫士 qq保镖等难以结束的进程(javac# php ) 1.1. 这些流氓软件使用操作系统os提供的普通api根本就杀不掉啊1 1.2. 使用 ...