Skip to content

Commit

Permalink
feat: re-implement dynamicImportVars
Browse files Browse the repository at this point in the history
  • Loading branch information
poyoho committed Apr 15, 2022
1 parent 9e86745 commit 25d64fb
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 38 deletions.
8 changes: 8 additions & 0 deletions packages/playground/dynamic-import/nested/index.js
Expand Up @@ -73,3 +73,11 @@ document.querySelector('.css').addEventListener('click', async () => {
function text(el, text) {
document.querySelector(el).textContent = text
}

const variable = 'mxd'
const mod = import(`../${variable}.js?raw`)
console.log(mod)

const base = ''
const glob = import.meta.glob(`./*.js`, { as: 'raw' })
console.log(glob)
4 changes: 2 additions & 2 deletions packages/vite/src/node/build.ts
Expand Up @@ -26,7 +26,7 @@ import { copyDir, emptyDir, lookupFile, normalizePath } from './utils'
import { manifestPlugin } from './plugins/manifest'
import commonjsPlugin from '@rollup/plugin-commonjs'
import type { RollupCommonJSOptions } from 'types/commonjs'
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars'
import { dynamicImportVars } from './plugins/dynamicImportVars'
import type { RollupDynamicImportVarsOptions } from 'types/dynamicImportVars'
import type { Logger } from './logger'
import type { TransformOptions } from 'esbuild'
Expand Down Expand Up @@ -313,7 +313,7 @@ export function resolveBuildPlugins(config: ResolvedConfig): {
watchPackageDataPlugin(config),
commonjsPlugin(options.commonjsOptions),
dataURIPlugin(),
dynamicImportVars(options.dynamicImportVarsOptions),
dynamicImportVars(config),
assetImportMetaUrlPlugin(config),
...(options.rollupOptions.plugins
? (options.rollupOptions.plugins.filter(Boolean) as Plugin[])
Expand Down
179 changes: 146 additions & 33 deletions packages/vite/src/node/importGlob.ts
Expand Up @@ -13,11 +13,13 @@ import {
singlelineCommentsRE,
multilineCommentsRE,
blankReplacer,
normalizePath
normalizePath,
parseRequest
} from './utils'
import type { RollupError } from 'rollup'
import type { RollupError, TransformPluginContext } from 'rollup'
import type { Logger } from '.'
import colors from 'picocolors'
import { dynamicImportToGlob } from '@rollup/plugin-dynamic-import-vars'

interface GlobParams {
base: string
Expand All @@ -36,6 +38,10 @@ interface GlobOptions {
}
}

interface DynamicImportRequest {
raw?: boolean
}

function formatGlobRelativePattern(base: string, pattern: string): GlobParams {
let parentDepth = 0
while (pattern.startsWith('../')) {
Expand All @@ -50,13 +56,15 @@ function formatGlobRelativePattern(base: string, pattern: string): GlobParams {
return { base, pattern, parentDepth, isAbsolute: false }
}

export async function transformImportGlob(
export async function transformGlob(
source: string,
pos: number,
importer: string,
importIndex: number,
importEndIndex: number,
query: DynamicImportRequest,
userPattern: string,
root: string,
logger: Logger,
normalizeUrl?: (url: string, pos: number) => Promise<[string, string]>,
resolve?: (url: string, importer?: string) => Promise<string | undefined>,
preload = true
Expand All @@ -69,21 +77,9 @@ export async function transformImportGlob(
pattern: string
base: string
}> {
const isEager = source.slice(pos, pos + 21) === 'import.meta.globEager'
const isEagerDefault =
isEager && source.slice(pos + 21, pos + 28) === 'Default'

const err = (msg: string) => {
const e = new Error(`Invalid glob import syntax: ${msg}`)
;(e as any).pos = pos
return e
}

importer = cleanUrl(importer)
const importerBasename = path.basename(importer)

const [userPattern, options, endIndex] = lexGlobPattern(source, pos)

let globParams: GlobParams | null = null
if (userPattern.startsWith('/')) {
globParams = {
Expand All @@ -106,9 +102,7 @@ export async function transformImportGlob(
}

if (!globParams) {
throw err(
`pattern must start with "." or "/" (relative to project root) or alias path`
)
throw `pattern must start with "." or "/" (relative to project root) or alias path`
}
const { base, parentDepth, isAbsolute, pattern } = globParams

Expand All @@ -117,6 +111,8 @@ export async function transformImportGlob(
// Ignore node_modules by default unless explicitly indicated in the pattern
ignore: /(^|\/)node_modules\//.test(pattern) ? [] : ['**/node_modules/**']
})

const isEager = source.slice(pos, pos + 21) === 'import.meta.globEager'
const imports: string[] = []
let importsString = ``
let entries = ``
Expand All @@ -133,23 +129,13 @@ export async function transformImportGlob(
;[importee] = await normalizeUrl(file, pos)
}
imports.push(importee)
// TODO remove assert syntax for the Vite 3.0 release.
const isRawAssert = options?.assert?.type === 'raw'
const isRawType = options?.as === 'raw'
if (isRawType || isRawAssert) {
if (isRawAssert) {
logger.warn(
colors.yellow(
colors.bold(
"(!) import.meta.glob('...', { assert: { type: 'raw' }}) is deprecated. Use import.meta.glob('...', { as: 'raw' }) instead."
)
)
)
}
if (query.raw) {
entries += ` ${JSON.stringify(file)}: ${JSON.stringify(
await fsp.readFile(path.join(base, files[i]), 'utf-8')
)},`
} else {
const isEagerDefault =
isEager && source.slice(pos + 21, pos + 28) === 'Default'
const importeeUrl = isCSSRequest(importee) ? `${importee}?used` : importee
if (isEager) {
const identifier = `__glob_${importIndex}_${i}`
Expand All @@ -175,13 +161,140 @@ export async function transformImportGlob(
imports,
importsString,
exp: `{${entries}}`,
endIndex,
endIndex: importEndIndex,
isEager,
pattern,
base
}
}

export async function transformImportGlob(
source: string,
pos: number,
importer: string,
importIndex: number,
root: string,
logger: Logger,
normalizeUrl?: (url: string, pos: number) => Promise<[string, string]>,
resolve?: (url: string, importer?: string) => Promise<string | undefined>,
preload = true
): Promise<{
importsString: string
imports: string[]
exp: string
endIndex: number
isEager: boolean
pattern: string
base: string
}> {
const err = (msg: string) => {
const e = new Error(`Invalid glob import syntax: ${msg}`)
;(e as any).pos = pos
return e
}
const [userPattern, options, endIndex] = lexGlobPattern(source, pos)
const query: DynamicImportRequest = {}

// TODO remove assert syntax for the Vite 3.0 release.
const isRawAssert = options?.assert?.type === 'raw'
const isRawType = options?.as === 'raw'
if (isRawType || isRawAssert) {
if (isRawAssert) {
logger.warn(
colors.yellow(
colors.bold(
"(!) import.meta.glob('...', { assert: { type: 'raw' }}) is deprecated. Use import.meta.glob('...', { as: 'raw' }) instead."
)
)
)
}
query.raw = true
}
try {
return transformGlob(
source,
pos,
importer,
importIndex,
endIndex,
query,
userPattern,
root,
normalizeUrl,
resolve,
preload
)
} catch (error) {
throw err(error)
}
}

export async function transformDynamicImportGlob(
ctx: TransformPluginContext,
source: string,
expStart: number,
expEnd: number,
importer: string,
start: number,
end: number,
root: string,
normalizeUrl?: (url: string, pos: number) => Promise<[string, string]>,
resolve?: (url: string, importer?: string) => Promise<string | undefined>,
preload = false
): Promise<{
importsString: string
imports: string[]
exp: string
endIndex: number
isEager: boolean
pattern: string
rawPattern: string
base: string
} | null> {
const err = (msg: string) => {
const e = new Error(`Invalid dynamic import syntax: ${msg}`)
;(e as any).pos = start
return e
}
const original = source.slice(expStart, expEnd)
const filename = source.slice(start + 1, end - 1)
const [rawPattern, _] = filename.split('?')
const rawQuery = parseRequest(filename)
const query: DynamicImportRequest = {}
const ast = (ctx.parse(original) as any).body[0].expression

const userPattern = dynamicImportToGlob(ast.source, rawPattern)

if (!userPattern) {
return null
}

if (rawQuery?.raw) {
query.raw = true
}

try {
return {
rawPattern,
...(await transformGlob(
source,
start,
importer,
expStart,
expEnd,
query,
userPattern,
root,
normalizeUrl,
resolve,
preload
))
}
} catch (error) {
throw err(error)
}
}

const enum LexerState {
inCall,
inSingleQuoteString,
Expand Down
113 changes: 113 additions & 0 deletions packages/vite/src/node/plugins/dynamicImportVars.ts
@@ -0,0 +1,113 @@
import MagicString from 'magic-string'
import { init, parse as parseImports } from 'es-module-lexer'
import type { ImportSpecifier } from 'es-module-lexer'
import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
import { transformDynamicImportGlob } from '../importGlob'
import { createFilter } from '@rollup/pluginutils'

export function dynamicImportVars(config: ResolvedConfig): Plugin {
const resolve = config.createResolver({
preferRelative: true,
tryIndex: false,
extensions: []
})
const { include, exclude, warnOnError } =
config.build.dynamicImportVarsOptions
const filter = createFilter(include, exclude)

return {
name: 'vite:dynamic-import-vars',

async transform(source, importer) {
if (
!filter(importer) ||
(importer.includes('node_modules') &&
!source.includes('import.meta.glob'))
) {
return
}

await init

let imports: readonly ImportSpecifier[] = []
try {
imports = parseImports(source)[0]
} catch (e: any) {
this.error(e, e.idx)
}

if (!imports.length) {
return null
}

let s: MagicString | undefined
let importIndex = 0
for (let index = 0; index < imports.length; index++) {
const {
s: start,
e: end,
ss: expStart,
se: expEnd,
d: dynamicIndex
} = imports[index]

if (dynamicIndex === -1 || source[start] !== '`') {
continue
}

s ||= new MagicString(source)
let result
try {
result = await transformDynamicImportGlob(
this,
source,
expStart,
expEnd,
importer,
start,
end,
config.root,
undefined,
resolve
)
} catch (error) {
if (warnOnError) {
this.warn(error)
} else {
this.error(error)
}
}

if (!result) {
continue
}

const { rawPattern, exp } = result

s.prepend(`function __variableDynamicImportRuntime_${importIndex}_(path) {
const glob = ${exp}
return glob[path] ?? new Promise((resolve, reject) => {
(typeof queueMicrotask === 'function' ? queueMicrotask : setTimeout)(
reject.bind(null, new Error("Unknown variable dynamic import: " + path))
);
})
}\n`)
s.overwrite(
expStart,
expEnd,
`__variableDynamicImportRuntime_${importIndex}_(\`${rawPattern}\`)`
)
importIndex++
}

if (s) {
return {
code: s.toString(),
map: config.build.sourcemap ? s.generateMap({ hires: true }) : null
}
}
}
}
}

0 comments on commit 25d64fb

Please sign in to comment.