Files
BoosAPI/desktop/libs/markstream-vue/playground-vue2-cli/vue.config.js
T
boos e0a5f57793 feat(desktop): embed BoosAPI Desktop client as a subproject
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
2026-06-22 11:32:57 +08:00

293 lines
12 KiB
JavaScript

const fs = require('node:fs')
const path = require('node:path')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const webpack = require('webpack')
function tryResolve(specifier) {
try {
return require.resolve(specifier)
}
catch {
return null
}
}
function resolveNodeModulesPackageRoot(pkgName) {
// Some ESM-only packages cannot be resolved via `require.resolve(pkgName)` in
// this CJS config file (no `require` entry + `exports` restrictions). Fall
// back to checking `node_modules/<pkgName>` directly.
const candidates = [
path.join(__dirname, 'node_modules', pkgName),
path.resolve(__dirname, '../node_modules', pkgName),
]
for (const p of candidates) {
try {
if (fs.existsSync(p))
return p
}
catch {}
}
return null
}
function resolvePackageRoot(pkgName) {
const entry = tryResolve(pkgName)
if (!entry)
return null
// `markstream-vue2` resolves to `<pkgRoot>/dist/index.cjs`
return path.dirname(path.dirname(entry))
}
function resolveSiblingPackagePath(fromPkgRoot, siblingPkgName) {
if (!fromPkgRoot)
return null
const candidate = path.join(path.dirname(fromPkgRoot), siblingPkgName)
try {
if (fs.existsSync(candidate))
return candidate
}
catch {}
return null
}
function setAliasIfResolved(alias, key, specifier) {
const resolved = tryResolve(specifier)
if (resolved)
alias[key] = resolved
}
function setAliasIfExists(alias, key, filePath) {
if (!filePath)
return
alias[key] = filePath
}
const markstreamVue2Root = resolvePackageRoot('markstream-vue2')
const streamMonacoRoot = resolvePackageRoot('stream-monaco')
const streamMarkdownRoot = resolvePackageRoot('stream-markdown') || resolveNodeModulesPackageRoot('stream-markdown')
const floatingUiDomEntry = tryResolve('@floating-ui/dom')
const floatingUiDomRoot = resolvePackageRoot('@floating-ui/dom')
const floatingUiCoreRoot = resolveSiblingPackagePath(floatingUiDomRoot, 'core')
const floatingUiUtilsRoot = resolveSiblingPackagePath(floatingUiDomRoot, 'utils')
// `monaco-editor` does not provide a Node `main` entry, so `require.resolve('monaco-editor')`
// fails even when it is installed. Resolve it via the `stream-monaco` peer location instead.
const monacoEditorRoot = resolveSiblingPackagePath(streamMonacoRoot, 'monaco-editor')
const shikiRoot = resolvePackageRoot('shiki')
const shikijsLangsRoot = resolvePackageRoot('@shikijs/langs')
const shikijsThemesRoot = resolvePackageRoot('@shikijs/themes')
const shikijsOnigurumaRoot = resolvePackageRoot('@shikijs/engine-oniguruma')
const shikijsMonacoRoot = resolvePackageRoot('@shikijs/monaco')
const alias = {}
function loadPlaygroundMarkdown() {
const file = path.resolve(__dirname, '../playground/src/const/markdown.ts')
try {
const src = fs.readFileSync(file, 'utf8')
const marker = 'export const streamContent = `'
const start = src.indexOf(marker)
if (start === -1)
return ''
const from = start + marker.length
const end = src.lastIndexOf('`')
if (end <= from)
return ''
// The source file is a TS template literal. We want the *runtime string*
// value (i.e. decode common template escapes) without executing any code.
// This is enough for our demo needs and avoids leaking double-escaped
// delimiters like `\\[` (which breaks math parsing).
const raw = src.slice(from, end)
return raw
// `\\` -> `\`
.replace(/\\\\/g, '\\')
// `\`` -> '`'
.replace(/\\`/g, '`')
// `\${` -> `${` (avoid template interpolation escapes being shown)
.replace(/\\\$\{/g, '${')
}
catch {
return ''
}
}
// Important: ensure the entire app (including workspace-linked packages) uses a
// single Vue 2 runtime instance. Otherwise you will see Composition API warnings
// like "provide() can only be used inside setup()" due to duplicate Vue copies.
// Use the CJS runtime so CommonJS bundles (like markstream-vue2/dist/index.cjs)
// can access Vue static helpers (e.g. defineComponent) without ESM interop issues.
setAliasIfResolved(alias, 'vue$', 'vue/dist/vue.runtime.common.js')
// Force vue-demi into Vue 2 mode so defineComponent comes from @vue/composition-api.
setAliasIfResolved(alias, 'vue-demi$', 'vue-demi/lib/v2/index.cjs')
// Force Webpack 4 to use the CJS entry. The ESM entry (`dist/index.js`) is
// currently not compatible with Webpack 4 consumption in this playground.
setAliasIfExists(
alias,
'markstream-vue2$',
markstreamVue2Root ? path.join(markstreamVue2Root, 'dist/index.cjs') : null,
)
// Webpack 4 doesn't support `package.json#exports`, so subpath imports like
// `markstream-vue2/index.css` won't resolve. Map them to real files in `dist/`.
setAliasIfExists(alias, 'markstream-vue2/index.css', markstreamVue2Root ? path.join(markstreamVue2Root, 'dist/index.css') : null)
setAliasIfExists(alias, 'markstream-vue2/index.tailwind.css', markstreamVue2Root ? path.join(markstreamVue2Root, 'dist/index.tailwind.css') : null)
setAliasIfExists(alias, 'markstream-vue2/workers/katexRenderer.worker', markstreamVue2Root ? path.join(markstreamVue2Root, 'dist/workers/katexRenderer.worker.js') : null)
setAliasIfExists(alias, 'markstream-vue2/workers/mermaidParser.worker', markstreamVue2Root ? path.join(markstreamVue2Root, 'dist/workers/mermaidParser.worker.js') : null)
// Force Floating UI to CJS/UMD-compatible entries. Webpack 4 + pnpm otherwise
// follows the ESM build into symlinked package internals and misses peer deps.
setAliasIfExists(alias, '@floating-ui/dom$', floatingUiDomEntry)
setAliasIfExists(alias, '@floating-ui/core$', floatingUiCoreRoot ? path.join(floatingUiCoreRoot, 'dist/floating-ui.core.umd.js') : null)
setAliasIfExists(alias, '@floating-ui/utils$', floatingUiUtilsRoot ? path.join(floatingUiUtilsRoot, 'dist/floating-ui.utils.umd.js') : null)
setAliasIfExists(alias, '@floating-ui/utils/dom$', floatingUiUtilsRoot ? path.join(floatingUiUtilsRoot, 'dom/floating-ui.utils.dom.umd.js') : null)
// The CLI demo does not need the full D2 runtime. Use a tiny shim so the
// optional peer still degrades to source view instead of failing the build.
setAliasIfExists(alias, '@terrastruct/d2$', path.resolve(__dirname, 'shims/optional-d2.cjs'))
// Webpack 4 doesn't support `package.json#exports`, so subpath imports like
// `stream-monaco/legacy` won't resolve. Map them to real files in `dist/`.
setAliasIfExists(
alias,
'stream-monaco/legacy',
// Webpack 4 doesn't support `package.json#exports`, so subpath imports like
// `stream-monaco/legacy` won't resolve. Map it to the built legacy entry.
streamMonacoRoot ? path.join(streamMonacoRoot, 'dist/index.legacy.js') : null,
)
// Force `stream-monaco` to resolve to the local package we installed for this
// playground. This avoids accidentally picking up a hoisted copy from the
// workspace root.
setAliasIfExists(alias, 'stream-monaco$', streamMonacoRoot ? path.join(streamMonacoRoot, 'dist/index.js') : null)
setAliasIfExists(alias, 'stream-monaco', streamMonacoRoot ? path.join(streamMonacoRoot, 'dist') : null)
// Make `monaco-editor` resolvable for Webpack 4 + pnpm.
// We alias the package directory so both `monaco-editor` and subpath imports work.
setAliasIfExists(alias, 'monaco-editor', monacoEditorRoot)
// Webpack 4 doesn't support `package.json#exports`, so deep imports like
// `@shikijs/langs/javascript` won't resolve. Map package prefixes to `dist/`.
setAliasIfExists(alias, '@shikijs/langs', shikijsLangsRoot ? path.join(shikijsLangsRoot, 'dist') : null)
setAliasIfExists(alias, '@shikijs/themes', shikijsThemesRoot ? path.join(shikijsThemesRoot, 'dist') : null)
setAliasIfExists(alias, '@shikijs/engine-oniguruma', shikijsOnigurumaRoot ? path.join(shikijsOnigurumaRoot, 'dist') : null)
setAliasIfExists(alias, '@shikijs/engine-oniguruma$', shikijsOnigurumaRoot ? path.join(shikijsOnigurumaRoot, 'dist/index.mjs') : null)
setAliasIfExists(alias, '@shikijs/engine-oniguruma/wasm-inlined', shikijsOnigurumaRoot ? path.join(shikijsOnigurumaRoot, 'dist/wasm-inlined.mjs') : null)
setAliasIfExists(alias, '@shikijs/engine-oniguruma/wasm-inlined$', shikijsOnigurumaRoot ? path.join(shikijsOnigurumaRoot, 'dist/wasm-inlined.mjs') : null)
setAliasIfExists(alias, '@shikijs/monaco', shikijsMonacoRoot ? path.join(shikijsMonacoRoot, 'dist') : null)
// Force a single Shiki instance so `loadWasm()` initializes the same
// Oniguruma binding used by stream-monaco.
setAliasIfExists(alias, 'shiki$', shikiRoot ? path.join(shikiRoot, 'dist/index.mjs') : null)
setAliasIfExists(alias, 'shiki', shikiRoot ? path.join(shikiRoot, 'dist') : null)
setAliasIfExists(alias, 'shiki/wasm', shikiRoot ? path.join(shikiRoot, 'dist/wasm.mjs') : null)
// Webpack 4 doesn't support `package.json#exports`, so ensure `stream-markdown`
// resolves to a real file.
setAliasIfExists(alias, 'stream-markdown$', streamMarkdownRoot ? path.join(streamMarkdownRoot, 'dist/index.js') : null)
setAliasIfExists(alias, 'stream-markdown', streamMarkdownRoot ? path.join(streamMarkdownRoot, 'dist') : null)
// Webpack 4 doesn't support `package.json#exports`, so map `alien-signals`
// (a dependency of stream-monaco) to an actual file.
setAliasIfResolved(alias, 'alien-signals$', 'alien-signals/esm')
setAliasIfResolved(alias, 'alien-signals', 'alien-signals/esm')
// Third-party packages that rely on `exports` subpaths (optional features).
setAliasIfResolved(alias, '@antv/infographic/jsx-runtime', '@antv/infographic/jsx-runtime')
setAliasIfResolved(alias, '@antv/infographic/jsx-dev-runtime', '@antv/infographic/jsx-dev-runtime')
setAliasIfResolved(alias, 'measury/fonts/AlibabaPuHuiTi-Regular', 'measury/fonts/AlibabaPuHuiTi-Regular')
function createOptionalIgnoreRegex() {
const alwaysIgnore = [
'mermaid',
'@mermaid-js/parser',
'langium',
'@antv/infographic',
'@antv/hierarchy',
]
// If Monaco is installed, we keep it enabled so `code_block` renders with
// CodeBlockNode instead of falling back to <pre>.
const maybeIgnore = []
if (!streamMarkdownRoot)
maybeIgnore.push('stream-markdown')
if (!tryResolve('stream-monaco'))
maybeIgnore.push('stream-monaco')
if (!monacoEditorRoot)
maybeIgnore.push('monaco-editor')
if (!tryResolve('shiki'))
maybeIgnore.push('shiki')
if (!tryResolve('@shikijs/langs'))
maybeIgnore.push('@shikijs/langs')
if (!tryResolve('@shikijs/themes'))
maybeIgnore.push('@shikijs/themes')
const combined = [...alwaysIgnore, ...maybeIgnore]
return new RegExp(`^(${combined.join('|')})$`)
}
function createMonacoAssetCopyPlugins() {
if (!monacoEditorRoot)
return []
// Monaco ESM workers import other `esm/vs/*` modules by relative paths, so we
// copy the whole `esm/vs` tree into the app's static output. This avoids CDN
// workers and keeps everything same-origin for Worker security.
const from = path.join(monacoEditorRoot, 'esm/vs')
if (!fs.existsSync(from))
return []
return [
// Vue CLI 4 ships CopyWebpackPlugin v5 which uses the array signature.
new CopyWebpackPlugin([
{ from, to: 'monaco/vs' },
]),
]
}
module.exports = {
transpileDependencies: [
'markstream-vue2',
'stream-markdown-parser',
'stream-markdown',
'stream-monaco',
'monaco-editor',
'shiki',
'@shikijs/core',
'@shikijs/engine-javascript',
'@shikijs/engine-oniguruma',
'@shikijs/types',
'@shikijs/vscode-textmate',
'@shikijs/monaco',
'@shikijs/langs',
'@shikijs/themes',
'oniguruma-to-es',
'@antv/infographic',
],
configureWebpack: {
plugins: [
new webpack.DefinePlugin({
__MARKSTREAM_VUE2_CLI_DEMO_MD__: JSON.stringify(loadPlaygroundMarkdown()),
}),
// These are optional features. On Webpack 4 they may pull in `exports`-heavy
// dependency graphs (Shiki languages, AntV internals) that are hard to
// polyfill. Ignoring them keeps the core renderer usable.
new webpack.IgnorePlugin({
resourceRegExp: createOptionalIgnoreRegex(),
}),
...createMonacoAssetCopyPlugins(),
],
resolve: {
// pnpm uses symlinks; this helps webpack 4 resolve dependencies correctly.
symlinks: false,
// Add workspace root `node_modules` for Webpack 4's resolver.
modules: [
'node_modules',
path.resolve(__dirname, '../node_modules'),
],
alias,
},
},
chainWebpack: (config) => {
// Avoid linting generated bundles / linked workspace packages in this playground.
config.module.rules.delete('eslint')
},
}