📒 实现一个 WebAssembily 版本的 Python 解释器
- wasm 可以把代码编译出来,但是能否执行
- 如果 Python 代码涉及系统调用,例如代码中经常需要进行文件 IO,这种情况下 wasm 能否实现
📒 Webpack5 配置了 devServer.hot = true
是否会自动配置 HotModuleReplacementPlugin
📒 看下 axios 源码,响应拦截中第一个回调 reject
能否进入第二个回调
📒 webpack-dev-server 如何配置代理
查看详情
在 CRA 搭建的项目中,我们知道可以在 src/setupProxy.js
文件中写入代理配置:
const proxy = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
proxy(
'/course',
{
target: 'https://ke.study.163.com',
changeOrigin: true,
},
),
)
}
那么手动搭建的项目该如何配置代理呢?我们看一下 CRA 源码:
// react-scripts/config/paths.js:87
module.exports = {
// ...
proxySetup: resolveApp('src/setupProxy.js'),
// ...
}
然后去找哪里用到了 proxySetup
:
// react-scripts/config/webpackDevServer.config.js:112
onBeforeSetupMiddleware(devServer) {
// Keep `evalSourceMapMiddleware`
// middlewares before `redirectServedPath` otherwise will not have any effect
// This lets us fetch source contents from webpack for the error overlay
devServer.app.use(evalSourceMapMiddleware(devServer));
if (fs.existsSync(paths.proxySetup)) {
// This registers user provided middleware for proxy reasons
require(paths.proxySetup)(devServer.app);
}
},
看了下上面的配置,说明应该是这么用的:
const compiler = webpack(config);
const devServer = new WebpackDevServer(options, compiler);
devServer.app.use(
proxy(
'/course',
{
target: 'https://ke.study.163.com',
changeOrigin: true,
},
),
)
📒 为什么可以用函数模拟一个模块
在一个模块中,有一些属性和方法是私有的,另外一些是对外暴露的:
// main.js
let foo = 1;
let bar = 2;
export const getFoo = () => foo;
export const getBar = () => bar;
const defaultExport = () => foo + bar;
export default defaultExport;
// index.js
import main, { getFoo, getBar } from "./main";
这种行为就可以通过函数模拟出来,其中私有变量、方法以闭包的形式实现,这样只有模块内部才能访问:
const main = (function() {
let foo = 1;
let bar = 2;
const getFoo = () => foo;
const getBar = () => bar;
const defaultExport = () => foo + bar;
return {
getFoo,
getBar,
default: defaultExport
}
})();
可以看到给默认导出加了一个 deafult
属性。
另外推荐看看 browserify
这个库,如何在浏览器端实现 CommonJS 模块机制:
📒 Webpack 中 loader 处理流程
有点像责任链模式,上一个函数的返回值会作为参数传入下一个函数。需要注意使用 call
方法让每个 loader 内部可以获取到 loaderAPI:
import { readFileSync } from 'node:fs';
const loaders = [];
const raw = readFileSync('xxx');
const loaderAPI = {
emitFile: () => {},
}
const parsed = loaders.reduce(
(accu, cur) => cur.call(loaderAPI, accu),
raw
);
📒 字体文件的 hash 是如何生成的,file-loader
中如何处理的
写一篇文章:《你不知道的 Webpack loader —— file-loader 源码探秘》
webpack 源码解析:file-loader 和 url-loader
📒 Golang 编译为 WebAssembly
在 Golang 中可以使用 syscall/js
这个库与 JS 环境进行交互,可以调用 JS 的 API,以及传递 JSON 数据:
package main
import (
"encoding/json"
"fmt"
"syscall/js"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
// Work around for passing structs to JS
frank := &Person{Name: "Frank", Age: 28}
p, err := json.Marshal(frank)
if err != nil {
fmt.Println(err)
return
}
obj := js.Global().Get("JSON").Call("parse", string(p))
js.Global().Set("aObject", obj)
}
📒 Golang 中的指针
对于原始类型来说,赋值就等于 copy,相当于在内存中创建一个一模一样的值,具有不同的内存地址:
func main() {
a := 42
b := a
fmt.Println(a, b) // 42 42
a = 27
fmt.Println(a, b) // 27 42
}
可以通过 &
操作符取到内存地址:
func main() {
var a int = 42
var b *int = &a
fmt.Println(a, b) // 42 0×1040a124
}
还可以通过 *
操作符根据内存地址访问对应的值:
func main() {
var a int = 42
var b *int = &a
fmt.Println(a, *b) // 42 42
}
由于 b
实际持有的是 a
的指针引用,因此修改 a
会导致 b
指向的值发生变化:
func main() {
var a int = 42
var b *int = &a
fmt.Println(a, *b) // 42 42
a = 27
fmt.Println(a, *b) // 27 27
*b = 14
fmt.Println(a, *b) // 14 14
}
📒 Golang 中的 struct
注意 struct
与 slice
、map
不同,下面这个操作实际上是完整 copy 了一个对象,内存开销较大:
package main
import (
"fmt"
)
type Doctor struct {
name string
}
func main() {
aDoctor := Doctor{
name: "John Pertwee"
}
anotherDoctor := aDoctor
anotherDoctor.name = "Tom Baker"
fmt.Println(aDoctor) // {John Pertwee}
fmt.Println(anotherDoctor) // {Tom Baker}
}
可以使用 &
操作符拿到对象的指针进行赋值,这时候两边就是联动的:
func main() {
aDoctor := Doctor{
name: "John Pertwee"
}
anotherDoctor := &aDoctor
anotherDoctor.name = "Tom Baker"
fmt.Println(aDoctor) // {Tom Baker}
fmt.Println(anotherDoctor) // &{Tom Baker}
}
注意 array
进行赋值也会 copy:
func main() {
a := [3]int{1, 2, 3}
b := a
fmt.Println(a, b) // [1, 2, 3] [1, 2, 3]
a[1] = 42
fmt.Println(a, b) // [1, 42, 3] [1, 2, 3]
}
但如果将 array
改为 slice
,赋值传递的就是指针:
func main() {
a := []int{1, 2, 3}
b := a
fmt.Println(a, b) // [1, 2, 3] [1, 2, 3]
a[1] = 42
fmt.Println(a, b) // [1, 2, 3] [1, 2, 3]
}
📒 年终盘点:2022基于Monorepo的首个大趋势-TurboRepo
📒 GitHub 定时任务
下面的代码中,on
字段指定了两种触发条件,一个是代码 push
进仓库,另一种是定时任务,每天在国际标准时间21点(北京时间早上5点)运行。
on:
push:
schedule:
- cron: '0 21 * * *'
定时任务配置参考:
另外推荐一个项目,可以使用 curl wttr.in
命令获取天气预报:
📒 如何开发一个 CLI 工具
参考下尤大的项目:
const templateDir = path.join(__dirname, `template-${template}`)
const write = (file, content) => {
const targetPath = renameFiles[file]
? path.join(root, renameFiles[file])
: path.join(root, file)
if (content) {
fs.writeFileSync(targetPath, content)
} else {
copy(path.join(templateDir, file), targetPath)
}
}
const files = fs.readdirSync(templateDir)
for (const file of files.filter((f) => f !== 'package.json')) {
write(file)
}
注意这里有两个文件要处理下,一个是给 package.json
修改包名:
const pkg = require(path.join(templateDir, `package.json`))
pkg.name = packageName || targetDir
write('package.json', JSON.stringify(pkg, null, 2))
还有是 .gitignore
修改文件名:
const renameFiles = {
_gitignore: '.gitignore'
}
https://github.com/vitejs/vite/blob/main/packages/create-vite/index.js
📒 命令行工具开发技术栈
chalk/kolorist
inquirer/prompts
ora
semver
pkg-install
ncp
commander/yargs
execa
(个人觉得 Node 原生child_process
的exec
就够用了)minimist
网上一些文章也都实现了递归拷贝文件,但是是否考虑到了跨平台,可以看下 ncp
的实现
https://github.com/AvianFlu/ncp
Node.js 原生的 child_process.exec
也可以执行命令,看下 execa
是如何支持 Promise 的
现在开发已经不需要自己组装 pick 了,common-bin
、oclif
这两个,约定式路由。
另外脚手架工具,可以看看 plop
和 yeoman
,一个是基于 action
和 inquirer
的生态,一个是内核加自定义模板项目。
其实最简单的脚手架,不是通过cli界面选择模板,然后到 github 上去下载对应的模板文件,而是 start-kit
。
https://github.com/digipolisantwerp/starter-kit-ui_app_nodejs