You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
varUglifyJS=require('uglify-js')varcode='function foo() {\n\ function x(){}\n\ function y(){}\n\}\n\function bar() {}'varast=UglifyJS.parse(code)varwalker=newUglifyJS.TreeWalker(function(node){console.log(node.print_to_string());if(nodeinstanceofUglifyJS.AST_Defun){// string_template is a cute little function that UglifyJS uses for warningsconsole.log(UglifyJS.string_template('Found function {name} at {line},{col}',{name: node.name.name,line: node.start.line,col: node.start.col}))}})ast.walk(walker)
/*global Backbone, jQuery, _, ENTER_KEY, ESC_KEY */varapp=app||{};(function($){'use strict';// Todo Item View// --------------// The DOM element for a todo item...app.TodoView=Backbone.View.extend({//... is a list tag.tagName: 'li',// Cache the template function for a single item.template: _.template($('#item-template').html()),// The DOM events specific to an item.events: {'click .toggle': 'toggleCompleted','dblclick label': 'edit','click .destroy': 'clear','keypress .edit': 'updateOnEnter','keydown .edit': 'revertOnEscape','blur .edit': 'close'},// The TodoView listens for changes to its model, re-rendering. Since// there's a one-to-one correspondence between a **Todo** and a// **TodoView** in this app, we set a direct reference on the model for// convenience.initialize: function(){this.listenTo(this.model,'change',this.render);this.listenTo(this.model,'destroy',this.remove);this.listenTo(this.model,'visible',this.toggleVisible);},// Re-render the titles of the todo item.render: function(){// Backbone LocalStorage is adding `id` attribute instantly after// creating a model. This causes our TodoView to render twice. Once// after creating a model and once on `id` change. We want to// filter out the second redundant render, which is caused by this// `id` change. It's known Backbone LocalStorage bug, therefore// we've to create a workaround.// https://github.com/tastejs/todomvc/issues/469if(this.model.changed.id!==undefined){return;}this.$el.html(this.template(this.model.toJSON()));this.$el.toggleClass('completed',this.model.get('completed'));this.toggleVisible();this.$input=this.$('.edit');returnthis;},toggleVisible: function(){this.$el.toggleClass('hidden',this.isHidden());},isHidden: function(){returnthis.model.get('completed') ?
app.TodoFilter==='active' :
app.TodoFilter==='completed';},// Toggle the `"completed"` state of the model.toggleCompleted: function(){this.model.toggle();},// Switch this view into `"editing"` mode, displaying the input field.edit: function(){this.$el.addClass('editing');this.$input.focus();},// Close the `"editing"` mode, saving changes to the todo.close: function(){varvalue=this.$input.val();vartrimmedValue=value.trim();// We don't want to handle blur events from an item that is no// longer being edited. Relying on the CSS class here has the// benefit of us not having to maintain state in the DOM and the// JavaScript logic.if(!this.$el.hasClass('editing')){return;}if(trimmedValue){this.model.save({title: trimmedValue});}else{this.clear();}this.$el.removeClass('editing');},// If you hit `enter`, we're through editing the item.updateOnEnter: function(e){if(e.which===ENTER_KEY){this.close();}},// If you're pressing `escape` we revert your change by simply leaving// the `editing` state.revertOnEscape: function(e){if(e.which===ESC_KEY){this.$el.removeClass('editing');// Also reset the hidden input back to the original value.this.$input.val(this.model.get('title'));}},// Remove the item, destroy the model from *localStorage* and delete its view.clear: function(){this.model.destroy();}});})(jQuery);
/*jshint quotmark: false *//*jshint white: false *//*jshint trailing: false *//*jshint newcap: false *//*global React */varapp=app||{};(function(){'use strict';varESCAPE_KEY=27;varENTER_KEY=13;app.TodoItem=React.createClass({handleSubmit: function(event){varval=this.state.editText.trim();if(val){this.props.onSave(val);this.setState({editText: val});}else{this.props.onDestroy();}},handleEdit: function(){this.props.onEdit();this.setState({editText: this.props.todo.title});},handleKeyDown: function(event){if(event.which===ESCAPE_KEY){this.setState({editText: this.props.todo.title});this.props.onCancel(event);}elseif(event.which===ENTER_KEY){this.handleSubmit(event);}},handleChange: function(event){if(this.props.editing){this.setState({editText: event.target.value});}},getInitialState: function(){return{editText: this.props.todo.title};},/** * This is a completely optional performance enhancement that you can * implement on any React component. If you were to delete this method * the app would still work correctly (and still be very performant!), we * just use it as an example of how little code it takes to get an order * of magnitude performance improvement. */shouldComponentUpdate: function(nextProps,nextState){return(nextProps.todo!==this.props.todo||nextProps.editing!==this.props.editing||nextState.editText!==this.state.editText);},/** * Safely manipulate the DOM after updating the state when invoking * `this.props.onEdit()` in the `handleEdit` method above. * For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate * and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate */componentDidUpdate: function(prevProps){if(!prevProps.editing&&this.props.editing){varnode=React.findDOMNode(this.refs.editField);node.focus();node.setSelectionRange(node.value.length,node.value.length);}},render: function(){return(<liclassName={classNames({completed: this.props.todo.completed,editing: this.props.editing})}><divclassName="view"><inputclassName="toggle"type="checkbox"checked={this.props.todo.completed}onChange={this.props.onToggle}/><labelonDoubleClick={this.handleEdit}>{this.props.todo.title}</label><buttonclassName="destroy"onClick={this.props.onDestroy}/></div><inputref="editField"className="edit"value={this.state.editText}onBlur={this.handleSubmit}onChange={this.handleChange}onKeyDown={this.handleKeyDown}/></li>);}});})();
思路:我们知道Uglifyjs可以混淆代码,那么它必然有一个js的解析器,并能对解析后的AST树进行调整,再用js生成器输出为js代码。同样的Babel作为一个强大的js预处理工具,它也必然有一个js的解析器,并能对解析后的AST树进行调整,再用js生成器输出为js代码。
在Uglifyjs的官网上示意了Uglifyjs的解析器的使用,
JS解析器为
UglifyJS.parse
JS代码生成器为UglifyJS.parse("").print_to_string()
或UglifyJS.parse("").print(UglifyJS.OutputStream())
写测试代码如下
感觉得到的AST树结构不那么好理解和操作,是否有更好理解和操作的AST树,看看Babel吧
在Babel的项目 https://github.com/babel/babel 中发现它使用的解析器是
babylon
,生成器是babel-generator
进入到Babylon项目 https://github.com/babel/babylon 发现它是基于 acorn 扩展的,并提到它的 AST 又扩展了 ESTree 标准。
进入 Acorn 项目 https://github.com/ternjs/acorn 发现它的示例代码中用到的生成器是 Escodegen 。
进入 Escodegen 项目 https://github.com/estools/escodegen 知道可以把 Mozilla's Parser API 格式的AST 生成js代码,在它的示例中用的js解析器是 esprima
进入到 esprima 项目 https://github.com/jquery/esprima 没有新的发现
在网上搜索 这几个关键词,找到一篇文章
babel/babel#3921
又知道了另外一个解析器及生成器,
综上,我们可以得到几种js解析为ast及ast生成js的几组工具。
1、UglifyJS 自带解析器、遍历器、生成器
2、
babylon
+babel-traverse
+babel-generator
3、
babylon
+babylon-to-espree
+escodegen
4、
acorn
+escodegen
5、
esprima
+estraverse
+escodegen
6、
shift-parser
+shift-codegen
可能还有
代码生成器
patrisika
没有再细究了。看来 Uglifyjs 是较少被使用的,可能因为它不是ESTree标准
解析器中
acorn
是最快的babylon
是最慢的生成器中
shift-codegen
是最快的babel-generator
是最慢的https://zhuanlan.zhihu.com/p/21620242
SiZapPaaiGwat/inhuman-cpc.github.io#106
http://wwsun.github.io/posts/javascript-ast-tutorial.html
Parser建议从Esprima开始学习,相比较于其它Parser文档和示例更加丰富和形象。
另外还可以使用 Estraverse https://github.com/estools/estraverse 来作节点的遍历和转换
esprima在线工具
http://esprima.org/demo/parse.html
从这篇文章中提到的 https://astexplorer.net/ 网站中发现好多种JS解析器
acorn acorn-to-esprima babylon babylon6 esformatter-parser espree esprima flow recast shift traceur typescript uglify-js
从文章 http://tech.meituan.com/abstract-syntax-tree.html
和
https://github.com/sinolz/amd2cmd/blob/master/src/AMD2CMDTransformer.js
还有
http://www.cnblogs.com/ziyunfei/p/3183325.html
会发现,代替更改的一般方式是对最终代码的替换
acorn acorn-to-esprima babylon babylon6 esformatter-parser espree esprima flow recast shift traceur typescript uglify-js
对这几个解析器解析出的AST再作观察,
又以acorn和esprima可以配置让得到的AST数据比较简洁
其他解析器位置都存在range属性中,acorn可以设置为存在range属性同时存在start和end属性上,
acorn解析出的AST
esprima解析出的AST
到时此我们可以定下要使用到的库
即只用
acorn
遍历有它自己的acorn.walk
方法,因为使用字符替换,也就不需要用到遍历器了。先转换作代码转换测试,找一两个典型性的,功能类似的的,分别用Backbone和React实现过的代码
http://todomvc.com/examples/backbone/
http://todomvc.com/examples/react/
下载里面涉及的源码
以下将把backbone写的view类todo-view.js转为React写的todoItem.js
打开 https://astexplorer.net/
和 https://github.com/ternjs/acorn
备查,开始写代码
第一个目标
Backbone.View.extend(
替换为
React.createClass(
替换成功,可以进行更多的处理了。
在最后作字符的替换时要求
collectedDatas
中要处理的位置是严格按照先后顺序的,这一点处理复杂情况时显然无法保证,只所以封装了一个字符串处理类。涉及到lodash模板转为jsx,需要引入一个不依赖bom的html操作工具,
网上搜索一下,找到一个12034个star的项目 https://github.com/cheeriojs/cheerio
在转换lodash到jsx中发现cheerio有一个问题,期望得到
实际得到
经翻源代码发现拼接html的方法位于
node_modules\dom-serializer\index.js
,对个js文件中的
formatAttrs()
作调整,改为
对个js文件中的
renderText()
作调整,改为
加配置项
{jsx:true}
时,以{
开始、以}
结束的属性,不用双引号引起来,并具不对其中的特殊符进行编码。终于成功将一个BackboneView类转为React类,但其中写了很多特例处理,代码并不通用,据此得出的结论是,Backbone View类自动转React类可以有限地减少手工修改的工作量(30%~60%)但无法代替手工修改
The text was updated successfully, but these errors were encountered: