├── 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)
--------------------------------------------------------------------------------