以webpack为例来看微内核架构

微内核系统一般分为两个部分——核心系统和插件系统,这样就提供了很好的灵活性和可扩展性。 核心系统是最小可运行的模块,它提供的是通用逻辑(比如Tapable),而插件系统这是些具体的逻辑(比如HtmlWebpackPlugin插件)。 比如系统怎么跑起来,插件之间怎么通信等模块都属于核心系统里面,让系统更加丰富多彩就是插件系统了。 插件之间可以相互独立,也可以有依赖,比如webpack的ModernModelPlugin插件就依赖HtmlWebpackPlugin插件。 核心系统怎么知道哪些插件可用呢? 这就需要注册表了,其实我们的webpack.config.js就起到了这样的作用,它告诉我们需要使用哪些插件。 插件系统和核心系统怎么通信呢? 这就需要用到Tapable了,里面有各种hooks,并且在运行各个生命周期过程中会执行对应的回调了。我们的核心系统有生命周期的概念,插件里面也有,因为它们的架构是类似的。 我们的核心系统在运行后会先读取注册表信息,这个过程其实就是订阅事件,注册回调的过程。插件可以在运行回调的过程中再不断订阅自己需要的其它事件,注册其它回调。ModernModelPlugin插件就订阅HtmlWebpackPlugin插件的某些事件,注册自己的回调的。 服务于具体逻辑的插件模块是独立于核心系统之外的,但是他可能会需要操作核心模块的系统服务来实现这些定制化功能,此时核心系统需要提供一个上下文对象(Context),当然,插件模块与外部进行交互只允许通过此上下文对象完成。上下文对象提供了基础操作(调起其他插件模块、调起系统服务、获取系统信息)的API和事件。 这样待核心系统的生命周期顺序执行的过程,也就伴随这对应时期的插件的生命周期交替执行,生命周期走完了,整个程序流程也就结束了。

道招网终于完成改版了

这个清明节放假三天,由于疫情还没有完全退去,大家还不太适合大规模聚集,更重要的是我本来也爱宅着不动。放假前就决定这三天干点正事,于是就想起把之前立的flag扶一下,把道招网的vue服务端渲染版彻底上线,之前的flag就是把将道招网的wordpress只作为后台管理用,前端页面改用自己更加得心应手的vue,2019年下半年就把道招网常用的几个接口的graphql接口写好了,但是当时由于服务端渲染后感觉排版后有点问题和工作调整等事情凑在一起就暂时没有继续弄了。4月4日就优先解决排版的问题,最后发现这个跟vue的ssr没什么关系,主要是我当时希望列表页能够展示一部分正文内容,但是因为WordPress的正文内容在数据库存储的是html格式,当时的截取过于粗暴,简单截取了前200个字符串,并且为了让样式html生效,我直接使用了v-html指令,最终导致页面的分页器渲染出现了问题:在#app这个div外面居然重复渲染了一次,渲染后端页面大致如下 最外面的这个分页器纯粹是多余的。 第一个问题结局了,接下来就要看怎么让vue服务端渲染的服务器接管原daozhao.com的前端了。 顺便说一下,之前设立这个flag的时候就考虑url的完全兼容,所以在写后端接口和设计前端路由就充分考虑了这一点,为的就是以后在不影响用户访问路径的情况下实现替换。 为了确保万无一失,我在主机另外一个目录又重新装了个新的wordpress来作为试验,因为之前wordpress用的服务器被我换成了强大的nginx,并且WordPress文章的固定链接用的是%post_id%.html(虽然这样对seo不够好),所以调整起来并不算麻烦,我只需要设置后相应的location就好。 .php结尾的文件路径,当然是走原来的php fastcgi 其它资源都在ssr的node服务器 最好WordPress原来文章的固定链接改成%post_id%.php,那样我们的文章既可以以vue方式访问,将url路径改成.php后又可以用WordPress来访问了,方便对比下文章排版 在新的WordPress登录实测过程发现了一点小问题 WordPress后台界面大量使用了js和css,如果又插件的话会更多,这个文件一般是放在wp-admin和wp-includes文件夹里面,这一部分资源也是要走原来的WordPress的 WordPress创建的比如www.daozhao.com/feed 等自定义路径也是要走WordPress的,除非你想在node服务器那边再弄一份 这样弄完再试试,如果主要流程没问题,没有发现有资源请求错误的,就可以直接切了。 既然现在flag里面有提到PWA,所以上线前一并把这一块弄好,之前练习过这部分,加上本身难度也不大,所以加上也很容易。现在道招网要变成缓存狂魔了,很缓存的尽量缓存了,哈哈。除了post请求外都缓存。 之前还没发现,原来WordPress编辑文章时需要请求的资源是那么的多,那样能不慢吗,原来这就是我一直不想更新的原因啊。 我就点进去新建文章wp-admin/post-new.php,就会有如此多的请求。再加上我平时都是把network里面的disable cache 勾上的,所以没有用缓存,速度可想而知了。 主要问题都解决了,可以上线了,道招网在2020年4月5日 21点20分左右切换成了vue服务端渲染 上线时用的扁平化风格常见的蓝色作为主色调,今天早上想了想,自己网站原来就是红色调风格,网站logo还有favicon全是红色调的,所以还是保留红色吧,改动太大怕老用户还以为走错了呢,其实对我而言换个背景色肯定比换图片容易啊。 现在网站相比以前还是有部分功能缺失的,后续完善。 分类信息那部分在优化接口数的时候改动导致没有用到服务端渲染 没有搜索功能 左侧菜单和右边的副栏内容过于寒酸 没有相关推荐和上一篇下一篇 通过router-link访问的时候只调用了接口,实际是没有请求整个页面的,导致PWA无法缓存这部分,目前右边的几篇是我用a标签实现的,可以用到PWA缓存。 上述几点也不在本次计划之内,今天不准备花太多精力搞这个了,此次清明节假期任务圆满完成,最后一天假期,出去溜达溜达。

webpack笔记——在html-webpack-plugin插件中提供给其它插件是使用的hooks

最近在这段时间刚好在温故下webpack源码,webpack5都出来了,4还不再学习下? 这次顺便学习下webpack的常用插件html-webpack-plugin。 发现这个插件里面还额外加入了自己的hooks,方便其它插件来实现自己的功能,不得不说作者真是个好人。 部分代码如下 // node_modules/html-webpack-plugin/index.js app(compiler) { // setup hooks for webpack 4 if (compiler.hooks) { compiler.hooks.compilation.tap(‘HtmlWebpackPluginHooks’, compilation => { const SyncWaterfallHook = require(‘tapable’).SyncWaterfallHook; const AsyncSeriesWaterfallHook = require(‘tapable’).AsyncSeriesWaterfallHook; compilation.hooks.htmlWebpackPluginAlterChunks = new SyncWaterfallHook([‘chunks’, ‘objectWithPluginRef’]); compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration = new AsyncSeriesWaterfallHook([‘pluginArgs’]); compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing = new AsyncSeriesWaterfallHook([‘pluginArgs’]); compilation.hooks.htmlWebpackPluginAlterAssetTags = new AsyncSeriesWaterfallHook([‘pluginArgs’]); compilation.hooks.htmlWebpackPluginAfterHtmlProcessing = new AsyncSeriesWaterfallHook([‘pluginArgs’]); Read more…

从vuecli3学习webpack记录(四)vue是怎么进行默认配置的

在我们讲到从vuecli3学习webpack记录(一)vue-cli-serve机制 vue cli3中在commands文件夹里面的是调用api.registerCommand方法,在config文件夹里面的(teserOptions.js和html除外)是调用api.chainWebpack方法,该方法会将传得的参数(该参数是一个方法)push到this.service.webpackChainFns数组。 今天就展开看看里面具体是什么。 this.service其实就是cli3里面的Service(node_modules/@vue/cli-service/lib/Service.js)的实例,通过api.registerCommand方法将对应的serve(就是npm run serve那个serve)等command加入到this.commands这个对象属性里面,通过api.chainWebpack方法将app、base等webpack配置加入到this.webpackChainFns这个数组属性里面。 上面的api其实是PluginApi(node_modules/@vue/cli-service/lib/PluginApi.js)的实例, 部分代码如下 // node_modules/@vue/cli-service/lib/PluginApi.js constructor (id, service) { this.id = id this.service = service } registerCommand (name, opts, fn) { if (typeof opts === ‘function’) { fn = opts opts = null } this.service.commands[name] = { fn, opts: opts || {}} } chainWebpack Read more…

webpack笔记——hook执行时call的是什么

我们一般使用的插件都是Hook子类,比如SyncHook,没有复杂的重写基类Hook的compile方法 先看Hook基类 // node_module/tapable/Hook.js class Hook { constructor(args) { if (!Array.isArray(args)) args = []; this._args = args; this.taps = []; this.interceptors = []; this.call = this._call; this.promise = this._promise; this.callAsync = this._callAsync; this._x = undefined; } compile(options) { throw new Error("Abstract: should be overriden"); } _createCall(type) { // 调用对应Hook的compile方法了 return Read more…

前端使用karma+mocha+babel+istanbul进行单元测试覆盖

先看看项目结构 这是一个vue项目,其中project位于当前项目的根目录下。 废话不多说,先上配置文件karma.conf.js const webpackConfig = require(‘./build/webpack.test.conf’); module.exports = function karmaConfig (config) { config.set({ browsers: [‘Chrome’], frameworks: [‘mocha’, ‘sinon-chai’], reporters: [‘spec’, ‘coverage’], files: [ ‘./test/unit/**/*.spec.js’, ], preprocessors: { ‘**/*.spec.js’: [‘webpack’, ‘sourcemap’] // 这些文件里面可能有es6等语法,所以需要先经过webpack处理下 }, webpack: webpackConfig, webpackMiddleware: { noInfo: true }, coverageReporter: { dir: ‘./test/unit/coverage’, includeAllSources: true, reporters: [ { Read more…

webpack引入UMD风格JS报错Cannot set property xxx of undefined

我们在项目(比如vue项目)中可能会引入一些umd风格的js库,比如wangEditor,这类的库一般会这么写 (function (global, factory) { typeof exports === ‘object’ && typeof module !== ‘undefined’ ? module.exports = factory() : typeof define === ‘function’ && define.amd ? define(factory) : (global.wangEditor = factory()); }(this, (function () { ‘use strict’; … // 返回 var index = window.wangEditor || Editor; return index; }))); 它一般会这么报错 Read more…

vue-cli设置css不生效

我们有的项目使用的是老的vue-cli脚手架生成的,今天想写点东西,发现.vue文件里面style里面写的样式都不生效了,很自然就想到是不是loader的问题。 在这种项目的webpack.base.config.js一般设置loader的 rules: [ …(config.dev.useEslint ? [createLintingRule()] : []), { test: /\.vue$/, loader: ‘vue-loader’, options: vueLoaderConfig }, { test: /\.js$/, loader: ‘babel-loader’, include: [resolve(‘src’), resolve(‘test’)] }, … } 很显然,我们在.vue文件里面的样式怎么生效跟.vue的这个loader有关了。 上面的vueLoaderConfig是对vue-loader的配置 打印出来如下: 通过阅读/build/utils.js的源码我们很容易知道上面返回的loaders就是针对css、postcss等的对象,每一项的值是一个数组,它是类似于[css-loader信息, postcss-loader信息]的数组,并且第一个是css-loader信息,由于是loader的加载顺序是从右到左的,所以最后一个使用的是css-loader。 以前这样写是没有问题,现在打开好久不用的老项目删除node-modules后重新安装发现问题,我猜测多半是vue-loader之类的升级版本了,毕竟我们使用的package.json默认并没有锁定版本。我们安装的很可能并不是我们在package.json里面写的版本。 解决方案 方法一 现在只用再加上vue-style-loader即可,也就是使用[vue-style-loader信息, css-loader信息, postcss-loader信息]即可。 我们可以更改.vue的loader相关配置 { test: /\.vue$/, loader: ‘vue-loader’, // options: vueLoaderConfig options: { Read more…

前端也会需要一个消费队列

现在我们已经习惯了模块化,微服务化,这样的确带来了很多的好处,但是有时也会有些小麻烦。比如: A组件和B组件各要调用一个接口,并且根据接口调用完成后执行回调,要求A组件的回调较B组件的先执行。 如果是在同一个组件里面,我们比较容易的控制接口的调用时机(同时调用两个接口),并且通过Promise.all来完成这个需求。但是在两个组件里面一不好控制接口的调用时机,二不好控制回调执行顺序。 这时我们就需要一个事件通知系统和一个消费队列了,在微服务中事件系统一般都有的,我们只需要额外加一个消费队列就行了。 首先我们理一下: 多个组件的回调到位的顺序不可控(可能存在后执行的回调先到位,有的可能得根据接口调用结果来确定回调的内容) 开发者自己是知道各个回调期望的执行顺序的。将各个回调指定设置顺序放置于队列中,队列讲回调排序后顺序执行。 需不需要设置回调的最后执行时间,不能因为一个排序前面的回调没有成功进入队列(比如接口调用失败导致),其它的回调都得不到执行 简单的实现代码(typescript)如下: interface task { id: number; max: number; maxMs?: number; callback: Function; } export default class ConsumeQueue { private queue: task[]; timeId; constructor() { this.queue = []; this.timeId = null; } guaranteed(ms) { clearTimeout(this.timeId); this.timeId = setTimeout(() => { if (this.queue.length>0) { Read more…

从vuecli3学习webpack记录(零)整体流程

今天看了下自己之前写的从vuecli3学习webpack记录系列,感觉自己居然没有在一开始的时候把vuecli的npm run serve的整体流程在一篇文章里面完整的讲完,可能是因为打字打的手疼,不想写了吧。今天特来补充一下。 这里是整体脉络,所以不会讲细节,细节在本系列里面已经讲到了。 const Service = require(‘../lib/Service’) const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd()) const rawArgv = process.argv.slice(2) const args = require(‘minimist’)(rawArgv, { boolean: [ // build ‘modern’, ‘report’, ‘report-json’, ‘watch’, // serve ‘open’, ‘copy’, ‘https’, // inspect ‘verbose’ ] }) const command = args._[0] service.run(command, args, rawArgv).catch(err => Read more…