Skip to content

Commit

Permalink
feat: tsx? support for build
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed May 6, 2020
1 parent 7cbaf5d commit 81ffbc5
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 62 deletions.
2 changes: 2 additions & 0 deletions bin/vite.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ if (argv._[0] === 'build') {
})
.catch((err) => {
console.error(chalk.red(`[vite] Build errored out.`))
// TODO pretty print this
// rollup errors contain helpful information
console.log(err)
})
} else {
Expand Down
29 changes: 18 additions & 11 deletions src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { createBuildResolvePlugin } from './buildPluginResolve'
import { createBuildHtmlPlugin } from './buildPluginHtml'
import { createBuildCssPlugin } from './buildPluginCss'
import { createBuildAssetPlugin } from './buildPluginAsset'
import { createMinifyPlugin } from './esbuildService'
import { createEsbuildPlugin } from './buildPluginEsbuild'

export interface BuildOptions {
/**
Expand Down Expand Up @@ -75,6 +75,13 @@ export interface BuildOptions {
* https://github.com/vuejs/rollup-plugin-vue/blob/next/src/index.ts
*/
rollupPluginVueOptions?: Partial<Options>
/**
* Configure what to use for jsx factory and fragment
*/
jsx?: {
factory?: string
fragment?: string
}
/**
* Whether to emit index.html
*/
Expand Down Expand Up @@ -136,6 +143,7 @@ export async function build(options: BuildOptions = {}): Promise<BuildResult> {
rollupInputOptions = {},
rollupOutputOptions = {},
rollupPluginVueOptions = {},
jsx = {},
emitIndex = true,
emitAssets = true,
write = true,
Expand All @@ -158,14 +166,6 @@ export async function build(options: BuildOptions = {}): Promise<BuildResult> {
resolver
)

// terser is used by default for better compression, but the user can also
// opt-in to use esbuild which is orders of magnitude faster.
const minifyPlugin = minify
? minify === 'esbuild'
? await createMinifyPlugin()
: require('rollup-plugin-terser').terser()
: null

// lazy require rollup so that we don't load it when only using the dev server
// importing it just for the types
const rollup = require('rollup').rollup as typeof Rollup
Expand All @@ -180,6 +180,8 @@ export async function build(options: BuildOptions = {}): Promise<BuildResult> {
createBuildResolvePlugin(root, cdn, [root, ...srcRoots], resolver),
// vite:html
...(htmlPlugin ? [htmlPlugin] : []),
// vite:esbuild
await createEsbuildPlugin(minify === 'esbuild', jsx),
// vue
require('rollup-plugin-vue')({
transformAssetUrls: {
Expand Down Expand Up @@ -213,8 +215,13 @@ export async function build(options: BuildOptions = {}): Promise<BuildResult> {
),
// vite:asset
createBuildAssetPlugin(publicBasePath, assetsDir, assetsInlineLimit),
// minify
...(minifyPlugin ? [minifyPlugin] : [])
// minify with terser
// this is the default which has better compression, but slow
// the user can opt-in to use esbuild which is much faster but results
// in ~8-10% larger file size.
...(minify && minify !== 'esbuild'
? [require('rollup-plugin-terser').terser()]
: [])
],
onwarn(warning, warn) {
if (warning.code !== 'CIRCULAR_DEPENDENCY') {
Expand Down
52 changes: 52 additions & 0 deletions src/node/buildPluginEsbuild.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Plugin } from 'rollup'
import { startService, Service } from 'esbuild'
import { tjsxRE, transformWithService } from './esbuildService'

export const createEsbuildPlugin = async (
minify: boolean,
jsx: {
factory?: string
fragment?: string
}
): Promise<Plugin> => {
let service: Service | undefined

const jsxConfig = {
jsxFactory: jsx.factory,
jsxFragment: jsx.fragment
}

return {
name: 'vite:esbuild',

async transform(code, file) {
if (tjsxRE.test(file)) {
return transformWithService(
service || (service = await startService()),
code,
file,
{ ...jsxConfig }
)
}
},

async renderChunk(code, chunk) {
if (minify) {
return transformWithService(
service || (service = await startService()),
code,
chunk.fileName,
{
minify: true
}
)
} else {
return null
}
},

generateBundle() {
service && service.stop()
}
}
}
42 changes: 15 additions & 27 deletions src/node/esbuildService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import path from 'path'
import { startService, Service, TransformOptions } from 'esbuild'
import { Plugin } from 'rollup'

export const tjsxRE = /\.(tsx?|jsx)$/

// Note: when the esbuild service is held in a module level variable, it
// somehow prevents the build process from exiting even after explicitly
Expand All @@ -20,23 +22,27 @@ const ensureService = async () => {
// transform used in server plugins with a more friendly API
export const transform = async (
code: string,
options: TransformOptions,
operation: string
file: string,
options: TransformOptions = {}
) => {
return _transform(await ensureService(), code, options, operation)
return transformWithService(await ensureService(), code, file, options)
}

// trasnform that takes the service via arguments, used in build plugins
const _transform = async (
export const transformWithService = async (
service: Service,
code: string,
options: TransformOptions,
operation: string
file: string,
options: TransformOptions = {}
) => {
try {
if (!options.loader) {
options.loader = path.extname(file).slice(1) as any
}
options.sourcemap = true
const result = await service.transform(code, options)
if (result.warnings.length) {
console.error(`[vite] warnings while ${operation} with esbuild:`)
console.error(`[vite] warnings while transforming ${file} with esbuild:`)
// TODO pretty print this
result.warnings.forEach((w) => console.error(w))
}
Expand All @@ -45,29 +51,11 @@ const _transform = async (
map: result.jsSourceMap || ''
}
} catch (e) {
console.error(`[vite] error while ${operation} with esbuild:`)
console.error(`[vite] error while transforming ${file} with esbuild:`)
console.error(e)
return {
code: '',
map: ''
}
}
}

export const createMinifyPlugin = async (): Promise<Plugin> => {
const service = await startService()
return {
name: 'vite:minify',
async renderChunk(code, chunk) {
return _transform(
service,
code,
{ minify: true },
`minifying ${chunk.fileName}`
)
},
generateBundle() {
service.stop()
}
}
}
29 changes: 8 additions & 21 deletions src/node/serverPluginEsbuild.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,25 @@
import { Plugin } from './server'
import { readBody, isImportRequest, genSourceMapString } from './utils'
import { TransformOptions } from 'esbuild'
import { transform } from './esbuildService'

const testRE = /\.(tsx?|jsx)$/
import { tjsxRE, transform } from './esbuildService'

export const esbuildPlugin: Plugin = ({ app, watcher, jsxConfig }) => {
app.use(async (ctx, next) => {
await next()
if (isImportRequest(ctx) && ctx.body && testRE.test(ctx.path)) {
if (isImportRequest(ctx) && ctx.body && tjsxRE.test(ctx.path)) {
ctx.type = 'js'
let options: TransformOptions = {}
if (ctx.path.endsWith('.ts')) {
options = { loader: 'ts' }
} else if (ctx.path.endsWith('tsx')) {
options = { loader: 'tsx', ...jsxConfig }
} else if (ctx.path.endsWith('jsx')) {
options = { loader: 'jsx', ...jsxConfig }
}
const src = await readBody(ctx.body)
const { code, map } = await transform(
src!,
options,
`transpiling ${ctx.path}`
)
ctx.body = code
const { code, map } = await transform(src!, ctx.path, jsxConfig)
let res = code
if (map) {
ctx.body += genSourceMapString(map)
res = res.replace(/\/\/# sourceMappingURL.*/, '')
res += genSourceMapString(map)
}
ctx.body = res
}
})

watcher.on('change', (file) => {
if (testRE.test(file)) {
if (tjsxRE.test(file)) {
watcher.handleJSReload(file)
}
})
Expand Down
4 changes: 1 addition & 3 deletions src/node/serverPluginVue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,7 @@ async function compileSFCMain(
if (descriptor.script) {
let content = descriptor.script.content
if (descriptor.script.lang === 'ts') {
content = (
await transform(content, { loader: 'ts' }, `transpiling ${publicPath}`)
).code
content = (await transform(content, publicPath, { loader: 'ts' })).code
}

code += content.replace(`export default`, 'const __script =')
Expand Down

0 comments on commit 81ffbc5

Please sign in to comment.