Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow referencing markdown links from the repository root #4039

Closed
Tracked by #4085
vjpr opened this issue Jan 13, 2021 · 6 comments
Closed
Tracked by #4085

Allow referencing markdown links from the repository root #4039

vjpr opened this issue Jan 13, 2021 · 6 comments
Labels
closed: wontfix A fix will bring significant overhead, or is out of scope (for feature requests) feature This is not a bug or issue with Docusausus, per se. It is a feature request for the future.

Comments

@vjpr
Copy link

vjpr commented Jan 13, 2021

🚀 Feature

Have you read the Contributing Guidelines on issues?

Yes

Motivation

In a monorepo setting with multiple docs plugins, you may want to link between two sites.

For example I write:

packages/lib1/docs/readme.md

See [shared template docs](/repos/live/packages/app-templates/chrome-extension/docs-shared/readme.md).

And I get this error:

warn Docs markdown link couldn't be resolved: (/repos/live/packages/app-templates/chrome-extension/docs-shared/readme.md) in /xxx/repos/live/packages/app-templates/chrome-extension/docs/readme.md for version current

In my IDE, I can cmd+click on the link

Pitch

Relative links would solve this but its painful to keep up to date.

Maybe allow a config option to control the linking behaviour.

@vjpr vjpr added feature This is not a bug or issue with Docusausus, per se. It is a feature request for the future. status: needs triage This issue has not been triaged by maintainers labels Jan 13, 2021
@vjpr vjpr changed the title Allow referencing markdown links Allow referencing markdown links from the repository root Jan 13, 2021
@slorber
Copy link
Collaborator

slorber commented Jan 13, 2021

I understand such motivation but at the same time using relative links is advised for 2 reasons:

  • It does not break navigation on Github
  • If you version the docs, the versioned doc will link to another doc of the same version, instead of linking to the doc of the upstream version (as the path is hardcoded)

Also, a Docusaurus site is not "aware" of the repository root, and you might use Docusaurus without a repository anyway.

My suggestion would be to support absolute file paths resolved from the docs version root, as it would at least keep working with versioning (but breaks github UI nav), and is easier to maintain.

See [shared template docs](/readme.md)

Does it make sense?

@vjpr
Copy link
Author

vjpr commented Jan 13, 2021

It does not break navigation on Github

A leading slash on Github corresponds to repo root.

https://github.com/vjpr/readme-github-symlink-test/tree/master/docs

If you version the docs, the versioned doc will link to another doc of the same version

If you use multiple docs plugins, won't each plugin have their own versioning? I would only use absolute paths for linking to other plugins.

My suggestion would be to support absolute file paths resolved from the docs version root

For me, keeping IDE navigation working is pretty important. I use the Markdown Navigator plugin for WebStorm and it the links to be refactored when they are moved around. Relative paths do work with this though.

Would be great if the plugin-content-docs was a bit more configurable. I am hesistant to fork it because I don't want to have to update it as its probably going to change a bit.

@vjpr
Copy link
Author

vjpr commented Jan 13, 2021

Just posting a tree to clarify my setup.

- packages
  - cli-template  (this is a template that the user copies)
    - docs-shared (user doesn't copy this dir when duplicating a template)
      - readme.md
    - docs <- plugin-content-docs
      - readme.md <- has a ref to docs-shared
  - foo-cli
    - docs <- plugin-content-docs
      - readme.md <- has a ref to docs-shared
  - bar-cli
    - docs <- plugin-content-docs
      - readme.md <- has a ref to docs-shared

@slorber
Copy link
Collaborator

slorber commented Jan 14, 2021

A leading slash on Github corresponds to repo root.

I see. Still not sure we should couple Docusaurus behavior to Git/GitHub specificities. Some might use docusaurus with a FTP or SVN or Mercurial as well

If you use multiple docs plugins, won't each plugin have their own versioning? I would only use absolute paths for linking to other plugins.

You can't link from instance1 to instance 2 with relative paths for that reason.
You have to link through URLs in such case.
If Android 2.0 exist, there's no guarantee that iOS 2.0 would also exist, so Android 2.0 docs can't link to the unexisting ios 2.0 docs in such case anyway: you have to be explicit by using the correct versioned URLs because we can't infer easily which version to target.

A docs instance is sandboxed and does not know about the docs and slugs of another version (or even other plugins like blog and pages). Using relative or absolute md file paths, a plugin should only reference md docs of the same plugin, not from other plugin's folders.


We could maybe allow providing a list of paths from which to resolve the md files, so that your absolute links above would resolve fine (as long as they reference files of the same plugin instance). You'd just have to add something like resolveMarkdownPathFrom: ["../.."] (ie your repo root)

Can this work for your usecase?

I'm not sure because you seem to want cross-plugin paths to work according to your FS structure example, and we currently don't support that.

Plugin1/doc1.md has no way to figure out how to replace a markdown link path to Plugin2/doc2.md by a concrete url, as the 2nd plugin has its own custom logic to create that URL. That's something we would try to support but it would require some infra in core first so that each plugin can somehow register to core its md->URL resolution mapping, making this data accessible globally.

@vjpr
Copy link
Author

vjpr commented Jan 21, 2021

A docs instance is sandboxed and does not know about the docs and slugs of another version (or even other plugins like blog and pages). Using relative or absolute md file paths, a plugin should only reference md docs of the same plugin, not from other plugin's folders.

So the monorepo use case would be if you had a top-level docs plugin for your repo, and you wanted to link to the docs for all the open-source packages you have.

  1. You want these links to work in the IDE.
  2. You want them to work in dev mode.
  3. You want them to work in prod build.

I agree that docs instances should be sandboxed, but the link resolution should be configurable.

I have included a remark plugin workaround further down. But I would rather not use a plugin and just have a function I can pass in allowing me to transform links (with parameters of the build environment maybe).

We could maybe allow providing a list of paths from which to resolve the md files, so that your absolute links above would resolve fine (as long as they reference files of the same plugin instance).

This would be a nice addition for sure. But it doesn't solve the cross-plugin paths.

Plugin1/doc1.md has no way to figure out how to replace a markdown link path to Plugin2/doc2.md by a concrete url, as the 2nd plugin has its own custom logic to create that URL. That's something we would try to support but it would require some infra in core first so that each plugin can somehow register to core its md->URL resolution mapping, making this data accessible globally.

Yeh this is what I am looking for.

I was able to hackily work around it by creating the following remark plugin:

remark-docusaurus-monorepo-refs
var visit = require('unist-util-visit')
var toString = require('mdast-util-to-string')
const findUp = require('find-up')
const path = require('path')

const repoRoot = path.dirname(findUp.sync('pnpm-workspace.yaml'))
const findWorkspacePackagesSync = require('get-workspace-pkgs-sync')

// Get all workspace packages in monorepo.
const pkgs = findWorkspacePackagesSync(process.cwd(), {cached: true})

// Filter packages that have Docusaurus docs.
let docusaurusPkgs = pkgs.filter(p => p.manifest.live?.docusaurus?.enable)

// Obtain the docusaurus plugin id (which becomes the slug of the plugin root)
//   using the same recipe as is used to create it when configuring the plugin.
docusaurusPkgs.map(pkg => {
  pkg.docusaurusId = idFromPackageName(pkg.manifest.name)
})

// Convert references to Docusaurus URLs/slugs.
module.exports = opts => {
  // DEBUG
  //console.log(JSON.stringify(docusaurusPkgs, null, 2))

  return transform

  function transform(tree, vfile) {
    // Get the filename of the current file being processed.
    const filename = vfile.history[0]

    // Assign a visitor function for every markdown link found. I.e. `[foo](/foo)`.
    visit(tree, 'link', visitor)

    function visitor(node) {
      // a. URLs.

      if (node.url.startsWith('http://')) return

      // b. Abs paths.

      if (node.url.startsWith('/')) {
        // Resolve to repo root.
        node.url = path.join(repoRoot, node.url)
        return
      }

      // c. Rel paths.
      // Resolve relative to file.

      // Get absolute path on disk.
      const absDiskPath = path.resolve(path.dirname(filename), node.url)

      // We must avoid nested docs paths.

      // Find all packages with roots that contain this absolute disk path.
      const dps = docusaurusPkgs.filter(pkg => absDiskPath.startsWith(pkg.dir))
      // We choose the longest path.
      const docsRoot = longestDir(dps)

      // --

      // Replace path with repo root relative path.
      node.url = path.join(
        docsRoot.docusaurusId,
        absDiskPath.replace(docsRoot.dir, '').replace(/.mdx?/, ''),
      )
    }
  }
}

////////////////////////////////////////////////////////////////////////////////

function idFromPackageName(pkgName) {
  // @live/foo-bar.baz => live_foo_bar__baz
  return pkgName
    .replace('@', '')
    .replace(/-/g, '_')
    .replace('/', '_')
    .replace(/\./g, '__')
}

////////////////////////////////////////////////////////////////////////////////

var longestDir = arr =>
  arr.reduce(function (a, b) {
    return a.dir.length > b.dir.length ? a : b
  })

I would prefer to avoid this by having a function to transform markdown links as mentioned earlier.

Also, maybe some built-in helper function that takes a path and returns and url for the plugin instance that contains it.

In my code above I simply look for the closest package.json file and check whether it has a docs plugin (custom monorepo logic though).

@Josh-Cena Josh-Cena removed the status: needs triage This issue has not been triaged by maintainers label Oct 30, 2021
@Josh-Cena
Copy link
Collaborator

GitHub does this, because there's no sense of "file system" on GitHub. However, these Markdown file links beginning with a slash are already well-defined. They are absolute file paths, for example, /Users/Josh-Cena/Desktop/docusaurus/README.md. There's no easy way to customize this behavior, however, because Markdown linking happens before any remark plugins get loaded. We will track this in #6370 instead, to see how we can integrate this linkification step in the actual MDX loader instead of using a separate loader. This issue itself is certainly a wontfix in our codebase.

@Josh-Cena Josh-Cena added the closed: wontfix A fix will bring significant overhead, or is out of scope (for feature requests) label Feb 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed: wontfix A fix will bring significant overhead, or is out of scope (for feature requests) feature This is not a bug or issue with Docusausus, per se. It is a feature request for the future.
Projects
None yet
Development

No branches or pull requests

3 participants