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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rework dynamic-import-vars #7756

Merged
merged 36 commits into from May 10, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9bbf3dd
feat: re-implement dynamicImportVars
poyoho Apr 15, 2022
cc59a3b
fix: types
poyoho Apr 15, 2022
6d2ec2c
fix: ?raw
poyoho Apr 19, 2022
a58c98d
fix: dynamic import runtime helper
poyoho Apr 19, 2022
5cbc0a9
test: error
poyoho Apr 19, 2022
9228c48
fix: type error
poyoho Apr 19, 2022
09cc654
test: resolve path TODO
poyoho Apr 20, 2022
63b9bd4
chore: rebase
poyoho Apr 20, 2022
b505f42
feat: support dynamic import path alias
poyoho Apr 20, 2022
1bf029e
feat: virtual module for dynamicImportHelper
poyoho Apr 21, 2022
fc37bdc
fix: normal path
poyoho Apr 21, 2022
0d556c6
feat: test
poyoho Apr 21, 2022
fe061ee
chore: rebase
poyoho May 6, 2022
88a2e7f
fix: types
poyoho May 6, 2022
27a230c
chore: rebase
poyoho May 9, 2022
a0e999c
fix: bugs
poyoho May 9, 2022
9c42293
feat: hmr
poyoho May 9, 2022
4092cf1
feat: hmr
poyoho May 9, 2022
eab35a0
feat: snapshot
poyoho May 9, 2022
511e827
chore: merge
antfu May 9, 2022
d37615e
chore: update
antfu May 9, 2022
a2250ec
fix: simplify the implementation
antfu May 9, 2022
a998435
chore: update snap
antfu May 9, 2022
d4204ec
chore: try fix windows
antfu May 9, 2022
ef9041f
chore: fix windows
antfu May 9, 2022
79c1909
fix(dynamic-import-vars): simplify the implementation
poyoho May 9, 2022
7e4c42a
fix: window
poyoho May 9, 2022
07c2f8f
chore: rebase
poyoho May 9, 2022
5d11b74
fix: window
poyoho May 9, 2022
b987e85
chore: safe
poyoho May 9, 2022
e715d41
fix: edge case
poyoho May 10, 2022
e88ec20
fix: sourcemap generate logic
poyoho May 10, 2022
1be3693
chore: nice judge
poyoho May 10, 2022
de3582f
fix: parseUrl
poyoho May 10, 2022
c8f62f8
chore: useless
poyoho May 10, 2022
837624e
chore: format
poyoho May 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -60,6 +60,30 @@ test('should load dynamic import with css', async () => {
)
})

test('should load dynamic import with vars', async () => {
await untilUpdated(
() => page.textContent('.dynamic-import-with-vars'),
'hello',
true
)
})

test('should load dynamic import with vars alias', async () => {
await untilUpdated(
() => page.textContent('.dynamic-import-with-vars-alias'),
'hello',
true
)
})

test('should load dynamic import with vars raw', async () => {
await untilUpdated(
() => page.textContent('.dynamic-import-with-vars-raw'),
'export function hello()',
true
)
})

test('should load dynamic import with css in package', async () => {
await page.click('.pkg-css')
await untilUpdated(() => getColor('.pkg-css'), 'blue', true)
Expand Down
3 changes: 3 additions & 0 deletions packages/playground/dynamic-import/alias/hello.js
@@ -0,0 +1,3 @@
export function hello() {
return 'hello'
}
3 changes: 3 additions & 0 deletions packages/playground/dynamic-import/alias/hi.js
@@ -0,0 +1,3 @@
export function hi() {
return 'hi'
}
14 changes: 14 additions & 0 deletions packages/playground/dynamic-import/index.html
Expand Up @@ -10,6 +10,20 @@
<button class="css">css</button>
<button class="pkg-css">pkg-css</button>

<p>dynamic-import-with-vars</p>
<div class="dynamic-import-with-vars">todo</div>

<p>dynamic-import-with-vars-alias</p>
<div class="dynamic-import-with-vars-alias">todo</div>

<p>dynamic-import-with-vars-raw</p>
<div class="dynamic-import-with-vars-raw">todo</div>

<div class="view"></div>

<script type="module" src="./nested/index.js"></script>
<style>
p {
color: #0088ff;
}
</style>
3 changes: 3 additions & 0 deletions packages/playground/dynamic-import/nested/hello.js
@@ -0,0 +1,3 @@
export function hello() {
return 'hello'
}
14 changes: 14 additions & 0 deletions packages/playground/dynamic-import/nested/index.js
Expand Up @@ -78,3 +78,17 @@ document.querySelector('.pkg-css').addEventListener('click', async () => {
function text(el, text) {
document.querySelector(el).textContent = text
}

const base = 'hello'

import(`../alias/${base}.js`).then((mod) => {
text('.dynamic-import-with-vars', mod.hello())
})

import(`@/${base}.js`).then((mod) => {
text('.dynamic-import-with-vars-alias', mod.hello())
})

import(`../alias/${base}.js?raw`).then((mod) => {
text('.dynamic-import-with-vars-raw', JSON.stringify(mod))
})
12 changes: 9 additions & 3 deletions packages/playground/dynamic-import/vite.config.js
@@ -1,7 +1,8 @@
const fs = require('fs')
const path = require('path')
const vite = require('vite')

module.exports = {
module.exports = vite.defineConfig({
plugins: [
{
name: 'copy',
Expand All @@ -20,5 +21,10 @@ module.exports = {
)
}
}
]
}
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'alias')
}
}
})
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 { dynamicImportVarsPlugin } from './plugins/dynamicImportVars'
import type { RollupDynamicImportVarsOptions } from 'types/dynamicImportVars'
import type { Logger } from './logger'
import type { TransformOptions } from 'esbuild'
Expand Down Expand Up @@ -285,7 +285,7 @@ export function resolveBuildPlugins(config: ResolvedConfig): {
watchPackageDataPlugin(config),
commonjsPlugin(options.commonjsOptions),
dataURIPlugin(),
dynamicImportVars(options.dynamicImportVarsOptions),
dynamicImportVarsPlugin(config),
assetImportMetaUrlPlugin(config),
...(options.rollupOptions.plugins
? (options.rollupOptions.plugins.filter(Boolean) as Plugin[])
Expand Down
242 changes: 242 additions & 0 deletions packages/vite/src/node/plugins/dynamicImportVars.ts
@@ -0,0 +1,242 @@
import path from 'path'
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 { parseRequest } from '../utils'
import { parse as parseJS } from 'acorn'
import { createFilter } from '@rollup/pluginutils'
import { dynamicImportToGlob } from '@rollup/plugin-dynamic-import-vars'
import type { TransformGlobImportResult } from './importMetaGlob'
import { transformGlobImport } from './importMetaGlob'
import type { ViteDevServer } from '../server'

interface DynamicImportRequest {
as?: 'raw'
}

interface DynamicImportPattern {
globParams: DynamicImportRequest | null
userPattern: string
rawPattern: string
}

const dynamicImportHelper = (glob: Record<string, any>, path: string) => {
const v = glob[path]
if (v) {
return typeof v === 'function' ? v() : Promise.resolve(v)
}
return new Promise((_, reject) => {
;(typeof queueMicrotask === 'function' ? queueMicrotask : setTimeout)(
reject.bind(null, new Error('Unknown variable dynamic import: ' + path))
)
})
}
export const dynamicImportHelperId = '/@vite/dynamic-import-helper'

export function parseDynamicImportPattern(
strings: string
): DynamicImportPattern | null {
const filename = strings.slice(1, -1)
const rawQuery = parseRequest(filename)
poyoho marked this conversation as resolved.
Show resolved Hide resolved
let globParams: DynamicImportRequest | null = null
const ast = (
parseJS(strings, {
ecmaVersion: 'latest',
sourceType: 'module'
}) as any
).body[0].expression

const userPatternQuery = dynamicImportToGlob(ast, filename)
if (!userPatternQuery) {
return null
}

const [userPattern] = userPatternQuery.split('?', 2)
const [rawPattern] = filename.split('?', 2)

if (rawQuery?.raw !== undefined) {
globParams = { as: 'raw' }
}

return {
globParams,
userPattern,
rawPattern
}
}

export async function transformDynamicImportGlob(
poyoho marked this conversation as resolved.
Show resolved Hide resolved
source: string,
root: string,
importer: string,
start: number,
end: number,
resolve: (url: string, importer?: string) => Promise<string | undefined>
): Promise<{
glob: TransformGlobImportResult
pattern: string
rawPattern: string
} | null> {
let fileName = source.slice(start, end)

if (fileName[1] !== '.' && fileName[1] !== '/' && resolve) {
const resolvedFileName = await resolve(fileName.slice(1, -1), importer)
if (!resolvedFileName) {
return null
}
const relativeFileName = path.posix.relative(
path.dirname(importer),
resolvedFileName
)
fileName = `\`${relativeFileName}\``
}

const dynamicImportPattern = parseDynamicImportPattern(fileName)
if (!dynamicImportPattern) {
return null
}
const { globParams, rawPattern, userPattern } = dynamicImportPattern
const params = globParams ? `, ${JSON.stringify(globParams)}` : ''
const exp = `import.meta.glob(${JSON.stringify(userPattern)}${params})`
const glob = await transformGlobImport(
exp,
importer,
root,
resolve,
false,
false
)
poyoho marked this conversation as resolved.
Show resolved Hide resolved

return {
rawPattern,
pattern: userPattern,
glob: glob!
}
}

export function dynamicImportHelperPlugin(): Plugin {
return {
name: 'vite:dynamic-import-helper',
resolveId(id) {
if (id === dynamicImportHelperId) {
return id
}
},

load(id) {
if (id === dynamicImportHelperId) {
return 'export default' + dynamicImportHelper.toString()
}
}
}
}

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

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

configureServer(_server) {
server = _server
},

async transform(source, importer) {
if (!filter(importer)) {
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 needDynamicImportHelper = false

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(
source,
config.root,
importer,
start,
end,
resolve
)
} catch (error) {
if (warnOnError) {
this.warn(error)
} else {
this.error(error)
}
}

if (!result) {
continue
}

const { rawPattern, glob } = result

needDynamicImportHelper = true
s.overwrite(
expStart,
expEnd,
`__variableDynamicImportRuntimeHelper(${glob.s.toString()}, \`${rawPattern}\`)`
)
if (server) {
const allGlobs = glob.matches.map((i) => i.globsResolved)
server._importGlobMap.set(importer, allGlobs)
glob.files.forEach((file) => {
// update watcher
server!.watcher.add(path.posix.dirname(file))
})
}
}

if (s) {
if (needDynamicImportHelper) {
s.prepend(
`import __variableDynamicImportRuntimeHelper from "${dynamicImportHelperId}";`
)
}
return {
code: s.toString(),
map: config.build.sourcemap ? s.generateMap({ hires: true }) : null
poyoho marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
}