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

cct-html-ast

v1.0.5

Published

html-ast htmlast语法树操作,ast语法树转html

Downloads

18

Readme

cct-html-ast html语法树操作与渲染

  1. 基于hyntax开源项目的html语法树操作
  2. 支持html及vue文件的ast语法树操作,添加了常用的语法树操作方法与语法树递归钩子
  3. 修复了hyntax自关闭标签只支持内置标签的问题,支持自定义的自关闭标签
  4. 添加了ast转html代码的能力,添加了ast转html时递归生成html的钩子

安装

使用npm或cnpm安装

    // npm安装
    npm install --save cct-html-ast
    // cnpm安装
    cnpm install --save cct-html-ast
    // yarn安装
    yarn add cct-html-ast

使用

esm模块化引用

    import HTMLAST,{HTMLCompiler,HTMLRender} from 'cct-html-ast';

cjs模块化引用

    const {HTMLCompiler,HTMLRender,default:HTMLAST} = require('cct-html-ast');

HTMLAST类

  1. 该类只是提供了一些快捷方法,实际功能是由HTMLCompiler与HTMLRender类实现
  2. 为了方便扩展自定义的需求预留了astWalk与renderWalk回调函数,在ast递归时与ast渲染时执行
  3. 建议尽可能的通过ast语法树的节点操作来改变渲染的结果,不建议通过renderWalk来改变html的渲染结果,除非html渲染存在bug或者无法通过ast的修改实现自己的需求
  4. 默认在递归ast的时候将html中的src与href属性节点保存在了srcList与hrefList对象中以便操作
  5. ast的节点都继承于Node类,该类提供了一些常用的节点操作的api
import hyntax from './hyntax/index.js';
import { HTMLCompiler } from './lib/html-compiler.js';
import { HTMLRender } from './lib/html-render.js';
export const { tokenize, constructTree } = hyntax;
class HTMLAST {
  constructor ({ content = '', astWalk, renderWalk }) {
    const compiler = new HTMLCompiler({ content, astWalk });
    const render = new HTMLRender({ renderWalk });
    render.updateAst(compiler.ast);
    this._compiler = compiler;
    this._render = render;
  }

  get ast () {
    return this._compiler.ast;
  }

  get html () {
    return this._render.html;
  }

  get srcList () {
    return this._compiler.srcList;
  }

  get hrefList () {
    return this._compiler.hrefList;
  }

  render () {
    this._render.render();
  }
}
export default HTMLAST;

Node节点类

class Node {
  constructor (attrs = {}) {
    for (const key in attrs) {
      this[key] = attrs[key];
    }
  }

  index () {
    const { children } = this.parent;
    const { length } = children;
    for (let i = 0; i < length; i++) {
      if (children[i] === this) {
        return i;
      }
    }
    return -1;
  }

  createNode (node) {
    /**
     * 如果参数是Node节点对象则直接返回该节点引用,并没有对节点进行复制
     * 或许你本就希望将同一个节点复用并插入在不同节点或位置,引用的好处就是任何对节点的操作都会同步发生变化
     * 也有可能你希望对已有的节点进行复制避免引用造成的问题
     * 还有可能你希望节点的操作像DOM中的节点操作一样,将一个节点插入另一个节点会先将这个节点从原有的父级删除
     * 所以我不太确定每个人的需求是什么样的,因此默认是引用方式没有做其它的处理
     * 其实无论是哪种需求都很简单,所以用的时候有自己的需求就自己来写吧
     */
    if (typeof node === 'string') {
      return new HTMLCompiler({ content: node }).ast.children;
    } else {
      if (node instanceof Node) {
        return node;
      } else {
        throw new Error('无效的node');
      }
    }
  }

  append (node) {
    const { children } = this;
    children.push(...this.createNode(node));
  }

  prepend (node) {
    const { children } = this;
    children.unshift(...this.createNode(node));
  }

  after (node) {
    if (this.parent) {
      const { children } = this.parent;
      const index = this.index() + 1;
      children.splice(index, 0, ...this.createNode(node));
    } else {
      throw new Error('this.parent is not defined!');
    }
  }

  before (node) {
    if (this.parent) {
      const { children } = this.parent;
      const index = this.index();
      children.splice(index, 0, ...this.createNode(node));
    } else {
      throw new Error('this.parent is not defined!');
    }
  }

  removeChildren () {
    this.children.splice(0);
  }

  removeSelf () {
    const { children } = this.parent;
    const { length } = children;
    for (let i = 0; i < length; i++) {
      if (children[i] === this) {
        children.splice(i, 1);
        break;
      }
    }
  }
}

HTMLRender类

export class HTMLRender {
  constructor ({ ast = {}, renderWalk } = {}) {
    this.html = '';
    this.ast = ast;
    this.renderWalk = typeof renderWalk === 'function' ? renderWalk : false;
    this.renderNodeType = {
      10: 'renderDocument',
      9: 'renderDoctype',
      8: 'renderComment',
      1: 'renderTag',
      3: 'renderText'
    };
  }

  updateAst (ast) {
    if (ast) {
      this.ast = ast;
    }
  }

  renderDocument (node) {
    return [''];
  }

  renderDoctype ({ attrs }) {
    return [
      `<!Doctype ${this.renderAttrs(attrs)}/>`
    ];
  }

  renderComment (node) {
    return [`<!--${node.text}-->`];
  }

  renderTag ({ tag, attrs = {}, selfClosing }) {
    return [
      `<${tag} ${this.renderAttrs(attrs)}${selfClosing ? '' : '>'}`,
      selfClosing ? '/>' : `</${tag}>`
    ];
  }

  renderText ({ text }) {
    return [`${text}`];
  }

  renderAttrs (attrs = {}) {
    return Object.entries(attrs).map(([key, value]) => {
      return value === '' ? key : `${key}="${value}"`;
    }).join(' ');
  }

  render () {
    const { ast, renderNodeType, renderWalk } = this;
    const walk = (node) => {
      const { type, children = [], text } = node;
      const renderMethod = renderNodeType[type];
      if (this[renderMethod]) {
        const [openTag, closeTag = ''] = this[renderMethod](node);
        const content = type === 1 && text ? text : children.map(child => {
          return walk(child);
        }).join('');
        const [open, close] = renderWalk ? renderWalk(node, openTag, closeTag) || [] : [];
        return open === undefined && close === undefined ? `${openTag}${content}${closeTag} ` : `${open}${content}${close}`;
      } else {
        throw new Error(`无法解析的节点类型【${type} 】`);
      }
    };
    this.html = walk(ast);
  }
}

示例

import HTMLAST from '../src/index.js';
const code = `<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>cct-html-ast</title>
</head>
<body>
  <div id="app"></div>
  <!--{{dll}}-->
  <a href="a.html">sdf</a>
  <a href="/a.html">sdf</a>
  <a href="https://nick.com/a.html">sdf</a>
  <img src="a/b/c.png"/>
  <img src="https://nick.com/a/b/c.png"/>
</body>

</html>

`;
const obj = new HTMLAST({
  content: code,// 要操作的html源码或vue源码

// ast语法树递归的钩子函数,接收一个node节点参数,该node节点为引用类型,
// 直接操作该节点将可以改变ast语法树,如果回调函数返回false则该节点将被忽略
// 以下示例是通过node.type过滤掉了所有的text文本节点
// 如果只是想修改node节点的属性可以不返回任何值 比如可以node.tag='div'可以修改节点tag名
  astWalk: node => node.type !== 3,

  // ast渲染成html代码的钩子函数,接收一个node节点参数,返回的数据必须是数组格式[openTag,closeTag]
  // 标签中的内容可以在保存在第一个数组中,如果没有结束标签数组第二元素可以为空或undefined
  renderWalk: node => {
    if (node.tag === 'a') {
        //此处通过钩子函数修改了a标签的渲染结果
      return ['<a>test', '</a>'];
    }
  }
});
// 通过ast节点的before方法插入了新的节点
obj.ast.children[1].before('<div>sdfsd</div>adfss');
// 执行ast的渲染
obj.render();
// HTMLAST 对象的结构参见下图
console.log(obj);

HTMLAST对象数据结构 HTMLAST对象数据结构