Skip to content

Commit

Permalink
feat(v2): add support to import assets using relative link in markdow…
Browse files Browse the repository at this point in the history
…n syntax (#3096)

* add a rehyper plugin

* fix yarn.lok

* add target

* convert to remark

* add docs

* remove unused package

* remove file-loader

* add test for file-loader

* fix test
  • Loading branch information
Anshul Goyal authored Aug 3, 2020
1 parent 64293bf commit 3255599
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 0 deletions.
6 changes: 6 additions & 0 deletions packages/docusaurus-mdx-loader/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const stringifyObject = require('stringify-object');
const slug = require('./remark/slug');
const rightToc = require('./remark/rightToc');
const transformImage = require('./remark/transformImage');
const tranformAsset = require('./remark/transformAssets');

const DEFAULT_OPTIONS = {
rehypePlugins: [],
Expand All @@ -34,11 +35,16 @@ module.exports = async function (fileString) {
transformImage,
{staticDir: reqOptions.staticDir, filePath: this.resourcePath},
],
[
tranformAsset,
{staticDir: reqOptions.staticDir, filePath: this.resourcePath},
],
...(reqOptions.remarkPlugins || []),
],
rehypePlugins: [
...(reqOptions.beforeDefaultRehypePlugins || []),
...DEFAULT_OPTIONS.rehypePlugins,

...(reqOptions.rehypePlugins || []),
],
filepath: this.resourcePath,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`transformAsset plugin fail if asset does not exist 1`] = `"Asset packages/docusaurus-mdx-loader/src/remark/transformAssets/__tests__/fixtures/doesNotExist.pdf used in packages/docusaurus-mdx-loader/src/remark/transformAssets/__tests__/fixtures/fail.md not found."`;

exports[`transformAsset plugin fail if asset url is absent 1`] = `"Markdown link url is mandatory. filePath=packages/docusaurus-mdx-loader/src/remark/transformAssets/__tests__/fixtures/noUrl.md"`;

exports[`transformAsset plugin pathname protocol 1`] = `
"[asset](/asset/unchecked.pdf)
"
`;

exports[`transformAsset plugin transform md links to <a /> 1`] = `
"[asset](https://example.com/asset.pdf)
<a target=\\"_blank\\" href={require('./asset.pdf').default} ></a>
<a target=\\"_blank\\" href={require('./asset.pdf').default} >asset</a>
[asset](asset.pdf \\"Title\\") ![seet](asset)
## Heading
\`\`\`md
[asset](./asset.pdf)
\`\`\`
<a target=\\"_blank\\" href={require('!file-loader!./asset.pdf').default} >assets</a>
[assets](/github/!file-loader!/assets.pdf)
[asset](asset.pdf)
"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[asset](https://example.com/asset.pdf)

[](./asset.pdf)

[asset](./asset.pdf)

[asset](asset.pdf 'Title') ![seet](asset)

## Heading

```md
[asset](./asset.pdf)
```

[assets](!file-loader!./asset.pdf)

[assets](/github/!file-loader!/assets.pdf)

[asset](asset.pdf)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[asset](./doesNotExist.pdf)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[asset]()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[asset](pathname:///asset/unchecked.pdf)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {join} from 'path';
import remark from 'remark';
import mdx from 'remark-mdx';
import vfile from 'to-vfile';
import plugin from '..';
import slug from '../../slug';

const processFixture = async (name, options) => {
const path = join(__dirname, 'fixtures', `${name}.md`);
const file = await vfile.read(path);
const result = await remark()
.use(slug)
.use(mdx)
.use(plugin, {...options, filePath: path})
.process(file);

return result.toString();
};

describe('transformAsset plugin', () => {
test('fail if asset does not exist', async () => {
await expect(processFixture('fail')).rejects.toThrowErrorMatchingSnapshot();
});
test('fail if asset url is absent', async () => {
await expect(
processFixture('noUrl'),
).rejects.toThrowErrorMatchingSnapshot();
});

test('transform md links to <a />', async () => {
const result = await processFixture('asset');
expect(result).toMatchSnapshot();
});

test('pathname protocol', async () => {
const result = await processFixture('pathname');
expect(result).toMatchSnapshot();
});
});
91 changes: 91 additions & 0 deletions packages/docusaurus-mdx-loader/src/remark/transformAssets/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const visit = require('unist-util-visit');
const path = require('path');
const url = require('url');
const fs = require('fs-extra');

// Needed to throw errors with computer-agnostic path messages
// Absolute paths are too dependant of user FS
function toRelativePath(filePath) {
return path.relative(process.cwd(), filePath);
}

async function ensureAssetFileExist(assetPath, sourceFilePath) {
const assetExists = await fs.exists(assetPath);
if (!assetExists) {
throw new Error(
`Asset ${toRelativePath(assetPath)} used in ${toRelativePath(
sourceFilePath,
)} not found.`,
);
}
}

async function processLinkNode(node, index, parent, {filePath}) {
if (!node.url) {
throw new Error(
`Markdown link url is mandatory. filePath=${toRelativePath(filePath)}`,
);
}
const parsedUrl = url.parse(node.url);
const assetPath = node.url;
if (parsedUrl.protocol) {
// pathname:// is an escape hatch,
// in case user does not want his assets to be converted to require calls going through webpack loader
// we don't have to document this for now,
// it's mostly to make next release less risky (2.0.0-alpha.59)
if (parsedUrl.protocol === 'pathname:') {
node.url = node.url.replace('pathname://', '');
}
return;
}
if (
assetPath.match(/#|.md|.mdx/) ||
path.isAbsolute(assetPath) ||
!path.extname(assetPath) ||
!assetPath.startsWith('.')
) {
if (!assetPath.startsWith('!')) {
return;
}
}

const expectedAssetPath = path.join(
path.dirname(filePath),
assetPath.replace(/!.*!/, ''),
);
await ensureAssetFileExist(expectedAssetPath, filePath);

node.type = 'jsx';
node.value = `<a target="_blank" ${
assetPath ? `href={require('${assetPath}').default}` : ''
} ${node.title ? `title={${node.title}}` : ''} >`;
const {children} = node;
delete node.children;

parent.children.splice(index + 1, 0, {
type: 'paragraph',
children,
});

parent.children.splice(index + 2, 0, {type: 'jsx', value: '</a>'});
}

const plugin = (options) => {
const transformer = async (root) => {
const promises = [];
visit(root, 'link', (node, index, parent) => {
promises.push(processLinkNode(node, index, parent, options));
});
await Promise.all(promises);
};
return transformer;
};

module.exports = plugin;
20 changes: 20 additions & 0 deletions website/docs/markdown-features.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,10 @@ In the same way, you can link to existing assets by requiring them and using the
href={require('./assets/docusaurus-asset-example-pdf.pdf').default}>
Download this PDF !!!
</a>

or

[Download this PDF using Markdown !!!](./assets/docusaurus-asset-example-pdf.pdf)
```
<a
Expand All @@ -1005,6 +1009,9 @@ In the same way, you can link to existing assets by requiring them and using the
Download this PDF !!!
</a>
[Download this PDF using Markdown !!!](./assets/docusaurus-asset-example-pdf.pdf)
### Unknown assets
This require behavior is not supported for all file extensions, but as an escape hatch you can use the special Webpack syntax to force the `file-loader` to kick-in:
Expand All @@ -1017,10 +1024,23 @@ This require behavior is not supported for all file extensions, but as an escape
href={require('!file-loader!./assets/docusaurus-asset-example.xyz').default}>
Download this unknown file !!!
</a>

or

[Download this unknown file using Markdown](!file-loader!./assets/docusaurus-asset-example.xyz)
```
<a
target="_blank"
href={require('!file-loader!./assets/docusaurus-asset-example.xyz').default}>
Download this unknown file !!!
</a>
[Download this unknown file using Markdown !!!](!file-loader!./assets/docusaurus-asset-example.xyz)
```md
[![](./assets/docusaurus-asset-example-banner.png)](./assets/docusaurus-asset-example-pdf.pdf)
```
[![](./assets/docusaurus-asset-example-banner.png)](./assets/docusaurus-asset-example-pdf.pdf)

0 comments on commit 3255599

Please sign in to comment.