npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@snack-kit/core

v0.4.1

Published

Snack by Para FED

Readme

@snack-kit/core

Snack 动态模块加载与渲染 SDK,基于 React + RequireJS 构建。支持从服务端加载 UMD 格式的 Snack 模块,并将设计器页面 JSON 配置渲染为 React 组件树,同时提供完整的表单数据管理、校验、国际化与事件系统。


安装

npm install @snack-kit/core

Peer Dependencies

npm install react react-dom @snack-kit/lib

快速开始

import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { SnackSDK } from '@snack-kit/core';
import * as SnackKitLib from '@snack-kit/lib';

const sdk = new SnackSDK({
  service: 'https://your-snack-service.com',
  importMaps: {
    'react': React,
    'react-dom': ReactDOM,
    '@snack-kit/lib': SnackKitLib
  }
});

// 渲染设计器页面为 React 组件
const Page = await sdk.createPageComponent({ id: 'page-id', type: 'portal' });

createRoot(document.getElementById('app')!).render(<Page />);

SnackSDK 构造参数

| 参数 | 类型 | 说明 | |------|------|------| | service | string | Snack 服务端地址 | | importMaps | object | 模块依赖注入,key 为模块名,value 为模块对象 | | defaultFormAttr | SnackFormAttribute | 表单组件 FormAttr 全局默认值 | | runtime | boolean | 是否为运行时模式,默认 true;设计器模式设为 false | | runtimeMapping | object | 运行时模块名映射 | | modUrl | string | 模块加载根路径,默认 service + modPath | | modPath | string | 模块请求路径,默认 /package | | cdnUrl | string | CDN 根路径,默认 service + cdnPath | | cdnPath | string | CDN 路径,默认 /lib | | pageUrl | string | 页面数据请求根路径 | | pagePath | string | 页面请求路径,默认 /page | | staticUrl | string | 模块静态资源 CDN 路径 | | importModules | ImportModules | 预置 Snack 模块 class,加载时优先从此查找 | | basicsType | string | 基础模块分类名,默认 basics | | i18n | I18nData | 国际化数据复写 | | localeMapping | object | 国际化语言映射关系,例如 { zh: 'zh_CN' } | | localeDefault | string | 默认国际化语言,默认 zh | | message | object | 消息提示对象,供 evalJS 脚本使用 | | argv | Record<string, ...> | 全局环境变量,在表达式中通过 $.argv 访问 |


核心 API

页面渲染

sdk.createPageComponent(params, config?)

将设计器页面渲染为 React 函数组件。

const Page = await sdk.createPageComponent(
  { id: 'my-page', type: 'portal' },
  {
    data: { username: 'admin' },  // 表单回填数据
    onComplete: (event) => {
      console.log('渲染完成', event.fields);
    }
  }
);

<Page />

sdk.createPageForElement(el, params, config?)

将页面直接渲染到指定 DOM 元素。

await sdk.createPageForElement(document.getElementById('container')!, { id: 'my-page' });

sdk.renderPage(params, config?)

渲染页面并返回根容器 DropOjb 实例(低层 API)。支持传入数组一次渲染多个页面到同一表单容器。


表单操作

渲染完成后,onComplete 回调参数 event: SnackPageCompleteEvent 提供以下方法:

// 获取表单数据(全量)
const formData = event.getData();

// 获取单个字段值
const username = event.getData('username');

// 设置表单数据
event.setData('username', 'new-value');
event.setData({ username: 'a', password: 'b' });

// 表单校验(返回 Promise<boolean>)
const passed = await event.verify();
const fieldPassed = await event.verify('username');

// 清空表单
event.empty();

// 销毁页面(解绑事件、释放模块)
event.destroy();

模块渲染

sdk.createModuleComponent(args, data?)

加载并渲染单个 Snack 模块为 React 函数组件。

const Editor = await sdk.createModuleComponent(
  { name: 'codeeditor', type: 'code-editor' },
  { readOnly: false }
);

<Editor />

sdk.createClassModule(SnackClass, data?, main?)

通过 Snack Class 直接创建模块实例。

const mod = sdk.createClassModule(MySnackClass, { value: 'hello' });

sdk.createClassModuleComponent(SnackClass, data?, main?)

通过 Snack Class 创建模块并返回 React 函数组件。


模块加载

sdk.module(config)

按模块名称加载 UMD 模块,返回 Snack class 键值对。

const { mymodule } = await sdk.module({
  module: [{ name: 'mymodule', type: 'custom' }]
});

sdk.moduleURL(config)

按模块 URL 加载模块。

const mods = await sdk.moduleURL({
  urls: 'https://cdn.example.com/package/custom/mymodule/index.js',
  cdnUrl: 'https://cdn.example.com/lib'
});

事件系统

// 注册事件
sdk.addEvent('onComplete', (root, params) => {
  console.log('页面加载完成', root);
});

// 页面内组件事件(onChange、onBlur 等)由 SDK 自动分发
// 手动发送事件
sdk.sendEvent('custom-event', [arg1, arg2]);

// 卸载指定回调
sdk.removeEvent('onComplete', myCallback);

// 卸载所有同名事件
sdk.removeEvent('onComplete');

内置事件

| 事件名 | 触发时机 | |--------|----------| | onComplete | 整个页面加载渲染完成 | | onRemove | 模块被移除 | | snack-event | 组件内部事件(onChange、onBlur 等)分发 |


国际化

// SDK 级别
sdk.intl('required');               // 返回 string | ReactNode
sdk.setI18n({ zh: { required: '此字段必填' } });
sdk.language('en-US');              // 切换所有已加载模块的语言

// 模块内(Snack 基础类)
this.$intl({ id: 'required' });
this.$intl('required', { label: '用户名' });
this.$lang;                         // 获取当前语言
this.$lang = 'en-US';              // 设置语言

基础类

Snack<T>

所有 Snack 模块的基础类,继承此类实现自定义模块。

import { Snack, SnackData } from '@snack-kit/core';

interface MyData extends SnackData {
  value: string;
  label: { zh: string; en: string };
}

export class MyModule extends Snack<MyData> {
  public $component(data?: MyData) {
    return <div>{data?.value}</div>;
  }
}

常用属性与方法

| 成员 | 说明 | |------|------| | data | 模块数据对象 | | sdk | 所属 SDK 实例 | | $id | 模块唯一实例 ID | | $name | 模块名称 | | $parent | 父级 Snack 实例(通常为 Drag 容器) | | $page | 所属页面事件对象 | | $forceUpdate(reload?) | 强制重绘 | | $style(key?, value?) | 读写样式并触发重绘 | | $display(visible) | 显示 / 隐藏当前组件 | | $intl(ops, params?) | 获取国际化文本 | | $language(lang) | 切换语言 | | $destroy() | 销毁模块,从 SDK moduleMaps 中移除 | | FC(data) | React 渲染入口,由 SDK 调用 | | $component(data?) | 子类重写此方法实现 UI |

SnackSetting

设置面板模块的基础类,通过 $setData 同步修改关联主模块(main)的数据。

import { SnackSetting } from '@snack-kit/core';

export class MyModuleSetting extends SnackSetting {
  public $component() {
    return (
      <input
        value={String(this.main.data?.value ?? '')}
        onChange={(e) => this.$setData('value', e.target.value)}
      />
    );
  }
}

表达式系统

页面 JSON 配置中支持 {{$.xxx}} 占位符,在运行时由 SDK 自动替换:

{
  "data": {
    "M": {
      "label": "{{$.argv.username}}"
    }
  }
}

可用变量

| 变量 | 说明 | |------|------| | $.argv | SDK 构造时传入的 argv 全局变量 | | $.data | 当前组件的 data 对象 | | $.sdk | SDK 实例 |


模块打包(UMD 格式)

Snack 模块需打包为 UMD 格式并通过 snackdefine 注册(由 scripts/config/webpack.snack.config.js 提供):

// webpack.snack.config.js 关键配置
output: {
  filename: 'index.js',
  library: { type: 'umd' }
},
externals: {
  'react': 'react',
  '@snack-kit/lib': '@snack-kit/lib'
}

打包产物头部会自动注入:

if (typeof window !== 'undefined' && window.snackdefine) {
  var define = window.snackdefine;
}

开发

# 本地开发调试(webpack-dev-server)
npm run dev

# 构建库(ESM + CJS + Types)
npm run build

# 运行单元测试
npm test

# 类型检查
npm run lint

# 生成 API 文档
npm run docs

Changelog

0.4.0

Bug 修复

  • 修复 tsup ESM 构建产物扩展名问题:新增 outExtension: () => ({ js: '.js' }) 配置,强制输出 dist/es/index.js,与 package.jsonexports.import 路径一致,解决 Vite 等构建工具无法解析 @snack-kit/core 入口("Failed to resolve entry")的问题

0.2.0

新增

  • 新增 src/utils/reactAdapter.tscreateReactRoot / SnackReactRoot 运行时兼容层,支持 React 17 / 18 / 19 三版本并存
    • React 18+:使用 createRoot(react-dom/client)
    • React 17:回退至旧版 ReactDOM.render / unmountComponentAtNode
    • 通过变量形式 require,绕过 webpack 静态分析,避免 React 17 宿主环境因 react-dom/client 不存在报 "Module not found"
  • 导出 createReactRootSnackReactRoot 供外部使用

依赖调整

  • react / react-domdependencies 移至 peerDependencies,支持版本范围 >=17.0.0,宿主项目可自行管理 React 版本

0.1.0

破坏性变更

  • 移除 Vue 2.0 支持(删除 SnackVueSnackVueSetting 及相关导出)
  • 依赖由 @paraview/lib 迁移至 @snack-kit/lib@paraview/lib 作为 importMaps 别名保持向后兼容)
  • 构建工具由 webpack 迁移至 tsup,产物格式调整为 ESM + CJS + Types

安全修复

  • 移除所有 new Function 动态字段访问,改用 safeGet / safeSet 路径工具(消除代码注入风险)
  • window.define 全局污染修复:改用 window.snackdefine,不再覆盖标准 AMD define

Bug 修复

  • 修复 $defaultDisplay(false) 逻辑反转问题,调用后组件现可正确隐藏
  • 修复模块渲染失败时 childCount 未递减,导致 onComplete 永不触发的问题
  • 修复渲染错误日志缺少模块 key 信息,现输出 key / field 辅助定位

新增

  • 新增 src/utils/pathAccess.tssafeGet / safeSet 支持 .[] 混合路径语法
  • 并发加载去重:同一 URL 的模块并发请求只发出一次网络请求
  • destroy() 方法现会清理所有待处理的 verifyTimeout 防抖计时器,防止内存泄漏
  • 新增 vitest 单元测试(tests/utils/pathAccess.test.ts
  • 新增 typedoc 支持,npm run docs 生成 API 文档

类型系统

  • 全局类型安全审查,大幅减少 any 使用
  • 新增导出类型:SnackRequireFunctionSnackDefineFunctionSnackSettingModelItemExprArgv
  • 支持 React 19

License

ISC