Skip to content

Commit 707778e

Browse files
authored
feat: moduleType support in dev (#172)
1 parent 7016327 commit 707778e

File tree

15 files changed

+190
-18
lines changed

15 files changed

+190
-18
lines changed

packages/vite/src/node/__tests__/plugins/json.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ describe('transform', () => {
3737
) => {
3838
const plugin = jsonPlugin(opts, isBuild)
3939
// @ts-expect-error transform.handler should exist
40-
return plugin.transform.handler(input, 'test.json').code
40+
return plugin.transform.handler(input, 'test.json', { moduleType: 'json' })
41+
.code
4142
}
4243

4344
test("namedExports: true, stringify: 'auto' should not transformed an array input", () => {

packages/vite/src/node/__tests__/plugins/pluginFilter.spec.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import util from 'node:util'
22
import path from 'node:path'
33
import { describe, expect, test } from 'vitest'
4+
import type { ModuleTypeFilter } from 'rolldown'
45
import {
6+
type StringFilter,
57
createCodeFilter,
68
createFilterForTransform,
79
createIdFilter,
@@ -226,7 +228,22 @@ describe('createCodeFilter', () => {
226228
})
227229

228230
describe('createFilterForTransform', () => {
229-
const filters = [
231+
type Filters = {
232+
inputFilter: [
233+
idFilter: StringFilter | undefined,
234+
codeFilter: StringFilter | undefined,
235+
moduleTypeFilter?: ModuleTypeFilter | undefined,
236+
]
237+
cases:
238+
| {
239+
id: string
240+
code: string
241+
moduleType?: string
242+
expected: boolean
243+
}[]
244+
| undefined
245+
}[]
246+
const filters: Filters = [
230247
{ inputFilter: [undefined, undefined], cases: undefined },
231248
{
232249
inputFilter: ['*.js', undefined],
@@ -283,14 +300,29 @@ describe('createFilterForTransform', () => {
283300
{ id: 'a', code: 'a', expected: true },
284301
],
285302
},
303+
{
304+
inputFilter: [undefined, undefined, ['js']],
305+
cases: [
306+
{ id: 'foo.js', code: 'foo', moduleType: 'js', expected: true },
307+
{ id: 'foo.ts', code: 'foo', moduleType: 'ts', expected: false },
308+
],
309+
},
310+
{
311+
inputFilter: [undefined, undefined, { include: ['js'] }],
312+
cases: [
313+
{ id: 'foo.js', code: 'foo', moduleType: 'js', expected: true },
314+
{ id: 'foo.ts', code: 'foo', moduleType: 'ts', expected: false },
315+
],
316+
},
286317
]
287318

288319
for (const filter of filters) {
289320
test(`${util.inspect(filter.inputFilter)}`, () => {
290-
const [idFilter, codeFilter] = filter.inputFilter
321+
const [idFilter, codeFilter, moduleTypeFilter] = filter.inputFilter
291322
const filterForTransform = createFilterForTransform(
292323
idFilter,
293324
codeFilter,
325+
moduleTypeFilter,
294326
'',
295327
)
296328
if (!filter.cases) {
@@ -300,10 +332,11 @@ describe('createFilterForTransform', () => {
300332
expect(filterForTransform).not.toBeUndefined()
301333

302334
for (const testCase of filter.cases) {
303-
const { id, code, expected } = testCase
304-
expect(filterForTransform!(id, code), util.inspect({ id, code })).toBe(
305-
expected,
306-
)
335+
const { id, code, moduleType, expected } = testCase
336+
expect(
337+
filterForTransform!(id, code, moduleType ?? 'js'),
338+
util.inspect({ id, code, moduleType }),
339+
).toBe(expected)
307340
}
308341
})
309342
}

packages/vite/src/node/plugin.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
CustomPluginOptions,
33
LoadResult,
44
ModuleType,
5+
ModuleTypeFilter,
56
ObjectHook,
67
PluginContext,
78
ResolveIdResult,
@@ -151,7 +152,13 @@ export interface Plugin<A = any> extends RolldownPlugin<A> {
151152
ssr?: boolean
152153
},
153154
) => Promise<TransformResult> | TransformResult,
154-
{ filter?: { id?: StringFilter; code?: StringFilter } }
155+
{
156+
filter?: {
157+
id?: StringFilter
158+
code?: StringFilter
159+
moduleType?: ModuleTypeFilter
160+
}
161+
}
155162
>
156163
/**
157164
* Opt-in this plugin into the shared plugins pipeline.

packages/vite/src/node/plugins/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ export function getCachedFilterForPlugin<
303303
filters.transform = createFilterForTransform(
304304
rawFilters?.id,
305305
rawFilters?.code,
306+
rawFilters?.moduleType,
306307
)
307308
filter = filters.transform
308309
break

packages/vite/src/node/plugins/json.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export function jsonPlugin(
4747
transform: {
4848
filter: {
4949
id: { include: jsonExtRE, exclude: SPECIAL_QUERY_RE },
50+
// don't transform if the file is already transformed to a different format
51+
moduleType: ['json'],
5052
},
5153
handler(json, id) {
5254
// for backward compat this if statement is needed

packages/vite/src/node/plugins/pluginFilter.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import path from 'node:path'
22
import picomatch from 'picomatch'
3+
import type { ModuleTypeFilter } from 'rolldown'
34
import { arraify } from '../utils'
45
import { slash } from '../../shared/utils'
56

67
export type PluginFilter = (input: string) => boolean
7-
export type TransformHookFilter = (id: string, code: string) => boolean
8+
export type TransformHookFilter = (
9+
id: string,
10+
code: string,
11+
moduleType: string,
12+
) => boolean
813

914
export type StringFilter<Value = string | RegExp> =
1015
| Value
@@ -117,16 +122,30 @@ export function createCodeFilter(
117122
return createFilter(excludeFilter, includeFilter)
118123
}
119124

125+
function createModuleTypeFilter(
126+
filter: ModuleTypeFilter | undefined,
127+
): PluginFilter | undefined {
128+
if (!filter) return
129+
const include = Array.isArray(filter) ? filter : (filter.include ?? [])
130+
return (moduleType: string) => include.includes(moduleType)
131+
}
132+
120133
export function createFilterForTransform(
121134
idFilter: StringFilter | undefined,
122135
codeFilter: StringFilter | undefined,
136+
moduleTypeFilter: ModuleTypeFilter | undefined,
123137
cwd?: string,
124138
): TransformHookFilter | undefined {
125-
if (!idFilter && !codeFilter) return
139+
if (!idFilter && !codeFilter && !moduleTypeFilter) return
126140
const idFilterFn = createIdFilter(idFilter, cwd)
127141
const codeFilterFn = createCodeFilter(codeFilter)
128-
return (id, code) => {
129-
let fallback = true
142+
const moduleTypeFilterFn = createModuleTypeFilter(moduleTypeFilter)
143+
return (id, code, moduleType) => {
144+
let fallback = moduleTypeFilterFn?.(moduleType) ?? true
145+
if (!fallback) {
146+
return false
147+
}
148+
130149
if (idFilterFn) {
131150
fallback &&= idFilterFn(id)
132151
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { getModuleTypeFromId } from '../transformRequest'
3+
4+
describe('getModuleTypeFromId', () => {
5+
const testCases = [
6+
{ id: 'foo.js', expected: 'js' },
7+
{ id: 'foo.ts', expected: 'ts' },
8+
{ id: 'foo.a.js', expected: 'js' },
9+
{ id: '', expected: undefined },
10+
]
11+
12+
for (const { id, expected } of testCases) {
13+
test(`should return ${expected} for id: ${id}`, () => {
14+
const result = getModuleTypeFromId(id)
15+
expect(result).toBe(expected)
16+
})
17+
}
18+
})

packages/vite/src/node/server/pluginContainer.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import type {
4343
LoadResult,
4444
ModuleInfo,
4545
ModuleOptions,
46+
ModuleType,
4647
NormalizedInputOptions,
4748
OutputOptions,
4849
ParallelPluginHooks,
@@ -473,11 +474,16 @@ class EnvironmentPluginContainer {
473474
id: string,
474475
options?: {
475476
inMap?: SourceDescription['map']
477+
moduleType?: string
476478
},
477-
): Promise<{ code: string; map: SourceMap | { mappings: '' } | null }> {
479+
): Promise<{
480+
code: string
481+
map: SourceMap | { mappings: '' } | null
482+
moduleType?: ModuleType
483+
}> {
478484
const ssr = this.environment.config.consumer === 'server'
479485
const optionsWithSSR = options
480-
? { ...options, ssr, moduleType: 'js' }
486+
? { ...options, ssr, moduleType: options.moduleType ?? 'js' }
481487
: { ssr, moduleType: 'js' }
482488
const inMap = options?.inMap
483489

@@ -489,7 +495,7 @@ class EnvironmentPluginContainer {
489495
throwClosedServerError()
490496

491497
const filter = getCachedFilterForPlugin(plugin, 'transform')
492-
if (filter && !filter(id, code)) continue
498+
if (filter && !filter(id, code, optionsWithSSR.moduleType)) continue
493499

494500
ctx._updateActiveInfo(plugin, id, code)
495501
const start = debugPluginTransform ? performance.now() : 0
@@ -519,6 +525,9 @@ class EnvironmentPluginContainer {
519525
ctx.sourcemapChain.push(result.map)
520526
}
521527
}
528+
if (result.moduleType !== undefined) {
529+
optionsWithSSR.moduleType = result.moduleType
530+
}
522531
ctx._updateModuleInfo(id, result)
523532
} else {
524533
code = result
@@ -527,6 +536,7 @@ class EnvironmentPluginContainer {
527536
return {
528537
code,
529538
map: ctx._getCombinedSourcemap(),
539+
moduleType: optionsWithSSR.moduleType,
530540
}
531541
}
532542

packages/vite/src/node/server/transformRequest.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import { performance } from 'node:perf_hooks'
44
import getEtag from 'etag'
55
import MagicString from 'magic-string'
66
import { init, parse as parseImports } from 'es-module-lexer'
7-
import type { PartialResolvedId, SourceDescription, SourceMap } from 'rolldown'
7+
import type {
8+
ModuleType,
9+
PartialResolvedId,
10+
SourceDescription,
11+
SourceMap,
12+
} from 'rolldown'
813
import colors from 'picocolors'
914
import type { EnvironmentModuleNode } from '../server/moduleGraph'
1015
import {
@@ -261,6 +266,7 @@ async function loadAndTransform(
261266

262267
let code: string | null = null
263268
let map: SourceDescription['map'] = null
269+
let moduleType: ModuleType | undefined
264270

265271
// load
266272
const loadStart = debugLoad ? performance.now() : 0
@@ -314,12 +320,18 @@ async function loadAndTransform(
314320
timestamp: true,
315321
})
316322
}
323+
324+
const guessedModuleType = getModuleTypeFromId(id)
325+
if (guessedModuleType && guessedModuleType !== 'js') {
326+
moduleType = guessedModuleType
327+
}
317328
}
318329
} else {
319330
debugLoad?.(`${timeFrom(loadStart)} [plugin] ${prettyUrl}`)
320331
if (isObject(loadResult)) {
321332
code = loadResult.code
322333
map = loadResult.map
334+
moduleType = loadResult.moduleType
323335
} else {
324336
code = loadResult
325337
}
@@ -356,6 +368,7 @@ async function loadAndTransform(
356368
const transformStart = debugTransform ? performance.now() : 0
357369
const transformResult = await pluginContainer.transform(code, id, {
358370
inMap: map,
371+
moduleType,
359372
})
360373
const originalCode = code
361374
if (transformResult.code === originalCode) {
@@ -534,3 +547,30 @@ async function handleModuleSoftInvalidation(
534547

535548
return result
536549
}
550+
551+
// https://github.com/rolldown/rolldown/blob/cc66f4b7189dfb3a248608d02f5962edb09b11f8/crates/rolldown/src/utils/normalize_options.rs#L95-L111
552+
const defaultModuleTypes: Record<string, ModuleType | undefined> = {
553+
js: 'js',
554+
mjs: 'js',
555+
cjs: 'js',
556+
jsx: 'jsx',
557+
ts: 'ts',
558+
mts: 'ts',
559+
cts: 'ts',
560+
tsx: 'tsx',
561+
json: 'json',
562+
txt: 'text',
563+
css: 'css',
564+
}
565+
566+
// https://github.com/rolldown/rolldown/blob/bf53a100edf1780d5a5aa41f0bc0459c5696543e/crates/rolldown/src/utils/load_source.rs#L53-L89
567+
export function getModuleTypeFromId(id: string): ModuleType | undefined {
568+
let pos = -1
569+
while ((pos = id.indexOf('.', pos + 1)) >= 0) {
570+
const ext = id.slice(pos + 1)
571+
const moduleType = defaultModuleTypes[ext]
572+
if (moduleType) {
573+
return moduleType
574+
}
575+
}
576+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
import { expect, test } from 'vitest'
12
import { tests } from './tests'
3+
import { page } from '~utils'
4+
5+
test('module type should be supported', async () => {
6+
expect(await page.textContent('#module-type-json-pre')).toBe('json')
7+
expect(await page.textContent('#module-type-json-post')).toBe('js')
8+
})
29

310
tests()

0 commit comments

Comments
 (0)