Transformer development
Transformer Development
Transformers play a central role in the Vite.js ecosystem, responsible for performing various transformation operations on source code during the build process. Vite utilizes transformers to handle different types of files, such as JavaScript, TypeScript, JSX, CSS, etc., converting them into browser-executable code.
Basic Concepts of Transformers
In Vite, transformers are essentially functions that receive source code and return transformed code. They can modify ASTs, inject code, or completely rewrite the original content. Transformers are implemented in Vite's plugin system and registered via the transform
hook:
// A simple transformer plugin example
export default function myTransformer() {
return {
name: 'my-transformer',
transform(code, id) {
if (/\.custom$/.test(id)) {
return `// Transformed code\n${code.replace(/foo/g, 'bar')}`
}
}
}
}
The typical workflow of a transformer includes:
- Parsing source code to generate an AST
- Traversing and modifying the AST
- Generating new code
- Returning the transformation result and sourcemap
Analysis of Built-in Transformers
Vite includes various built-in transformers to handle common file types:
JavaScript/TypeScript Transformation
Vite uses esbuild as the default JS/TS transformer. Configuration example:
// vite.config.js
export default {
esbuild: {
jsxFactory: 'h',
jsxFragment: 'Fragment',
target: 'esnext'
}
}
CSS Transformer
Vite provides multiple transformation options for CSS files:
// Processing Sass files
export default {
css: {
preprocessorOptions: {
scss: {
additionalData: `$injectedColor: orange;`
}
}
}
}
Custom Transformer Development
Developing a custom transformer typically requires implementing the transform
method:
export default function customTransformer(options = {}) {
return {
name: 'custom-transformer',
// Transform a single file
transform(code, id) {
if (!filter(id)) return
const result = doTransform(code, options)
return {
code: result.code,
map: result.map // Optional sourcemap
}
},
// Transform the entire module graph
transformIndexHtml(html) {
return html.replace(/<title>(.*?)<\/title>/, `<title>${options.title}</title>`)
}
}
}
Advanced Transformation Techniques
AST Manipulation
Using Babel or swc for complex AST operations:
import { parse, traverse, generate } from '@babel/core'
function transformWithBabel(code) {
const ast = parse(code, {
sourceType: 'module',
plugins: ['jsx']
})
traverse(ast, {
Identifier(path) {
if (path.node.name === 'oldName') {
path.node.name = 'newName'
}
}
})
return generate(ast, { sourceMaps: true })
}
Code Replacement Patterns
Implementing macro-like code replacement:
const macroPattern = /\/\*\s*@macro\s+(\w+)\s*\*\/([\s\S]*?)\/\*\s*@endmacro\s*\*\//g
function expandMacros(code) {
const macros = {}
// Collect macro definitions
code = code.replace(macroPattern, (_, name, content) => {
macros[name] = content.trim()
return ''
})
// Expand macro calls
Object.entries(macros).forEach(([name, content]) => {
const callPattern = new RegExp(`\\/\\*\\s*@use\\s+${name}\\s*\\*\\/`, 'g')
code = code.replace(callPattern, content)
})
return code
}
Performance Optimization Strategies
Transformer performance directly impacts the development experience. Key optimization points:
- Caching Mechanism: Implement a reasonable caching strategy
const cache = new Map()
function transformWithCache(code, id) {
if (cache.has(id)) {
return cache.get(id)
}
const result = doTransform(code)
cache.set(id, result)
return result
}
- Incremental Transformation: Only process changed files
- Parallel Processing: Utilize worker thread pools
Debugging and Testing
Debugging transformers requires special configuration:
// vite.config.js
export default {
plugins: [
{
name: 'debug-transformer',
transform(code, id) {
if (id.includes('target-file')) {
debugger // Use with node --inspect-brk
}
return code
}
}
]
}
Writing transformer tests:
import { transform } from 'vite'
import myTransformer from '../src/transformer'
test('transforms code correctly', async () => {
const result = await transform('input code', {
plugins: [myTransformer()]
})
expect(result.code).toMatchSnapshot()
expect(result.map).toBeDefined()
})
Practical Application Examples
SVG Component Transformer
Converting SVG files into Vue components:
// svg-transformer.js
import { optimize } from 'svgo'
export default function svgTransformer() {
return {
name: 'svg-transformer',
async transform(code, id) {
if (!id.endsWith('.svg')) return
const optimized = optimize(code, {
plugins: ['preset-default']
})
return `
<template>
${optimized.data}
</template>
<script>
export default {
name: 'SvgComponent'
}
</script>
`
}
}
}
Internationalization Transformer
Automatically extracting and replacing text:
// i18n-transformer.js
const messagePattern = /_\('(.+?)'\)/g
const messages = new Map()
export default function i18nTransformer() {
return {
name: 'i18n-transformer',
transform(code, id) {
if (!id.includes('src/')) return
let match
while ((match = messagePattern.exec(code))) {
messages.set(match[1], '')
}
return code
},
buildEnd() {
// Generate language files
fs.writeFileSync('locales/en.json',
JSON.stringify(Object.fromEntries(messages), null, 2))
}
}
}
Integration with Other Tools
Integration with PostCSS
import postcss from 'postcss'
import autoprefixer from 'autoprefixer'
export default function postcssTransformer() {
const processor = postcss([autoprefixer])
return {
name: 'postcss-transformer',
async transform(code, id) {
if (!id.endsWith('.css')) return
const result = await processor.process(code, {
from: id,
to: id,
map: { inline: false }
})
return {
code: result.css,
map: result.map.toJSON()
}
}
}
}
Integration with GraphQL
import { parse, print } from 'graphql'
export default function graphqlTransformer() {
return {
name: 'graphql-transformer',
transform(code, id) {
if (!id.endsWith('.graphql')) return
try {
const ast = parse(code)
const minified = print(ast)
return `export default ${JSON.stringify(minified)}`
} catch (e) {
this.error(`GraphQL syntax error: ${e.message}`)
}
}
}
}
Error Handling and Logging
Robust transformers require comprehensive error handling:
export default function robustTransformer() {
return {
name: 'robust-transformer',
transform(code, id) {
try {
// Transformation logic
if (hasError) {
this.warn('Warning message', { line: 1, column: 5 })
}
return transformedCode
} catch (e) {
this.error(e, {
line: e.lineNumber,
col: e.columnNumber
})
return null
}
}
}
}
Transformer Composition Pattern
Multiple transformers can be combined:
// vite.config.js
import { compose } from 'vite'
export default {
plugins: [
compose([
transformerA(),
transformerB(),
transformerC()
])
]
}
Example of composed transformers:
function composeTransformers(...transformers) {
return {
name: 'composed-transformer',
async transform(code, id) {
let result = { code, map: null }
for (const transformer of transformers) {
result = await transformer.transform.call(this, result.code, id)
if (!result) break
}
return result
}
}
}
Source Map Handling
Proper sourcemap handling ensures a good debugging experience:
import { SourceMapGenerator } from 'source-map'
function createSourceMap(code, transformedCode, mappings) {
const map = new SourceMapGenerator({
file: 'transformed.js'
})
map.setSourceContent('original.js', code)
mappings.forEach(([original, generated]) => {
map.addMapping({
source: 'original.js',
original,
generated
})
})
return map.toJSON()
}
Transformer Configuration Design
Good configuration design enhances flexibility:
export default function configurableTransformer(options = {}) {
const defaults = {
include: /\.(js|ts|jsx|tsx)$/,
exclude: /node_modules/,
patterns: [],
debug: false
}
const config = { ...defaults, ...options }
return {
name: 'configurable-transformer',
transform(code, id) {
if (
!config.include.test(id) ||
config.exclude.test(id)
) return
// Perform transformation using configuration
}
}
}
Transformer and HMR Integration
Supporting Hot Module Replacement:
export default function hmrAwareTransformer() {
return {
name: 'hmr-transformer',
transform(code, id) {
const result = doTransform(code)
if (this.meta.watchMode) {
result.code += `\nimport.meta.hot.accept()`
}
return result
}
}
}
Transformer Scope Control
Precisely controlling transformer scope:
export default function scopedTransformer() {
let isProduction = false
return {
name: 'scoped-transformer',
configResolved(config) {
isProduction = config.command === 'build'
},
transform(code, id) {
if (isProduction && id.includes('debug')) {
return null // Skip debug files in production
}
// Transformation logic
}
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn