Skip to content

各环境引入对照 ​

包对外只有两条 JS 入口,所有环境差异都收敛在三件事上:

判断维度说明
选择挂载方式任意框架都可用主入口 new LarkDoc();标签方式用 lark-doc/element;Vue2 options 兼容对象从 lark-doc/legacy 引入。三者均无 Vue runtime import
打包器认不认 exports认(webpack5 / vite / rollup / Node)→ 零配置,浏览器与 SSR 吃同一份产物;不认(webpack4 / 老 rspack)→ 主入口要一行 alias 指到 CJS 档(见 env-vue2-wp4),子路径(lark-doc/element 等)照旧要写全 dist/ 路径
渲染在哪一端浏览器 → 直接挂载;Node / SSR → require() CJS 入口(保留兄弟区块)或 client-only 动态 import()

本文给出每个环境的编译工具配置 + 可直接粘贴运行的代码 demo。改代码前要看的踩坑清单在仓库 dev/02-多环境接入.md,两者结论一致、面向对象不同(本文面向使用方,手册面向改代码的人)。

只想先跑起来?安装与引用总表 用一张表给出「怎么装 / 怎么引 / 内网怎么搬」,配置细节再回到本文对应小节。

一张表看全 ​

在「宿主环境」列里找到你的那一行,点进去就是完整配置和可运行代码。

这张表只管「怎么配、怎么引」。怎么装包、内网怎么搬、dist/ 怎么拷在安装与引用总表,两页配合看:先按总表装好,再回本文贴配置。

宿主环境引入写法编译工具配置编辑器初始化
Vue 2.7 + webpack5 / vue-cli 5import { LarkDoc } from 'lark-doc'不用配(vue.config.js 只放业务配置)new LarkDoc({ element })
Vue 2.7 + Vite同上不用配,装 @vitejs/plugin-vue2 即可同上
Vue 2.7 + webpack4import { LarkDoc } from 'lark-doc'一行 alias(lark-doc$ → lark-doc/dist/lark-doc.common.js)+样式写全路径;CJS 通过相对 require 加载兄弟区块同上
Vue 2.7 组件式(options 兼容)import { LarkDocEditor } from 'lark-doc/legacy'ESM 注入样式;CJS 引 dist/lark-doc-legacy.css 并保留字体资源<lark-doc-editor v-model="html" />
Vue 3import 'lark-doc/element' + import { resolveVue3Attrs } from 'lark-doc/vue3'vite.config.js:compilerOptions.isCustomElement<lark-doc v-bind="resolveVue3Attrs(props)">
React 16.8+import 'lark-doc/element' + createReactLarkDoc(React),或 mountLarkDoc(el, opts)CRA5 / Vite 不用配;TSX 直接写标签才补 .d.tsmountLarkDoc(ref.current, opts)
原生 / CDN / 内网静态<script src="…/lark-doc-element.umd.min.js">不用配,dist/ 整目录同位托管<lark-doc> 标签
Node / jsdom(单测)require('lark-doc')jest.config.js:testEnvironment: 'jsdom',CJS 无需转译new LarkDoc({ element: host })
SSR · Next 13客户端内 await import('lark-doc/element')不用配,文件头 'use client'client-only 挂载
SSR · Nuxt 2dist/lark-doc-element.umd.min.js 或 <script> 直引要:nuxt.config.js 里 build.transpile + alias 到 distclient-only 挂载

通用的编辑器配置 ​

每个环境的 demo 都用同一份 options,先集中讲清楚,后面 demo 里只写与「该环境有关」的部分。

js
const doc = new LarkDoc({
  element: '#editor',                 // 必填:选择器 / HTMLElement / Vue 组件实例(自动取 $el)
  title: '未命名文档',
  content: '<p>开始编辑。</p>',        // HTML / Tiptap JSON / DocumentRecord 三种都收
  placeholder: '输入正文,或输入 / 插入内容',

  // ── 编辑状态 ──
  readonly: false,
  theme: 'light',                     // 'light' | 'dark'
  storage: 'local',                   // 默认 'local':自动存浏览器草稿;'none' 关掉持久化
  autosave: { enabled: true, delay: 650 },  // 自动保存(默认开,delay 毫秒)
  autofocus: false,
  contentFormat: 'auto',              // 'auto' | 'vditor' | 'html',老数据归一化策略
  saveHtml: false,                    // true 时持久化 record 额外带 html 派生副本

  // ── 编辑工具(工具栏)──
  toolbarKeys: ['history', 'headings', 'marks', 'textStyle', 'blockStyle', 'insert', 'tables'],
  toolbarExcludes: ['shortcuts'],

  // ── 能力开关 / 版式 / 上限 / 网络 ──
  features: { ai: false, folding: true, importExport: true, diagram: true },
  layout: { headerVisible: true, titleBarFixed: false, toolbarFixed: true },
  limits: { maxLength: 0, maxImageSize: 5 * 1024 * 1024, maxIndent: 5 },
  network: { mode: 'open' },          // 'open' | 'allowlist' | 'offline'

  // ── 宿主注入 ──
  uploader: { upload: (file) => uploadToOss(file) },
  ai: { stream: myStreamFn },
  persistence: myPersistenceAdapter,

  // ── 生命周期 ──
  onReady: ({ editor }) => {},
  onUpdate: ({ html, json, text }) => {},
  onSave: ({ status }) => {},
  onError: (error) => console.error(error),
})

配置分组的完整字段见配置总览,这里只强调三个最容易搞错的点:

容易搞错正确做法
以为还要自己开自动保存storage 默认 'local'、autosave.enabled 默认 true,开箱即存;不想要就显式写 storage: 'none'(关持久化)或 autosave: { enabled: false }(只停自动、保留落点,手动 doc.save() 仍可写)
用 toolbarKeys 想"禁用"能力它只隐藏入口;要真正禁用改 features,历史 JSON 仍可读
传 uploadImage 还是 uploader两者都收:uploader.upload 优先,uploadImage 作兼容别名;SFC 里只有 upload-image 一个 prop

编辑工具(工具栏)配置 ​

toolbarKeys 白名单、toolbarExcludes 黑名单,粒度是 12 个分组 + 8 个 mark,别名(heading / table / undo)自动落位,未知 key 只记进 instance.unknownToolbarKeys,不会初始化失败。

js
// ① 全功能:不传,默认全部 12 组
new LarkDoc({ element: '#editor' })

// ② 轻量写作:只留写作必需
new LarkDoc({
  element: '#editor',
  toolbarKeys: ['history', 'headings', 'marks', 'textStyle', 'insert'],
})

// ③ 表格/报表场景:把表格工具条顶上来
new LarkDoc({
  element: '#editor',
  toolbarKeys: ['history', 'headings', 'marks', 'tables', 'insert'],
  toolbarExcludes: ['highlight'],     // 在保留的分组里再摘掉高亮
})

// ④ 只读展示:隐藏所有入口(推荐直接 readonly,而不是靠 toolbarKeys: [])
new LarkDoc({ element: '#editor', readonly: true, toolbarKeys: [] })
写法效果
不传 toolbarKeys12 个分组全部平铺
toolbarKeys: []空白名单,一个分组都不显示
toolbarKeys: ['bold']自动打开 marks 分组,且只留加粗
toolbarExcludes: ['marks']整组隐藏;['highlight'] 只隐藏单个 mark

分组 key 与别名全表见工具栏配置。

分环境完整示例 ​

以下 demo 均以「Vue 2.7 里挂一个文档页」或「对应框架最小可跑片段」为准,省略掉的只有你自己的业务代码。每个环境先给编译工具配置,再给业务代码。

1. Vue 2.7 + webpack5 / vue-cli 5 ​

进内网的分步操作(出包 → vendor 目录 → 改 package.json → 验收)见内网迁移实战 · 方案 B。

编译工具:不用配。 宿主认 exports,import 命中 dist/lark-doc.esm.js,懒区块(公式 / Word / Markdown / 代码高亮 / mermaid)由宿主自己的 import() 分割并输出。

lark-doc 没有专属配置,所以这里给的是一份明确「不配什么」的 vue.config.js:

js
// vue.config.js(vue-cli 5)—— 与 lark-doc 相关的部分全是"不需要"
const { defineConfig } = require('@vue/cli-service')

module.exports = defineConfig({
  // ① 不用 alias:webpack5 默认 mainFields 含 'module',命中 exports.import
  // ② 不用 transpileDependencies:esm 产物已按 browserslist 编好
  // ③ 不用改 rules:webpack5 自己能吃 import.meta(webpack4 才是那个坑)
  // ④ 不用引 CSS:esm 产物随 JS 注入 <style>(想拆独立 css 文件才引 style.css)
  // transpileDependencies: [],        // ← 默认值,保持不动即可
  configureWebpack: {
    // 只有你自己的业务配置放这里,例如:
    resolve: { alias: { '@': require('path').resolve(__dirname, 'src') } },
  },
})

非 vue-cli 的原生 webpack5 宿主,vue-loader 用 15.x(Vue 2 对应版本):

js
// webpack.config.js(webpack5 + Vue 2.7,非 vue-cli)
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')   // 必须 vue-loader@15

module.exports = {
  mode: 'production',
  entry: './src/main.js',
  output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[contenthash:8].js' },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: { '@': path.resolve(__dirname, 'src') },
    // lark-doc 用不到 alias;webpack5 天然读 package.json 的 exports
  },
  module: {
    rules: [
      { test: /\.vue$/, loader: 'vue-loader' },
      {
        test: /\.js$/,
        exclude: /node_modules/,       // lark-doc 在 node_modules 里,不要一起转译
        use: { loader: 'babel-loader' },
      },
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
    ],
  },
  plugins: [new VueLoaderPlugin()],
  optimization: { splitChunks: { chunks: 'all' } },   // 懒区块自动切分
}
jsonc
// package.json 片段:Vue 2 要求 vue 与 vue-template-compiler 严格同版本
{
  "dependencies": { "vue": "2.7.16", "lark-doc": "^0.11.6" },
  "devDependencies": {
    "vue-template-compiler": "2.7.16",
    "vue-loader": "^15.11.1",
    "@vue/cli-service": "^5.0.8"
  }
}
js
// main.js
import Vue from 'vue'
import App from './App.vue'
// 不需要 import 'lark-doc/style.css' —— ESM 产物随 JS 注入 <style>,
// 再引一份只是多一份重复规则(无害)。若你确实想要独立 CSS 文件,也可以引。
new Vue({ render: (h) => h(App) }).$mount('#app')
vue
<!-- src/views/Doc.vue -->
<template>
  <div ref="host" class="doc-host" />
</template>

<script>
import { LarkDoc } from 'lark-doc'

export default {
  data: () => ({ doc: null }),
  async mounted() {
    const record = await fetch(`/api/doc/${this.$route.params.id}`).then((r) => r.json())
    this.doc = new LarkDoc({
      element: this.$refs.host,
      title: record.title,
      content: record.content,
      toolbarKeys: ['history', 'headings', 'marks', 'insert', 'tables'],
      features: { ai: false },
      uploader: { upload: (file) => this.upload(file) },
      onUpdate: ({ json }) => this.scheduleSave(json),
    })
  },
  beforeDestroy() {
    if (this.doc) { this.doc.destroy(); this.doc = null }
  },
}
</script>

<style scoped>
.doc-host { height: calc(100vh - 60px); }
</style>

2. Vue 2.7 + Vite ​

编译工具:不用配 alias,与 webpack5 同一套(exports.import → esm.js)。唯一要装对的是 Vue 2 的 Vite 插件。

js
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue2'      // 官方插件(Vite 3~5 均支持 Vue 2.7)

export default defineConfig({
  plugins: [vue()],
  // 无需 alias / optimizeDeps.include:vite 读 exports,
  // import 'lark-doc' 直接命中 esm 产物,懒区块由 Rollup 自动分割。
  build: {
    // 默认 'modules' 即可;只有要兼容老浏览器时才降 target,
    // 降了要自己补 polyfill(包不假设宿主有原生可选链)
    target: 'modules',
  },
})
js
// 如果用的是社区版插件(二选一,别同时装)
import { defineConfig } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'
export default defineConfig({ plugins: [createVuePlugin()] })
jsonc
// package.json 片段
{
  "dependencies": { "vue": "2.7.16", "lark-doc": "^0.11.6" },
  "devDependencies": {
    "vite": "^5.4.0",
    "@vitejs/plugin-vue2": "^2.3.3",
    "vue-template-compiler": "2.7.16"
  }
}

Vite 下 Vue 2.7 的两个硬约束

  1. vue 与 vue-template-compiler 必须同版本(都是 2.7.16),版本错位会在编译模板时报 Vue packages version mismatch;
  2. 插件要和 Vite 大版本匹配:@vitejs/plugin-vue2@2.x 配 Vite 3/4/5,vite-plugin-vue2@2.x 只到 Vite 4 左右。装错插件报的错通常是 Cannot find module 'vite/plugin',与 lark-doc 无关。
vue
<template><div ref="host" style="height:100vh" /></template>
<script>
import { LarkDoc } from 'lark-doc'
export default {
  mounted() {
    this.doc = new LarkDoc({
      element: this.$refs.host,
      content: '<p>hello vite</p>',
      toolbarKeys: ['history', 'headings', 'marks'],
    })
  },
  beforeUnmount() { this.doc && this.doc.destroy() },
}
</script>

3. Vue 2.7 + webpack4(vue-cli 4) ​

0.13 只要一行 alias。 webpack4 不读 exports,resolve.mainFields 默认 ['browser', 'module', 'main']; 而 0.13 的 package.json 刻意不再有 browser 字段(它一旦存在,webpack4/5 会跳过 ESM 直落 UMD 的「浏览器直挂档」,SSR 与浏览器就吃到两份不同产物了)。于是 webpack4 会落到 module → esm.js;该档保留现代语法,不承诺 webpack4 的 acorn 6.4.2 可解析。把主入口显式指到 CJS 档即可 —— CJS 档和 UMD / element 一样已整体降到 ES2018,所以不用再挂 babel:

js
// vue.config.js —— 就这么一处,没有第二处
module.exports = {
  configureWebpack: {
    resolve: { alias: { 'lark-doc$': 'lark-doc/dist/lark-doc.common.js' } },
  },
}
js
// src/main.js
import { LarkDoc } from 'lark-doc'
import 'lark-doc/dist/lark-doc.css' // webpack4 读不到 exports,样式要写全 dist 路径
你担心的事0.13 的实际情况
Module parse failed / 不认识 import.metaalias 命中的是 CJS 档,不含 import.meta,不会走到 esm 那条路
不认识 ?. / ??CJS 与 UMD / element 产物已整体降到 ES2018 语法,acorn 6.4.2 能解析
还要装 babel 插件吗不用,@babel/plugin-proposal-optional-chaining 之类可以卸了
样式怎么进ESM 随 JS 注入;CJS 显式引样式,webpack4 写 lark-doc/dist/lark-doc.css(不读 exports)

好消息:按需区块的基址不用管了。 __LARK_DOC_PUBLIC_PATH__ 只服务 UMD / element 那两份 旁挂懒区块的产物(公式 / Word 导出 / Mermaid / 代码高亮是运行时按 document.currentScript 反推目录去拉的)。CJS 档通过 require() 加载兄弟 chunk,由 Node 或宿主打包器处理,不使用 script URL 基址;分发时仍需保留整个 dist/。

只有当你继续用 UMD / element 档(<script src> 直挂,或被打包器 import)时才需要设:

js
// src/main.js —— 指向 node_modules/lark-doc/dist 被拷到/托管到的 URL 前缀
window.__LARK_DOC_PUBLIC_PATH__ = '/vendor/lark-doc/'
import 'lark-doc/dist/lark-doc-element.umd.min.js'
  • 用了 copy-webpack-plugin 把 dist/*.js 拷到 public/vendor/lark-doc/,就填 /vendor/lark-doc/;
  • 不用公式、不出 Word 的话可以不设,报错时才需要。

子路径仍然要写全

exports 不支持,所以 lark-doc/element、lark-doc/style.css 这类子路径在 webpack4 下解析不到,得写 lark-doc/dist/lark-doc-element.umd.min.js、lark-doc/dist/lark-doc.css。主入口 lark-doc 不受影响。

当前默认、Element 和 legacy 都不导入 Vue,也没有 Vue peer;无需为编辑器安装 Vue2。legacy 只是供旧宿主注册的 options 对象,并不保留另一套 UI。

进内网的完整搬运步骤(含 dist/ 怎么拷、nginx 怎么配)见内网迁移实战 · 方案 A。

0.12 及更早版本的老写法(alias 到 common.js + babel 降语法)

以下保留旧版历史配置。当前阶段7工作区仍需主入口 alias 和显式 CSS,只需删除额外的 Babel 降语法规则。

编译工具:两处配置,缺一不可。 不配就编译不过或白屏:

问题现象配置
webpack4 不读 exports,按 resolve.mainFields 落到 module(esm.js)Module parse failed: Unexpected token —— esm 产物里 katex 字体地址是 new URL('./fonts/x.woff2', import.meta.url),acorn 6.4.2 解析不了 import.metaalias: { 'lark-doc$': 'lark-doc/dist/lark-doc.common.js' } 显式指到 main
lib 产物里仍有 ES2020 语法(?. / ??)同上,acorn 6.4.2 不认识给该文件单独挂 babel,只加两个 proposal 插件
webpack4 不读 exportsimport 'lark-doc/style.css' 解析不到写全路径 lark-doc/dist/lark-doc.css
同上import 'lark-doc/element' 报"找不到模块"写 lark-doc/dist/lark-doc-element.umd.min.js,或 <script> 直引
js
// vue.config.js(vue-cli 4)
module.exports = {
  configureWebpack: {
    resolve: {
      alias: {
        // ① 强制命中 main 的 lib 单文件(webpack4 唯一能用的入口)
        'lark-doc$': 'lark-doc/dist/lark-doc.common.js',
      },
    },
  },
  chainWebpack(config) {
    // ② lib 产物含 ?. / ??,webpack4 的 acorn 6.4.2 解析不了,
    //    用 babel-loader 只降级这两个语法。configFile/babelrc 必须关掉,
    //    否则会被项目根 babel.config.js 二次转译,破坏产物模块语义。
    config.module.rules.unshift({
      test: /lark-doc[\\/]dist[\\/]lark-doc(\.common|\.umd\.min)\.js$/,
      use: {
        loader: 'babel-loader',
        options: {
          configFile: false,
          babelrc: false,
          sourceType: 'script',
          compact: false,
          plugins: [
            '@babel/plugin-proposal-optional-chaining',
            '@babel/plugin-proposal-nullish-coalescing-operator',
          ],
        },
      },
    })
    // ③ 规则必须排在默认 js 规则之前才生效(unshift 已保证)
  },
}
bash
npm i -D @babel/plugin-proposal-optional-chaining@7 @babel/plugin-proposal-nullish-coalescing-operator@7
npm i lark-doc          # 版本号锁死就写 npm i lark-doc@<你锁的版本> -E

不用 vue-cli 的原生 webpack4 宿主,同一套规则搬到 webpack.config.js(顺序也是 rules 在最前):

js
// webpack.config.js(webpack4 + Vue 2.7,非 vue-cli)
const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')      // webpack4 用 vue-loader@15

module.exports = {
  mode: 'production',
  entry: './src/main.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash:8].js',
    publicPath: '/',
    chunkFilename: '[name].[contenthash:8].js',          // 懒区块(公式/图表)靠它命名
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      '@': path.resolve(__dirname, 'src'),
      // ★ 与 vue-cli 版完全等价:强制走 main 的 lib 单文件
      'lark-doc$': 'lark-doc/dist/lark-doc.common.js',
    },
  },
  module: {
    rules: [
      // ① 必须放在最前面(数组前面的规则优先匹配)
      {
        test: /lark-doc[\\/]dist[\\/]lark-doc(\.common|\.umd\.min)\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            configFile: false,          // 不要读项目根的 babel.config.js
            babelrc: false,
            sourceType: 'script',       // 产物是脚本,不是 ESM
            compact: false,
            plugins: [
              '@babel/plugin-proposal-optional-chaining',
              '@babel/plugin-proposal-nullish-coalescing-operator',
            ],
          },
        },
      },
      { test: /\.vue$/, loader: 'vue-loader' },
      { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader' } },
      // ② css 走常规链路;lark-doc 的 dist/lark-doc.css 也是从这条进来
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
    ],
  },
  plugins: [new VueLoaderPlugin()],
  optimization: { splitChunks: { chunks: 'all' } },
}

判断你的宿主到底是不是 webpack4

npx webpack --version(或 vue-cli-service inspect --version)。webpack4 的 webpack --version 输出 4.x;vue-cli 4 内部固定 4.x,vue-cli 5 内部是 5.x。只有 4.x 需要上面这段配置,5.x 里加了反而多一层无谓编译。

vue
<!-- 业务组件 -->
<template>
  <div ref="host" class="doc-host" />
</template>

<script>
// webpack4 走 lib 单文件:样式必须写全 dist 路径
import 'lark-doc/dist/lark-doc.css'
import { LarkDoc } from 'lark-doc'

export default {
  mounted() {
    this.doc = new LarkDoc({
      element: this.$refs.host,
      title: '未命名文档',
      content: this.record,          // 老的 Vditor HTML 也能直接喂,contentFormat: 'auto' 会自动归一化
      contentFormat: 'auto',
      storage: 'none',               // 草稿交给宿主自己管
      toolbarKeys: ['history', 'headings', 'marks', 'textStyle', 'blockStyle', 'insert', 'tables'],
      features: { ai: false, importExport: true },
      uploader: { upload: (file) => this.$forage.upload(file) },
      onUpdate: ({ json }) => this.debouncedSave(json),
    })
  },
  beforeDestroy() {
    if (this.doc) { this.doc.destroy(); this.doc = null }   // 路由切走必须销毁,否则定时器残留
  },
}
</script>

webpack4 的两个历史坑,0.11.2 起已由包侧修掉

  1. 历史 runtime 标识符遮蔽:旧版 lib 产物曾把 Vue external require 与宿主的 __webpack_require__ 混淆,导致 Cannot read properties of undefined (reading 'call')。当前阶段7产物不外置或内联 Vue,且包侧内层 runtime 使用 __larkDocRequire__;无需为当前版本安装 Vue 或在宿主侧改写 require。
  2. ESM 产物导出为空:0.11.0 / 0.11.1 的 dist/lark-doc.esm.js 缺 output.library = { type: 'module' },import { LarkDoc } 会拿到 undefined。0.11.2 已补。

上述 Vue external 与额外 Babel 规则属于旧版历史。当前阶段7工作区没有 Vue runtime;webpack4 仍需 alias 到 CJS 主文件并显式引 CSS,但不需要再次降语法。

4. Vue 2.7 组件式(options 兼容适配器) ​

主入口还导出了 LarkDocEditor / LarkDocWorkspace,适合"模板里就想要一个编辑器"的场景。注意它是 v-model 语义(value + input),不是 content。

编译工具:与宿主完全相同(webpack5 / Vite 零配置;webpack4 0.13 起也零配置,见上一节)。组件式不需要额外的 loader 或 transpileDependencies,因为组件本身就在主入口的产物里。全局注册写法:

js
// src/main.js
import Vue from 'vue'
import App from './App.vue'
import { LarkDocEditor } from 'lark-doc/legacy'
// ESM 自动注入样式;webpack4 改用 dist/lark-doc-legacy.common.js 并引 dist/lark-doc-legacy.css

Vue.component('LarkDocEditor', LarkDocEditor)  // 注册后才能直接写 <lark-doc-editor>
new Vue({ render: (h) => h(App) }).$mount('#app')

组件式没有 lark-doc 专属编译配置,只要确认两件已有的东西对齐(这两条走错才是踩坑源头):

bash
# ① Vue 2 对应 vue-loader 15.x(Vue 3 才是 17.x)
npm ls vue-loader          # 期望 vue-loader@15.11.x

# ② vue 与 vue-template-compiler 同版本
npm ls vue vue-template-compiler   # 期望两行都是 2.7.16

vue-cli 5 宿主的最小 vue.config.js:

js
// vue.config.js(vue-cli 5,组件式)
const { defineConfig } = require('@vue/cli-service')

module.exports = defineConfig({
  // 组件就在 esm 产物里、已按 browserslist 编好,不需要再转译
  // transpileDependencies: [],
  chainWebpack: (config) => {
    // 组件式会引 lark-doc/style.css,里面有 @font-face 指向 dist/fonts/*.woff2(公式字体)。
    // vue-cli 5 默认已按 asset/resource 处理 woff2,这条只是显式写出来、防止被自己的规则覆盖。
    config.module.rule('fonts').test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i).type('asset/resource')
  },
})

非 vue-cli 的 webpack5 宿主,字体这条必须自己写,否则公式字体 404(控制台会看到 .woff2 返回 text/html):

js
// webpack.config.js(非 vue-cli 的 webpack5)—— 只列组件式真正会踩的两处
module.exports = {
  module: {
    rules: [
      { test: /\.vue$/, loader: 'vue-loader' },                    // 组件式必须有
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },     // style.css 要能被吃
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
        type: 'asset/resource',                                    // webpack4 用 url-loader
        generator: { filename: 'fonts/[name][ext]' },
      },
    ],
  },
}

宿主是 webpack4 时,第 3 节那条针对 lark-doc/dist/lark-doc.common.js 的 alias + babel 规则照样必须存在——组件式只是换个用法,产物没变;style.css 也要改用 lark-doc/dist/lark-doc.css,字体交给 url-loader。

vue
<template>
  <lark-doc-editor
    v-model="html"
    :title="title"
    :readonly="readonly"
    :config="editorConfig"
    content-format="auto"
    @ready="onReady"
    @change="onChange"
    @ai-request="onAiRequest"
  />
</template>

<script>
import { LarkDocEditor } from 'lark-doc/legacy'
// ESM 自动注入样式;webpack4 使用 dist/lark-doc-legacy.common.js 与 dist/lark-doc-legacy.css

export default {
  components: { LarkDocEditor },
  data: () => ({
    html: '<p>hello</p>',
    title: '未命名文档',
    readonly: false,
    // config 是内部 createLarkDocConfig 结构,等价于门面的 features/layout/limits/toolbar
    editorConfig: {
      features: { ai: false, folding: true },
      layout: { headerVisible: true, toolbarFixed: true },
      toolbar: { history: true, headings: true, marks: true, insert: true },
      limits: { maxLength: 0 },
    },
  }),
  methods: {
    onReady(editor) { this.editor = editor },
    onChange({ html, json, text }) { this.html = html; this.lastJson = json },
    onAiRequest(action) { /* 接管 AI 动作 */ },
  },
}
</script>
prop类型说明
v-model / valuestringHTML 内容双向绑定(不是 content)
titlestring标题,配合 update:title
readonlyboolean只读
configobject内部配置结构(features / layout / limits / toolbar)
content-formatstringauto | vditor | html
upload-imagefunction图片上传钩子(SFC 里没有 uploader 对象)
templates / max-length / image-max-size / fold-persist-key-见 配置总览

5. Vue 3 ​

Vue 3 可以直接 import { LarkDoc } from 'lark-doc',在 onMounted 创建、onBeforeUnmount 销毁。下面演示的是可选的 Element + 属性薄壳接法,同样不包含 Vue2 运行时。

两条 import 就够:

js
import 'lark-doc/element'                                     // 元素本体 + 样式(自动注册 <lark-doc>)
import { resolveVue3Attrs } from 'lark-doc/vue3'              // 薄壳:props → 属性、事件名单、漏引报错

lark-doc/vue3 是 3.0 KiB 的薄壳,不含任何第三方依赖,只做三件事:按属性白名单把 props 序列化成 HTML 属性(未登记的 key 会告警而不是静默失效)、导出 VUE3_EVENTS 事件名单、在你忘了 import 'lark-doc/element' 时打一条可行动的 error。它不注册元素,也不替你做下面的 isCustomElement。

编译工具:告诉 Vue 编译器 <lark-doc> 是自定义元素,否则会报 Failed to resolve component: lark-doc。

js
// vite.config.js(Vue 3)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag === 'lark-doc',
        },
      },
    }),
  ],
})

用 vue-cli 5 + Vue 3(webpack5)时,改的是 vue-loader 的 compilerOptions:

js
// vue.config.js(vue-cli 5 + Vue 3)
module.exports = {
  chainWebpack: (config) => {
    config.plugin('vue-loader').tap((args) => {
      const options = args[0] || {}
      options.compilerOptions = Object.assign({}, options.compilerOptions, {
        isCustomElement: (tag) => tag === 'lark-doc',
      })
      args[0] = options
      return args
    })
  },
}

忘了配 isCustomElement 会怎样

开发期控制台刷 Failed to resolve component: lark-doc。Vue 3 对解析失败的标签一般仍按原生元素输出,所以自定义元素有可能照常升级、功能看起来是好的 —— 但这是靠运行期兜底,别依赖:SSR 场景会报水合不匹配,属性透传也不再受控。按上面的配置显式声明才是正解。

vue
<template>
  <lark-doc ref="el" v-bind="attrs" />
</template>

<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import 'lark-doc/element'                          // 元素本体,自动注册 <lark-doc>,样式随产物注入
import { resolveVue3Attrs } from 'lark-doc/vue3'   // props → 属性(白名单序列化,拼错会告警)

const props = { title: '会议纪要', theme: 'light', toolbarKeys: 'history,headings,marks,insert' }
const attrs = computed(() => resolveVue3Attrs(props))

const el = ref(null)
onMounted(() => {
  // 对象型能力(uploader / ai / persistence)写不进 HTML 属性,用 options 注入
  el.value.options = {
    uploader: { upload: async (file) => (await upload(file)).url },
    ai: { enabled: false },
  }
  el.value.addEventListener('lark-doc-change', (e) => console.log(e.detail.payload.json))
})
onBeforeUnmount(() => el.value && el.value.destroy())
</script>

options 的时序

options 赋值会重建实例(运行时内容回到初始值),所以要在意保留现场就先 getJSON()。connectedCallback 延迟一个宏任务,命令式调用请等 lark-doc-ready 事件。

6. React 18 ​

编译工具:CRA(react-scripts@5)零配置,它内部是 webpack5,读 exports;Vite 也只要装 React 插件。只有 TSX 里想直接写 <lark-doc> 才需要补类型声明。

三种写法,按需挑一个:

写法适合要点
mountLarkDoc(el, opts)大多数场景(推荐)命令式拿门面实例,options 里对象型配置随便写,没有属性序列化的限制
createReactLarkDoc(React)(薄壳,4.2 KiB)想要 <LarkDoc onChange={fn} /> 这种声明式写法props → 属性白名单、onChange/onSave/onReady/onError 转成元素事件、ref 透传方法
直接写 <lark-doc> 标签属性全是字符串、事件用 addEventListener需补 .d.ts;对象型配置要靠 el.options 赋值
jsonc
// package.json 片段:CRA 5(webpack5)—— 没有任何 lark-doc 专属配置
{
  "dependencies": { "lark-doc": "^0.11.6", "react": "^18.3.1", "react-dom": "^18.3.1" },
  "devDependencies": { "react-scripts": "^5.0.1" }
}
js
// vite.config.js(React + Vite)
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  // 无需 optimizeDeps.include / alias:vite 读 exports,按需区块自动分割
})
ts
// src/lark-doc.d.ts —— 只在 TSX 里直接写 <lark-doc> 标签时需要
import type { DetailedHTMLProps, HTMLAttributes } from 'react'

// 元素实例:门面常用方法已代理到元素上,不必先 getInstance()
export interface LarkDocElementHost extends HTMLElement {
  ready: boolean
  /** 门面实例(需要全量 API 时用它) */
  document: unknown
  /** 对象型能力入口;赋值会重建实例 */
  options: Record<string, unknown>
  getInstance(): unknown
  getEditor(): unknown
  getHTML(): string
  getJSON(): unknown
  getText(): string
  getTitle(): string
  setTitle(title: string): void
  setContent(content: string | object): void
  setReadonly(readonly: boolean): void
  focus(): void
  undo(): void
  redo(): void
  save(): void
  destroy(): void
}

// 属性表与 src/web-component/attributes.js 一一对应,别自己发明
type LarkDocAttributes = {
  title?: string
  content?: string
  'content-format'?: 'auto' | 'vditor' | 'html'
  placeholder?: string
  theme?: 'light' | 'dark'
  readonly?: boolean | ''
  'save-html'?: boolean | ''
  autofocus?: boolean | ''
  storage?: 'none' | 'local'
  mode?: string
  'document-id'?: string
  'toolbar-keys'?: string          // 逗号分隔,如 "history,headings,marks"
  'toolbar-excludes'?: string
  'max-length'?: number
  /** json 型属性只能写字符串;对象请用 ref + el.options 注入 */
  features?: string
  limits?: string
  layout?: string
  network?: string
}

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'lark-doc': DetailedHTMLProps<HTMLAttributes<LarkDocElementHost>, LarkDocElementHost> &
        LarkDocAttributes
    }
  }
}

CRA 为什么不用 customize-cra

react-scripts@5 已是 webpack5,import 'lark-doc/element' 能正常解析;只有 react-scripts@4(webpack4)才需要 eject 或多加一层 alias,那种宿主建议直接升到 5 —— 比在 CRA 里打 webpack4 补丁便宜得多。

React 侧更推荐 mountLarkDoc(命令式,直接拿门面实例,绕开自定义元素的属性/对象限制):

jsx
import { useEffect, useRef } from 'react'
import { mountLarkDoc } from 'lark-doc/element'   // 同时完成 <lark-doc> 注册与样式注入

export default function DocEditor({ record, onSave }) {
  const hostRef = useRef(null)
  const docRef = useRef(null)

  useEffect(() => {
    const doc = mountLarkDoc(hostRef.current, {
      title: record.title,
      content: record.content,          // HTML / Tiptap JSON / DocumentRecord
      placeholder: '输入正文,或输入 / 插入内容',
      toolbarKeys: ['history', 'headings', 'marks', 'insert', 'tables'],
      features: { ai: false },
      uploader: { upload: (file) => uploadToOss(file) },
      onUpdate: ({ json }) => onSave(json),
      onError: (err) => console.error('[lark-doc]', err),
    })
    docRef.current = doc
    return () => { doc.destroy(); docRef.current = null }   // 卸载必须销毁
  }, [])                               // 依赖留空:文档身份变化请用 setContent 而不是重建

  return <div ref={hostRef} style={{ height: '100vh' }} />
}

想用标签写法也行(React 18 对未知属性能正常透传,事件用 addEventListener):

jsx
import 'lark-doc/element'
export default function Doc() {
  return <lark-doc title="未命名文档" theme="light" />
}

要用声明式组件但又不想手写属性/事件桥接,用薄壳工厂(lark-doc 源码零 React 依赖,React 实例由宿主传入):

jsx
import 'lark-doc/element'                              // 元素本体,必须最先引
import { createReactLarkDoc } from 'lark-doc/react'

const LarkDoc = createReactLarkDoc(React)              // 在模块级建一次,别放进组件体

export default function DocEditor({ record, onSave }) {
  const docRef = useRef(null)
  return (
    <LarkDoc
      ref={docRef}
      title={record.title}
      content={record.content}
      contentFormat="html"
      onChange={(payload) => onSave(payload.json)}      // 事件名已桥接,不用手写 addEventListener
    />
  )
}

7. 原生 / CDN / 内网静态托管 ​

编译工具:不用配,但 dist/ 必须整目录部署(主文件 + *.math.js、*.mermaid.js 等按需区块 + fonts/)。publicPath: 'auto' 按 <script> 自身 URL 推导区块地址,放在任意子目录都成立。

bash
# ① 从 node_modules 里把整目录拷进静态资源目录(CDN / 内网 nginx / 后端 public)
mkdir -p public/vendor/lark-doc
cp -r node_modules/lark-doc/dist/* public/vendor/lark-doc/
#   dist 里至少要有:lark-doc-element.umd.min.js、*.math.js / *.mermaid.js 等懒区块、fonts/
#   漏掉懒区块 → 公式 / 图表 / Word 导入 404,但编辑器本身能打开

# ② 只想要单文件离线副本时(不装 npm 包)
npm pack lark-doc && tar -xzf lark-doc-*.tgz && cp -r package/dist/* public/vendor/lark-doc/
js
// vite 宿主:把上面拷好的目录当静态资源,publicDir 内的文件原样输出
export default {
  publicDir: 'public',       // public/vendor/lark-doc/... → 站点根 /vendor/lark-doc/...
}
nginx
# nginx / 内网静态服务器:确认 .js 与字体都按静态文件返回,别把 .woff2 当 404
location /vendor/lark-doc/ {
  alias /srv/static/vendor/lark-doc/;
  types {
    application/javascript js;
    font/woff2              woff2;
  }
  add_header Cache-Control "public, max-age=31536000, immutable";
}
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <!-- 产物不含 Vue 运行时,样式随 JS 注入,不需要再引 css -->
  <script src="/vendor/lark-doc/lark-doc-element.umd.min.js"></script>
</head>
<body>
  <lark-doc
    title="会议纪要"
    theme="light"
    toolbar-keys="history,headings,marks,insert,tables"
    features='{"ai":false,"folding":true}'
  >
    <h1>标签内的 HTML 会成为初始内容</h1>
    <p>长正文直接写在这里,不必往属性里塞转义字符串。</p>
  </lark-doc>

  <script>
    const el = document.querySelector('lark-doc')
    el.options = { uploader: { upload: (file) => uploadToOss(file) } }
    el.addEventListener('lark-doc-ready', () => {
      console.log('ready', el.getJSON())
    })
    el.addEventListener('lark-doc-change', (event) => {
      localStorage.setItem('draft', JSON.stringify(event.detail.payload.json))
    })
  </script>
</body>
</html>
常见故障原因处理
编辑器能开,插公式 / 图表 / 导出 Word 报 404只拷了主文件整个 dist/ 目录同位部署
想复用站点自己的 mermaid内置副本被加载挂载前置 window.mermaid,或设 options.performance.diagramScriptUrl
页面 unload 报错没销毁离开前调 el.destroy()

8. Node / jsdom(单测、服务端渲染 HTML 快照) ​

require('lark-doc') 命中 CJS 主文件,按需区块由相对 require() 加载,必须保留兄弟 chunk。浏览器宿主还需引 lark-doc/style.css;纯 Node 不加载 CSS。

编译工具:测试运行器默认 node 环境跑不了编辑器,换成 jsdom 即可,产物本身不用转译(main 是 CJS):

js
// babel.config.js —— 只为把你自己 src/ 里的 ESM 压成 jest 能吃的 node 语法
// lark-doc 是 CJS,不需要过 babel,所以不用去 transformIgnorePatterns 里开白名单
module.exports = {
  presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
}
js
// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',                 // Jest 28+ 需另装 jest-environment-jsdom
  // 不需要 transformIgnorePatterns 白名单:main 是 CJS,jest 直接 require 即可;
  // 若你的项目全局设了 ESM("type":"module"),给本包单独留 CJS 例外即可。
  setupFiles: ['<rootDir>/test/bootstrap.js'],   // 补 customElements / ResizeObserver 等
  testTimeout: 20000,                       // 首次挂载要初始化 Tiptap,别用默认 5s
}
js
// vitest.config.js(Vite 项目里的等价写法)
import { defineConfig } from 'vitest/config'
export default defineConfig({
  test: {
    environment: 'jsdom',
    setupFiles: ['./test/bootstrap.js'],
    server: { deps: { inline: ['lark-doc'] } },   // 让 lark-doc 走 node 侧 require 而不是外部化
  },
})
js
// test/bootstrap.js —— 只补 jsdom 缺的东西,不要自己再造一个 JSDOM
// (testEnvironment 已经是 jsdom,再造一份会出现两套 window,坑自己)
if (!global.ResizeObserver) {
  global.ResizeObserver = class {
    observe() {} unobserve() {} disconnect() {}
  }
}
if (!window.matchMedia) {
  window.matchMedia = () => ({ matches: false, addListener() {}, removeListener() {},
    addEventListener() {}, removeEventListener() {} })
}
// 只有单测里要用 lark-doc/element(自定义元素)时才需要 customElements:
// 用主入口的 LarkDoc 门面不需要它,别为此绕路。
if (typeof HTMLElement !== 'undefined' && !global.customElements) {
  global.customElements = {
    _m: new Map(),
    define(n, c) { this._m.set(n, c) },
    get(n) { return this._m.get(n) },
    whenDefined(n) { return Promise.resolve(this._m.get(n)) },
    upgrade() {},
  }
}
js
// test/lark-doc.test.js
const { LarkDoc } = require('lark-doc')

test('round trip', async () => {
  document.body.innerHTML = '<div id="host"></div>'
  const doc = new LarkDoc({
    element: document.getElementById('host'),
    content: '<p>hi</p>',
    storage: 'none',          // 单测别写本地草稿
    network: { mode: 'offline' },   // 不发起任何外网请求
  })
  expect(doc.getHTML()).toContain('<p>hi</p>')
  doc.destroy()
})

为什么单测要关 network

默认 network.mode = 'open',粘贴外链图片 / 加载 mermaid 会走真实请求,CI 里既慢又偶发失败。直接设 'offline' 最省心(该模式下外链图片降级为占位,公式仍本地渲染)。另外 storage 默认已是 'local',单测必须显式写 'none'(或 test/setup 里清 localStorage / indexedDB),否则用例之间会互相串草稿。

9. SSR(Nuxt 2 / Next 13 App Router) ​

element 产物在模块求值时就会 customElements.define 并注入 <style>,Node 端直接 import 会炸,必须 client-only。

Nuxt 2 是 webpack 4,import('lark-doc/element') 会失败

Nuxt 2 内部固定 webpack4,不读 exports → 报 Cannot find module 'lark-doc/element'。要么写全 lark-doc/dist/lark-doc-element.umd.min.js,要么干脆走 <script> 直引(最省事、不经过 bundler)。Nuxt 3 用 Vite,lark-doc/element 可以直接写。

js
// nuxt.config.js(Nuxt 2)—— 走 static 直引,最稳
export default {
  // ① 把 node_modules/lark-doc/dist 整目录拷进 static/
  //    cp -r node_modules/lark-doc/dist static/lark-doc
  head: {
    script: [{ src: '/lark-doc/lark-doc-element.umd.min.js', defer: true }],
  },
  // ② 若坚持走 bundler 动态导入,则必须指全产物路径 + 让 babel 处理 ?. / ??
  build: {
    transpile: ['lark-doc'],
    extend(config) {
      config.resolve.alias['lark-doc$'] = 'lark-doc/dist/lark-doc.common.js'
    },
  },
}
js
// plugins/lark-doc.client.js(.client 后缀 = 只在浏览器加载)
export default async function () {
  if (typeof customElements === 'undefined') return      // 双保险:SSR 阶段直接跳过
  if (!customElements.get('lark-doc')) {
    // Nuxt 2 必须写全 dist 路径;Nuxt 3 可写 'lark-doc/element'
    await import('lark-doc/dist/lark-doc-element.umd.min.js')
  }
}
js
// next.config.js(Next 13)—— 通常一个字都不用改
/** @type {import('next').NextConfig} */
module.exports = {
  // 只有用 turbopack dev 且遇到自定义元素解析问题时才需要显式排除
  // experimental: { turbo: { resolveAlias: { 'lark-doc': 'lark-doc' } } },
}
vue
<!-- Nuxt 2 页面:只在客户端挂载 -->
<template>
  <div ref="host" style="height: 100vh" />
</template>

<script>
export default {
  mounted() {
    // UMD 全局名带横线(output.library = 'lark-doc-element'),只能按字符串取
    const { mountLarkDoc } = window['lark-doc-element'] || {}
    if (!mountLarkDoc) return console.error('[lark-doc] UMD 未加载')
    this.doc = mountLarkDoc(this.$refs.host, {
      title: '未命名文档',
      content: '<p></p>',
      toolbarKeys: ['history', 'headings', 'marks', 'insert'],
    })
  },
  beforeDestroy() { this.doc && this.doc.destroy() },
}
</script>
tsx
// Next 13 App Router
'use client'                                    // 整个文件只在客户端执行
import { useEffect, useRef } from 'react'
import { mountLarkDoc } from 'lark-doc/element'

export default function Editor() {
  const host = useRef<HTMLDivElement>(null)
  useEffect(() => {
    const doc = mountLarkDoc(host.current!, { title: '未命名文档', content: '<p></p>' })
    return () => doc.destroy()
  }, [])
  return <div ref={host} style={{ height: '100vh' }} />
}
ts
// Next 12(Pages Router):动态导入关掉 SSR
import dynamic from 'next/dynamic'
const Editor = dynamic(() => import('../components/Editor'), { ssr: false })

SSR 只想要 HTML 时

require('lark-doc') 可加载 CJS 模块,但创建编辑器与调用 getHTML() 仍需 DOM 环境,例如上文的 jsdom 配置;它不是纯 Node 的无 DOM 渲染器。保留兄弟 chunk,且不要在服务端引 Element 产物。

排错对照 ​

报错 / 现象大概率原因处理
Module parse failed: Unexpected token + import.meta.urlwebpack4 落到了 esm.js(0.13 起没有 browser 字段兜底)加一行 alias: { 'lark-doc$': 'lark-doc/dist/lark-doc.common.js' }(CJS 档已降到 ES2018,不用再挂 babel)
Cannot read properties of undefined (reading 'call')(白屏)0.11.1 及以下的 lib 内层 runtime 被遮蔽升级到 lark-doc@^0.11.2
LarkDoc is undefined(import { LarkDoc })0.11.1 及以下的 ESM 产物缺 library.type同上,升级到 ^0.11.2
Failed to resolve component: lark-doc框架把标签当自家组件Vue 3 配 isCustomElement;Vue 2 用 Vue.config.ignoredElements
[lark-doc] <lark-doc> 尚未注册引了 lark-doc/vue3 / lark-doc/react 薄壳,但漏了 import 'lark-doc/element'宿主入口最先 import 'lark-doc/element'(薄壳只桥接,元素本体在 element 入口)
Cannot find module 'lark-doc/element'webpack4 不读 exports(vue-cli 4、Nuxt 2)写 lark-doc/dist/lark-doc-element.umd.min.js,或 <script> 直引
插公式 / 导出 Word 无反应,Network 里 *.math.js / *.office.js 404走打包器 import 时按需区块基址被算成宿主目录(document.currentScript 指向宿主 bundle)window.__LARK_DOC_PUBLIC_PATH__ = '/vendor/lark-doc/',放在引包之前
Vue packages version mismatchvue 与 vue-template-compiler 版本不一致两者锁同一版本(如都 2.7.16)
Cannot find module 'vite/plugin' / 插件报内部错误Vue 2 的 Vite 插件与 Vite 大版本不匹配@vitejs/plugin-vue2@2.x 配 Vite 3~5;社区版 vite-plugin-vue2 只到 Vite 4
Property 'lark-doc' does not exist on type 'JSX.IntrinsicElements'TSX 里直接写自定义元素标签,缺类型声明补 JSX.IntrinsicElements 声明(见第 6 节)
单测 Exceeded timeout of 5000 ms首次挂载要初始化 Tiptap / 加载区块testTimeout: 20000,并把 testEnvironment 设为 jsdom
Failed to execute 'define' ... has already been used同一页引了两次 element 产物(UMD + 打包各一份)只保留一处引入;打包版靠 customElements.get() 判重
字体 404 或 .woff2 返回 text/html静态服务器 MIME / 目录没覆盖 fonts/;或打包时没给 woff2 配 asset 规则打包侧见第 4 节的 asset/resource;部署侧拷 dist/ 整目录并确认 font/woff2(见第 7 节 nginx 片段)
样式全丢漏引 CSS,或用的是 0.12 及更早的 CJS/lib 产物(那时不注入样式)ESM / Element 产物自带样式注入;CJS 宿主仍需引 lark-doc/dist/lark-doc.css(webpack5 可写 lark-doc/style.css),并保留兄弟 chunk;legacy CJS 改引 dist/lark-doc-legacy.css
公式 / 图表 404只部署了主文件,或按需区块基址算错dist/ 整目录同位托管;打包器 import 的场景设 __LARK_DOC_PUBLIC_PATH__
SSR 报 customElements is not defined服务端引了 element 产物client-only 动态 import()
页面切换后卡顿 / 定时器警告没销毁实例卸载钩子里 doc.destroy()
Cannot find module 'vue/dist/vue.runtime.common.js'加载的是旧版 lark-doc 或旧源码 alias重建/升级完整产物并检查解析路径;当前主入口不要求 Vue peer

下一步 ​