functioncreateWindow() { // 加载UI代码 const mainWindow = newBrowserWindow({ fullscreen: true, // 全屏窗口 // kiosk: true, // 服务亭模式 // frame: false, // 是否显示窗口边缘框架 // resizable: false, // 不可更改窗口尺寸 // maximizable: true, // 支持最大化 // show: false, // 为了让初始化窗口显示无闪烁,先关闭显示,等待加载完成后再显示。 // width: 800, // height: 600, // ...windowOpenConfig, webPreferences: { // 用于读取本地语音文件 webSecurity: false, // 是否能能在渲染页面中使用node模块 nodeIntegration: true, nodeIntegrationInWorker: true, // contextBridge usage 开启后打开沙盒模式 无法直接通过preload挂在在global上 // contextIsolation: true, preload: path.join(__dirname, 'preload.js'), } }) // 加载html代码 也可以是http链接 可以是个单页应用服务入口url mainWindow.loadFile('./demo.html') // mainWindow.loadURL('http://localhost:3000/demo.html') } // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.whenReady().then(() => { createWindow() // 隐藏系统菜单 Menu.setApplicationMenu(null)
app.on('activate', function () { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) createWindow() }) })
// Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. app.on('window-all-closed', function () { systemLogger.info('主程序关闭') // 手动销毁子进程 经测试子进程内的socket会自动随子进程一起销毁 if (process.platform !== 'darwin') { app.quit() } })
preload.js 负责与渲染进程进行通信
1 2 3 4 5 6 7 8 9 10 11 12 13
/** * The preload script runs before. It has access to web APIs * as well as Electron's renderer process modules and some * polyfilled Node.js functions. * * https://www.electronjs.org/docs/latest/tutorial/sandbox */ const { contextBridge, ipcRenderer } = require('electron')
functionmodel ( el, dir, _warn ) { warn$1 = _warn; var value = dir.value; var modifiers = dir.modifiers; var tag = el.tag; var type = el.attrsMap.type;
{ // inputs with type="file" are read only and setting the input's // value will throw an error. if (tag === 'input' && type === 'file') { warn$1( "<" + (el.tag) + " v-model=\"" + value + "\" type=\"file\">:\n" + "File inputs are read only. Use a v-on:change listener instead.", el.rawAttrsMap['v-model'] ); } }
if (el.component) { genComponentModel(el, value, modifiers); // component v-model doesn't need extra runtime returnfalse } elseif (tag === 'select') { genSelect(el, value, modifiers); } elseif (tag === 'input' && type === 'checkbox') { genCheckboxModel(el, value, modifiers); } elseif (tag === 'input' && type === 'radio') { genRadioModel(el, value, modifiers); } elseif (tag === 'input' || tag === 'textarea') { genDefaultModel(el, value, modifiers); } elseif (!config.isReservedTag(tag)) { genComponentModel(el, value, modifiers); // component v-model doesn't need extra runtime returnfalse } else { warn$1( "<" + (el.tag) + " v-model=\"" + value + "\">: " + "v-model is not supported on this element type. " + 'If you are working with contenteditable, it\'s recommended to ' + 'wrap a library dedicated for that purpose inside a custom component.', el.rawAttrsMap['v-model'] ); }
functiongenDefaultModel ( el, value, modifiers ) { var type = el.attrsMap.type;
// warn if v-bind:value conflicts with v-model // except for inputs with v-bind:type { var value$1 = el.attrsMap['v-bind:value'] || el.attrsMap[':value']; var typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type']; if (value$1 && !typeBinding) { var binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'; warn$1( binding + "=\"" + value$1 + "\" conflicts with v-model on the same element " + 'because the latter already expands to a value binding internally', el.rawAttrsMap[binding] ); } }
var ref = modifiers || {}; var lazy = ref.lazy; var number = ref.number; var trim = ref.trim; var needCompositionGuard = !lazy && type !== 'range'; var event = lazy ? 'change' : type === 'range' ? RANGE_TOKEN : 'input';
var valueExpression = '$event.target.value'; if (trim) { valueExpression = "$event.target.value.trim()"; } if (number) { valueExpression = "_n(" + valueExpression + ")"; }
var code = genAssignmentCode(value, valueExpression); if (needCompositionGuard) { code = "if($event.target.composing)return;" + code; }
var computedWatcherOptions = { lazy: true }; functioninitComputed (vm, computed) { // $flow-disable-line var watchers = vm._computedWatchers = Object.create(null); // computed properties are just getters during SSR var isSSR = isServerRendering();
for (var key in computed) { var userDef = computed[key]; var getter = typeof userDef === 'function' ? userDef : userDef.get; if (process.env.NODE_ENV !== 'production' && getter == null) { warn( ("Getter is missing for computed property \"" + key + "\"."), vm ); } if (!isSSR) { // create internal watcher for the computed property. watchers[key] = newWatcher( vm, getter || noop, noop, computedWatcherOptions ); }
// component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef); } elseif (process.env.NODE_ENV !== 'production') { if (key in vm.$data) { warn(("The computed property \"" + key + "\" is already defined in data."), vm); } elseif (vm.$options.props && key in vm.$options.props) { warn(("The computed property \"" + key + "\" is already defined as a prop."), vm); } } } }
functionreactiveSetter (newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (customSetter) { customSetter(); } // #7981: for accessor properties without setter if (getter && !setter) { return } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); }
1 2 3 4 5 6 7 8 9 10 11 12 13
notify () { // stabilize the subscriber list first const subs = this.subs.slice() if (process.env.NODE_ENV !== 'production' && !config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort((a, b) => a.id - b.id) } for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } }
exportfunctionqueueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { has[id] = true if (!flushing) { queue.push(watcher) } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately. let i = queue.length - 1 while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // queue the flush if (!waiting) { waiting = true
// Sort queue before flush. // This ensures that: // 1. Components are updated from parent to child. (because parent is always // created before the child) // 2. A component's user watchers are run before its render watcher (because // user watchers are created before the render watcher) // 3. If a component is destroyed during a parent component's watcher run, // its watchers can be skipped. queue.sort(function (a, b) { return a.id - b.id; });
// do not cache length because more watchers might be pushed // as we run existing watchers for (index = 0; index < queue.length; index++) { watcher = queue[index]; if (watcher.before) { watcher.before(); } id = watcher.id; has[id] = null; watcher.run(); // in dev build, check and stop circular updates. if (has[id] != null) { circular[id] = (circular[id] || 0) + 1; if (circular[id] > MAX_UPDATE_COUNT) { warn( 'You may have an infinite update loop ' + ( watcher.user ? ("in watcher with expression \"" + (watcher.expression) + "\"") : "in a component render function." ), watcher.vm ); break } } }
// keep copies of post queues before resetting state var activatedQueue = activatedChildren.slice(); var updatedQueue = queue.slice();
resetSchedulerState();
// call component updated and activated hooks callActivatedHooks(activatedQueue); callUpdatedHooks(updatedQueue);
// devtool hook /* istanbul ignore if */ if (devtools && config.devtools) { devtools.emit('flush'); } }
run () { if (this.active) { const value = this.get() if ( value !== this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value if (this.user) { try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { this.cb.call(this.vm, value, oldValue) } } } }
/** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method var original = arrayProto[method]; def(arrayMethods, method, functionmutator () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ];
var result = original.apply(this, args); var ob = this.__ob__; var inserted; switch (method) { case'push': case'unshift': inserted = args; break case'splice': inserted = args.slice(2); break } if (inserted) { ob.observeArray(inserted); } // notify change ob.dep.notify(); return result }); }); functiondef (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }); }
Dep.prototype.notify = functionnotify () { // stabilize the subscriber list first var subs = this.subs.slice(); if (!config.async) { // subs aren't sorted in scheduler if not running async // we need to sort them now to make sure they fire in correct // order subs.sort(function (a, b) { return a.id - b.id; }); } for (var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } };
// The current target watcher being evaluated. // This is globally unique because only one watcher // can be evaluated at a time. Dep.target = null; var targetStack = [];
Watcher.prototype.get = functionget () { pushTarget(this); var value; var 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 };
看到pushTarget和popTarget是不是很眼熟,这不就是Dep里提供的两个方法嘛 这两个方法的作用就是形成一个闭包 value = this.getter.call(vm, vm) 这一步会触发被劫持数据的get钩子 此时Dep.target就是当前的watcher实例 所有触发getter的对象都会触发dep.depend() 如下:
1 2 3 4 5
depend () { if (Dep.target) { Dep.target.addDep(this) } }
进而触发当前watcher的addDep方法:
1 2 3 4 5 6 7 8 9 10
addDep (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) } } }
functionmountComponent ( vm, el, hydrating ) { vm.$el = el; if (!vm.$options.render) { vm.$options.render = createEmptyVNode; { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ); } else { warn( 'Failed to mount component: template or render function not defined.', vm ); } } } callHook(vm, 'beforeMount');
var updateComponent; /* istanbul ignore if */ if (config.performance && mark) { updateComponent = function () { var name = vm._name; var id = vm._uid; var startTag = "vue-perf-start:" + id; var endTag = "vue-perf-end:" + id;
mark(startTag); var vnode = vm._render(); mark(endTag); measure(("vue " + name + " render"), startTag, endTag);
// we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined newWatcher(vm, updateComponent, noop, { before: functionbefore () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } } }, true/* isRenderWatcher */); hydrating = false;
// manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true; callHook(vm, 'mounted'); } return vm }
服务端渲染(SSR: Server Side Rendering),用户请求服务器,服务器上直接生成 HTML 内容并返回给浏览器。服务器端渲染来,页面的内容是由 Server 端生成的。一般来说,服务器端渲染的页面交互能力有限,如果要实现复杂交互,还是要通过引入 JavaScript 文件来辅助实现。服务器端渲染这个概念,适用于任何后端语言。。
为什么要使用SSR技术
CSR 项目的 SEO 能力极弱,在搜索引擎中基本上不可能有好的排名。因为目前大多数搜索引擎主要识别的内容还是 HTML,对 JavaScript 文件内容的识别都还比较弱。如果一个项目的流量入口来自于搜索引擎,这个时候你使用 CSR 进行开发,就非常不合适了
SSR 的产生,主要就是为了解决上面所说的问题。在 React 中使用 SSR 技术,我们让 React 代码在服务器端先执行一次,使得用户下载的 HTML 已经包含了所有的页面展示内容,这样,页面展示的过程只需要经历一个 HTTP 请求周期,TTFP 时间得到一倍以上的缩减。
同时,由于 HTML 中已经包含了网页的所有内容,所以网页的 SEO 效果也会变的非常好。之后,我们让 React 代码在客户端再次执行,为 HTML 网页中的内容添加数据及事件的绑定,页面就具备了 React 的各种交互能力。
// The module cache var installedModules = {}; // object to store loaded and loading chunks // undefined = chunk not loaded, null = chunk preloaded/prefetched // Promise = chunk loading, 0 = chunk loaded var installedChunks = { "index": 0 }; // The require function function__webpack_require__(moduleId) { // Check if module is in cache if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // Create a new module (and put it into the cache) varmodule = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // Execute the module function modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // Flag the module as loaded module.l = true; // Return the exports of the module returnmodule.exports; }