Hexo 构建过程中报错 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed – JavaScript heap out of memory 如何处理?

green and black digital device

最近在处理 Linux 中国的静态站点,在技术选项上,为了方便修改,选择了 Hexo 来建设。

数据从 Discuz 转换到 Markdown 已经处理好了,但在构建过程中遇到了一些问题,会报如下错误

☁  linux [main] ⚡  hexo g
INFO  Validating config
INFO  Start processing
INFO  Files loaded in 2.37 min

<--- Last few GCs --->

[4685:0x118008000]   188193 ms: Scavenge (reduce) 3974.1 (4131.6) -> 3974.1 (4131.6) MB, 1.96 / 0.00 ms  (average mu = 0.143, current mu = 0.117) allocation failure;
[4685:0x118008000]   188198 ms: Scavenge (reduce) 3977.5 (4135.0) -> 3977.5 (4135.0) MB, 1.88 / 0.00 ms  (average mu = 0.143, current mu = 0.117) allocation failure;
[4685:0x118008000]   188202 ms: Scavenge (reduce) 3981.0 (4138.5) -> 3981.0 (4138.5) MB, 1.79 / 0.00 ms  (average mu = 0.143, current mu = 0.117) allocation failure;


<--- JS stacktrace --->

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
----- Native stack trace -----

 1: 0x104762660 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 2: 0x1048dcc84 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 3: 0x1048dcc34 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 4: 0x104a82410 v8::internal::Heap::CallGCPrologueCallbacks(v8::GCType, v8::GCCallbackFlags, v8::internal::GCTracer::Scope::ScopeId) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 5: 0x104a84e98 v8::internal::Heap::ComputeMutatorUtilization(char const*, double, double) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 6: 0x104a84b80 v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 7: 0x104a83f08 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::internal::GarbageCollectionReason, char const*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 8: 0x104a829a4 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags)::$_6::operator()() const [/opt/homebrew/Cellar/node/21.5.0/bin/node]
 9: 0x104a8277c void heap::base::Stack::SetMarkerAndCallbackImpl<v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags)::$_6>(heap::base::Stack*, void*, void const*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
10: 0x104680028 PushAllRegistersAndIterateStack [/opt/homebrew/Cellar/node/21.5.0/bin/node]
11: 0x104a8122c v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
12: 0x104a7977c v8::internal::HeapAllocator::AllocateRawWithLightRetrySlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
13: 0x104a79f20 v8::internal::HeapAllocator::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
14: 0x104a61988 v8::internal::Factory::AllocateRaw(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
15: 0x104a58874 v8::internal::MaybeHandle<v8::internal::SeqTwoByteString> v8::internal::FactoryBase<v8::internal::Factory>::NewRawStringWithMap<v8::internal::SeqTwoByteString>(int, v8::internal::Tagged<v8::internal::Map>, v8::internal::AllocationType) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
16: 0x104d74a6c v8::internal::Runtime_StringBuilderConcat(int, unsigned long*, v8::internal::Isolate*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
17: 0x104573954 Builtins_CEntry_Return1_ArgvOnStack_NoBuiltinExit [/opt/homebrew/Cellar/node/21.5.0/bin/node]
18: 0x1045e94c4 Builtins_RegExpReplace [/opt/homebrew/Cellar/node/21.5.0/bin/node]
19: 0x1045625ac Builtins_StringPrototypeReplace [/opt/homebrew/Cellar/node/21.5.0/bin/node]
20: 0x1044e8b84 Builtins_InterpreterEntryTrampoline [/opt/homebrew/Cellar/node/21.5.0/bin/node]
21: 0x1289db514
22: 0x1289dbae4
23: 0x128def07c
24: 0x1289db514
25: 0x128d2de58
26: 0x1289db514
27: 0x1289d9e20
28: 0x1288da1d8
29: 0x128de4df4
30: 0x1288ce2bc
31: 0x1288c99d8
32: 0x1288de57c
33: 0x1288de6d8
34: 0x1288d0654
35: 0x1044e68ac Builtins_JSEntryTrampoline [/opt/homebrew/Cellar/node/21.5.0/bin/node]
36: 0x1044e6594 Builtins_JSEntry [/opt/homebrew/Cellar/node/21.5.0/bin/node]
37: 0x1049fca88 v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
38: 0x1049fc480 v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
39: 0x1048f093c v8::Function::Call(v8::Local<v8::Context>, v8::Local<v8::Value>, int, v8::Local<v8::Value>*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
40: 0x104681110 node::InternalMakeCallback(node::Environment*, v8::Local<v8::Object>, v8::Local<v8::Object>, v8::Local<v8::Function>, int, v8::Local<v8::Value>*, node::async_context) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
41: 0x104681508 node::MakeCallback(v8::Isolate*, v8::Local<v8::Object>, v8::Local<v8::Function>, int, v8::Local<v8::Value>*, node::async_context) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
42: 0x1047001fc node::Environment::CheckImmediate(uv_check_s*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
43: 0x107d57ec8 uv__run_check [/opt/homebrew/Cellar/libuv/1.47.0/lib/libuv.1.dylib]
44: 0x107d52cc4 uv_run [/opt/homebrew/Cellar/libuv/1.47.0/lib/libuv.1.dylib]
45: 0x1046819dc node::SpinEventLoopInternal(node::Environment*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
46: 0x1047a9ad4 node::NodeMainInstance::Run(node::ExitCode*, node::Environment*) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
47: 0x1047a983c node::NodeMainInstance::Run() [/opt/homebrew/Cellar/node/21.5.0/bin/node]
48: 0x104722e80 node::Start(int, char**) [/opt/homebrew/Cellar/node/21.5.0/bin/node]
49: 0x187fb10e0 start [/usr/lib/dyld]
[1]    4685 abort      hexo g
Code language: HTML, XML (xml)

这个报错中,最有价值的便是 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed – JavaScript heap out of memory

这个报错的意思是目前 JavaScript 使用的内存已经超出了可用范围,导致程序挂掉,而如果想要解决这个问题,只需要分配更多的内存给 Hexo 即可。

只需要在构建的命令前加入NODE_OPTIONS=--max-old-space-size=24576,就可以分配更多的内存给 Node.js 。这里的 24576 是 24GB 内存的含义,你可以根据你的需要来选择。

PS. Linux 中国的数据太多,以至于我用 Hexo 分配了 24GB 都不行。。我决定换 Hugo 了。。希望 Hugo 可以。。。

给你的 console.log 添加一些特定的输出

9a1f326b911de6c1629837f3b57551e5 1

在写 Node.js 代码时,常常会使用 console.log 来输出内容,以便于调试。但默认的 console.log 只能标准的输出,在很多需要上下文 debug 的时候,可能信息是不足的。除了使用 debugger 以外,你还可以试着改造 console.log

在你的 index.js 顶部添加如下代码,即可实现在使用 console.log 时自动在前面加上时间信息。当然,你也可以实现自己需要的上下文,比如当前的文件、当前的行数等。

console.log = (function() {
  var console_log = console.log;
  return function() {
    var args = [];
    args.push(`${new Date().toLocaleString()}` + ' -> ');
    for(var i = 0; i < arguments.length; i++) {
      args.push(arguments[i]);
    }
    console_log.apply(console, args);
  };
})();
Code language: JavaScript (javascript)

这个函数的逻辑不复杂,对 console.log 进行了覆盖,写如了新的函数,并通过 arguments 将开发者传入的参数重新打印,以确保不丢失开发者传入的参数。

使用 Sheetjs 将 JSON Array 转化为 Excel

white printing paper with numbers

使用 node-excel-stream 来按行处理 Excel 数据 中,我提到,如果你希望简单的完成 Excel 的读取和处理,那么 node-excel-stream 是个不错的选择。而反过来,如果你希望将 JSON Array 导出为 Excel,那么 Sheetjs 是个不错的选择。

注意

Sheetjs 和 exceljs 不同,区分了商业版和社区版。我们这里使用的是社区版 Sheetjs CE

用法

使用 Sheetjs 对数据进行导出时,你只需要调用 XLSX 方法当中的 json_to_sheet ,就可以将你的 JSON Array 变为一个 worksheet,接下来只需要将其放入一个新的 workbook 当中,并导出为文件,就可以完成 JS 数据导出为 Excel。

const XLSX = require("xlsx");

const data = [
  {
    ...
  },
  ...
]

const worksheet = XLSX.utils.json_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "sheetNameIsFirst");
XLSX.writeFile(workbook, "output.xlsx");
Code language: JavaScript (javascript)

使用 node-excel-stream 来按行处理 Excel 数据

white printing paper with numbers

数据分析是一个非常常见的需求,而在实际的落地场景当中, Python 是使用最多的。不过我因为写了很久的前端,其实对于Python已经生疏了。当我开始启动项目时,就会选择执行 npm init 来初始化一个项目。既然如此,就试着使用 Node.js 来做数据分析。

在 Node.js 当中操作 Excel ,最好的便是 Exceljs。不过 ExcelJs 封装了大量的函数,对于绝大多数的数据分析场景来说,可能并不适用(也不一定,只是我比较喜欢用代码来描述逻辑,Excel 更多是一个导入导出)。

当明确了我只是需要一个简单的导入导出后,那么 node-excel-stream 就进入了我的视野。

读取 Excel 内容

和 Exceljs 不同,node-excel-stream 的封装相对简单,就是一个 Reader 和 Writer ,提供的方法也十分简单:读取文件、定义格式,按行处理内容;

需要注意的是,node-excel-stream 只支持 xlsx ,而不支持 xls,所以如果你用的是旧版,则需要重新保存成 xlsx 来进行处理。

let dataStream = fs.createReadStream('data.xlsx');
let reader = new ExcelReader(dataStream, {
    sheets: [{
        name: 'Users',
        rows: {
            headerRow: 1,
            allowedHeaders: [{
                name: 'User Name',
                key: 'userName'
            }, {
                name: 'Value',
                key: 'value',
                type: Number
            }]
        }
    }]
})
console.log('starting parse');
reader.eachRow((rowData, rowNum, sheetSchema) => {
    console.log(rowData);
})
.then(() => {
    console.log('done parsing');
});
Code language: JavaScript (javascript)

写入 Excel 内容

写入时和读取时相比,稍微复杂一点,需要将所有的输入使用 Promise.all包裹起来

let writer = new ExcelWriter({
    sheets: [{
        name: 'Test Sheet',
        key: 'tests',
        headers: [{
            name: 'Test Name',
            key: 'name'
        }, {
            name: 'Test Coverage',
            key: 'testValue',
            default: 0
        }]
    }]
});
let dataPromises = inputs.map((input) => {
    // 'tests' is the key of the sheet. That is used
    // to add data to only the Test Sheet
    writer.addData('tests', input);
});
Promise.all(dataPromises)
.then(() => {
    return writer.save();
})
.then((stream) => {
    stream.pipe(fs.createWriteStream('data.xlsx'));
});
Code language: JavaScript (javascript)

总结

如果你需要将 Excel 导入到 Js 当中进行处理,那么 node-excel-stream 是一个不错的选择。

warehouse — 一个简单易用的 JSON 数据库

black and yellow printed paper

在 Hexo 的 Github 组织下,有一个不明显,但却很有用的仓库 —— warehouse。

warehouse 是一个 JSON 数据库,基于 JSON 实现了各种类似于 SQL 的查询,可以帮助我们基于一个 JSON 文件来进行查询。在 Hexo 的静态生成过程中,warehouse 帮了大忙。

用官方的话来说,warehouse 就是 A JSON database with Models, Schemas, and a flexible querying interface.

在实际使用上,warehouse 确实如他所说的那边方便(虽然某些方法没有,但依然不影响他的使用很方便)。

Example

比如,如下代码就定义了 一个 Post 模型和对应的表。并实现了在这个表中插入一个新的数据。

var Database = require('warehouse');
var db = new Database();

var Post = db.model('posts', {
  title: String,
  created: {type: Date, default: Date.now}
});

Post.insert({
  title: 'Hello world'
}).then(function(post){
  console.log(post);
});
Code language: JavaScript (javascript)

如果你需要将这些数据保存为一个单独的文件,只需要修改初始化的参数,并执行 save 方法,就可以将 JSON 导出到指定的文件中

var db = new Database({
  path: "./test.json", // 将数据存储在 test.json 当中
});
db.save();
Code language: JavaScript (javascript)

类似的,如果数据已经构建好了,也只需要执行 load 方法,就可以加载数据。

var db = new Database({
  path: "./test.json", // 将数据存储在 test.json 当中
});
db.load();
Code language: JavaScript (javascript)

场景

如果你希望在内存当中对于 JSON 有一个更好的操作方式,那么 warehouse 是个不错的选择,不需要另外单独安装数据库,就可以实现类似于数据库的查询方式,体验还是非常好的。

如果你想了解更多,可以查看

在项目中使用 Dead Simple LESS CSS Watch Compiler 来自动生成 css 文件

9a1f326b911de6c1629837f3b57551e5 1

最近在写一个 WordPress 主题来帮助我完成从 WordPress 到微信公众号的实现。在这个过程中,我需要借助于一些 CSS 的超集,来帮助我完成样式的编写。考虑到 SCSS 的 C++ 依赖问题,我选择了 Less 来完成。但如果直接使用 lessc 的话,主要面临的问题在于无法检测文件更新,这样对于需要实时查看效果的我来说,是比较麻烦的。所以我选择使用 Dead Simple LESS CSS Watch Compiler 来完成自动监控文件变化并刷新的功能。

教程

安装

执行 npm 命令来安装 Dead Simple LESS CSS Watch Compiler

yarn global add less less-watch-compiler 

安装完成后,你就可以执行命令来监听文件的变化。

配置

这里为了方便,我在 WordPress 插件目录中初始化了 npm, 因此,可以非常方便的借助于 npm script 来完成命令的配置。

通过配置了单独的 Build 命令,实现了执行 npm run build 就会自动监听 less 文件夹下的文件,并转换成对应的 css 文件,放置在 css 目录中。

{
  "private": true,
  "scripts": {
    "build": "less-watch-compiler ./less ./css"
  },
  "devDependencies": {
    "less": "^4.1.2",
    "less-watch-compiler": "^1.16.3"
  }
}
Code language: JSON / JSON with Comments (json)

其他

如果你需要对 less 运行有更多配置的诉求,还可以创建一个 less-watch-compiler.config.json 来配置具体的执行目录。不过我对于这部分没有要求,就直接整个目录来进行配置了。

{
    "watchFolder": "<input_folder>",   
    "outputFolder": "<output_folder>",
    "mainFile": "<main-file>",   
    "includeHidden": false,
    "sourceMap": false,
    "plugins": "plugin1,plugin2",
    "lessArgs": "option1=1,option2=2",
    "runOnce": false,
    "enableJs": true
}

总结

SCSS 因为 node-scss 的编译问题被各种吐槽,虽然换成了 dart-scss ,但历史的阴影还在。选择了 less 后,通过一些配置,可以让我自己的开发变得更加简单。何乐而不为?

在 Pug 中实现类似 get_sidebar() 全局方法

javascript

在开发 WordPress 主题时,我们会用到一些全局方法,来帮助我们快速加载特定的区域的代码。如果我们在设计和开发主题的时候,也可以实现类似的功能,则在开发对应的页面和应用的时候,我们就可以根据自己的需求来进行特定区域的代码的封装。这样我们在进行后续的开发的时候,就可以简化自己的代码,同时还可以按照 WordPress 的规范提前拆分代码,在实际进行项目开发的时候,提升效率。

原理

本次实现主要是基于 Pug 自带的 Mixins 机制来实现在主题中提供自定义的函数,从而实现我们想要的内容。在原理上,和 WordPress 的 get_sidebar 之类的方法不完全一样。所以,在体验上还没有做到像 WordPress 那么方便。

实现方式

1. 创建 Mixins

在项目的根目录下创建一个 mixins 目录,并在其中创建一个 includes.pug 文件和一个 getHeader.pug 文件。

includes.pug 文件中添加如下代码

include getHeader
Code language: PHP (php)

getHeader.pug 文件中添加如下代码

mixin getHeader
    p header

2. 引入 includes.pug

在你的 layouts.pug 文件中加入 includes.pug 的引入。需要注意的是,要加在 doctype 之前。

include ../mixins/includes
doctype html
html
  head
Code language: PHP (php)

3. 在需要的地方调用

当你完成上述的配置后,即可在需要的地方进行调用。

extends layout

block content
  +getHeader
  h1= title
  p hi to #{title}
Code language: PHP (php)

调用的效果如下:

d2b5ca33bd970f64a6301fa75ae2eb22 39

如果你希望实现更加强大的功能(比如参数),可以参考 Pug 官网的 Mixins 页面。

为 Express 项目添加文件变更自动刷新

javascript

我最近在准备开发新的 WordPress 的主题的工具,为了方便自己开发主题,正在准备一个脚手架,本文是开发过程中的经验总结。

白宦成

在进行主题开发的时候,一个很好的体验是来自于变更能够实时生效。过去我们想要实现这样的功能需要自己手动刷新浏览器。但随着前端技术的进步,我们可以借助诸如 browser-sync 这样的工具来完成自动的刷新,从而实现无需手动刷新,代码修改后自动刷新。此外,除了 browser-sync, 你还可以选择 webpack-dev-server

原理分析

此类工具的工作模式是比较简单的,一般而言,会要求你设定一个代理的端口,他会将对应端口的请求进行转发,并在其中加入 BrowserSync 的 JS 文件,从而实现可以无需手动操作来刷新。

d2b5ca33bd970f64a6301fa75ae2eb22 37
browser-sync-client.js

而其刷新的能力则是源自于 BrowserSync 本地提供的 WebSocket Server。嵌入的 JS 文件中加入了 WebSocket 的相关链接代码, 从而实现从 Command Line 当中获取刷新指令,前端则在获取到指令后进行刷新。

d2b5ca33bd970f64a6301fa75ae2eb22 36
WebSocket 请求

具体步骤

1. 安装 BrowserSync

首先在项目中引入 browser-sync ,执行如下命令

npm install browser-sync --save-dev

2. 修改启动文件

我是使用 express generator 生成的项目,需要修改 bin/www 文件,如果你是自己建设的项目,则需要自行修改。

在启动文件中找到监听端口的代码, 并在其回调函数中添加如下代码

# 添加前
server.listen(port)
# 添加后
var bs = require('browser-sync').create();
server.listen(port, () => { 
  bs.init({
    open: false,
    ui: false,
    notify: false,
    proxy: 'localhost:' + port,
    files: ['./views/**', './routes/**'],
    port: 8080
  });
});
Code language: PHP (php)

上述配置中,可以根据你的需要修改 proxy 、files、port 等选项,实现自定义配置。

关于配置的更多信息,你可以参考 https://browsersync.io/docs/options

3. 启动服务,并测试

在命令行中执行 npm start ,在浏览器中打开对应的页面,当可以看到内容后,可以修改文件,等待页面的自动刷新,如果成功刷新了,则表示你已经完成了相关能力的配置。

Jest 如何将复杂的判断条件中的具体问题暴露出来?

code 1076536 640

在写测试的时候,如果你需要对大量的数据进行 compare 处理的时候,你大概率不会把所有需要对比的对象都列出来,而是选择直接循环处理。

在测试中如果有循环处理的时候,很有可能会出现的一个问题是你可能无法在测试无法通过时快速定位道具体是循环中的哪一个元素出现的问题。这个时候的定位就会比较麻烦。

一个比较好的办法是,可以在 Jest 中加入 try/catch 中来处理错误,这样可以在出现错误的时候,打印一些辅助信息来快速定位,比如

it('test-error-catch-example',() => {

   let needTestData = [1,2,3,4]

   needTestData.foreach( item => {
       let result = doSomething(item)
       // 这里开始是新增的
       try{
          expect(result).toBe(true)
       }catch(e){
          console.log("error key",item)
          throw e;
       }
       // 新增的错误处理结束
   })

})
Code language: JavaScript (javascript)

通过添加一个自定义的 try catch ,可以在出现问题的时候,一方面将 Error 按照常规的方式抛出,等待 Jest 处理,另一方面,可以在 catch 时输出自定义的信息,方便我们进行排查和修复。

Vite 添加 alias

12a22fceeb6fde42f27df1458362a030

如果你希望简化你的 import 引用,可以通过在 Vite 中配置 alias ,来简化你的 import 引用。

只需要定义 reslove.alias 属性即可配置。

import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
    plugins: [vue()],
    resolve: {
        alias: {
            '@': path.resolve(__dirname, './src'),
            '@c': path.resolve(__dirname, './src/components'),
        },
    }
})

Code language: JavaScript (javascript)

参考地址:https://cn.vitejs.dev/config/#resolve-alias

如何使用 GitHub Action 自动发布 NPM

black and white penguin toy

我经常会用 GitHub 来存储我的代码,其中很大一类是各种 npm 包。

由于本地常年配置了 npm 的 mirror,我更喜欢使用 GitHub 自动发布。

有了这个配置,我只需要编写完代码后,并执行如下命令,即可实现自动发布包.

npm version patch
git push --all
git push --tags

以下是对应的 GitHub action file。

使用时需要配置 npm_token 为你自己的 NPM Token。这个 Token 可以在 NPM 后台获取到。

name: Node.js Package
on:
  push:
    tags:
    - "*"
jobs:
  publish-npm:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 14
          registry-url: https://registry.npmjs.org/
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{secrets.npm_token}}
Code language: JavaScript (javascript)

彩云翻译 API 丢失换行的问题

Hello World text

在使用彩云翻译的 API 进行应用开发的时候,遇见了一个很尴尬的问题,提交上去的内容会丢失换行。

202108292019753
示意图

于是给彩云翻译的邮箱发了一封邮件,一天后,我就收到了官方人员的微信好友邀请(因为我的微信写在邮件的footer)。

经过一番沟通后了解到,目前彩云翻译的 API 会默认移除换行符 & HTML 标签,因此,在开发时,暂时还需要自行对文本进行切割,提取其中的文本内容,按段进行翻译,解决这个问题。

不过,官方也在反馈,会评估提供翻译 HTML 文档的能力,期待一下吧。

202108292022971

发布 NPM 包时,遭遇 You should bug the author to publish it (or use the name yourself!) 怎么办?

orange pink keyboard

在发布一个 NPM 包时,我遭遇了这样的一个问题:

You should bug the author to publish it (or use the name yourself!)
Code language: PHP (php)

经过查询后发现,是因为我之前使用的 Login Token 失效了,在这种情况下,只需要重新执行npm adduser添加用户,即可解决问题。


我之前以为是 login 没用,要 adduser 才能使用,后来查询了一下 npm 的文档发现 login 是 adduser 的一个别名,二者功能是一样的,因此,不存在 login 的权限不如 adduser 的问题。

如何控制发布到 NPM 中的文件?

orange pink keyboard

如果你使用 Typescript 编写 npm 包,可能会发现自己编写的 ts 文件也被发布到了 NPM 上去。

一般来说,这个其实并没有什么,除非你的包并不开源,又或者你有精神洁癖,希望用户看到的包的文件目录是足够简单明了的。

这个时候,你可以借助 npm 中的 files 这个字段,来控制你需要上传的文件。

比如,如果你按照下面的方式配置 files 就可以控制你的包在发布时,只发布有限的文件,比如必须的 package.jsonLICENSEREADME 以及构建产物 dist 文件夹。

{
   "name":"your-package",
   "files":[
     "package.json",
     "README.md",
     "LICENSE",
     "dist"
   ]
}
Code language: JSON / JSON with Comments (json)

如何解决 JavaScript 中 RegExp 不幂等的问题?

text

在对 WXMarkdown 进行改造的时候,我准备使用 Vercel 提供的 cloudFunction 来实现。

在执行时,出现了一个奇怪的问题,当我使用 RegExp 进行匹配的时候,会出现匹配成功和匹配失败的情况。

当我回溯代码时,发现问题出现在

import type { NextApiRequest, NextApiResponse } from "next";
const URL = require("url");
const rule = /BV([0-9a-zA-Z]{1,10})/g;
export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<MDResponse>
) {
    const { url } = req.body;
    const result = rule.exec(url);
}
Code language: JavaScript (javascript)

result 的匹配结果不是每次都是可用的,会交替一次 null,一次有结果。

搜索后了解到,这是由于 RegExp 本身是具有状态的,在这种情况下,由于后一次的调用复用了之前的请求,所以出现了一次有结果,一次没有结果。

在这种情况下,有两种解决方案:

  1. 在使用 Rule 之前,对其进行重置状态,即在进行匹配之前,先设定 rule.lastIndex 为 0
  2. 另一种解决方案是将 rule 的定义放置在导出的函数内部,这样就可以在后续执行的过程中,重新初始化。

Reference

https://stackoverflow.com/questions/38910334/regular-expression-exec-function-does-not-work-multiple-times

用 KOA 做 API Mock

node js 736399 640

在测试一些服务的时候,会需要用到一些第三方 API, 但如果你在测试的时候需要调用这个 API 的同时,又不太关注这个 API 具体的返回值的时候(比如你要测一个功能,但这个功能依赖了一个第三方服务,这个服务的返回值并不是你所关注的)。你需要一个比较好用的 Mock 方案,来解决这个问题。

写一个 Mock 服务并不是很复杂,想要写好的话,又不是一个很简单的事情。因此,各种语言提供的一些轻框架就是一个不错的选择。比如说我比较常用的是 KOA。

Koa 是 Node.js 下的一个轻量级框架,你可以通过简单的几行代码,构建一个简单的 HTTP 服务。

比如说我测试的服务依赖了两个第三方服务,这个时候我就可以借助于 Koa 提供的基础能力,快速编写出两个 API,从而在本地构建一个高性能的 Mock 服务。

比如说,这是我的一个 Mock 服务的代码案例。

其中我只使用了一个中间件,在中间件中使用 startsWith 来匹配请求路径,从而接管该路径下的所有请求,实现针对同一个 API 的所有请求返回一个特定的结果。

const Koa = require('koa');
const app = new Koa();
// response
app.use(async ctx => {
    ctx.body = "ok"
    if(ctx.url.startsWith("/api/xxx/")){
        ctx.body = 'need return2';
    }
    if(ctx.url.startsWith("/v2/xxx/")){
        ctx.body = 'need return 1'
    }
});
app.listen(3000);
Code language: JavaScript (javascript)

如果你希望这个方案进一步优化,则可以考虑在返回结果时,给出一组数据,使用 Random 来在每一次返回时给出一个随机值,确保返回的数据不是唯一的。