一.构建环境
(1)新建名为TemplateEngine的文件夹,然后 ? ? ??
npm init??
npm i
npm i webpack@4 -D
npm i webpack-cli@3?-D
npm i webpack-dev-server@3?-D
使用webpack和webpack-dev-server构建?
-webpack侧重开发体验,结合webpack-dev-server实时更新
-nodeis调试控制台不太好用
-rollup更擅长于最终把几个js文件打包一起
-生成库UMD,同时在nodejs.js环境和浏览器环境中使用,实现UMD,只需一个“通用头”
?新建一个webpack.config.js文件
const path = require('path')
module.exports = {
//模式,开发
mode:'development',
//入口
entry:'./src/index.js',
//打包到什么文件
output:{
filename:'bundle.js'
},
//配置一下webpack-dev-server
devServer:{
//静态文件根目录
contentBase:path.join(__dirname,'www'),
//不压缩
compress:false,
//端口号
port:8080,
//虚拟打包的路径,bundle.js文件没有真正的生成
publicPath:'/xuni/'
}
}
接着在package.json文件改动:
"scripts": {
"dev": "webpack-dev-server"
},
刚刚看到关于学习源码的建议:
(1)源码思想要借鉴,而不要抄袭,要能够发现源码中书写的精彩的地方。
(2)将独立的功能拆写为独立的js文件中完成,通常是一个独立的类,每个单独的功能必须能独立的“单元测试”。
(3)应该围绕中心功能,先把主干完成,然后修剪枝叶
(4)功能并不需要一步到位,功能的扩展要一步步完成,有的非核心功能甚至不需要实现
二.将模板字符串变成tokens
?举个简单的例子~
我买了一个{{thing}},好{{mood}}啊
主要思想:
设置一个指针,从下标为0开始扫描,碰到 {{ 则停止扫描并返回之前经过的文字【“我买了一个”】,接着又继续扫描碰到 }}?则停止扫描并返回之前经过的文字【“thing”】,又继续干活碰到 {{?停止扫描并返回之前经过的文字【“,好”】,接着又继续扫描碰到 }}?则停止扫描并返回之前经过的文字【“mood”】,接下来以此类推。
新建一个Scanner.js文件,实现一个Scanner类【扫描器类】,类里实现两个方法:
一个是scanUtils方法,主要功能:让指针进行扫描,直到遇见指定内容结束,并且能够返回结束之前路过的文字;
另一个是scan方法,主要功能:走过指定内容,没有返回值
三个属性:
?templateStr 接收过来的模板字符串
?pos 起始位置(可以理解为指针)
tail 扫描过后的字符串,一开始设置为templateStr
export default class Scanner{
constructor(templateStr){
this.templateStr = templateStr;
this.pos = 0;
this.tail = this.templateStr
}
//功能弱,就是走过指定内容,没有返回值
scan(tag){
if(this.tail.indexOf(tag)==0){
this.pos+=tag.length
this.tail = this.templateStr.substring(this.pos)
}
}
//让指针进行扫描,直到遇见指定内容结束,并且能够返回结束之前路过的文字
scanUtils(stopTag){
let pos_backup = this.pos
//当尾巴的开头不是stopTag的时候,就说明还没有扫描stopTag
while(this.tail.indexOf(stopTag)!=0 && !this.eos()){
this.pos++
//改变尾巴为从当前指针这个字符开始,到后面全部字符
this.tail = this.templateStr.substring(this.pos)
}
return this.templateStr.substring(pos_backup,this.pos)
}
//判断指针是否到头,返回布尔值
eos(){
return this.pos>=this.templateStr.length
}
}
三.生成tokens数组
新建一个parseTemplateToTokens.js文件,向外暴露一个函数,函数名为parseTemplateToTokens,接收的参数为templateStr
var tokens = []
var words;
扫描器类已经准备好,接下来就让扫描器干活!
var scanner = new Scanner(templateStr)
思想:
当this.pos<this.templateStr.length时,先执行scanner.scanUtils('{{'),返回一个word用于收集开始标记出现之前的文字;接着判断文字内容不为空的话就push到tokens数组里,push的内容为['text',words],然后过双大括号scanner.scan("{{"),执行到这一步,意味着咱们进入到了 {{ 里,接着?words?=?scanner.scanUtils('}}'),这里的words就是{{}}中间的东西;接着判断一下首字符words[0],如果为#,tokens.push(['#',words.substring(1)]);如果为/,tokens.push(['/',words.substring(1)]);否则就返回tokens.push(['name',words]),接着过掉双大括号scanner.scan('}}'),最后就返回token。
import Scanner from './Scanner'
import nestTokens from './nestTokens'
export default function parseTemplateToTokens(templateStr){
var tokens = [];
var words ;
//创建扫描器
var scanner = new Scanner(templateStr)
//让扫描器工作
while(!scanner.eos()){
//收集开始标记出现之前的文字
words = scanner.scanUtils('{{')
//存起来
if(words!=''){
tokens.push(['text',words])
}
//过双大括号
scanner.scan("{{")
//收集开始标记出现之前的文字
words = scanner.scanUtils('}}')
//这里的words就是{{}}中间的东西,接着判断一下首字符
if(words!=""){
if(words[0]=='#'){
//存起来,从下标为1的项开始存,因为下标为0的项是#
tokens.push(['#',words.substring(1)])
}else if(words[0]=='/'){
//存起来,从下标为1的项开始存,因为下标为0的项是/
tokens.push(['/',words.substring(1)])
}else{
tokens.push(['name',words])
}
}
//过双大括号
scanner.scan('}}')
}
return tokens;
//return nestTokens(tokens);
}
四.将零散的tokens嵌套起来
新建一个nestTokens.js,向外暴露一个函数,函数名为nestTokens,接收的参数为tokens
?var?nestTokens?=?[];
??var?sections?=?[];
??var?collector?=?nestTokens;//这一步是关键,利用引用类型传递
思想:
利用栈(sections)先进后出的特点,遍历所有的tokens,
遇到一个 #,就把当前 token 放入这个栈中,让 collector 指向这个 token 的第三个元素。遇到下一个 # 就把新的 token 放入栈中,collector 指向新的 token 的第三个元素。遇到 / 就把栈顶的 token 移出栈,collector 指向移出完后的栈顶 token。
利用了栈的先进后出的特点,保证了遍历的每个 token 都能放在正确的地方,也就是 collector 都能指向正确的地址。
export default function nestTokens(tokens){
var nestTokens = [];
var sections = [];
var collector = nestTokens;
var token;
for(let i=0;i<tokens.length;i++){
token = tokens[i];
switch(token[0]){
case '#':
collector.push(token);
sections.push(token);
collector = token[2] = [];
break;
case '/':
sections.pop();
collector = sections.length>0?sections[sections.length-1][2]:nestTokens
break;
default:
collector.push(token)
break;
}
}
return nestTokens
}
|