2024-08-03 npm模块在web中加载

最近在搞xterm.js在web浏览器中的使用,碰到需要将addon引入进来,但是addon不支持直接在web浏览器中引用,搞了好久,由于不是专业的web开发人员,就只能手动翻译成js代码了,在翻译的过程中,还是借助了npm的工具,这里记录下过程。

npm install xterm xterm-addon-web-links browserify
npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/preset-typescript
touch .babelrc
code -g .babelrc
npx babel src/WebLinksAddon.ts --out-file ./src/WebLinksAddon.js
npx babel src/WebLinkProvider.ts --out-file ./src/WebLinkProvider.js

其中.babelrc的内容是:

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-typescript"
    ]
}

然后将./src/WebLinksAddon.js和./src/WebLinkProvider.js合并到一个文件中,这里采用的是手动,在浏览器中发现问题就删除,或者修改成标准JavaScript class类型的方法。

JavaScript 类(class) | 菜鸟教程 (runoob.com)

JavaScript 静态方法 | 菜鸟教程 (runoob.com)

"use strict";


function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }

class LinkComputer {
  constructor() {
    _classCallCheck(this, LinkComputer);
  }
  static computeLink(y, regex, terminal, activate) {
    var rex = new RegExp(regex.source, (regex.flags || '') + 'g');
    var _LinkComputer$_getWin = LinkComputer._getWindowedLineStrings(y - 1, terminal),
      _LinkComputer$_getWin2 = _slicedToArray(_LinkComputer$_getWin, 2),
      lines = _LinkComputer$_getWin2[0],
      startLineIndex = _LinkComputer$_getWin2[1];
    var line = lines.join('');
    var match;
    var result = [];
    while (match = rex.exec(line)) {
      var _text = match[0];

      // check via URL if the matched text would form a proper url
      // NOTE: This outsources the ugly url parsing to the browser.
      // To avoid surprising auto expansion from URL we additionally
      // check afterwards if the provided string resembles the parsed
      // one close enough:
      // - decodeURI  decode path segement back to byte repr
      //              to detect unicode auto conversion correctly
      // - append /   also match domain urls w'o any path notion
      try {
        var url = new URL(_text);
        var urlText = decodeURI(url.toString());
        if (_text !== urlText && _text + '/' !== urlText) {
          continue;
        }
      } catch (e) {
        continue;
      }

      // map string positions back to buffer positions
      // values are 0-based right side excluding
      var _LinkComputer$_mapStr = LinkComputer._mapStrIdx(terminal, startLineIndex, 0, match.index),
        _LinkComputer$_mapStr2 = _slicedToArray(_LinkComputer$_mapStr, 2),
        startY = _LinkComputer$_mapStr2[0],
        startX = _LinkComputer$_mapStr2[1];
      var _LinkComputer$_mapStr3 = LinkComputer._mapStrIdx(terminal, startY, startX, _text.length),
        _LinkComputer$_mapStr4 = _slicedToArray(_LinkComputer$_mapStr3, 2),
        endY = _LinkComputer$_mapStr4[0],
        endX = _LinkComputer$_mapStr4[1];
      if (startY === -1 || startX === -1 || endY === -1 || endX === -1) {
        continue;
      }

      // range expects values 1-based right side including, thus +1 except for endX
      var range = {
        start: {
          x: startX + 1,
          y: startY + 1
        },
        end: {
          x: endX,
          y: endY + 1
        }
      };
      result.push({
        range: range,
        text: _text,
        activate: activate
      });
    }
    return result;
  }
  static _getWindowedLineStrings(lineIndex, terminal) {
    var line;
    var topIdx = lineIndex;
    var bottomIdx = lineIndex;
    var length = 0;
    var content = '';
    var lines = [];
    if (line = terminal.buffer.active.getLine(lineIndex)) {
      var currentContent = line.translateToString(true);

      // expand top, stop on whitespaces or length > 2048
      if (line.isWrapped && currentContent[0] !== ' ') {
        length = 0;
        while ((line = terminal.buffer.active.getLine(--topIdx)) && length < 2048) {
          content = line.translateToString(true);
          length += content.length;
          lines.push(content);
          if (!line.isWrapped || content.indexOf(' ') !== -1) {
            break;
          }
        }
        lines.reverse();
      }

      // append current line
      lines.push(currentContent);

      // expand bottom, stop on whitespaces or length > 2048
      length = 0;
      while ((line = terminal.buffer.active.getLine(++bottomIdx)) && line.isWrapped && length < 2048) {
        content = line.translateToString(true);
        length += content.length;
        lines.push(content);
        if (content.indexOf(' ') !== -1) {
          break;
        }
      }
    }
    return [lines, topIdx];
  }
  static _mapStrIdx(terminal, lineIndex, rowIndex, stringIndex) {
    var buf = terminal.buffer.active;
    var cell = buf.getNullCell();
    var start = rowIndex;
    while (stringIndex) {
      var line = buf.getLine(lineIndex);
      if (!line) {
        return [-1, -1];
      }
      for (var i = start; i < line.length; ++i) {
        line.getCell(i, cell);
        var chars = cell.getChars();
        var width = cell.getWidth();
        if (width) {
          stringIndex -= chars.length || 1;

          // correct stringIndex for early wrapped wide chars:
          // - currently only happens at last cell
          // - cells to the right are reset with chars='' and width=1 in InputHandler.print
          // - follow-up line must be wrapped and contain wide char at first cell
          // --> if all these conditions are met, correct stringIndex by +1
          if (i === line.length - 1 && chars === '') {
            var _line = buf.getLine(lineIndex + 1);
            if (_line && _line.isWrapped) {
              _line.getCell(0, cell);
              if (cell.getWidth() === 2) {
                stringIndex += 1;
              }
            }
          }
        }
        if (stringIndex < 0) {
          return [lineIndex, i];
        }
      }
      lineIndex++;
      start = 0;
    }
    return [lineIndex, start];
  }
}

class WebLinkProvider {
  constructor(_terminal, _regex, _handler) {
    var _options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
    _classCallCheck(this, WebLinkProvider);
    this._terminal = _terminal;
    this._regex = _regex;
    this._handler = _handler;
    this._options = _options;
  }
  provideLinks(y, callback) {
    var links = LinkComputer.computeLink(y, this._regex, this._terminal, this._handler);
    callback(this._addCallbacks(links));
  }
  _addCallbacks(links) {
    var _this = this;
    return links.map(function (link) {
      link.leave = _this._options.leave;
      link.hover = function (event, uri) {
        if (_this._options.hover) {
          var range = link.range;
          _this._options.hover(event, uri, range);
        }
      };
      return link;
    });
  }
}

/**
 * Copyright (c) 2019 The xterm.js authors. All rights reserved.
 * @license MIT
 */
// consider everthing starting with http:// or https://
// up to first whitespace, `"` or `'` as url
// NOTE: The repeated end clause is needed to not match a dangling `:`
// resembling the old (...)*([^:"\'\\s]) final path clause
// additionally exclude early + final:
// - unsafe from rfc3986: !*'()
// - unsafe chars from rfc1738: {}|\^~[]` (minus [] as we need them for ipv6 adresses, also allow ~)
// also exclude as finals:
// - final interpunction like ,.!?
// - any sort of brackets <>()[]{} (not spec conform, but often used to enclose urls)
// - unsafe chars from rfc1738: {}|\^~[]`
var strictUrlRegex = /https?:[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/;
function handleLink(event, uri) {
  var newWindow = window.open();
  if (newWindow) {
    try {
      newWindow.opener = null;
    } catch (_unused) {
      // no-op, Electron can throw
    }
    newWindow.location.href = uri;
  } else {
    console.warn('Opening link blocked as opener could not be cleared');
  }
}
class WebLinksAddon {
  constructor() {
    var _handler = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : handleLink;
    var _options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    _classCallCheck(this, WebLinksAddon);
    this._handler = _handler;
    this._options = _options;
  }
  activate(terminal) {
    this._terminal = terminal;
    var options = this._options;
    var regex = options.urlRegex || strictUrlRegex;
    this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, regex, this._handler, options));
  }
  dispose() {
    var _this$_linkProvider;
    (_this$_linkProvider = this._linkProvider) === null || _this$_linkProvider === void 0 || _this$_linkProvider.dispose();
  }
}

测试一把:

<!doctype html>
<html>

<head>
  <link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
  <script src="node_modules/xterm/lib/xterm.js"></script>
  <script src="./src/xterm-addon-web-links.js"></script>
</head>

<body>
  <div id="terminal"></div>
  <div id="terminal-container"></div>
  <script>

    var terminal = new Terminal();
    var webLinksAddon = new WebLinksAddon((event, uri) => {
      console.log('Opening link:', uri);
    });

    terminal.loadAddon(webLinksAddon);

    terminal.open(document.getElementById('terminal-container'));

    // webLinksAddon.setLinkHandler(function (link) {
    //   console.log('Opening link:', link);
    //   window.open(link, '_blank');
    // });
    terminal.writeln('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
    terminal.writeln('Here is a link: https://example.com');

  </script>
</body>

</html>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容