Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const config: UserConfig = {
vueTransformAssetUrls: {
img: ['src', 'data-src']
},
indexHtmlTransforms: [
({ code }) => code.replace(/Vite App/, 'Vite Playground')
],
emitManifest: true
}

Expand Down
32 changes: 25 additions & 7 deletions src/node/build/buildPluginHtml.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { Plugin, RollupOutput, OutputChunk } from 'rollup'
import path from 'path'
import fs from 'fs-extra'
import { isExternalUrl, cleanUrl, isDataUrl } from '../utils/pathUtils'
import MagicString from 'magic-string'
import {
isExternalUrl,
cleanUrl,
isDataUrl,
transformIndexHtml
} from '../utils'
import { resolveAsset, registerAssets } from './buildPluginAsset'
import { InternalResolver } from '../resolver'
import { UserConfig } from '../config'
import {
parse as Parse,
transform as Transform,
Expand All @@ -11,8 +19,6 @@ import {
TextNode,
AttributeNode
} from '@vue/compiler-dom'
import MagicString from 'magic-string'
import { InternalResolver } from '../resolver'

export const createBuildHtmlPlugin = async (
root: string,
Expand All @@ -21,7 +27,8 @@ export const createBuildHtmlPlugin = async (
assetsDir: string,
inlineLimit: number,
resolver: InternalResolver,
shouldPreload: ((chunk: OutputChunk) => boolean) | null
shouldPreload: ((chunk: OutputChunk) => boolean) | null,
config: UserConfig
) => {
if (!indexPath || !fs.existsSync(indexPath)) {
return {
Expand All @@ -31,10 +38,16 @@ export const createBuildHtmlPlugin = async (
}

const rawHtml = await fs.readFile(indexPath, 'utf-8')
const preprocessedHtml = await transformIndexHtml(
rawHtml,
config.indexHtmlTransforms,
'pre',
true
)
const assets = new Map<string, Buffer>()
let { html: processedHtml, js } = await compileHtml(
root,
rawHtml,
preprocessedHtml,
publicBasePath,
assetsDir,
inlineLimit,
Expand Down Expand Up @@ -91,7 +104,7 @@ export const createBuildHtmlPlugin = async (
}
}

const renderIndex = (bundleOutput: RollupOutput['output']) => {
const renderIndex = async (bundleOutput: RollupOutput['output']) => {
for (const chunk of bundleOutput) {
if (chunk.type === 'chunk') {
if (chunk.isEntry) {
Expand All @@ -112,7 +125,12 @@ export const createBuildHtmlPlugin = async (
}
}
}
return processedHtml
return await transformIndexHtml(
processedHtml,
config.indexHtmlTransforms,
'post',
true
)
}

return {
Expand Down
5 changes: 3 additions & 2 deletions src/node/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ export async function build(options: BuildConfig): Promise<BuildResult> {
assetsDir,
assetsInlineLimit,
resolver,
shouldPreload
shouldPreload,
options
)

const basePlugins = await createBaseRollupPlugins(root, resolver, options)
Expand Down Expand Up @@ -443,7 +444,7 @@ export async function build(options: BuildConfig): Promise<BuildResult> {

spinner && spinner.stop()

const indexHtml = emitIndex ? renderIndex(output) : ''
const indexHtml = emitIndex ? await renderIndex(output) : ''

if (write) {
const printFilesInfo = async (
Expand Down
15 changes: 14 additions & 1 deletion src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import {
} from './build/buildPluginEsbuild'
import { Context, ServerPlugin } from './server'
import { Resolver, supportedExts } from './resolver'
import { Transform, CustomBlockTransform } from './transform'
import {
Transform,
CustomBlockTransform,
IndexHtmlTransform
} from './transform'
import { DepOptimizationOptions } from './optimizer'
import { ServerOptions } from 'https'
import { lookupFile } from './utils'
Expand Down Expand Up @@ -114,6 +118,10 @@ export interface SharedConfig {
* Custom file transforms.
*/
transforms?: Transform[]
/**
* Custom index.html transforms.
*/
indexHtmlTransforms?: IndexHtmlTransform[]
/**
* Define global variable replacements.
* Entries will be defined on `window` during dev and replaced during build.
Expand Down Expand Up @@ -431,6 +439,7 @@ export interface Plugin
UserConfig,
| 'alias'
| 'transforms'
| 'indexHtmlTransforms'
| 'define'
| 'resolvers'
| 'configureServer'
Expand Down Expand Up @@ -607,6 +616,10 @@ function resolvePlugin(config: UserConfig, plugin: Plugin): UserConfig {
...config.define
},
transforms: [...(config.transforms || []), ...(plugin.transforms || [])],
indexHtmlTransforms: [
...(config.indexHtmlTransforms || []),
...(plugin.indexHtmlTransforms || [])
],
resolvers: [...(config.resolvers || []), ...(plugin.resolvers || [])],
configureServer: ([] as ServerPlugin[]).concat(
config.configureServer || [],
Expand Down
21 changes: 19 additions & 2 deletions src/node/server/serverPluginHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { rewriteImports, ServerPlugin } from './index'
import { debugHmr, ensureMapEntry, importerMap } from './serverPluginHmr'
import { clientPublicPath } from './serverPluginClient'
import { init as initLexer } from 'es-module-lexer'
import { cleanUrl, readBody, injectScriptToHtml } from '../utils'
import {
cleanUrl,
readBody,
injectScriptToHtml,
transformIndexHtml
} from '../utils'
import LRUCache from 'lru-cache'
import path from 'path'
import chalk from 'chalk'
Expand All @@ -24,6 +29,12 @@ export const htmlRewritePlugin: ServerPlugin = ({

async function rewriteHtml(importer: string, html: string) {
await initLexer
html = await transformIndexHtml(
html,
config.indexHtmlTransforms,
'pre',
false
)
html = html.replace(scriptRE, (matched, openTag, script) => {
if (script) {
return `${openTag}${rewriteImports(
Expand All @@ -45,7 +56,13 @@ export const htmlRewritePlugin: ServerPlugin = ({
return matched
}
})
return injectScriptToHtml(html, devInjectionCode)
const processedHtml = injectScriptToHtml(html, devInjectionCode)
return await transformIndexHtml(
processedHtml,
config.indexHtmlTransforms,
'post',
false
)
}

app.use(async (ctx, next) => {
Expand Down
20 changes: 20 additions & 0 deletions src/node/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ export interface Transform {
transform: TransformFn
}

export interface IndexHtmlTransformContext {
code: string
isBuild: boolean
}

export type IndexHtmlTransformFn = (
ctx: IndexHtmlTransformContext
) => string | Promise<string>

export type IndexHtmlTransform =
| IndexHtmlTransformFn
| {
/**
* Timing for applying the transform.
* @default: 'post'
*/
apply?: 'pre' | 'post'
transform: IndexHtmlTransformFn
}

export type CustomBlockTransform = TransformFn

export function createServerTransformPlugin(
Expand Down
24 changes: 24 additions & 0 deletions src/node/utils/transformUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { IndexHtmlTransform } from '../transform'

export async function asyncReplace(
input: string,
re: RegExp,
Expand Down Expand Up @@ -27,3 +29,25 @@ export function injectScriptToHtml(html: string, script: string) {
// if no <head> tag or doctype is present, just prepend
return script + html
}

export async function transformIndexHtml(
html: string,
transforms: IndexHtmlTransform[] = [],
apply: 'pre' | 'post',
isBuild = false
) {
const trans = transforms
.map((t) => {
return typeof t === 'function' && apply === 'post'
? t
: t.apply === apply
? t.transform
: undefined
})
.filter(Boolean)
let code = html
for (const transform of trans) {
code = await transform!({ isBuild, code })
}
return code
}
8 changes: 4 additions & 4 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,7 @@ describe('vite', () => {
declareTests(false)

test('hmr (index.html full-reload)', async () => {
expect(await getText('title')).toMatch('Vite App')
expect(await getText('title')).toMatch('Vite Playground')
// hmr
const reload = page.waitForNavigation({
waitUntil: 'domcontentloaded'
Expand All @@ -841,12 +841,12 @@ describe('vite', () => {
content.replace('Vite App', 'Vite App Test')
)
await reload
await expectByPolling(() => getText('title'), 'Vite App Test')
await expectByPolling(() => getText('title'), 'Vite Playground Test')
})

test('hmr (html full-reload)', async () => {
await page.goto('http://localhost:3000/test.html')
expect(await getText('title')).toMatch('Vite App')
expect(await getText('title')).toMatch('Vite Playground')
// hmr
const reload = page.waitForNavigation({
waitUntil: 'domcontentloaded'
Expand All @@ -855,7 +855,7 @@ describe('vite', () => {
content.replace('Vite App', 'Vite App Test')
)
await reload
await expectByPolling(() => getText('title'), 'Vite App Test')
await expectByPolling(() => getText('title'), 'Vite Playground Test')
})

// Assert that all edited files are reflected on page reload
Expand Down