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

feat(core): allow sourcing from multiple static directories #4095

Merged
merged 16 commits into from
Nov 18, 2021
Merged
14 changes: 11 additions & 3 deletions packages/docusaurus-mdx-loader/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const DEFAULT_OPTIONS: RemarkAndRehypePluginOptions = {
};

type Options = RemarkAndRehypePluginOptions & {
staticDir?: string;
staticDirs: string[];
siteDir: string;
isMDXPartial?: (filePath: string) => boolean;
isMDXPartialFrontMatterWarningDisabled?: boolean;
removeContentTitle?: boolean;
Expand Down Expand Up @@ -123,8 +124,15 @@ export default async function mdxLoader(
remarkPlugins: [
...(reqOptions.beforeDefaultRemarkPlugins || []),
...DEFAULT_OPTIONS.remarkPlugins,
[transformImage, {staticDir: reqOptions.staticDir, filePath}],
[transformLinks, {staticDir: reqOptions.staticDir, filePath}],
[transformImage, {staticDirs: reqOptions.staticDirs, filePath}],
[
transformLinks,
{
staticDirs: reqOptions.staticDirs,
filePath,
siteDir: reqOptions.siteDir,
},
],
...(reqOptions.remarkPlugins || []),
],
rehypePlugins: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
![img](https://example.com/img.png)

![](./static/img.png)

![img](./static/img.png)

![img from second static folder](/img2.png)

![img from second static folder](./static2/img2.png)

![img](./static/img.png 'Title') ![img](/img.png)

![img with "quotes"](./static/img.png ''Quoted' title')

## Heading

```md
![img](./static/img.png)
```
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`transformImage plugin fail if image does not exist 1`] = `"Image packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/fixtures/img/doesNotExist.png used in packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/fixtures/fail.md not found."`;
exports[`transformImage plugin fail if image does not exist 1`] = `"Image packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static/img/doesNotExist.png or packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static2/img/doesNotExist.png used in packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/fail.md not found."`;

exports[`transformImage plugin fail if image url is absent 1`] = `"Markdown image URL is mandatory in \\"packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/fixtures/noUrl.md\\" file"`;
exports[`transformImage plugin fail if image url is absent 1`] = `"Markdown image URL is mandatory in \\"packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/noUrl.md\\" file"`;

exports[`transformImage plugin pathname protocol 1`] = `
"![img](/img/unchecked.png)
Expand All @@ -12,20 +12,22 @@ exports[`transformImage plugin pathname protocol 1`] = `
exports[`transformImage plugin transform md images to <img /> 1`] = `
"![img](https://example.com/img.png)

<img src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!./img.png\\").default} />
<img src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!./static/img.png\\").default} />

<img alt={\\"img\\"} src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!./img.png\\").default} />
<img alt={\\"img\\"} src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!./static/img.png\\").default} />

<img alt={\\"img\\"} src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!./img.png\\").default} title=\\"Title\\" /> <img alt={\\"img\\"} src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/fixtures/img.png\\").default} />
<img alt={\\"img from second static folder\\"} src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static2/img2.png\\").default} />

<img alt={\\"img with &quot;quotes&quot;\\"} src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!./img.png\\").default} title=\\"&#39;Quoted&#39; title\\" />
<img alt={\\"img from second static folder\\"} src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!./static2/img2.png\\").default} />

<img alt={\\"img\\"} src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!./static/img.png\\").default} title=\\"Title\\" /> <img alt={\\"img\\"} src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static/img.png\\").default} />

<img alt={\\"img with &quot;quotes&quot;\\"} src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!./static/img.png\\").default} title=\\"&#39;Quoted&#39; title\\" />

## Heading

\`\`\`md
![img](./img.png)
![img](./static/img.png)
\`\`\`

<img alt={\\"img\\"} src={require(\\"!url-loader?limit=10000&name=assets/images/[name]-[hash].[ext]&fallback=file-loader!./img.png\\").default} />
"
`;

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,56 @@
* LICENSE file in the root directory of this source tree.
*/

import {join, relative} from 'path';
import path from 'path';
import remark from 'remark';
import mdx from 'remark-mdx';
import vfile from 'to-vfile';
import plugin from '../index';
import headings from '../../headings/index';

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

return result.toString();
};

// avoid hardcoding absolute
const staticDir = `./${relative(process.cwd(), join(__dirname, 'fixtures'))}`;
const staticDirs = [
// avoid hardcoding absolute in the snapshot
`./${path.relative(
process.cwd(),
path.join(__dirname, '__fixtures__/static'),
)}`,
`./${path.relative(
process.cwd(),
path.join(__dirname, '__fixtures__/static2'),
)}`,
];

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

test('transform md images to <img />', async () => {
const result = await processFixture('img', {
staticDir,
});
const result = await processFixture('img', {staticDirs});
expect(result).toMatchSnapshot();
});

test('pathname protocol', async () => {
const result = await processFixture('pathname', {
staticDir,
});
const result = await processFixture('pathname', {staticDirs});
expect(result).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const {

interface PluginOptions {
filePath: string;
staticDir: string;
staticDirs: string[];
}

const createJSX = (node: Image, pathUrl: string) => {
Expand Down Expand Up @@ -63,9 +63,25 @@ async function ensureImageFileExist(imagePath: string, sourceFilePath: string) {
}
}

async function findImage(possiblePaths: string[], sourceFilePath: string) {
// eslint-disable-next-line no-restricted-syntax
for (const possiblePath of possiblePaths) {
if (await fs.pathExists(possiblePath)) {
return possiblePath;
}
}
throw new Error(
`Image ${possiblePaths
.map((p) => toMessageRelativeFilePath(p))
.join(' or ')} used in ${toMessageRelativeFilePath(
sourceFilePath,
)} not found.`,
);
}

async function processImageNode(
node: Image,
{filePath, staticDir}: PluginOptions,
{filePath, staticDirs}: PluginOptions,
) {
if (!node.url) {
throw new Error(
Expand All @@ -88,9 +104,11 @@ async function processImageNode(
// images without protocol
else if (path.isAbsolute(node.url)) {
// absolute paths are expected to exist in the static folder
const expectedImagePath = path.join(staticDir, node.url);
await ensureImageFileExist(expectedImagePath, filePath);
createJSX(node, posixPath(expectedImagePath));
const possibleImagePaths = staticDirs.map((dir) =>
path.join(dir, node.url),
);
const imagePath = await findImage(possibleImagePaths, filePath);
createJSX(node, posixPath(imagePath));
}
// We try to convert image urls without protocol to images with require calls
// going through webpack ensures that image assets exist at build time
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

[asset](asset.pdf)

[asset2](/asset2.pdf)

[staticAsset.pdf](/staticAsset.pdf)

[@site/static/staticAsset.pdf](@site/static/staticAsset.pdf)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`transformAsset plugin fail if asset url is absent 1`] = `"Markdown link URL is mandatory in \\"packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/fixtures/noUrl.md\\" file (title: asset, line: 1)."`;
exports[`transformAsset plugin fail if asset url is absent 1`] = `"Markdown link URL is mandatory in \\"packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/noUrl.md\\" file (title: asset, line: 1)."`;

exports[`transformAsset plugin pathname protocol 1`] = `
"[asset](pathname:///asset/unchecked.pdf)
Expand Down Expand Up @@ -30,6 +30,8 @@ exports[`transformAsset plugin transform md links to <a /> 1`] = `

<a target=\\"_blank\\" href={require('!file-loader?name=assets/files/[name]-[hash].[ext]!./asset.pdf').default}>asset</a>

<a target=\\"_blank\\" href={require('!file-loader?name=assets/files/[name]-[hash].[ext]!./static2/asset2.pdf').default}>asset2</a>

<a target=\\"_blank\\" href={require('!file-loader?name=assets/files/[name]-[hash].[ext]!./static/staticAsset.pdf').default}>staticAsset.pdf</a>

<a target=\\"_blank\\" href={require('!file-loader?name=assets/files/[name]-[hash].[ext]!./static/staticAsset.pdf').default}>@site/static/staticAsset.pdf</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,29 @@
* LICENSE file in the root directory of this source tree.
*/

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

const processFixture = async (name, options) => {
const path = join(__dirname, 'fixtures', `${name}.md`);
const staticDir = join(__dirname, 'fixtures', 'static');
const file = await vfile.read(path);
const processFixture = async (name: string, options?) => {
const filePath = path.join(__dirname, `__fixtures__/${name}.md`);
const staticDirs = [
path.join(__dirname, '__fixtures__/static'),
path.join(__dirname, '__fixtures__/static2'),
];
const file = await vfile.read(filePath);
const result = await remark()
.use(mdx)
.use(transformImage, {...options, filePath: path, staticDir})
.use(plugin, {...options, filePath: path, staticDir})
.use(transformImage, {...options, filePath, staticDirs})
.use(plugin, {
...options,
filePath,
staticDirs,
siteDir: path.join(__dirname, '__fixtures__'),
})
.process(file);

return result.toString();
Expand Down
35 changes: 17 additions & 18 deletions packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const hashRegex = /#.*$/;

interface PluginOptions {
filePath: string;
staticDir: string;
staticDirs: string[];
siteDir: string;
}

async function ensureAssetFileExist(
Expand Down Expand Up @@ -81,11 +82,10 @@ function toAssetRequireNode({
// If the link looks like an asset link, we'll link to the asset,
// and use a require("assetUrl") (using webpack url-loader/file-loader)
// instead of navigating to such link
async function convertToAssetLinkIfNeeded({
node,
staticDir,
filePath,
}: {node: Link} & PluginOptions) {
async function convertToAssetLinkIfNeeded(
node: Link,
{filePath, siteDir, staticDirs}: PluginOptions,
) {
const assetPath = node.url.replace(hashRegex, '');

const hasSiteAlias = assetPath.startsWith('@site/');
Expand All @@ -107,17 +107,20 @@ async function convertToAssetLinkIfNeeded({
}

if (assetPath.startsWith('@site/')) {
const siteDir = path.join(staticDir, '..');
const fileSystemAssetPath = path.join(
siteDir,
assetPath.replace('@site/', ''),
);
await ensureAssetFileExist(fileSystemAssetPath, filePath);
toAssetLinkNode(fileSystemAssetPath);
} else if (path.isAbsolute(assetPath)) {
const fileSystemAssetPath = path.join(staticDir, assetPath);
if (await fs.pathExists(fileSystemAssetPath)) {
toAssetLinkNode(fileSystemAssetPath);
// eslint-disable-next-line no-restricted-syntax
for (const staticDir of staticDirs) {
const fileSystemAssetPath = path.join(staticDir, assetPath);
if (await fs.pathExists(fileSystemAssetPath)) {
toAssetLinkNode(fileSystemAssetPath);
return;
}
}
} else {
const fileSystemAssetPath = path.join(path.dirname(filePath), assetPath);
Expand All @@ -127,19 +130,15 @@ async function convertToAssetLinkIfNeeded({
}
}

async function processLinkNode({
node,
filePath,
staticDir,
}: {node: Link} & PluginOptions) {
async function processLinkNode(node: Link, options: PluginOptions) {
if (!node.url) {
// try to improve error feedback
// see https://github.com/facebook/docusaurus/issues/3309#issuecomment-690371675
const title = node.title || (node.children[0] as Literal)?.value || '?';
const line = node?.position?.start?.line || '?';
throw new Error(
`Markdown link URL is mandatory in "${toMessageRelativeFilePath(
filePath,
options.filePath,
)}" file (title: ${title}, line: ${line}).`,
);
}
Expand All @@ -149,14 +148,14 @@ async function processLinkNode({
return;
}

await convertToAssetLinkIfNeeded({node, staticDir, filePath});
await convertToAssetLinkIfNeeded(node, options);
}

const plugin: Plugin<[PluginOptions]> = (options) => {
const transformer: Transformer = async (root) => {
const promises: Promise<void>[] = [];
visit(root, 'link', (node: Link) => {
promises.push(processLinkNode({node, ...options}));
promises.push(processLinkNode(node, options));
});
await Promise.all(promises);
};
Expand Down
Loading