awxxxxxx / awxxxxxx.github.io Goto Github PK
View Code? Open in Web Editor NEWblog
blog
Electron is framework which lets developers build cross-platfom applications using web technologies like HTML, CSS, Javascript/TypeScript effectively.
Electron builds upon chromium, node and v8. Chromium takes charges of the render. Node supplies other abilities like file system, network access.
Below is the structure of the Electron.
Electron
├── build/ - Build configuration files needed to build with GN.
├── buildflags/ - Determines the set of features that can be conditionally built.
├── chromium_src/ - Source code copied from Chromium that isn't part of the content layer.
├── default_app/ - A default app run when Electron is started without
| providing a consumer app.
|
├── docs/ - Electron's documentation.
|
├── lib/ - JavaScript/TypeScript source code.
| ├── browser/ - Main process initialization code.
| | ├── api/ - API implementation for main process modules.
| | └── remote/ - Code related to the remote module as it is
| | used in the main process.
| ├── common/ - Relating to logic needed by both main and renderer processes.
| | └── api/ - API implementation for modules that can be used in
| | both the main and renderer processes
| ├── isolated_renderer/ - Handles creation of isolated renderer processes when
| | contextIsolation is enabled.
| ├── renderer/ - Renderer process initialization code.
| | ├── api/ - API implementation for renderer process modules.
| | ├── extension/ - Code related to use of Chrome Extensions
| | | in Electron's renderer process.
| | ├── remote/ - Logic that handles use of the remote module in
| | | the main process.
| | └── web-view/ - Logic that handles the use of webviews in the
| | renderer process.
| ├── sandboxed_renderer/ - Logic that handles creation of sandboxed renderer
| | | processes.
| | └── api/ - API implementation for sandboxed renderer processes.
| └── worker/ - Logic that handles proper functionality of Node.js
| environments in Web Workers.
├── patches/ - Patches applied on top of Electron's core dependencies
| | in order to handle differences between our use cases and
| | default functionality.
| ├── boringssl/ - Patches applied to Google's fork of OpenSSL, BoringSSL.
| ├── chromium/ - Patches applied to Chromium.
| ├── node/ - Patches applied on top of Node.js.
| └── v8/ - Patches applied on top of Google's V8 engine.
├── shell/ - C++ source code.
| ├── app/ - System entry code.
| ├── browser/ - The frontend including the main window, UI, and all of the
| | | main process things. This talks to the renderer to manage web
| | | pages.
| | ├── ui/ - Implementation of UI stuff for different platforms.
| | | ├── cocoa/ - Cocoa specific source code.
| | | ├── win/ - Windows GUI specific source code.
| | | └── x/ - X11 specific source code.
| | ├── api/ - The implementation of the main process APIs.
| | ├── net/ - Network related code.
| | ├── mac/ - Mac specific Objective-C source code.
| | └── resources/ - Icons, platform-dependent files, etc.
| ├── renderer/ - Code that runs in renderer process.
| | └── api/ - The implementation of renderer process APIs.
| └── common/ - Code that used by both the main and renderer processes,
| | including some utility functions and code to integrate node's
| | message loop into Chromium's message loop.
| └── api/ - The implementation of common APIs, and foundations of
| Electron's built-in modules.
├── spec/ - Components of Electron's test suite run in the renderer process.
├── spec-main/ - Components of Electron's test suite run in the main process.
└── BUILD.gn - Building rules of Electron.
|── npm - Using when installing via npm
|── script - scripts for developing like building, testing
|── typings - typescript typings
|── vendor - source code for third party
Electron Architecture
So, you are using Vue. It's an elegant and concise JavaScript Framework, right?
After building many projects with Vue, I realized it's time to dig into the Vue. Digging into the source code lets you understand the design philosophy behand it and avoid falling into the trap.
As the saying goes, 'You cannot make bricks without straw', there are some preparatory work before digging.
Objective
. In this article, I assume you are familiar with Vue. You should travel with your objective, may be it's how Vue's reactive system or Vue.compiler
works. With objective, you can quickly find what you want.The latest Vue source code you are using
. Vue has four versions listed in the official site, all of which are significantly different, and each version may be fairly different from early version. So, using lastest verson ensures we can follow the steps. In this article, we are at 2.5.17-beta.0
Notes and Drawing Tool
Vue has 191 files(about 13430 lines) in src
folder at lastest dev branch. Taking notes and drawing diagrams can help you keep context when switching in many files and clarify your mind.Are you ready? Let's go.
Blow is the overview about src folder.
-src
|
|-- compiler
| |-- codegen
| |-- directives
| |-- parser
| |-- create-compiler.js
| |-- error-detector.js
| |-- heplers.js
| |-- index.js
| |-- optimizer.js
|-- core
| |-- components
| |-- global-api // add global api to Vue
| |-- instance // vue instance
| |-- observer
| |-- util
| |-- vdom
| |-- config.js
| |-- index.js
|-- platforms
| |-- web
| |-- entry-compiler.js
| |-- entry-runtime-with-compiler.js // generate vue includes runtime and compiler
| |-- entry-runtime.js // generate vue only inludes runtime
| |-- entry-server-basic-renderer.js
| |-- entry-server-renderer.js
| |-- weex
| |-- entry-compiler.js
| |-- entry-framework.js
| |-- entry-runtime-factory.js
|-- server
|-- sfc
|-- shared
Each vue component is a vue instance.
In src/core/instance/index.js
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
It invokes five methods to do something.
initMixin
as the name suggests 'init', it inits events attached to parent, lifycycle, state, provider, render function.stateMixin
adds $data
, $props
, $set
, $delete
and $watch
to instance.eventsMixin
adds $on
, $once
, $off
and $emit
to instancelifecycleMixin
adds _update
, $forceUpdate
and $destroy
to instancerenderMixin
add render helpers, nextTick
and _render
to instance.页面搭建是许多团队在做的事情,平时或多或少也会了解相关的信息,例如阿里的云凤蝶和飞冰。由于近期工作的需要,便多花了点时间在这个方面调研。本文是参加某次关于页面搭建分享的流水帐也是对近期调研的一个总结。
这场分享主讲的是使用 schema 定义数据,在 json schema 的基础上扩展所需要的业务字段。推导数据结构的流程:业务 -> 转化为数据 -> 数据转化为定义 -> 定义规范成结构。推导流程值得参考。
上一场是组件的协议,那么这一场则是制定搭建系统的协议了
该产品的定位很明确,制定搭建规范,为搭建平台提供底层基础服务能力的支持。例如定制化的搭建编辑器。
现在业界许多公司都在做搭建系统,如果有一个统一的规范,基于这个规范扩展生态,搭建系统估计会更加繁荣。
这场分享的核心是如何设计数据模型,针对的运营 H5 页面这个场景。
「数据模型」从页面这个维度出发,包含页面模型和请求模型这两大部分。
在请求优化这一层中,设立数据中心,所有请求通过数据中心。
页面的组件可能使用的某一个接口的不同数据,发多次请求是会造成资源的浪费和多余的性能开销,引入数据中心和请求中心设计,可以合并聚合相关的请求,避免重复请求。
讲师也在他们的博客中写了相关的文章介绍,感兴趣可以移步 如何设计实现 h5 页面搭建-数据模型
这一场分享的是使用人工智能,根据设计稿直接生成页面代码,这一场分享的入门门槛很高,如果没有相关的前期了解和实战经验,会是云里雾里一般
智能生成的核心技术难点包括:
这场分享,介绍的是面向营销的系统搭建,着重分享了在终端的优化。
总结起来就是从客户端缓存 + 请求模块两个方面并行优化。
定义了一套模块协议,开发者使用约定的协议开发模块,供搭建使用。
从这里可以看出 schema 是各个搭建系统使用的主流数据结构了。
最后一场的分享主讲海量页面的管理部署了。该套系统面向的是 B 端,实现了一套部署-部署-监控平台
在接连听了七场关于搭建的介绍后,对搭建系统有了一个全新的认识。做搭建系统需要从诸多方面考量:
如果有更多关于搭建相关的想法,欢迎和我交流。
There is a question, how can I update UI conveniently when data changed and vice versa. Many MVVM
JavaScript frameworks/Libraries are designed to sovle it. Vue is the member of MVVM family.
Object.defineProperty
is the fundament of Vue reactive system.
According to MDN/ECMA, Object.defineProperty can set get
and set
method to a object’s property. So, we can get notification when object’s property was accessed or assigned.
In a typcial vue application, we put data in the data methods which should always return a new object. In last post we covered that initMixin
will init many things, one of them is initData
. initData
tries to get data property and invoke to get data. After that, Object.defineProperty
is called to make the data rective.
In src/core/instance/init.js
export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm)
initState(vm) // will init props/methods/data
initProvide(vm)
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
In src/core/instance/state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm) // will get and observer data
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
In src/core/observer/index.js
/**
* Attempt to create an observer instance for a value,
* returns the new observer if successfully observed,
* or the existing observer if the value already has one.
*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
/**
* Observer class that is attached to each observed
* object. Once attached, the observer converts the target
* object's property keys into getter/setters that
* collect dependencies and dispatch updates.
*/
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
...
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
// ignore un-configurable property
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val) // observe value recursively
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// collect dep
if (Dep.target) {
// will call Dep.target.addDep(this)
// Dep.target is the current Watcher
// And then will call dep.addSub(Watcher)
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
// ingore unchanged value
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
// notify watchers data was updated
dep.notify()
}
})
}
The call chain consists of initMixin -> initState -> initData -> observe -> defineReactive
. Here is another question, why does vue collect dependencies? Because Vue need to know which( aka Watcher
) is using this data. When data changed, watchers will get notification by dep.notify()
and do update.
A dep is a bridge between Watcher
and Observer
. An obsever has a dep, a watcher may have many deps, a dep also may have many watchers.
In src/core/observer/dep.js
export default class Dep {
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
const targetStack = []
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
In last section, we covered a terminology Watcher
. Watcher is a Subscriber, subcribes data and fires callback when data changes.
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.computed = !!options.computed
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.computed = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.computed // for computed watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
if (this.computed) {
this.value = undefined
this.dep = new Dep()
} else {
this.value = this.get()
}
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get () {}
/**
* Add a dependency to this directive.
*/
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps () {}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run () {
if (this.active) {
this.getAndInvoke(this.cb)
}
}
getAndInvoke (cb: Function) {}
/**
* Evaluate and return the value of the watcher.
* This only gets called for computed property watchers.
*/
evaluate () {}
/**
* Depend on this watcher. Only for computed property watchers.
*/
depend () {
if (this.dep && Dep.target) {
this.dep.depend()
}
}
/**
* Remove self from all dependencies' subscriber list.
*/
teardown () {}
}
Collecting or Re-collecting will happened in Watcher.get
get () {
// Dep.target = this
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
The relationship between the three looks like the blow diagram.
Recently, I'm working on a tool named vue23 that aims to migrate vue2 project to vue3 automatically.
Currently, it supports wrap variables hosted in data
with reactive
method and rename vue2 lifecycle hooks. Next it will support computed
, watch
, provide & inject
and typescript
.
vue3 comes up with composition api which is significantly different with vue2. Migrating existing project to vue3 one by one manually may take huge works. This tool can help developers automatically migrating and say no to 996.
vue23 is built up on babel, vue-template-compiler and typescript. The basic workflow likes below digram.
To complete this post, you may need some basic knowledge about AST and babel
vue2 puts all reactive variables into data
and uses object.defineproperty
to track dependencies.
In vue3, variables has to be wrapped with reactive
method and host in setup
lifecycle. To archive this, we can modify AST using @babel/traverse
. @babel/traverse
supplies traverse method that walks the whole AST tree using visitor pattern, so we can modify, replace, remove AST nodes conveniently when visiting.
Here is the core code about migrating reative.
{
ReturnStatement(path) {
const parent = path.getFunctionParent();
if (t.isObjectMethod(parent.node)) {
const gp = parent as NodePath<t.ObjectMethod>;
setupNode = gp;
if (t.isIdentifier(gp.node.key, { name: KeyWords.Data})) {
// rename 'data' to 'setup'
gp.node.key.name = KeyWords.Setup;
setupNode = gp;
// @TODO extract to another function
// make varibale reactivity
if (t.isIdentifier(path.node.argument)) {
const n = resolveTopIdentifier(path.node.argument.name, path)
if (n?.isVariableDeclarator()) {
let args = [];
if (n.node.init) {
args.push(n.node.init);
}
const call = wrapWithReactive(args)
const v = t.variableDeclarator(n.node.id, call);
n.replaceWith(v);
}
options.reactive = true;
} else if (path.node.argument) {
const call = wrapWithReactive([path.node.argument])
if (path.scope.hasOwnBinding('state')) {
path.scope.rename('state')
}
let re;
if (t.isBlockStatement(path.parentPath.node)) {
const nstateIdentifier = t.identifier('state');
gp.scope.push({ id: nstateIdentifier, init: call, kind: 'const' })
re = t.returnStatement(
t.objectExpression([t.objectProperty(nstateIdentifier, nstateIdentifier)])
);
} else {
re = t.returnStatement(
t.objectExpression([t.objectProperty(call, call)])
);
}
options.reactive = true;
path.replaceWith(re);
}
}
}
}
}
As you can see, we do the modification in ReturnStatement
, in this method we have to check whether the ReturnStatement
is hosted in data
method and wrap the returned obj with reactive
. Also we need to handle edge cases like the return statement yields a variable b
, b
references another variable a
which is an object. In this case we have to wrap a
rather than b
.
Update lifecycle hooks is quite straight. We can break down it into two steps.
{
Program: {
exit(path) {
importDependencies(path, options);
options = defaultImportOptions;
if (lifecycleHooksPath.length) {
const node = setupNode ? setupNode.node : generateSetupNode(lifecycleHooksPath[0].path);
const exps = lifecycleHooksPath.reduce((previous, current) => {
return previous.concat(current.exps);
}, [] as t.Statement[]);
if (t.isObjectMethod(node)) {
insertStatements(node.body.body, exps)
} else if (t.isObjectProperty(node) && t.isFunctionExpression(node.value)) {
const value = node.value as t.FunctionExpression;
insertStatements(value.body.body, exps)
}
lifecycleHooksPath = []
}
},
},
ObjectMethod(path) {
if (isLifecycleHook(path.node.key.name)) {
lifecycleHooksPath.push({ exps: convertHook(path), path });
}
},
ObjectProperty(path) {
if (isLifecycleHook(path.node.key.name) && t.isFunctionExpression(path.node.value)) {
lifecycleHooksPath.push({ exps: convertHook(path), path });
}
},
}
As above, we use ObjectMethod
and ObjectProperty
methods to collect lifecycle hooks, insert lifecycle hooks into setup
in Program
method.
All above is the basic the intro about vue23. If you are interested in this project, please feel free to contact me.
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.