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 可以。。。

使用 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)

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 是个不错的选择,不需要另外单独安装数据库,就可以实现类似于数据库的查询方式,体验还是非常好的。

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

在 Next.js 中直接引入 SVG

computer screen displaying website home page

随着 SVG 的兴起,过去使用雪碧图、IconFont 作为界面中图标用法已经过时。大家会更倾向于加载更加简单、空间占用更小、编写更容易的 SVG 作为图标的解决方案。

而 Next.js 同样对于 SVG 提供了支持。

最简单的加载方式 —— URL 加载

如果不希望对应用程序做任何的改进,一个最简单的方式是直接使用路径进行加载。只需要使用一个 img 标签,并设定 src 为 svg 文件的路径就可以了。

<img src="/logo.svg" alt="Logo SVG" />
Code language: HTML, XML (xml)

不过,使用 URL 来加载的问题是,当你的文件夹路径和结构比较复杂的时候,你需要使用一个较长的路径来加载 SVG,比较麻烦。

当然, 你也可以简单的优化这个方案 —— 比如自行封装一个 svg component,支持传入文件名 & alt 文字和其他参数。这样在调用的时候只需要输入文件名即可。

最舒服的加载方式 —— 编程加载

在 Next.js 当中,如果你希望更加舒服的引入 SVG,那比较好的方案是使用 import 标签来进行引入。不过,由于 SVG 本身并不是一个标准的 React 组件,你需要在 Next.js 上安装插件来支持对 SVG 的解析。

加入对 SVG 的解析需要安装 @svgr/webpack 插件。执行如下命令安装插件。

# yarn
yarn add @svgr/webpack
# npm
npm install --save @svgr/webpack
Code language: PHP (php)

安装完成后,修改 next.config.js添加相关的解析规则。

module.exports = {
  webpack(config) {
    config.module.rules.push({
      test: /\.svg$/,
      use: ["@svgr/webpack"]
    }); // 针对 SVG 的处理规则

    return config;
  }
};
Code language: JavaScript (javascript)

修改后保存,便可以直接使用 import 语法来处理

import Logo from "../assets/logo.svg";


// usage
<Logo />
Code language: JavaScript (javascript)

这样,你就可以直接将 SVG 作为一个 Component 引入,并使用你熟悉的 React Componet Props 来修改这个 Component 的各项属性。

总结

在这篇文章中,分享了两种在 Next.js 的方法,你可以根据自己的实际情况来决定选择具体使用什么样的方式来引入 SVG。

在项目中使用 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 ,在浏览器中打开对应的页面,当可以看到内容后,可以修改文件,等待页面的自动刷新,如果成功刷新了,则表示你已经完成了相关能力的配置。

如何使用 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)

用 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 来在每一次返回时给出一个随机值,确保返回的数据不是唯一的。