mosikoo / blog Goto Github PK
View Code? Open in Web Editor NEWblog
blog
AST type类型: 首先要理解抽象语法树中的类型
babel插件手册: 手册中陈述了大部分插件知识
babel处理步骤
babylon.parse(code, options)
生成AST, 不用去了解怎么获取AST,但是一定要能看懂AST内容(简单说,给你一段code,你能写出大概AST结构)babel-generator
生成代码babel-types: 类似Lodash库,提供节点的方法,用的比较多的方法如下
types.isXXX
, 如t.isIdentifier(path.node, { name: 'xxx'})
types.xxExpression
, 如新建函数调用表达式t.callExpression()
path: 表示两节之间连接的对象,path的方法及属性很多,主要方法在这里,下面是一些常用的方法及属性
isMemberExpression
表示对象成员表达式path.node
表示该节点属性path.parentPath
, or functionpath.findParent((path) => path.isXX())
path.replaceWith()
, path.replaceWithMultiple()
,path.replaceWithSourceString()
,path.insertBefore()
, path.insertAfter()
,path.remove()
path.skip()
, path.stop()
export default function ({ types: t }) { // 第一个参数为当前babel对象
const arr = [];
return {
visitor: { // visitor: 插件的主要访问者
Identifier(path, state) { // path
// 遍历Identifier节点
// 打印所有的id名
console.log(path.node.name);
},
FunctionDeclaration(path, state) {
// 函数申明节点
},
... // 其他任何expression
}
}
要求:将Revo.aaa.bbb.ccc.ddd({params})转换成Revo('/aaa/bbb/ccc/ddd')({params})
A.对id为Revo的节点进行下手
B.递归寻找父节点,找到相应的MemberExpression
C.替换该节点
visitor: {
Identifier(path, state) {
function recursion(path2) {
const parentPath = path2.parentPath;
if (parentPath && parentPath.isMemberExpression()) {
const prop = parentPath.node.property.name;
arr.push(prop);
recursion(parentPath);
return;
}
if (!arr.length) {
return;
}
// 此处为B,找到相应要替换的节点
path2.replaceWith( // 此处为C,进行替换
t.callExpression(
t.identifier('Revo'),
[t.stringLiteral(`/${arr.join('/')}${state.opts.addSuffix ? '.json' : ''}`)]
)
);
path.stop();
}
if (t.isIdentifier(path.node, { name: 'Revo'})) { // 此处为A
recursion(path);
}
}
}
详情demo可参考这里
司徒正美写的迷你React框架,通过学习它的源码可以简单的学习下react**
本文分析的是1.1.3版本, 源码在这里。
本文主要是为了读者理清源代码的思路,没去了解的anu源码的同学可能很难读懂
主要是创建虚拟DOM
如下所示接收type
, props
, children
,处理props
中的ref
及key
之后new Vnode(type, props, children)
创建虚拟节点(对象)。
type
可以为'div'
等节点字符串,也可以为Component(class或stateLess function)
<div {...props}>
{children}
</div>
// babel转义后
createElement(type, props, children)
cloneElement(vnode, props)
覆盖vnode中老的props,其中key值和ref需要单独处理,然后得到新的vnode
将大部分事件绑定到document进行委托
比如点击事件,点击触发时,从target到上层document收集绑定该事件的dom节点,
根据冒泡或捕获依次触发绑定的fn,执行完事件后会触发flushUpdater
(如果执行过setState
会刷新组件)
props更新在与每次dom的mount或update之后,将props更新分style
, event
, class
, property
,依次分别更新即可
初始化props
, context
, refs
, state
, 最重要的是为了继承它的setState。setState
机制后续再去讲
对Componnet的二次封装,绑定除生命周期函数外的函数的this
为当前Component的this, 处理mixin
从Component中继承, 对shouldComponentUpdate
的props或state进行浅比较
初次渲染的过程比较简单(不考虑ssr)
createTextNode
建立Text节点并返回。drainQueue
中运行(drainQueue(queue)
)。 这个函数会遍历队列,从里至外绑定ref,调用componentDidMount
函数,有二次渲染则进行二次渲染。所以渲染的过程就是各种递归调用,到最后总结收尾。
组件为什么更新?无非是调用了setState触发了更新,且父组件带动子组件更新。调用setState时,当updater._renderInNextCycle
被设为true时,则在最后drainQueue中会触发refreshComponent
刷新组件。
setState调用分以下几种情况(update时期)
updater._renderInNextCycle = true
,调用钩子函数存储脏组件,然后一次性调用drainQueue方法进行更新。updater._renderInNextCycle = true
,在最后drainQueue被调用时进行更新。drainQueue
。anu中用于更新component的方法是refreshComponent(updater, queue)
,updater为更新组件的更新器,queue为更新器队列。setState都会以各种方式触发refreshComponent
,或者是钩子函数(存储脏组件,然后一次性更新),或者生命周期末本身就带有是否二次render的检查。
所以我们可以暂定refreshComponent
为入口,了解react刷新组件的过程。
流程图如下所示
流程讲解
首先得明白refreshComponent
用于更新当前的组件, 生命周期从shouldComponentUpdate
到componentDidUpdate
。
updateComponent
用于更新一个全新的组件,比如父组件更新,其子组件从componentWillReceiveProps
开始渲染,到render
部分,接着调用refreshComponent
,才会完整的调用整个更新的生命周期。
以refreshComponent
为入口。
1.组件执行render前的更新生命周期钩子,然后更新render
部分(这里是个递归的过程),会进入alignVnode(方法)进入Vnode更新流程。
2.Vnode更新流程,又可更渲染部分一样分三种:Text,Element,Component.
3.Text直接替换就行
4.Element,采用diff算法,type不相等则直接删除重新mount,否则回到第二点(UpdateVnode)。
5.调用componentWillReceiveProps
,更新新的Vnode,Vparent,Props,context等属性,调用refreshComponent
(回到了第一点)。递归完了后在push(Updater),后续有用。
6.等所有更新流程走完了之后调用drainQueue
,绑定ref,调用componentDidUpdate
,如果存在二次渲染,则再次进入1。
至此更新部分也讲完,更新与渲染基本差不多,只是多了diff比较的过程。
react的代码与这还是有很大的出入的,虽然实现方式不一样,表达的**却没有很多的落差,熟悉其渲染及更新过程能更好的帮助我们了解react的**
是webpack中compiler的底层封装,是事件发布订阅执行的插件架构
简单模拟
function MyClass() {
Tapable.call(this);
}
MyClass.prototype = Object.create(Tapable.prototype);
const compiler = new MyClass();
compiler.plugin('emit', function() {
}); // 类似于观察者事件中的on
plugin: 类似于定语-subscribe
applyPlugins: 类似于发布-publish
applyPluginsWaterfall: 瀑布串行的方式,上次执行结果作为下个函数的实参(只是替换init参数,后面参数不变),返回最终结果
applyPluginsBailResult: 返回值不是undefined
便返回中断函数
applyPluginsAsync
Tapable.prototype.applyPluginsAsyncSeries = Tapable.prototype.applyPluginsAsync = function applyPluginsAsyncSeries(name) {
var args = Array.prototype.slice.call(arguments, 1);
var callback = args.pop();
var plugins = this._plugins[name];
if(!plugins || plugins.length === 0) return callback();
var i = 0;
var _this = this;
args.push(copyProperties(callback, function next(err) {
if(err) return callback(err);
i++;
if(i >= plugins.length) {
return callback();
}
plugins[i].apply(_this, args);
}));
// 最后一个参数为next()函数,需要在plugin中定义next才会执行下一个plugin,这就是异步
plugins[0].apply(this, args);
};
applyPluginsAsyncSeriesBailResult: 与applyPluginsAsync
差不多,next
函数有参数便中断执行,返回结果
applyPluginsAsyncWaterfall(name, init, next): 异步串行,value为第二个参数会代替init的值
applyPluginsParallel: 异步并行,remaining为0时说明所有异步执行完毕
applyPluginsParallelBailResult: 参数会依次增加,调用最后一个参数执行回调,回调的参数不为空的时候,会执行callback,且后续不会执行
如react
与react-dom
// webpack.config.js
module.exports = {
...
externals: {
react: 'React',
'react-dom': 'ReactDOM'
}
}
如常用的库loadsh
有时候只用lodash
中的一个深拷贝函数_.cloneDeep
.
调用方法应该是
import cloneDeep from lodash-cloneeep
而不是
import cloneDeep from lodash/lib/cloneDeep
如果一些工具或组件库没有做分离,则会将整个包打包进来,影响整个bundle大小
webpack 加上--display-modules --sort-modules-by size
参数
让chunks从小到大进行排列,可以看出哪些chunk体积较大,进行针对性优化
通过DllPlugin来前置第三方包的构建
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, "js", "[name]-manifest.json"),
name: "[name]_[hash]"
})
]
配置如下
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: {
main: './index.js',
// 公共文件
vendors: ['react', 'react-dom'],
// 要提取的css放入一个js中
'ct.style': './src/style.js'
}
}
plugins: [
new webpack.optimize.CommonChunkPlugin({
name: 'vendors',
filename: 'vendors.js',
minChunks: 2
}),
new ExtractTextPlugin({
filename: 'style.css',
allChunks: true
})
]
babel-loader加cacheDirectory
参数进行缓存,减少rebuild时间
modules:{
rules: [
{ use: ['babel-loader?cacheDirectory'] }
]
}
虽然目前requireJs已经不再流行,但是它的**在前端模块化历程中还是值得学习的。
翻看requireJs的源码,个人觉得基本的难点在于:
所以本篇主要围绕着这两点去讲,理清基本的思路(抛开config各种配置)
默认创建context
,调用context.makeRequire()
返回require
除了初始化的req({})
及req(cgf)
;其他js执行过程都是require(deps, callback) —>{script监听事件context.onScriptLoad
—> context.completeLoad(data.id);
—> 获取模块且初始化} —> 执行nextTick
基本用法最后只有一步 globalDefQueue.push([name, deps, callback]);
,name不定义为null
,
然后执行script监听事件context.onScriptLoad
defContextName = '-',config不配置context,都只用这一个context;
变量介绍:
context = {
config: {}, // newContext内定义
contextName: '_',
registry: {}, //已注册模块 registry.[id] = new Module(depMap)
defined: defined, // 函数defined中注册的方法,保存其结果 defined[id] = exports;
urlFetched: urlFetched, // {} urlFetched[url] = true; 表示这个url已经加载过了
defQueue: defQueue, // [], 定义的队列 --
defQueue.push([name, deps, callback]); this.defined函数中
defQueueMap: {}, 已定义队列所对应的map ,defQueueMap[name] = true;
Module: Module,
makeModuleMap: makeModuleMap,
nextTick: req.nextTick,
onError: onError,
configure(){}, // 配置config
makeShimExports() {},
makeRequire(){}, // 入口,返回localRequire
enable(){},
completeLoad(){},
nameToUrl(){},
load(){},
execCb(){},
onScriptLoad(){},
onScriptError(){},
}
this.events = getOwn(undefEvents, map.id) || {};
this.map = map; // 基本信息
this.shim = getOwn(config.shim, map.id);
this.depExports = []; // 依赖包的结果存储在这里
this.depMaps = []; // 依赖包的映射信息
this.depMatched = []; 被匹配到了为true,与depExports成对应
this.pluginMaps = {};
this.depCount = 0; // 依赖模块数量
this.factory // callback
this.errback = errback;
this.inited
this.check
this.exports //本身输出
状态
流程图
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.