README

{折腾 ⇌ 迷茫 ⇌ 思考]ing,在路上...

I'm Xin Chen, front-end developer from China. I love to explore and learn about new things...

ID wechat 公众号 gmail

github dev.to BOOK fe-cool/news

微信群

关注公众号《浮之静》,发送“进群”。拉你进群一起交流:目前有 wasmflutter前端动漫 群等。

fzj-qrcode

Languages and Tools

HTML5 CSS3 JavaScript React Sass Node.js
VS Code Sketch Git Rust Shell WebAssembly

lencx stats lencx's wakatime stats

Thinking

这里记录所学,所思,所感,所想。

技术是什么?

📅 2021.05.04

一条朋友圈评论所引发的一系列思考

背景

我发了朋友圈:做了个奇怪的梦,梦见被离职了,理由竟然是:你技术太菜了
我补充评论:我还没想明白怎么回事,就醒了。有点遗憾的是:想再努力反驳两句,结果没机会了…
很多人评论:梦是反的
我的回复:那就是到技术的瓶颈期了
YaoL.in评论:这衍生出一个很好的问题:「你如何证明技术不菜?」
我的回复:个人感觉还是影响力,即使你的技术很厉害,没人知道,或者别人也不会去使用你的技术,似乎一切为零(为零仅代表这个技术所产生的影响力,不代表技术本身的价值)


我对技术的理解

技术本身并没有价值,衡量一个技术的标准,应该是这门技术所产生的影响力。

我之前也一直认为只需要专心做好技术就可以了,是金子总会发光的,这句话没有任何问题,但是缺少了一个很重要的前提,何时才会发光? 人的生命是有限的,我认为能够更快的将自己曝光,用自己的技术或文章去影响到一些人,还是很有“必要的”(自己体会,这里可以代表着名,利,精神上,物质上等)。

我参与 GitHub 开源及写文章博客也很久了(惭愧,文章博客输出有点少),慢慢发现,很多好的技术,文章,项目都属于被埋没状态。一直被埋没,就不会产生价值,这就会造成一个很大的问题,技术自身有价值,但却不能用来解决问题,该如何去衡量这个技术?

自己封神,不如别人对你称神

帮别人解决问题,来体现自己的价值

回答别人提出的问题,也是对自身技术的 reviewreview 的次数多了,这些技术点就会刻在骨子里。当一个问题讨论的深入时,往往引发的是一系列的知识点。由点成面,再成网(单纯靠回答问题,很难构建出知识网,但是可以作为一个切入点)。

当我学习技术累的时候,就喜欢在技术群里看别人提问题,能回答的就回答,不能回答的时候就看别人如何回答。

闷头搞技术,提升的只是一个人的能力,分享,提升的是一群人。有些人可能会觉得技术如果拿来分享,别人比自己强了,怎么办,那岂不是越来越卷了? 不知道别人有没有这么想,反正我以前有过这种想法,但是随着自己做开源,写文章,回答别人的问题,不但没有这种想法了,反而想把更多的技术拿来分享,进行探讨。

原因有以下几点:

  • 表达能力:能够把自己掌握的技术,抽象表达出来,这个很难。要达到这个目标,让什么都不懂的人听懂你在说什么,就需要做到知识深入浅出。
  • Review & Share:每次分享,都是对技术的一次 “review”,会产生新的理解或者引申出新的知识点。用分享的方式去学习(共赢)。
  • 技术探讨:可遇不可求,分享技术,如果遇到知己,也是莫大的幸运。
  • 成就感:这属于学习的一个正反馈,奖赏机制在学习技术方面,我觉得还是很有必要的,因为学习本身就比较枯燥,能找到一个坚持下去的理由,不容易。举个例子,我个人就拿 每天一个 github 小绿点(github commit 记录),让自己持续学习与输出 作为目标。
  • 无形监督:这个其实是为了防止自欺欺人,以为自己不分享别人就不会学习了...,有压力才会有动力,因为当自己再无可分享的东西时,则证明自己已经没有了知识的输出,需要进行自我反思。

在各大技术群里呆的久了,发现群友们提出的问题虽千奇百怪,但整理下来其实也就几大类:

  • 想吃现成的:遇到问题不管三七二十一,先丢群里,等待别人的回答,如果有人回答,能吃到嘴也算“不亏”;运气不好的,直接让群变得安静起来(群静音神器);还有就是群友们会围绕这个问题开始风马牛不相及的吹水。等不到问题的解决方案,则白白浪费了时间。
  • 不会提问的:这种人似乎还不占少数,比想吃现成的能稍微“好点”吧,因为不光浪费自己的时间,还浪费了看问题回答问题人的时间,有效地防止了内卷。不会提问通常表现为问题没有上下文,只有一个报错信息,问怎么解决,在线急等。如果有人解决过类似问题还好说,没有解决过这类问题的则表示一脸懵逼,连个插话的机会都不给(抠门,惜字如金,只发问题不发产生背景及预期)。
  • 会提问的:这种人一般都是思路清晰的,也做过了大量的技术方案尝试,能够言简意赅的说明目前困境,希望能够得到什么样的帮助,目的性很强(褒义词,知道自己想要什么总比什么不知道要好)。
  • 进行探讨的:强烈推荐的方式,要讨论,就避免不了问题的梳理,在梳理问题的过程中,可能会发现自己漏掉的一些细节。小黄鸭调试法 值得拥有,有些问题解决不了,很可能也是因为自己钻了牛角尖,讨论也可以让自己快速走出思维误区。

学会提问

遇到问题,不要慌,常见的百分之八九十问题,网上都是有解决方案的。当搜索引擎都不能帮助你解决这个问题的时候,证明你遇到的问题“有点东西”了,这是好事情。需要去一些专业的技术提问社区去转转了,比如 Stack Overflow,或者 GitHub Issues(主要针对开源项目),还有就是此技术相关的社区,论坛,SlackDiscord等。

一个好的问题模板,例如 vite ISSUE_TEMPLATE/bug_report 一般包含以下几个要素:

  • 问题描述:简短的表述清楚问题,切记啰嗦。
  • 环境信息:问题发生的环境(系统信息,软件版本,浏览器版本等)。
  • 如何复现:提供问题复现步骤1,2,3,可以配合适当的错误截图及说明。
  • 预期结果:希望达到什么样的结果。

解决问题,根据我多年来的实践总结,一般分为以下几步:

  1. 抽象: 用技术关键词去描述问题
  2. 搜索:根据关键词去搜索问题
  3. 延伸搜索:围绕关键词,扩大搜索范围,查看一些相关链接

🎉 Google高级搜索的10个技巧
1)准确搜索(Exact phrase)
2)排除关键词( Exclude terms)
3)用 OR (或)逻辑进行搜索(Either OR)
4)同义词搜索 ~(Synonym search)
5)站内搜索(Search within a site)
6)善用 * 星号(The power of the asterisk)
7)在两个数值之间进行搜索(Searching between two values)
8)在网页标题, 链接和主体中搜索关键词(Search for word in the body, title or URL of a page)
9)搜索相关网站(Search for related sites)
10)搜索技能的组合使用(Combine them)

总结

所以再次回到文章的标题,技术是什么?,技术就是问题的解决方案,与编程语言无关,与人无关,当遇到的问题无法解决时,能够把这个问题解决掉的东西,我认为这就是技术。技术本身并不高大上,概括成一个流程就是:遇到问题 -> 分析问题 -> 抽象描述 -> 提供步骤 -> 解决问题。当问题被解决后,技术本身也就产生了价值。能解决问题越多的技术,其影响力也就越大,提供此问题解决方案的人也就越厉害(此结论只是站在一个角度的个人观点,请勿过度解读)。所以要证明自己不菜,就要不断地去解决问题,帮助的人和解决的问题越多,你就是别人眼中的“神”,而不是自封为神。自己时刻要保持着对知识的敬畏之心

WebAssembly

WebAssembly入门


Wasm是什么?

MDN 官方文档是这样给出定义

WebAssembly (为了书写方便,简称 Wasm )是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如 C / C ++ 等语言提供一个编译目标,以便它们可以 Web 上运行。它也被设计为可以与 JavaScript 共存,允许两者一起工作。

对于网络平台而言,WebAssembly 具有巨大的意义——它提供了一条途径,以使得以各种语言编写的代码都可以以接近原生的速度在 Web 中运行。在这种情况下,以前无法以此方式运行的客户端软件都将可以运行在 Web 中。

WebAssembly 被设计为可以和 JavaScript 一起协同工作——通过使用 WebAssembly 的 JavaScript API,你可以把 WebAssembly 模块加载到一个 JavaScript 应用中并且在两者之间共享功能。这允许你在同一个应用中利用 WebAssembly 的性能和威力以及 JavaScript 的表达力和灵活性,即使你可能并不知道如何编写 WebAssembly 代码。


环境安装及简介

1. Rust

一门赋予每个人
构建可靠且高效软件能力的语言。

安装

# macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 其他安装方式
# https://forge.rust-lang.org/infra/other-installation-methods.html

常用命令

# 版本更新
rustup update

# 查看版本
cargo --version

# 构建项目
cargo build

# 运行项目
cargo run

# 测试项目
cargo test

# 为项目构建文档
cargo doc

# 将库发布到 crates.io
cargo publish
# nightly rust
rustup toolchain install nightly

rustup toolchain list

rustup override set nightly

2. Node.js

Node.js 是基于 Chrome 的 V8 JavaScript 引擎构建的 JavaScript 运行时

3. wasm-pack

用于构建和使用您希望与 JavaScript,浏览器或 Node.js 互操作的 Rust 生成的 WebAssembly。

安装

# macOS
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

# 其他安装方式
# https://rustwasm.github.io/wasm-pack/installer

常用命令

# 创建
# https://rustwasm.github.io/docs/wasm-pack/commands/new.html
wasm-pack new <name> --template <template> --mode <normal|noinstall|force>

# 构建
# https://rustwasm.github.io/docs/wasm-pack/commands/build.html
wasm-pack build
  [--out-dir <out>]
  [--out-name <name>]
  [--<dev|profiling|release>]
  [--target <bundler|nodejs|web|no-modules>]
  [--scope <scope>]
  [mode <normal|no-install>]

# 测试
# https://rustwasm.github.io/docs/wasm-pack/commands/test.html
wasm-pack test

# 发包
# https://rustwasm.github.io/docs/wasm-pack/commands/pack-and-publish.html
# npm pack
wasm-pack pack
# npm publish
wasm-pack publish

4. rsw-node - 部署时构建wasm

wasm-pack build 在远程部署时执行,零依赖,可以安装到全局,直接使用 rsw 命令。也可以和 vite-plugin-rsw 插件配合使用

# 全局安装,执行 `rsw -h` 可查看帮助
npm i -g rsw-node

# ---------------------------------

# 项目中安装 rsw
npm i -D rsw-node

# 或者
yarn add -D rsw-node

Step1. 需要在项目根路径下创建 .rswrc.json 文件,例如

{
  "root": ".", // 默认为项目根路径,支持自定义路径,但是不可以超出项目根路径
  "crates": [
    "@rsw/chasm", // npm org
    "@rsw/game-of-life", // npm org
    { "name": "rsw-hello", "outDir": "custom/path" } // npm package,自定义输出路径
  ]
}

Step2. 当配置好 .rswrc.json 后,就可以在项目根路径下执行 rsw 命令,或者和 vite-plugin-rsw 配合使用,在 package.json 中添加如下代码。

{
  "scripts": {
    "rsw:deploy": "rsw && npm run build"
  },
}

具体使用,可以查看 lencx/learn-wasm

rsw cmd rsw cmd help

5. Vite

下一代前端工具

vite-plugin-rsw:vite插件,简称 Rsw - 集成 wasm-pack 的CLI

  • 支持rust包文件热更新,监听 src 目录和 Cargo.toml 文件变更,自动构建
  • vite启动优化,如果之前构建过,再次启动 npm run dev ,则会跳过 wasm-pack 构建
  • 通过配置 isLibtrue ,在执行 npm run build 时会生成可发布的npm包
  • 友好的错误提示:浏览器端弹窗 + 控制台及终端命令行
# 在vite项目中安装
npm i -D vite-plugin-rsw
# or
yarn add -D vite-plugin-rsw

rsw optimized rsw error rsw error outdir rsw error wasm-pack

6. create-mpl

脚手架 - ⚡️在几秒钟内创建一个项目!维护了多种项目模板。

# 根据命令行提示,输入项目名称,App 类型选择 `WebAssembly`,然后选择模板初始化项目
# template: `wasm-react` or `wasm-vue`
npm init mpl@latest my-wasm

直接指定 App 类型

# npm 6.x
npm init mpl@latest my-app --type wasm

# npm 7+, extra double-dash is needed:
npm init mpl@latest my-app -- --type wasm

wasm init

快速开始

  • 在原有 vite 项目中使用,只需安装配置 vite-plugin-rsw 插件即可。
  • 新项目可以使用 vite 提供的 @vitejs/app 初始化项目,然后安装配置 vite-plugin-rsw
  • 或者使用脚手架 create-mpl 初始化项目,模板包含 wasm-reactwasm-vue ,会定期更新维护相关版本依赖。

项目结构

# 推荐目录结构
[my-wasm-app] # 项目根路径
|- [wasm-hey] # npm包 `wasm-hey`
|    |- [pkg] # 生成 wasm 包的目录
|    |    |- wasm-hey_bg.wasm # wasm 文件
|    |    |- wasm-hey.js # 包入口文件
|    |    |- wasm-hey_bg.wasm.d.ts # ts 声明文件
|    |    |- wasm-hey.d.ts # ts 声明文件
|    |    |- package.json
|    |    `- ...
|    |- [src] # rust 源代码
|    | # 了解更多: https://doc.rust-lang.org/cargo/reference/cargo-targets.html
|    |- [target] # 项目依赖,类似于 npm 的 `node_modules`
|    | # 了解更多: https://doc.rust-lang.org/cargo/reference/manifest.html
|    |- Cargo.toml # rust 包管理清单
|    `- ...
|- [@rsw] # npm 组织包
|     |- [hey] # @rsw/hey, 目录结构同 `wasm-hey`
|     `- ...
| # 设置 `isLib` 为 true 时,会默认在 `libs` 目录下生成的npm包
| # 可以通过设置 `libRoot` 修改默认路径
|- [libs]
|     |- [@rsw]
|     `- [wasm-hey]
|- [node_modules] # 前端的项目包依赖
|- [src] # 前端源代码(可以是 vue, react, 或其他)
| # 了解更多: https://nodejs.dev/learn/the-package-json-guide
|- package.json # `npm` 或 `yarn` 包管理清单
| # 了解更多: https://vitejs.dev/config
|- vite.config.ts # vite 配置文件
| # 了解更多: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html
|- tsconfig.json # typescript 配置文件
` ...

乍一看,可能会觉得目录有点复杂,其实它就是一个标准的基于 vite 前端项目,然后,在根路径下去添加我们需要构建的wasm包(一个 rust crate 会对应生成一个 wasm 包,可单独发布到 npm 上)

创建Wasm包

# 两种方式创建

# 1.
# 如果报错,可查看:https://github.com/rustwasm/wasm-pack/issues/907
wasm-pack new <name>

# 2.
# name 可以是 npm 组织
# 例:cargo new --lib @rsw/hello
# 需要手动配置 Cargo.toml
cargo new --lib <name>

wasm-pack new cargo new

项目配置

以react项目为例

Step1: 配置 Vite 插件 - vite.config.ts

import reactRefresh from '@vitejs/plugin-react-refresh';
import { defineConfig } from 'vite';
import ViteRsw from 'vite-plugin-rsw';

export default defineConfig({
  plugins: [
    reactRefresh(),
    // 查看更多:https://github.com/lencx/vite-plugin-rsw
    ViteRsw({
      // 指定包管理器 - `pnpm` `yarn` `npm`,默认 `npm`
      cli: 'npm',
      // 自定义 rust 包路径,默认项目根路径
      root: '.',
      // wasm-pack build 参数 - `dev` `release` `profiling`,默认 `dev`
      profile: 'dev',
      // 编译目标 - `bundler` `web` `nodejs` `no-modules`,默认 `web`
      target: 'web',
      // 如果包在 `unLinks` 和 `crates` 都配置过
      // 会执行,先卸载(npm unlink),再安装(npm link)
      // 例如下面会执行
      // `npm unlink wasm-hey rsw-test`
      unLinks: ['wasm-hey', 'rsw-test'],
      // 项目根路径下的 rust 项目
      // `@`开头的为 npm 组织
      // 例如下面会执行:
      // `npm link wasm-hey @rsw/hey`
      // 因为执行顺序原因,虽然上面的 unLinks 会把 `wasm-hey` 卸载
      // 但是这里会重新进行安装
      // 同时支持字符串(string)和对象(RswCrateOptions)形式的配置
      // https://github.com/lencx/vite-plugin-rsw/issues/8
      // https://github.com/lencx/vite-plugin-rsw/blob/main/src/types.ts#L30
      crates: [
        'wasm-hey', // npm package
        '@rsw/hey', // npm org package
        { name: '@rsw/hello', outDir: 'custom/path' }, // 自定义输出路径
      ],
    }),
  ],
})

Step2: 配置 Rust 项目清单 - wasm-hey/Cargo.toml

# ...

# https://github.com/rustwasm/wasm-pack/issues/886
# https://developers.google.com/web/updates/2019/02/hotpath-with-wasm
[package.metadata.wasm-pack.profile.release]
wasm-opt = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]

[profile.release]
opt-level = "s"

[dependencies]
wasm-bindgen = "0.2.70"

Step3: 添加 Rust 代码 - wasm-hey/src/lib.rs


#![allow(unused)]
fn main() {
use wasm_bindgen::prelude::*;

// Import the `window.alert` function from the Web.
#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

// Export a `greet` function from Rust to JavaScript, that alerts a hello message.
#[wasm_bindgen]
pub fn greet(name: &str) {
    alert(&format!("Hello, {}!", name));
}
}

Step4: React 项目中调用 Wasm 方法 - src/App.tsx

import React, { useEffect } from 'react';
import init, { greet } from 'wasm-hey';

import logo from './logo.svg';
import './App.css';

function App() {
  useEffect(() => {
    // wasm 初始化,在调用 `wasm-hey` 包方法时
    // 必须先保证已经进行过初始化,否则会报错
    // 如果存在多个 wasm 包,则必须对每一个 wasm 包进行初始化
    init();
  }, [])

  const handleHey = () => {
    // 调用greet方法
    greet('wasm');
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>Hello WebAssembly!</p>
        <p>Vite + Rust + React</p>
        <p>
          <button onClick={handleHey}>hi wasm</button>
        </p>
        <p>Edit <code>App.tsx</code> and save to test HMR updates.</p>
      </header>
    </div>
  )
}

export default App

常见问题汇总

Rsw插件

  • 插件内部是通过 npm link 的形式实现的wasm包安装,在一些极端场景下会出现,找不到依赖的安装包,导入的包不存在等错误,可以根据提示路径删除其link的文件,重新启动 npm run dev 可以解决。

  • npm link 命令会把包 link 到全局环境,如果在多个项目使用相同wasm包名,可能会导致报错,解决办法,在全局npm的node_modules 中删除该包即可。推荐不同项目使用不同wasm包名避免此类异常。

  • 插件是处于Vite开发模式下运行构建,所以至少执行过一次 npm run dev ,生成 wasm 包之后,再执行 npm run build ,否则也会报错,到不到 .wasm 文件之类的。

  • 插件API可以配置需要卸载的包(仅限于之前通过插件配置 crates 中rust项目)

  • npm ERR! EEXIST: file already exists

    # https://docs.npmjs.com/cli/v6/commands/npm-link
    # npm link uses the global prefix (see npm prefix -g for its value)
    # /Users/lencx/.nvm/versions/node/v15.6.0
    npm prefix -g
    
    # after removing the folder, try again `npm run dev`
    rm -rf /Users/lencx/.nvm/versions/node/v15.6.0/lib/node_modules/@rsw/chasm
    

    rsw-error-link

前端

// init 是 wasm 实例的初始化方法
// 在调用其他方法之前,必须先调用一次 init 方法,否则会报错
// init 会请求 `.wasm` 文件并且返回一个 `Promise`
import init, { greet } from 'wasm-test';

// -----------------------------------------

// 调用init方法,有两种方式

// 1.
// 在 react,vue3 中可以将其抽离为 `hook` 组件,
// 在进入生命周期时调用
init();

// 在调用过 init 方法之后,可以单独调用 greet 方法
greet('wasm');

// 2.
// 在初始化之后直接调用方法
init()
  .then(wasm => wasm.greet('wasm'));

相关链接


康威生命游戏

Demo地址

康威生命游戏(英语:Conway's Game of Life),又称康威生命棋,是英国数学家约翰·何顿·康威在1970年发明的细胞自动机。 它最初于1970年10月在《科学美国人》杂志上马丁·葛登能的“数学游戏”专栏出现。

此文以学习笔记为主,如需了解更多,请查看原文

游戏规则

生命游戏的宇宙是一个无限的二维正交方格,每个方格处于两种可能的状态之一,即存活死亡,或处于填充未填充状态。每个单元都与其八个邻居进行交互,这八个邻居在水平,垂直或对角线相邻。在每个步骤中,都会发生以下转换:

  • 当前细胞为存活状态时,当周围的存活细胞低于2个时(不包含2个),该细胞变成死亡状态。(模拟生命数量稀少)
  • 当前细胞为存活状态时,当周围有2个或3个存活细胞时,该细胞保持原样。
  • 当前细胞为存活状态时,当周围有超过3个存活细胞时,该细胞变成死亡状态。(模拟生命数量过多)
  • 当前细胞为死亡状态时,当周围有3个存活细胞时,该细胞变成存活状态。(模拟繁殖)

宇宙的初始状态
initial universe

可以把最初的细胞结构定义为种子,当所有在种子中的细胞同时被以上规则处理后,可以得到第一代细胞图。按规则继续处理当前的细胞图,可以得到下一代的细胞图,周而复始。

游戏操作

  • 开始 / 暂停按钮
  • 点击单元格切换细胞状态: 存活 ⇋ 死亡

canvas