Skip to content

Commit

Permalink
feat: use oxc for lowering
Browse files Browse the repository at this point in the history
  • Loading branch information
sapphi-red committed Dec 11, 2024
1 parent f328a00 commit 69ded4b
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 7 deletions.
21 changes: 21 additions & 0 deletions packages/vite/foo.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { transform } from 'rolldown/experimental'

const result = transform('po.js', 'const bar = {..."bb"}', {
target: 'es2017',
sourceType: 'module',
})

console.log(result)

/*
result.code for `sourceType: 'module'` is:
import _objectSpread from "@babel/runtime/helpers/objectSpread2";\n' +
'const bar = _objectSpread({}, "bb");\n
result.code for `sourceType: 'script'` is:
var _objectSpread = require("@babel/runtime/helpers/objectSpread2");\n' +
'const bar = _objectSpread({}, "bb");\n
*/
6 changes: 3 additions & 3 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import type {
import { resolveConfig } from './config'
import type { PartialEnvironment } from './baseEnvironment'
import { buildReporterPlugin } from './plugins/reporter'
import { buildEsbuildPlugin } from './plugins/esbuild'
import { buildOxcPlugin } from './plugins/oxc'
import { type TerserOptions, terserPlugin } from './plugins/terser'
import {
arraify,
Expand Down Expand Up @@ -508,8 +508,8 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
],
post: [
...buildImportAnalysisPlugin(config),
...(config.esbuild !== false && !enableNativePlugin
? [buildEsbuildPlugin(config)]
...(config.oxc !== false && !enableNativePlugin
? [buildOxcPlugin(config)]
: []),
terserPlugin(config),
...(!config.isWorker
Expand Down
180 changes: 179 additions & 1 deletion packages/vite/src/node/plugins/oxc.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import path from 'node:path'
import { createRequire } from 'node:module'
import type {
TransformOptions as OxcTransformOptions,
TransformResult as OxcTransformResult,
} from 'rolldown/experimental'
import { transform } from 'rolldown/experimental'
import type { RawSourceMap } from '@ampproject/remapping'
import type { SourceMap } from 'rolldown'
import { type InternalModuleFormat, type SourceMap, rolldown } from 'rolldown'
import type { FSWatcher } from 'dep-types/chokidar'
import { TSConfckParseError } from 'tsconfck'
import type { RollupError } from 'rollup'
Expand All @@ -23,6 +24,12 @@ import type { ViteDevServer } from '../server'
import type { ESBuildOptions } from './esbuild'
import { loadTsconfigJsonForFile } from './esbuild'

// IIFE content looks like `var MyLib = (function() {`.
const IIFE_BEGIN_RE =
/(?:const|var)\s+\S+\s*=\s*\(?function\([^()]*\)\s*\{\s*"use strict";/
// UMD content looks like `(this, function(exports) {`.
const UMD_BEGIN_RE = /\(this,\s*function\([^()]*\)\s*\{\s*"use strict";/

const jsxExtensionsRE = /\.(?:j|t)sx\b/
const validExtensionRE = /\.\w+$/

Expand Down Expand Up @@ -267,6 +274,177 @@ export function oxcPlugin(config: ResolvedConfig): Plugin {
}
}

export const buildOxcPlugin = (config: ResolvedConfig): Plugin => {
return {
name: 'vite:oxc-transpile',
async renderChunk(code, chunk, opts) {
// @ts-expect-error injected by @vitejs/plugin-legacy
if (opts.__vite_skip_esbuild__) {
return null
}

const options = resolveOxcTranspileOptions(config, opts.format)

if (!options) {
return null
}

const res = await transformWithOxc(
this,
code,
chunk.fileName,
options,
undefined,
config,
)

const runtimeHelpers = Object.entries(res.helpersUsed)
if (runtimeHelpers.length > 0) {
const helpersCode = await generateRuntimeHelpers(runtimeHelpers)
switch (opts.format) {
case 'es': {
if (res.code.startsWith('#!')) {
let secondLinePos = res.code.indexOf('\n')
if (secondLinePos === -1) {
secondLinePos = 0
}
// inject after hashbang
res.code =
res.code.slice(0, secondLinePos) +
helpersCode +
res.code.slice(secondLinePos)
if (res.map) {
res.map.mappings = res.map.mappings.replace(';', ';;')
}
} else {
res.code = helpersCode + res.code
if (res.map) {
res.map.mappings = ';' + res.map.mappings
}
}
break
}
case 'cjs': {
if (/^\s*['"]use strict['"];/.test(res.code)) {
// inject after use strict
res.code = res.code.replace(
/^\s*['"]use strict['"];/,
(m) => m + helpersCode,
)
// no need to update sourcemap because the runtime helpers are injected in the same line with "use strict"
} else {
res.code = helpersCode + res.code
if (res.map) {
res.map.mappings = ';' + res.map.mappings
}
}
break
}
// runtime helpers needs to be injected inside the UMD and IIFE wrappers
// to avoid collision with other globals.
// We inject the helpers inside the wrappers.
// e.g. turn:
// (function(){ /*actual content/* })()
// into:
// (function(){ <runtime helpers> /*actual content/* })()
// Not using regex because it's too hard to rule out performance issues like #8738 #8099 #10900 #14065
// Instead, using plain string index manipulation (indexOf, slice) which is simple and performant
// We don't need to create a MagicString here because both the helpers and
// the headers don't modify the sourcemap
case 'iife':
case 'umd': {
const m = (
opts.format === 'iife' ? IIFE_BEGIN_RE : UMD_BEGIN_RE
).exec(res.code)
if (!m) {
this.error('Unexpected IIFE format')
return
}
const pos = m.index + m.length
res.code =
res.code.slice(0, pos) + helpersCode + '\n' + res.code.slice(pos)
break
}
case 'app': {
throw new Error('format: "app" is not supported yet')
break
}
default: {
opts.format satisfies never
}
}
}

return res
},
}
}

export function resolveOxcTranspileOptions(
config: ResolvedConfig,
format: InternalModuleFormat,
): OxcTransformOptions | null {
const target = config.build.target
if (!target || target === 'esnext') {
return null
}

return {
...(config.oxc || {}),
helpers: { mode: 'External' },
lang: 'js',
sourceType: format === 'es' ? 'module' : 'script',
target: target || undefined,
sourcemap: !!config.build.sourcemap,
}
}

let rolldownDir: string

async function generateRuntimeHelpers(
runtimeHelpers: readonly [string, string][],
): Promise<string> {
if (!rolldownDir) {
let dir = createRequire(import.meta.url).resolve('rolldown')
while (dir && path.basename(dir) !== 'rolldown') {
dir = path.dirname(dir)
}
rolldownDir = dir
}

const bundle = await rolldown({
cwd: rolldownDir,
input: 'entrypoint',
platform: 'neutral',
plugins: [
{
name: 'entrypoint',
resolveId: {
filter: { id: /^entrypoint$/ },
handler: (id) => id,
},
load: {
filter: { id: /^entrypoint$/ },
handler() {
return runtimeHelpers
.map(
([name, helper]) =>
`export { default as ${name} } from ${JSON.stringify(helper)};`,
)
.join('\n')
},
},
},
],
})
const output = await bundle.generate({
format: 'iife',
name: 'babelHelpers',
minify: true,
})
return output.output[0].code
}

export function convertEsbuildConfigToOxcConfig(
esbuildConfig: ESBuildOptions,
logger: Logger,
Expand Down
6 changes: 3 additions & 3 deletions playground/lib/__tests__/lib.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe.runIf(isBuild)('build', () => {
// esbuild helpers are injected inside of the UMD wrapper
expect(code).toMatch(/^\(function\(/)
expect(noMinifyCode).toMatch(
/^\(function\(global.+?"use strict";var.+?function\smyLib\(/s,
/^\/\*[^*]*\*\/\s*\(function\(global.+?"use strict";\s*var.+?function\smyLib\(/s,
)
expect(namedCode).toMatch(/^\(function\(/)
})
Expand All @@ -39,7 +39,7 @@ describe.runIf(isBuild)('build', () => {
// esbuild helpers are injected inside of the IIFE wrapper
expect(code).toMatch(/^var MyLib=function\(\)\{\s*"use strict";/)
expect(noMinifyCode).toMatch(
/^var MyLib\s*=\s*function\(\)\s*\{\s*"use strict";/,
/^\/\*[^*]*\*\/\s*var MyLib\s*=\s*function\(\)\s*\{\s*"use strict";/,
)
expect(namedCode).toMatch(
/^var MyLibNamed=function\([^()]+\)\{\s*"use strict";/,
Expand All @@ -51,7 +51,7 @@ describe.runIf(isBuild)('build', () => {
'dist/helpers-injection/my-lib-custom-filename.iife.js',
)
expect(code).toMatch(
`'"use strict"; return (' + expressionSyntax + ").constructor;"`,
`\\"use strict\\"; return (" + expressionSyntax + ").constructor;"`,
)
})

Expand Down

0 comments on commit 69ded4b

Please sign in to comment.