echozhaoh / notes Goto Github PK
View Code? Open in Web Editor NEW写一写工作中遇到的问题以及解决方案
Home Page: https://echozw.github.io/
写一写工作中遇到的问题以及解决方案
Home Page: https://echozw.github.io/
entry
、loader
、plugin
等qiankun
是基于 single-spa
实现,qiankun 中主要做了对 window
的 proxy 以及很重要的一步,将 single-spa
中需要开发自己开发的大部分内容都实现了配置化,其中支持配置化的很要的一个库 import-html-entry
路由是微前端实现的关键
目前的微前端的实现都是基于对路由的划分来加载不同的子应用,所以理解前端路由很重要,其中也要搞清楚前端路由和后端路由的区别
分包(我的直白翻译)
将同一仓库下的不同模块的代码单独打包,目前很多 ui 库都是使用 monorepo
来处理分包的,blueprintjs 是我 monorepo
的启蒙,这个库的源码值得一读。
monorepo
目前主流的工具是 lerna
阅读源码是必经的一步,但也不是鼓励什么准备都没有,上来直接看,这样是没有任何收益的
阅读源码之前,你需要熟悉这个库的大部分功能以及如何使用并且能够基本无障碍的使用,接下来就是将模块拆解,分解自己的目标,一步一步去理解源码中的每一个功能。
function cssEscape(str) {
const length = str.length;
let index = -1;
let codeUnit;
let result = '';
const firstCodeUnit = str.charCodeAt(0);
while (++index < length) {
codeUnit = str.charCodeAt(index);
// Note: there’s no need to special-case astral symbols, surrogate
// pairs, or lone surrogates.
// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
// (U+FFFD).
if (codeUnit === 0x0000) {
result += '\uFFFD';
continue;
}
// Comma
if (codeUnit === 44){
result += '\\2c ';
continue;
}
if (
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
// U+007F, […]
(codeUnit >= 0x0001 && codeUnit <= 0x001f) ||
codeUnit === 0x007f ||
// If the character is the first character and is in the range [0-9]
// (U+0030 to U+0039), […]
(index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
// If the character is the second character and is in the range [0-9]
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
(index === 1 &&
codeUnit >= 0x0030 &&
codeUnit <= 0x0039 &&
firstCodeUnit === 0x002d)
) {
// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
result += '\\' + codeUnit.toString(16) + ' ';
continue;
}
if (
// If the character is the first character and is a `-` (U+002D), and
// there is no second character, […]
index === 0 &&
length === 1 &&
codeUnit === 0x002d
) {
result += '\\' + str.charAt(index);
continue;
}
// If the character is not handled by one of the above rules and is
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
// U+005A), or [a-z] (U+0061 to U+007A), […]
if (
codeUnit >= 0x0080 ||
codeUnit === 0x002d ||
codeUnit === 0x005f ||
(codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
(codeUnit >= 0x0041 && codeUnit <= 0x005a) ||
(codeUnit >= 0x0061 && codeUnit <= 0x007a)
) {
// the character itself
result += str.charAt(index);
continue;
}
// Otherwise, the escaped character.
// https://drafts.csswg.org/cssom/#escape-a-character
result += '\\' + str.charAt(index);
}
return result;
}
来吧,直击要害
这个生命周期可以实现
componentWillReceiveProps(nextProps, nextContext)
but 我觉得你需要填的坑比较多还不一定达到你的业务要求
所以还是老老实实的用回调解决
想到 background-image 我们第一时间想到的应该是图片,但是图片会有很多因素的影响,比如大小、网络等,今天说的是将 svg 内联作为 background-image,以下是格式:
background-image: url('data:image/svg+xml,<svg>xxxx</svg>');
如果我们直接这样将 svg 放入,有可能会无法显示,比如 svg 中有颜色的属性 #000000
,其中 #
就回导致 svg 无法正常显示,我猜测的原因不是 svg 没有显示,而是已经显示了,但是颜色失效了,所以我们需要将 svg 的代码字符串放入 encodeURIComponent
函数处理才行,将 <
>
#
"
等特殊字符转义。
function currying() {
const arr = Array.from(arguments)
const fn = arr.shift()
let rest = arr
if (rest.length === fn.length) {
return fn.apply(this, rest)
}
return function loop() {
const args = rest.concat(...Array.from(arguments))
if (args.length === fn.length) {
return fn.apply(this, args)
} else {
return loop.apply(this, args)
}
}
}
function NewInstance(parent) {
const obj = Object.create({})
obj.__proto__ = Object.create(parent.prototype)
const result = parent.apply(obj)
return result instanceof Object ? result : obj
}
function instanceofClone(child, parent) {
let a = child.__proto__
while(a) {
if (a === parent.prototype) return true
a = a.__proto__
}
return false
}
package main
import (
"fmt"
"net/http"
"strings"
"log"
)
func sayHelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() // 解析参数
fmt.Println(r.Form) // 输出到服务端的打印信息
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
fmt.Println(r.Form["url_long"])
for k, v := range r.Form {
fmt.Println("key: ", k)
fmt.Println("val: ", strings.Join(v, ""))
}
fmt.Fprintf(w, "Hello World") // 写入到 w 输出到客户端
}
func main() {
http.HandleFunc("/", sayHelloName) // 设置访问路由
err := http.ListenAndServe(":9090", nil) // 设置监听端口
if err != nil {
log.Fatal("ListenAndServer: ", err)
}
}
plugin: TerserPlugin,OptimizeCSSAssetsPlugin
splitChunks
runtimeChunk: 用来共享 runtime
// flag 自己多实践
~function goTop(){
let delta = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
delta && window.scrollBy(0, - delta / 9.8)
delta && requestAnimationFrame(goTop)
}()
上述 9 个 hooks 都继承自 Hook 这个 class
hook 对外提供了 isUsed
call
promise
callAsync
compile
tap
tapAsync
tapPromise
intercept
这些方法
其中 tap
开头的方法是用来订阅事件的,call
promise
callAsync
是用来触发事件的,isUsed
返回了一个 boolean
值用来标记当前 hook
中注册的事件是否被执行完成。
isUsed
源码isUsed() { return this.taps.length > 0 || this.interceptors.length > 0; }
tap
tapAsync
tapPromise
这三个方法第一个参数传入可以支持传入 string
(一般是指 plugin 的名称) 或者一个 Tap
类型,第二个参数是一个回调用来接收事件被 emit
时的调用。
export interface Tap {
name: string; // 事件名称,一般就是 plugin 的名字
type: TapType; // 支持三种类型 'sync' 'async' 'promise'
fn: Function;
stage: number;
context: boolean;
}
call
promise
callAsync
这三个方法在传入参数的时候是依赖于 hook
被实例化的时候传入的 args
数组占位符的数量的,如下示例:
const sync = new SyncHook(['arg1', 'arg2']) // 'arg1' 'arg2' 为参数占位符
sync.tap('Test', (arg1, arg2) => {
console.log(arg1, arg2) // a2
})
sync.call('a', '2')
其中 promise
调用会返回一个 Promise
,callAsync
默认支持传入一个 callback
。
Sync
开头的 hook
不支持使用 tapAsync
和 tapPromise
,可以看下述的以 SyncHook
的源码为例
const TAP_ASYNC = () => {
throw new Error("tapAsync is not supported on a SyncHook");
};
const TAP_PROMISE = () => {
throw new Error("tapPromise is not supported on a SyncHook");
};
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
function SyncHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = SyncHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
hook.compile = COMPILE;
return hook;
}
SyncHook.prototype = null;
在这里面我们可以看到 tapAsync
和 tapPromise
是被重写了直接 throw error
了
下面的例子会给大家带来一个简单地示范
class TapableTest {
constructor() {
this.hooks = {
sync: new SyncHook(['context', 'hi']),
syncBail: new SyncBailHook(),
syncLoop: new SyncLoopHook(),
syncWaterfall: new SyncWaterfallHook(['syncwaterfall']),
asyncParallel: new AsyncParallelHook(),
asyncParallelBail: new AsyncParallelBailHook(),
asyncSeries: new AsyncSeriesHook(),
asyncSeriesBail: new AsyncSeriesBailHook(),
asyncSeriesWaterfall: new AsyncSeriesWaterfallHook(['asyncwaterfall'])
}
}
emitSync() {
this.hooks.sync.call(this, err => {
console.log(this.hooks.sync.promise)
console.log(err)
})
}
emitAyncSeries() {
this.hooks.asyncSeries.callAsync(err => {
if (err) console.log(err)
})
}
}
const test = new TapableTest()
test.hooks.sync.tap('TestPlugin', (context, callback) => {
console.log('trigger: ', context)
callback(new Error('this is sync error'))
})
test.hooks.asyncSeries.tapAsync('AsyncSeriesPlugin', callback => {
callback(new Error('this is async series error'))
})
test.emitSync()
test.emitAyncSeries()
上述的运行结果可以这查看 runkit
当我们定义了 webpack
的配置文件后,webpack
会根据这些配置生成一个或多个 compiler
,而插件就是在创建 compiler
时被添加到 webpack
的整个运行期间的, 可以看下述源码:(相关源码可以在 webpack
lib 下的 webpack.js
中找到)
const createCompiler = rawOptions => {
const options = getNormalizedWebpackOptions(rawOptions);
applyWebpackOptionsBaseDefaults(options);
const compiler = new Compiler(options.context);
compiler.options = options;
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
applyWebpackOptionsDefaults(options);
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
new WebpackOptionsApply().process(options, compiler);
compiler.hooks.initialize.call();
return compiler;
};
我们可以看到遍历 options.plugins
这一段,这一段分了两种情况来进行插件的插入
webpack
调用,也就是说我们可以用函数来写插件,这个函数的作用域是当前的 compiler
,函数也会接收到一个 compiler
apply
方法的对象实例,apply
方法会被传入 compiler
所以这也就解释了为什么我们的插件需要 new
出来之后传入到 webpack
上一个中我们了解到了 plugins
是何时被注入的,我们可以看到在 plugin
的注入时传入了当前被实例化出来的 Compiler
,所以现在我们需要了解下 Compiler
中做了什么
进入 Compiler.js
(也在 lib 中)我们可以第一时间看到 Compiler
的 constructor
中定义了一个庞大的 hooks
:
this.hooks = Object.freeze({
/** @type {SyncHook<[]>} */
initialize: new SyncHook([]),
/** @type {SyncBailHook<[Compilation], boolean>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<[Stats]>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {SyncHook<[Stats]>} */
afterDone: new SyncHook(["stats"]),
/** @type {AsyncSeriesHook<[]>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<[Compiler]>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<[Compiler]>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<[Compilation]>} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<[string, AssetEmittedInfo]>} */
assetEmitted: new AsyncSeriesHook(["file", "info"]),
/** @type {AsyncSeriesHook<[Compilation]>} */
afterEmit: new AsyncSeriesHook(["compilation"])
...
})
看到这些 hook
是不是很熟悉,全是 tapable
中的 hook
,webpack 正是依赖于这些复杂的构建 hook
而完成了我们的代码构建,所以在我们编写 plugin
时就可以利用这些 hook
来完成我们的特殊需求。
比如我们经常用到的 HtmlWebpackPlugin
,我们可以看下他是如何运行的,在 HtmlWebpackPlugin
的 apply
中我们可以找到这样一段代码:
compiler.hooks.emit.tapAsync('HtmlWebpackPlugin', (compiler, callback) => {
...
})
说明 HtmlWebpackPlugin
是利用了 Compiler
的 emit
的 hook
来完成的
通过深入了解,webpack
是在庞大的插件上运行的,他自己内置了很多插件
上述内容如有错误,请指正
package main
import (
"fmt"
)
func binarySearch(arr []int, target int) int {
left, right, mid := 0, len(arr) - 1, 0
for left <= right {
mid = (left + right) >> 1
if arr[mid] > target {
right = mid - 1
} else if arr[mid] < target {
left = mid + 1
}else {
return mid
}
}
return -1
}
func main() {
arr := make([]int, 0, 10)
for i := 0; i < 10; i++ {
arr = append(arr, i)
}
fmt.Println(arr)
mid := dichotomy(arr, 0)
fmt.Printf("selected value is: %d", mid)
}
es6
之前解决作用域都是手动的bind
、call
、apply
onClick.bind(ctx)
onClick.call(ctx, arg)
onClick.apply(ctx, args)
es6
之后我们还可以有👇的操作1 () => {onClick()}
2 在 class
中我们可以自定义箭头函数来代替一些事件函数
class Test extends React.Component {
render () {
return (
<input onChange={this.handleChange} />
)
}
handleChange = (event) => {
console.log(event.target.value)
}
}
vue-class-component
vue-property-decorator
vue-tsx-support
vuex-class
vue-tsx-support
是用来增强 Vue
的 Props
、Event
等类型的
不使用 vue-tsx-support
class Demo extends Vue {
@Prop() demo: string
}
在外部是无法感知这个 props
类型的
使用 vue-tsx-support
interface Props {
demo: string
}
class Demo extends tsx.Component<Props> implements Props {
@Prop() demo: string
}
这种写法外部是可以感知需要传入的 Props
的类型的
@typescript-eslint/eslint-plugin
@typescript-eslint/parser
@vue/eslint-config-typescript
eslint-plugin-import
eslint-plugin-typescript
ts-loader
typescript
还有一些 @types 需要添加可自行查找(缺什么 types 会报错提示)
eslint 中需要添加对 ts、tsx 的支持,同时在迁移的过程中还会存在 js 和 vue
主要这两个地方需要注意(添加对 ts 的支持)
plugins: [ 'typescript', '@typescript-eslint' ],
parserOptions: { parser: "@typescript-eslint/parser", "ecmaFeatures": { "jsx": true }, "ecmaVersion": 2018, "sourceType": "module", }
同时需要在你的 workspace 的 vscode setttings.json 中增加对 ts 和 tsx 的验证
"typescript.tsdk": "node_modules/typescript/lib",
"eslint.validate": [
"javascript",
"javascriptreact",
{
"language": "typescript",
"autoFix": true
},
{
"language": "typescriptreact",
"autoFix": true
}
],
"eslint.options": { //指定vscode的eslint所处理的文件的后缀 "extensions": [ ".js", ".ts", ".tsx", ".vue" ] }
在我们改造过程中不可避免的会遇到在写 jsx 的时候会应用到之前的一些 vue 的组件,而这些组件有没有被全局 install,这个时候我们可以先如下处理
@Component({
components: {
DemoVue,
},
})
class Demo extends Vue {
render() {
return (<demo-vue />)
}
}
对于 template 中的一些修饰符,我们在 jsx 中该怎么处理,比 xxx.sync 修饰符,sync 只是一个语法糖,我们可以在组件的 on 中进行监听,如下
@Component({
components: {
DemoVue,
},
})
class Demo extends Vue {
render() {
return (<demo-vue on={{'update:Click': () => {}}} />)
}
}
同时我们需要区分 attrs 和 props , props 可以直接在组件上进行传递,attrs 需要放到 attrs 属性下,如下
@Component({
components: {
DemoVue,
},
})
class Demo extends Vue {
render() {
return (<demo-vue on={{'update:Click': () => {}}} propsA attrs={{attrsA: 'xxx'}} />)
}
}
对于一些第三方库没有 decorator ,该怎么办,我们可以借助 vue-class-component 中的 createDecorator 来造一个简易的轮子,如下 apollo 和 metaInfo
export function ApolloDes() {
return function(_t: Vue, key: string, desc: any) {
createDecorator(options => {
options.apollo = desc.value()
})(_t, key)
}
}
export function MetaInfoDes() {
return function (_target: Vue, key: string, desc: any) {
createDecorator((options) => {
options.metaInfo = desc.value
})(_target, key)
}
}
使用方式
class Demo extends Vue {
@MetaInfoDes()
metaInfo() {
return {
title: 'xxx',
}
}
@ApolloDes
apollo() {
return {
reqA: {
query: xxxx
}
}
}
render() {
return (<demo-vue on={{'update:Click': () => {}}} propsA attrs={{attrsA: 'xxx'}} />)
}
}
如下例
class Demo extends Vue {
test = {name: 'xxx'}
render() {
const { test } = this
return (
<test props={test}/>
)
}
}
上述代码会在编译的运行的时候将组件 test 编译为 <xxxx />
,理解之后也不奇怪,因为 test 也是一个变量,只不过 这个组件被全局注册我们没在这 应用,刚好组件和变量 test 重名,而且 test 变量具有 name 属性,运行时,test 的 name 就会被编译成组件名了,组件和 test 都是变量,只不过组件这个变量在 Vue 中被全局注册了,而且这个组件名也是小写,lint 工具不会去检查,就造成了一种错觉。
在 Parser.js
中,我们可以看到两个比较核心的库:
acorn
用于解析 js
的 ast 语法🌲tapable
webpack 的一个用于 plugin 的小型事件处理的库接下来的东西我也不知道怎么写了,先看看吧
react
typescript
kao
mysql
typescript
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.