Skip to content

Commit f882bc1

Browse files
authored
Refactor content collection transforms (#6817)
* feat: json collection POC * wip: add test json file * refactor: rework content flag transforms * refactor: simplify propagatedAsset check * chore: remove JSON playground code * chore: respect build sourcemap option * deps: magic-string, source-map * chore: formatting * fix: add sourcemaps to MDX plugin * chore: changeset * deps: remove magic-string from mdx * chore: remove unecessary MagicString
1 parent b1d07bc commit f882bc1

File tree

8 files changed

+115
-67
lines changed

8 files changed

+115
-67
lines changed

.changeset/dirty-panthers-approve.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'astro': patch
3+
'@astrojs/mdx': patch
4+
---
5+
6+
Fix sourcemap warnings when using Content Collections and MDX with the `vite.build.sourcemap` option

packages/astro/src/content/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export { contentObservable, getContentPaths, getDotAstroTypeReference } from './
55
export { astroContentAssetPropagationPlugin } from './vite-plugin-content-assets.js';
66
export { astroContentImportPlugin } from './vite-plugin-content-imports.js';
77
export { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js';
8+
export { CONTENT_FLAG, PROPAGATED_ASSET_FLAG } from './consts.js';

packages/astro/src/content/vite-plugin-content-assets.ts

+41-39
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,9 @@ import {
1818
} from './consts.js';
1919
import { getContentEntryExts } from './utils.js';
2020

21-
function isPropagatedAsset(viteId: string, contentEntryExts: string[]): boolean {
22-
const url = new URL(viteId, 'file://');
23-
return (
24-
url.searchParams.has(PROPAGATED_ASSET_FLAG) &&
25-
contentEntryExts.some((ext) => url.pathname.endsWith(ext))
26-
);
21+
function isPropagatedAsset(viteId: string) {
22+
const flags = new URLSearchParams(viteId.split('?')[1]);
23+
return flags.has(PROPAGATED_ASSET_FLAG);
2724
}
2825

2926
export function astroContentAssetPropagationPlugin({
@@ -37,51 +34,56 @@ export function astroContentAssetPropagationPlugin({
3734
const contentEntryExts = getContentEntryExts(settings);
3835
return {
3936
name: 'astro:content-asset-propagation',
40-
enforce: 'pre',
4137
configureServer(server) {
4238
if (mode === 'dev') {
4339
devModuleLoader = createViteLoader(server);
4440
}
4541
},
46-
load(id) {
47-
if (isPropagatedAsset(id, contentEntryExts)) {
42+
async transform(_, id, options) {
43+
if (isPropagatedAsset(id)) {
4844
const basePath = id.split('?')[0];
45+
let stringifiedLinks: string, stringifiedStyles: string, stringifiedScripts: string;
46+
47+
// We can access the server in dev,
48+
// so resolve collected styles and scripts here.
49+
if (options?.ssr && devModuleLoader) {
50+
if (!devModuleLoader.getModuleById(basePath)?.ssrModule) {
51+
await devModuleLoader.import(basePath);
52+
}
53+
const { stylesMap, urls } = await getStylesForURL(
54+
pathToFileURL(basePath),
55+
devModuleLoader,
56+
'development'
57+
);
58+
59+
const hoistedScripts = await getScriptsForURL(
60+
pathToFileURL(basePath),
61+
settings.config.root,
62+
devModuleLoader
63+
);
64+
65+
stringifiedLinks = JSON.stringify([...urls]);
66+
stringifiedStyles = JSON.stringify([...stylesMap.values()]);
67+
stringifiedScripts = JSON.stringify([...hoistedScripts]);
68+
} else {
69+
// Otherwise, use placeholders to inject styles and scripts
70+
// during the production bundle step.
71+
// @see the `astro:content-build-plugin` below.
72+
stringifiedLinks = JSON.stringify(LINKS_PLACEHOLDER);
73+
stringifiedStyles = JSON.stringify(STYLES_PLACEHOLDER);
74+
stringifiedScripts = JSON.stringify(SCRIPTS_PLACEHOLDER);
75+
}
76+
4977
const code = `
5078
export async function getMod() {
5179
return import(${JSON.stringify(basePath)});
5280
}
53-
export const collectedLinks = ${JSON.stringify(LINKS_PLACEHOLDER)};
54-
export const collectedStyles = ${JSON.stringify(STYLES_PLACEHOLDER)};
55-
export const collectedScripts = ${JSON.stringify(SCRIPTS_PLACEHOLDER)};
81+
export const collectedLinks = ${stringifiedLinks};
82+
export const collectedStyles = ${stringifiedStyles};
83+
export const collectedScripts = ${stringifiedScripts};
5684
`;
57-
return { code };
58-
}
59-
},
60-
async transform(code, id, options) {
61-
if (!options?.ssr) return;
62-
if (devModuleLoader && isPropagatedAsset(id, contentEntryExts)) {
63-
const basePath = id.split('?')[0];
64-
if (!devModuleLoader.getModuleById(basePath)?.ssrModule) {
65-
await devModuleLoader.import(basePath);
66-
}
67-
const { stylesMap, urls } = await getStylesForURL(
68-
pathToFileURL(basePath),
69-
devModuleLoader,
70-
'development'
71-
);
7285

73-
const hoistedScripts = await getScriptsForURL(
74-
pathToFileURL(basePath),
75-
settings.config.root,
76-
devModuleLoader
77-
);
78-
79-
return {
80-
code: code
81-
.replace(JSON.stringify(LINKS_PLACEHOLDER), JSON.stringify([...urls]))
82-
.replace(JSON.stringify(STYLES_PLACEHOLDER), JSON.stringify([...stylesMap.values()]))
83-
.replace(JSON.stringify(SCRIPTS_PLACEHOLDER), JSON.stringify([...hoistedScripts])),
84-
};
86+
return { code, map: { mappings: '' } };
8587
}
8688
},
8789
};

packages/astro/src/content/vite-plugin-content-imports.ts

+18-25
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import {
2121
type ContentConfig,
2222
} from './utils.js';
2323

24-
function isContentFlagImport(viteId: string, contentEntryExts: string[]) {
25-
const { searchParams, pathname } = new URL(viteId, 'file://');
26-
return searchParams.has(CONTENT_FLAG) && contentEntryExts.some((ext) => pathname.endsWith(ext));
24+
function isContentFlagImport(viteId: string) {
25+
const flags = new URLSearchParams(viteId.split('?')[1]);
26+
return flags.has(CONTENT_FLAG);
2727
}
2828

2929
function getContentRendererByViteId(
@@ -65,26 +65,26 @@ export function astroContentImportPlugin({
6565
const plugins: Plugin[] = [
6666
{
6767
name: 'astro:content-imports',
68-
async load(viteId) {
69-
if (isContentFlagImport(viteId, contentEntryExts)) {
70-
const { fileId } = getFileInfo(viteId, settings.config);
68+
async transform(_, viteId) {
69+
if (isContentFlagImport(viteId)) {
70+
const fileId = viteId.split('?')[0];
7171
const { id, slug, collection, body, data, _internal } = await setContentEntryModuleCache({
7272
fileId,
7373
pluginContext: this,
7474
});
7575

7676
const code = escapeViteEnvReferences(`
77-
export const id = ${JSON.stringify(id)};
78-
export const collection = ${JSON.stringify(collection)};
79-
export const slug = ${JSON.stringify(slug)};
80-
export const body = ${JSON.stringify(body)};
81-
export const data = ${devalue.uneval(data) /* TODO: reuse astro props serializer */};
82-
export const _internal = {
83-
filePath: ${JSON.stringify(_internal.filePath)},
84-
rawData: ${JSON.stringify(_internal.rawData)},
85-
};
86-
`);
87-
return { code };
77+
export const id = ${JSON.stringify(id)};
78+
export const collection = ${JSON.stringify(collection)};
79+
export const slug = ${JSON.stringify(slug)};
80+
export const body = ${JSON.stringify(body)};
81+
export const data = ${devalue.uneval(data) /* TODO: reuse astro props serializer */};
82+
export const _internal = {
83+
filePath: ${JSON.stringify(_internal.filePath)},
84+
rawData: ${JSON.stringify(_internal.rawData)},
85+
};`);
86+
87+
return { code, map: { mappings: '' } };
8888
}
8989
},
9090
configureServer(viteServer) {
@@ -96,7 +96,7 @@ export const _internal = {
9696
// Content modules depend on config, so we need to invalidate them.
9797
for (const modUrl of viteServer.moduleGraph.urlToModuleMap.keys()) {
9898
if (
99-
isContentFlagImport(modUrl, contentEntryExts) ||
99+
isContentFlagImport(modUrl) ||
100100
Boolean(getContentRendererByViteId(modUrl, settings))
101101
) {
102102
const mod = await viteServer.moduleGraph.getModuleByUrl(modUrl);
@@ -108,13 +108,6 @@ export const _internal = {
108108
}
109109
});
110110
},
111-
async transform(code, id) {
112-
if (isContentFlagImport(id, contentEntryExts)) {
113-
// Escape before Rollup internal transform.
114-
// Base on MUCH trial-and-error, inspired by MDX integration 2-step transform.
115-
return { code: escapeViteEnvReferences(code) };
116-
}
117-
},
118111
},
119112
];
120113

packages/astro/src/vite-plugin-jsx/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { error } from '../core/logger/core.js';
1616
import { removeQueryString } from '../core/path.js';
1717
import { detectImportSource } from './import-source.js';
1818
import tagExportsPlugin from './tag.js';
19+
import { CONTENT_FLAG, PROPAGATED_ASSET_FLAG } from '../content/index.js';
1920

2021
const JSX_EXTENSIONS = new Set(['.jsx', '.tsx', '.mdx']);
2122
const IMPORT_STATEMENTS: Record<string, string> = {
@@ -104,6 +105,11 @@ interface AstroPluginJSXOptions {
104105
logging: LogOptions;
105106
}
106107

108+
// Format inspired by https://github.com/vitejs/vite/blob/main/packages/vite/src/node/constants.ts#L54
109+
const SPECIAL_QUERY_REGEX = new RegExp(
110+
`[?&](?:worker|sharedworker|raw|url|${CONTENT_FLAG}|${PROPAGATED_ASSET_FLAG})\\b`
111+
);
112+
107113
/** Use Astro config to allow for alternate or multiple JSX renderers (by default Vite will assume React) */
108114
export default function jsx({ settings, logging }: AstroPluginJSXOptions): Plugin {
109115
let viteConfig: ResolvedConfig;
@@ -133,6 +139,9 @@ export default function jsx({ settings, logging }: AstroPluginJSXOptions): Plugi
133139
},
134140
async transform(code, id, opts) {
135141
const ssr = Boolean(opts?.ssr);
142+
if (SPECIAL_QUERY_REGEX.test(id)) {
143+
return null;
144+
}
136145
id = removeQueryString(id);
137146
if (!JSX_EXTENSIONS.has(path.extname(id))) {
138147
return null;

packages/integrations/mdx/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"remark-gfm": "^3.0.1",
4646
"remark-smartypants": "^2.0.0",
4747
"shiki": "^0.11.1",
48+
"source-map": "^0.7.4",
4849
"unist-util-visit": "^4.1.0",
4950
"vfile": "^5.3.2"
5051
},

packages/integrations/mdx/src/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { VFile } from 'vfile';
1212
import type { Plugin as VitePlugin } from 'vite';
1313
import { getRehypePlugins, getRemarkPlugins, recmaInjectImportMetaEnvPlugin } from './plugins.js';
1414
import { getFileInfo, ignoreStringPlugins, parseFrontmatter } from './utils.js';
15+
import { SourceMapGenerator } from 'source-map';
1516

1617
export type MdxOptions = Omit<typeof markdownConfigDefaults, 'remarkPlugins' | 'rehypePlugins'> & {
1718
extendMarkdownConfig: boolean;
@@ -113,6 +114,9 @@ export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroI
113114
...(mdxPluginOpts.recmaPlugins ?? []),
114115
() => recmaInjectImportMetaEnvPlugin({ importMetaEnv }),
115116
],
117+
SourceMapGenerator: config.vite.build?.sourcemap
118+
? SourceMapGenerator
119+
: undefined,
116120
});
117121

118122
return {
@@ -168,7 +172,7 @@ export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroI
168172
import.meta.hot.decline();
169173
}`;
170174
}
171-
return escapeViteEnvReferences(code);
175+
return { code: escapeViteEnvReferences(code), map: null };
172176
},
173177
},
174178
] as VitePlugin[],

pnpm-lock.yaml

+34-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)