mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-18 03:26:57 +02:00
feat(v2): add support to import assets using relative link in markdown 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
This commit is contained in:
parent
64293bf87f
commit
325559933f
10 changed files with 218 additions and 0 deletions
|
@ -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: [],
|
||||
|
@ -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,
|
||||
|
|
|
@ -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\\") 
|
||||
|
||||
## 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)
|
||||
"
|
||||
`;
|
|
@ -0,0 +1,19 @@
|
|||
[asset](https://example.com/asset.pdf)
|
||||
|
||||
[](./asset.pdf)
|
||||
|
||||
[asset](./asset.pdf)
|
||||
|
||||
[asset](asset.pdf 'Title') 
|
||||
|
||||
## Heading
|
||||
|
||||
```md
|
||||
[asset](./asset.pdf)
|
||||
```
|
||||
|
||||
[assets](!file-loader!./asset.pdf)
|
||||
|
||||
[assets](/github/!file-loader!/assets.pdf)
|
||||
|
||||
[asset](asset.pdf)
|
|
@ -0,0 +1 @@
|
|||
[asset](./doesNotExist.pdf)
|
|
@ -0,0 +1 @@
|
|||
[asset]()
|
|
@ -0,0 +1 @@
|
|||
[asset](pathname:///asset/unchecked.pdf)
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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;
|
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -1017,6 +1024,10 @@ 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
|
||||
|
@ -1024,3 +1035,12 @@ 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>
|
||||
|
||||
[Download this unknown file using Markdown !!!](!file-loader!./assets/docusaurus-asset-example.xyz)
|
||||
|
||||
|
||||
```md
|
||||
[](./assets/docusaurus-asset-example-pdf.pdf)
|
||||
```
|
||||
|
||||
[](./assets/docusaurus-asset-example-pdf.pdf)
|
Loading…
Add table
Add a link
Reference in a new issue