|
| 1 | +# Migration from Documenter to DocumenterVitepress |
| 2 | + |
| 3 | +## Migration Steps |
| 4 | + |
| 5 | +### 1. Update dependencies |
| 6 | + |
| 7 | +File **docs/Project.toml** |
| 8 | + |
| 9 | +- Add `DocumenterVitepress` (UUID: `4710194d-e776-4893-9690-8d956a29c365`) |
| 10 | +- Add `LiveServer` for local preview |
| 11 | +- Keep `Documenter` as a dependency |
| 12 | + |
| 13 | +```toml |
| 14 | +[deps] |
| 15 | +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" |
| 16 | +DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" |
| 17 | +LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589" |
| 18 | + |
| 19 | +[compat] |
| 20 | +Documenter = "1" |
| 21 | +DocumenterVitepress = "0.3" |
| 22 | +LiveServer = "1" |
| 23 | +``` |
| 24 | + |
| 25 | +### 2. Modify make.jl |
| 26 | + |
| 27 | +File **docs/make.jl** |
| 28 | + |
| 29 | +- Add `using DocumenterVitepress` |
| 30 | +- Replace `format=Documenter.HTML(...)` with `format=DocumenterVitepress.MarkdownVitepress(...)` |
| 31 | +- Replace `deploydocs` with `DocumenterVitepress.deploydocs` |
| 32 | + |
| 33 | +```julia |
| 34 | +using Documenter |
| 35 | +using DocumenterVitepress |
| 36 | + |
| 37 | +makedocs(; |
| 38 | + # ... other arguments ... |
| 39 | + format=DocumenterVitepress.MarkdownVitepress(; |
| 40 | + repo="https://github.com/control-toolbox/CTBase.jl", |
| 41 | + devbranch="main", |
| 42 | + devurl="dev", |
| 43 | + sidebar_drawer=true, |
| 44 | + ), |
| 45 | + # ... |
| 46 | +) |
| 47 | + |
| 48 | +DocumenterVitepress.deploydocs(; |
| 49 | + repo="github.com/control-toolbox/CTBase.jl.git", |
| 50 | + devbranch="main", |
| 51 | + push_preview=true, |
| 52 | +) |
| 53 | +``` |
| 54 | + |
| 55 | +### 3. Install Julia dependencies |
| 56 | + |
| 57 | +After editing `docs/Project.toml`, resolve and instantiate (the Manifest must be regenerated to include the new packages): |
| 58 | + |
| 59 | +```bash |
| 60 | +julia --project=docs -e 'using Pkg; Pkg.resolve(); Pkg.instantiate()' |
| 61 | +``` |
| 62 | + |
| 63 | +### 4. Generate Vitepress configuration files |
| 64 | + |
| 65 | +`generate_template` requires `DocumenterVitepress` to be installed (step 3 must be done first). |
| 66 | + |
| 67 | +```bash |
| 68 | +julia --project=docs -e 'using DocumenterVitepress; DocumenterVitepress.generate_template("docs", "CTBase")' |
| 69 | +``` |
| 70 | + |
| 71 | +This creates the following files (do not create them manually): |
| 72 | + |
| 73 | +In `docs/src/`: |
| 74 | + |
| 75 | +- `.vitepress/config.mts` - Main Vitepress configuration |
| 76 | +- `.vitepress/theme/index.ts` - Theme customization |
| 77 | +- `.vitepress/theme/style.css` - Custom CSS styles |
| 78 | +- `.vitepress/theme/docstrings.css` - Docstring block styles |
| 79 | +- `.vitepress/mathjax-plugin.ts` - MathJax plugin |
| 80 | +- `.vitepress/julia-repl-transformer.ts` - Julia REPL transformer |
| 81 | +- `components/VersionPicker.vue` - Version picker navbar component |
| 82 | +- `components/SidebarDrawerToggle.vue` - Sidebar collapse toggle |
| 83 | +- `components/AuthorBadge.vue` - Author badge component |
| 84 | +- `components/Authors.vue` - Authors list component |
| 85 | + |
| 86 | +At the root of `docs/`: |
| 87 | + |
| 88 | +- `package.json` - npm dependencies |
| 89 | +- `.gitignore` - ignores `build/`, `node_modules/`, `package-lock.json`, `Manifest.toml` |
| 90 | + |
| 91 | +### 5. Patch config.mts for remote assets |
| 92 | + |
| 93 | +The generated `docs/src/.vitepress/config.mts` does not include the control-toolbox CSS/JS assets. Edit the `head` section to add them. |
| 94 | + |
| 95 | +Replace the generated `head` block: |
| 96 | + |
| 97 | +```typescript |
| 98 | +head: [ |
| 99 | + ['link', { rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON' }], |
| 100 | + ['script', {src: `${getBaseRepository(baseTemp.base)}versions.js`}], |
| 101 | + ['script', {src: `${baseTemp.base}siteinfo.js`}] |
| 102 | +], |
| 103 | +``` |
| 104 | + |
| 105 | +With the remote assets version (Option B): |
| 106 | + |
| 107 | +```typescript |
| 108 | +head: [ |
| 109 | + ['link', { rel: 'icon', href: 'REPLACE_ME_DOCUMENTER_VITEPRESS_FAVICON' }], |
| 110 | + ['link', { rel: 'stylesheet', href: 'https://control-toolbox.org/assets/css/vitepress-documentation.css' }], |
| 111 | + ['script', {src: `${getBaseRepository(baseTemp.base)}versions.js`}], |
| 112 | + ['script', {src: 'https://control-toolbox.org/assets/js/vitepress-documentation.js'}], |
| 113 | + ['script', {src: `${baseTemp.base}siteinfo.js`}] |
| 114 | +], |
| 115 | +``` |
| 116 | + |
| 117 | +#### Option A: Local assets (for development only) |
| 118 | + |
| 119 | +If assets are not yet published remotely, use local files placed in `docs/src/assets/`. Add a Vite plugin in the `vite.plugins` section of `config.mts` to copy them at build time: |
| 120 | + |
| 121 | +```typescript |
| 122 | +import { copyFileSync, mkdirSync } from 'fs' |
| 123 | + |
| 124 | +let ctOutDir = '' |
| 125 | + |
| 126 | +// inside vite.plugins: |
| 127 | +{ |
| 128 | + name: 'ct-static-assets', |
| 129 | + apply: 'build' as const, |
| 130 | + configResolved(config: any) { |
| 131 | + if (!config.build.ssr) ctOutDir = config.build.outDir |
| 132 | + }, |
| 133 | + closeBundle() { |
| 134 | + if (!ctOutDir) return |
| 135 | + const assetsDir = path.join(ctOutDir, 'assets') |
| 136 | + mkdirSync(assetsDir, { recursive: true }) |
| 137 | + for (const file of [ |
| 138 | + 'vitepress-documentation.css', |
| 139 | + 'vitepress-documentation.js', |
| 140 | + ]) { |
| 141 | + try { copyFileSync(path.resolve(__dirname, '../assets', file), path.join(assetsDir, file)) } catch (_) {} |
| 142 | + } |
| 143 | + } |
| 144 | +}, |
| 145 | +``` |
| 146 | + |
| 147 | +And reference them in `head` using `${baseTemp.base}assets/...` instead of the remote URLs. |
| 148 | + |
| 149 | +### 6. Install npm dependencies |
| 150 | + |
| 151 | +```bash |
| 152 | +cd docs && npm install |
| 153 | +``` |
| 154 | + |
| 155 | +### 7. Local build and preview |
| 156 | + |
| 157 | +```bash |
| 158 | +# Generate documentation |
| 159 | +julia --project=docs docs/make.jl |
| 160 | + |
| 161 | +# Local preview (output is in docs/build/1/, not docs/build/) |
| 162 | +julia --project=docs -e 'using LiveServer; LiveServer.serve(dir="docs/build/1")' |
| 163 | +``` |
| 164 | + |
| 165 | +## Important notes |
| 166 | + |
| 167 | +- **ANSI color codes in @repl blocks**: DocumenterVitepress does not automatically convert ANSI escape codes to HTML in `@repl` blocks (unlike `@example` blocks which are converted to `ansi` code blocks). To avoid raw ANSI codes appearing in the generated markdown, wrap `showerror` calls with `IOContext(stdout, :color => false)`: |
| 168 | + |
| 169 | + ```julia |
| 170 | + try |
| 171 | + throw(CTBase.Exceptions.IncorrectArgument("n must be positive"; got="-1")) |
| 172 | + catch e |
| 173 | + showerror(IOContext(stdout, :color => false), e) |
| 174 | + end |
| 175 | + ``` |
| 176 | + |
| 177 | + This is a known limitation tracked in [LuxDL/DocumenterVitepress.jl#321](https://github.com/LuxDL/DocumenterVitepress.jl/issues/321). |
| 178 | + |
| 179 | +- **Color-aware display functions**: If your package has custom display functions that use ANSI codes (e.g., error display helpers), make them color-aware by checking `get(io, :color, false)`. For example, in CTBase we implemented a helper function and updated all ANSI styling primitives: |
| 180 | + |
| 181 | + ```julia |
| 182 | + # src/Exceptions/display.jl |
| 183 | + _apply_ansi(s, code, io::IO) = get(io, :color, false) ? "\033[$(code)m$(s)\033[0m" : s |
| 184 | + |
| 185 | + _dim(s, io::IO) = _apply_ansi(s, "2", io) |
| 186 | + _bold(s, io::IO) = _apply_ansi(s, "1", io) |
| 187 | + _red(s, io::IO) = _apply_ansi(s, "1;31", io) |
| 188 | + _yellow(s, io::IO) = _apply_ansi(s, "33", io) |
| 189 | + _green(s, io::IO) = _apply_ansi(s, "32", io) |
| 190 | + ``` |
| 191 | + |
| 192 | + Then propagate the `io` parameter to all call sites in display functions: |
| 193 | + |
| 194 | + ```julia |
| 195 | + function _format_user_friendly_error(io::IO, e::CTException) |
| 196 | + # ... |
| 197 | + print(io, _red(type_name, io)) # Pass io to the helper |
| 198 | + # ... |
| 199 | + end |
| 200 | + ``` |
| 201 | + |
| 202 | + This ensures: |
| 203 | + - REPL / GitHub Actions → colors enabled (`:color => true` by default) |
| 204 | + - Documenter / VitePress → plain text when wrapped with `IOContext(stdout, :color => false)`) |
| 205 | + |
| 206 | +- **Git repository required**: DocumenterVitepress requires a git repository to function |
| 207 | +- **Build output**: Documentation is generated in `docs/build/1/` (not `docs/build/`) |
| 208 | +- **Do not create Vitepress files manually**: always use `generate_template` (step 4) — it generates all config, theme, components, and npm files |
| 209 | +- **Symlinks**: Before deployment, remove symlinks on the `gh-pages` branch (stable, v1, etc.) |
| 210 | + |
| 211 | + Documenter.jl uses symlinks on the `gh-pages` branch to manage documentation versions: |
| 212 | + |
| 213 | + - `stable` → points to the current stable version (e.g., `v0.5.0`) |
| 214 | + - `v1` → points to the latest major version |
| 215 | + - `v0.1`, `v0.2`, etc. → point to specific versions |
| 216 | + |
| 217 | + DocumenterVitepress cannot write to symlinks. If you are migrating from an existing Documenter documentation, your `gh-pages` branch likely contains these symlinks. They must be manually removed before the first deployment with DocumenterVitepress. |
| 218 | + |
| 219 | + **How to remove symlinks:** |
| 220 | + |
| 221 | + 1. Go to GitHub: `https://github.com/control-toolbox/CTBase.jl/tree/gh-pages` |
| 222 | + 2. Symlinks are identifiable by a small arrow ↗ |
| 223 | + 3. Click on each symlink (stable, v1, etc.) |
| 224 | + 4. Delete them via the context menu |
| 225 | + |
| 226 | + DocumenterVitepress handles versions differently, without using symlinks. |
| 227 | +- **Vitepress configuration**: The `REPLACE_ME_DOCUMENTER_VITEPRESS` strings are automatically replaced during the build |
| 228 | +- **TypeScript errors**: TypeScript errors in the IDE regarding `sidebar` and missing `node_modules` are normal before `npm install` — DocumenterVitepress replaces these values during the build |
| 229 | + |
| 230 | +## Deployment |
| 231 | + |
| 232 | +Deployment is done automatically via CI with `DocumenterVitepress.deploydocs`. Ensure that: |
| 233 | + |
| 234 | +- The GitHub repository exists |
| 235 | +- The `gh-pages` branch does not contain symlinks |
| 236 | +- CI workflows are configured for DocumenterVitepress |
0 commit comments