├── README.md ├── index.html ├── LICENSE ├── .gitignore └── der.js /README.md: -------------------------------------------------------------------------------- 1 | # tiny-compiler-by-vue 2 | tiny compiler for fun 3 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 花果山大圣 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /der.js: -------------------------------------------------------------------------------- 1 | // 一个非常der的compiler实现 2 | 3 | function tokenizer(input) { 4 | let tokens = [] 5 | let type = '' 6 | let val = '' 7 | // 粗暴循环 8 | for (let i = 0; i < input.length; i++) { 9 | let ch = input[i] 10 | if (ch === '<') { 11 | push() 12 | if (input[i + 1] === '/') { 13 | type = 'tagend' 14 | } else { 15 | type = 'tagstart' 16 | } 17 | } if (ch === '>') { 18 | push() 19 | type = "text" 20 | continue 21 | } else if (/[\s]/.test(ch)) { // 碰见空格夹断一下 22 | push() 23 | type = 'props' 24 | continue 25 | } 26 | val += ch 27 | } 28 | return tokens 29 | 30 | function push() { 31 | if (val) { 32 | if (type === "tagstart") val = val.slice(1) //
div 33 | if (type === "tagend") val = val.slice(2) //
div 34 | tokens.push({ 35 | type, 36 | val 37 | }) 38 | val = '' 39 | } 40 | } 41 | } 42 | 43 | function parse(template) { 44 | const tokens = tokenizer(template) 45 | let cur = 0 46 | let ast = { 47 | type: 'root', 48 | props:[], 49 | children: [] 50 | } 51 | while (cur < tokens.length) { 52 | ast.children.push(walk()) 53 | } 54 | return ast 55 | 56 | function walk() { 57 | let token = tokens[cur] 58 | if (token.type == 'tagstart') { 59 | let node = { 60 | type: 'element', 61 | tag: token.val, 62 | props: [], 63 | children: [] 64 | } 65 | token = tokens[++cur] 66 | while (token.type !== 'tagend') { 67 | if (token.type == 'props') { 68 | node.props.push(walk()) 69 | } else { 70 | node.children.push(walk()) 71 | } 72 | token = tokens[cur] 73 | } 74 | cur++ 75 | return node 76 | } 77 | if (token.type === 'tagend') { 78 | cur++ 79 | // return token 80 | } 81 | if (token.type == "text") { 82 | cur++ 83 | return token 84 | } 85 | if (token.type === "props") { 86 | cur++ 87 | const [key, val] = token.val.split('=') 88 | return { 89 | key, 90 | val 91 | } 92 | } 93 | } 94 | } 95 | function transform(ast) { 96 | // 优化一下ast 97 | let context = { 98 | // import { toDisplayString , createVNode , openBlock , createBlock } from "vue" 99 | helpers:new Set(['openBlock','createVnode']), // 用到的工具函数 100 | } 101 | traverse(ast, context) 102 | ast.helpers = context.helpers 103 | } 104 | function traverse(ast, context){ 105 | switch(ast.type){ 106 | case "root": 107 | context.helpers.add('createBlock') 108 | // log(ast) 109 | case "element": 110 | ast.children.forEach(node=>{ 111 | traverse(node,context) 112 | }) 113 | ast.flag = {props:false,class:false,event:false} 114 | ast.props = ast.props.map(prop=>{ 115 | const {key,val} = prop 116 | if(key[0]=='@'){ 117 | ast.flag.event = true 118 | return { 119 | key:'on'+key[1].toUpperCase()+key.slice(2), 120 | val 121 | } 122 | } 123 | if(key[0]==':'){ 124 | ast.flag.props = true 125 | return{ 126 | key:key.slice(1), 127 | val 128 | } 129 | } 130 | if(key.startsWith('v-')){ 131 | // pass such as v-model 132 | } 133 | return {...prop,static:true} 134 | }) 135 | break 136 | case "text": 137 | // trnsformText 138 | let re = /\{\{(.*)\}\}/g 139 | if(re.test(ast.val)){ 140 | //有{{ 141 | ast.static = false 142 | context.helpers.add('toDisplayString') 143 | ast.val = ast.val.replace(/\{\{(.*)\}\}/g,function(s0,s1){ 144 | return s1 145 | }) 146 | }else{ 147 | ast.static = true 148 | } 149 | } 150 | } 151 | 152 | function generate(ast) { 153 | const {helpers} = ast 154 | 155 | let code = ` 156 | import {${[...helpers].map(v=>v+' as _'+v).join(',')}} from 'vue'\n 157 | export function render(_ctx, _cache, $props){ 158 | return(_openBlock(), ${ast.children.map(node=>walk(node))})}` 159 | 160 | function walk(node){ 161 | switch(node.type){ 162 | case 'element': 163 | let {flag} = node // 编译的标记 164 | let props = '{'+node.props.reduce((ret,p)=>{ 165 | if(flag.props){ 166 | //动态属性 167 | ret.push(p.key +':_ctx.'+p.val.replace(/['"]/g,'') ) 168 | }else{ 169 | ret.push(p.key +':'+p.val ) 170 | } 171 | 172 | return ret 173 | },[]).join(',')+'}' 174 | return `_createVnode("${node.tag}",${props}),[ 175 | ${node.children.map(n=>walk(n))} 176 | ],${JSON.stringify(flag)}` 177 | break 178 | case 'text': 179 | if(node.static){ 180 | return '"'+node.val+'"' 181 | }else{ 182 | return `_toDisplayString(_ctx.${node.val})` 183 | } 184 | break 185 | } 186 | } 187 | return code 188 | } 189 | 190 | function compiler(template) { 191 | const ast = parse(template); 192 | console.log(JSON.stringify(ast,null,2)) 193 | transform(ast) 194 | const code = generate(ast) 195 | console.log(code) 196 | // return new Function(code); 197 | } 198 | 199 | let tmpl = `
200 |

{{name}}

201 |

技术摸鱼

202 |
` 203 | 204 | compiler(tmpl) --------------------------------------------------------------------------------