作者: Allison

  • webpack loader

    webpack 只能理解 JavaScript 和 JSON 文件。

    在 webpack 中,loader 用于对模块的源代码进行转换。可以将文件从不同的语言(如 TypeScript)转换为 JavaScript 或将内联图像转换为 data URL。

    配置

    module.rules 允许你在 webpack 配置中指定多个 loader。

    loader 从右到左(或从下到上)地取值(evaluate)/执行(execute)

    module.exports = {
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              { loader: 'style-loader' },
              {
                loader: 'css-loader',
                options: {
                  modules: true,
                },
              },
              { loader: 'sass-loader' },
            ],
          },
        ],
      },
    };

    在以上例子中:

    1. 执行 sass-loader :Sass 或 SCSS 文件编译成 CSS 代码。
    2. 执行 css-loader:解析第一步生成的 CSS 文件,将 CSS 代码转换成 JavaScript 模块;options: { modules: true } 启用 CSS 模块化功能,这意味着每个 CSS 文件都会被视为一个独立的模块,可以避免样式冲突。
    3. 执行 style-loader :将 css-loader 处理后的 CSS 代码动态地插入到 HTML 页面的 <style> 标签中。

    特性

    • 链式调用:一组链式的 loader 将按照相反的顺序执行。链中的第一个 loader 将其结果(也就是应用过转换后的资源)传递给下一个 loader,依此类推。最后,链中的最后一个 loader,返回 webpack 所期望的 JavaScript。
    • loader 可以是同步的,也可以是异步的
    • loader 运行在 Node.js 中,并且能够执行任何操作
    • loader 可以通过 options 对象配置
    • 除了常见的通过 package.json 的 main 来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用 loader 字段直接引用一个模块。
    • 插件(plugin)可以为 loader 带来更多特性。
    • loader 能够产生额外的任意文件。

    类型

    • loader: 核心的转换器,负责将源文件转换成 webpack 可以理解的模块。
    • preLoader: 在普通 loader 之前执行的 loader,主要用于预处理文件,例如代码检查(linting)。
    • postLoader: 在普通 loader 之后执行的 loader,主要用于后处理文件,例如优化或添加额外功能。

    执行顺序

    webpack 在处理模块时,loader 的执行顺序如下:

    1. preLoader
    2. loader
    3. postLoader

    举个例子

    preLoader 示例:ESLint 代码检查

    module: {
      rules: [
        {
          enforce: 'pre', // 指定为 preLoader
          test: /\.js$/,
          exclude: /node_modules/,
          loader: 'eslint-loader',
          options: {
            // eslint 配置选项
          },
        },
        // 其他 loader 配置
      ],
    },

    postLoader 示例:添加版权信息

    • 创建一个自定义的 postLoader,在代码处理完成后,自动添加版权信息到 JavaScript 文件的顶部。
    //copyright-loader.js
    module.exports = function(source) {
      const copyright = '// Copyright (c) 2024 Your Company\n';
      return copyright + source;
    };
    module: {
      rules: [
        {
          enforce: 'post',  // 指定为 postLoader
          test: /\.js$/,
          exclude: /node_modules/,
          use:{
            loader:path.resolve(__dirname,'copyright-loader.js')
          }
        },
        // 其他 loader 配置
      ],
    },

  • SSH: no matching host key type found.

    最近在使用 SSH 连接服务器时有报错:

    Unable to negotiate with xx.xx.xx.xx port 22: no matching host key type found. Their offer: ssh-rsa,ssh-dss

    主要原因是服务端较旧,客户端较新,服务端提供的密钥算法在客户端均已经默认被禁用。其中:

    这里只能进行一个 workaround,在连接时临时允许特定算法/密钥。

    ssh -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa user@host

    如果是 Git 推拉代码的场景,也是同理:

    GIT_SSH_COMMAND="ssh -oHostKeyAlgorithms=+ssh-rsa" git clone ssh://user@host/repo

    引用

    OpenSSH: Legacy Options

    web hosting – Unable to negotiate with XX.XXX.XX.XX: no matching host key type found. Their offer: ssh-dss – Stack Overflow

    Git error no matching host key type found. Their offer: ssh-rsa – Stack Overflow

  • Hello,鸿蒙应用

    感觉鸿蒙应用文档很多,初学者看起来有点乱乱的,记录一下快速开发方法。

    开发者账号注册

    你需要提前注册好华为开发者联盟账号,否则在后续开发调试应用过程中无法完成自动签名、发布等操作

    安装IDE

    HUAWEI DevEco Studio下载链接

    Hello World

    新建Project,在应用中选择第一个模版项目

    真机调试

    准备好你的华为手机,使用USB数据线连接到电脑。

    • 打开开发者模式
    • 打开“USB调试”开关

    基础知识

    应用名称修改

    1. 配置文件:src/resources/zh_CN/element/string.json
    2. 修改nameEntryAbility_label对应的value

    应用图标修改

    1. 配置文件:src/resources/base/media

    入口文件

    1. 配置文件:src/main/module.json5
    2. 查看 srcEntry 指向的文件
    3. 查看onWindowStageCreate方法中加载的页面为入口文件

    新建页面

    1. 配置文件:src/resources/base/profile/main_pages.json
    2. 在src/ets/pages目录下新建同名ets文件

    页面之间跳转

    1. 在A页面中注册按钮,绑定点击事件,使用router.pushUrl点击即跳转到第二个页面
    2. 在B页面中注册按钮,绑定点击事件,router.back()点击跳回A页面
    // Page A
    Button() {
      Text('Next')
        .fontSize(30)
        .fontColor('#FFFFFF')
        .fontWeight(FontWeight.Bold)
    }
    .type(ButtonType.Capsule)
    .backgroundColor('#409EFF')
    .width('40%')
    .height('5%')
    .onClick(() => {
      router.pushUrl({url: 'pages/Second'}).then(() => {
        console.info('Jumping to second page')
      }).catch((err) => {
        console.error(`Failed to jump to the second page. Code is ${err.code}, message is ${err.message}`)
      })
    // Page B
    Button() {
      Text('Back')
        .fontSize(25)
        .fontColor('#FFFFFF')
        .fontWeight(FontWeight.Bold)
    }
    .type(ButtonType.Normal)
    .backgroundColor('#409EFF')
    .width('40%')
    .height('5%')
    .onClick(() => {
      router.back()
    })

    常见问题

    真机运行报错:

    Failure[MSG_ERR_INSTALL_FAILED_NO_BUNDLE_SIGNATURE]

    解决:单击Open signing configs 或 通过File -> Project Structure -> Signing Config 重新登录

  • macOS部署Comfy UI

    安装Python

    brew install python

    安装PyTorch

    pip install torch torchvision torchaudio
    // or
    python3 -m pip install torch torchvision torchaudio

    克隆Comfy UI仓库

    git clone https://github.com/comfyanonymous/ComfyUI
    cd ComfyUI

    安装依赖

    pip install -r requirements.txt

    运行Comfy UI

    python main.py
    // or
    python3 main.py

    下载模型

    liblibhuggingface下载模型后,放在Comfy UI仓库 models/checkpoints 目录下,即可选择模型,运行工作流出图。

  • 离开页面时调接口

    场景:当前页面正在执行异步任务,当用户点击导航栏或直接关闭页面时,需调接口取消任务,减少资源浪费。

    问题:如果在beforeunload事件中调ajax 请求,则会出现浏览器cancel掉请求的情况。

    解决:使用sendBeacon方法,适用于发送少量数据到服务器。

    Navigator.sendBeacon()

    这个方法主要用于满足统计和诊断代码的需要,这些代码通常尝试在卸载(unload)文档之前向 Web 服务器发送数据。过早的发送数据可能导致错过收集数据的机会。然而,对于开发者来说保证在文档卸载期间发送数据一直是一个困难。因为用户代理通常会忽略在 unload 事件处理器中产生的异步 XMLHttpRequest

    Vue 3代码如下

    <script setup>
    import { onBeforeUnmount, onMounted } from 'vue';
    
    // 取消任务的API函数
    function cancelTask() {
      navigator.sendBeacon('/api/cancel-task', JSON.stringify({ task: 'taskId' }));
    }
    
    onMounted(() => {
      // 添加 beforeunload 事件监听器
      window.addEventListener('beforeunload', cancelTask);
    });
    
    onBeforeUnmount(() => {
      // 组件卸载前移除监听器
      window.removeEventListener('beforeunload', cancelTask);
    });
    </script>
    
  • 迁移monorepo保留原仓库提交记录

    将代码仓库转移到monorepo如果直接将代码复制到大仓中就会丢失原本的代码提交记录。而大多数情况下我们希望保留提交记录以方便后期问题定位等情况。

    • 在原仓库中进行以下操作,这里注意文件移动需要时间,确保全部文件移动完成后再进行下一步提交操作
    # 移动文件
    git mv -k * projects/{你的项目名}
    git mv -k .* projects/{你的项目名} 
    
    # 提交仓库
    git commit && git push
    • 在迁移目标仓库中进行以下操作
    git remote add -f my-app {原仓库git地址}
    
    git merge my-app/{分支名} --allow-unrelated-histories
    
    git remote remove my-app
    git push
  • parseInt

    parseInt永远将输入解析为字符串

    parseInt({toString: () => 2, valueOf: () => 1}); // -> 2
    Number({toString: () => 2, valueOf: () => 1}); // -> 1
    

    一个常见的栗子

    parseInt("apple"); // -> NaN
    parseInt("apple", 16); // -> 10
    

    上面这个例子是因为parseInt会一个个解析字符直到解析不了,所以解析十六进制中的a为10作为结果返回。

    parseInt Infinity

    parseInt("Infinity", 10); // -> NaN
    // ...
    parseInt("Infinity", 18); // -> NaN...
    parseInt("Infinity", 19); // -> 18
    // ...
    parseInt("Infinity", 23); // -> 18...
    parseInt("Infinity", 24); // -> 151176378
    // ...
    parseInt("Infinity", 29); // -> 385849803
    parseInt("Infinity", 30); // -> 13693557269
    // ...
    parseInt("Infinity", 34); // -> 28872273981
    parseInt("Infinity", 35); // -> 1201203301724
    parseInt("Infinity", 36); // -> 1461559270678...
    parseInt("Infinity", 37); // -> NaN
    

    parseInt null

    parseInt(null, 24); // -> 23
    

    上面的结果都是一样的解析过程,按字母表排序,每个字符对应数字是固定的。比如,I对应18,在18进制中只能解析0-17,所以parseInt("Infinity", 18); // -> NaN,在19进制中就能解析出结果:parseInt("Infinity", 19); // -> 18

    n对应23,在23进制中解析不了,只返回18,在24进制中就是18 * (24 ^ 5) + 23 * (24 ^ 4)+ 15 * (24 ^ 3)+ 18 * (24 ^ 2) + 23 * 24 + 18 = 151176378,其他依此类推。

    八进制

    parseInt("08"); // -> 8, 支持ES5
    parseInt("08"); // -> 0, 不支持ES5
    

    以0开头的数字可以被解析为10进制或8进制,ECMAScript 5规定为10进制,但不是所有浏览器都支持它。所以使用parseInt时指定进制是好习惯,有时可以避免不必要的bug。

    浮点数

    parseInt(0.000001); // -> 0
    parseInt(0.0000001); // -> 1
    parseInt(1/1999999); // -> 5
    

    parseInt 接受一个字符串参数并返回一个指定基数的整数。 parseInt 还会去除字符串参数中第一个非数字之后的任何内容。0.000001转换为字符串后是"0.000001",0.0000001转换后是"1e-7",所以返回1,1/1999999转换后是5.00000250000125e-7,返回结果是5。

  • 本地开发debug npm包

    使用pnpm包管理

    1. 在本地npm包目录下执行

    pnpm link --global

    2. 如果是cli,可直接在其他项目目录下调用

    3. 如果需要在其他项目中引入npm包,在其他项目目录下执行

    pnpm link --global npm-package-name

    使用npm包管理

    1. 在npm包目录下执行

    npm link

    2. 在其他目录使用

    npm link 本地npm包绝对路径

    官方文档:https://docs.npmjs.com/cli/v9/commands/npm-link

  • async/await class constructor

    背景:期望在新建类时调后台接口对类进行配置,配置成功后才可以执行其他操作。

    尝试封装类的构造函数为async/await,报错

    Class constructor may not be an async method

    原因:async函数返回值为promise,而构造函数返回值为object,同一个函数不可能返回值同时为promiseobject

    解决:参考 jQuery's ready()方法

    1. 类实现

        class myClass {
            constructor () {
    
            }
    
            async ready (callback) {
                await yourHttpRequest();
                // 回调函数绑定作用域
                callback.bind(this)();
            }
        }

    2. 调用

        const myObj = new myClass();
        myObj.ready(function() {
            // 保证配置完才能执行其他操作
        });
    

    参考:https://stackoverflow.com/questions/43431550/async-await-class-constructor

  • Vue 3 TypeScript项目报错类型{}不存在属性

    搜了一下tsconfig.json文件需要配置"jsx": "preserve",但我的config文件原先就有这个配置。因为项目一开始是可以正常报错的,迁移到大仓后就有问题了。中间也考虑了是否是配置文件位置的问题,调整了位置也没有解决问题。

    最后看到tsconfig.json文件另一个报错“找不到node的类型定义文件”

    原因是我没有安装@types/node,装上后重新打开IDE”类型{}不存在属性“和“找不到node的类型定义文件”报错都没有了。

    // npm
    npm i @types/node --save-dev
    
    // pnpm
    pnpm add @types/node