refactor: move compile into compiler-core

This commit is contained in:
Evan You 2019-09-20 12:16:19 -04:00
parent 3e1973f065
commit 8a923f6a52
12 changed files with 106 additions and 77 deletions

View File

@ -164,7 +164,7 @@ describe('compiler: transform', () => {
const ast = parse(`<div/>`) const ast = parse(`<div/>`)
const loc = ast.children[0].loc.start const loc = ast.children[0].loc.start
const plugin: Transform = (node, context) => { const plugin: Transform = (node, context) => {
context.onError( context.emitError(
createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc.start) createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc.start)
) )
} }

View File

@ -66,12 +66,11 @@ export function generate(
function createCodegenContext( function createCodegenContext(
ast: RootNode, ast: RootNode,
options: CodegenOptions { module = false, filename = `template.vue.html` }: CodegenOptions
): CodegenContext { ): CodegenContext {
const context: CodegenContext = { const context: CodegenContext = {
module: false, module,
filename: `template.vue.html`, filename,
...options,
source: ast.loc.source, source: ast.loc.source,
code: ``, code: ``,
column: 1, column: 1,

View File

@ -27,7 +27,7 @@ export const transformFor = createDirectiveTransform(
children: [node] children: [node]
}) })
} else { } else {
context.onError( context.emitError(
createCompilerError( createCompilerError(
ErrorCodes.X_FOR_MALFORMED_EXPRESSION, ErrorCodes.X_FOR_MALFORMED_EXPRESSION,
dir.loc.start dir.loc.start
@ -35,7 +35,7 @@ export const transformFor = createDirectiveTransform(
) )
} }
} else { } else {
context.onError( context.emitError(
createCompilerError(ErrorCodes.X_FOR_NO_EXPRESSION, dir.loc.start) createCompilerError(ErrorCodes.X_FOR_NO_EXPRESSION, dir.loc.start)
) )
} }

View File

@ -38,7 +38,7 @@ export const transformIf = createDirectiveTransform(
} }
sibling.branches.push(branch) sibling.branches.push(branch)
} else { } else {
context.onError( context.emitError(
createCompilerError( createCompilerError(
dir.name === 'else' dir.name === 'else'
? ErrorCodes.X_ELSE_NO_ADJACENT_IF ? ErrorCodes.X_ELSE_NO_ADJACENT_IF

View File

@ -5,6 +5,10 @@ export interface CompilerError extends SyntaxError {
loc: Position loc: Position
} }
export function defaultOnError(error: CompilerError) {
throw error
}
export function createCompilerError( export function createCompilerError(
code: ErrorCodes, code: ErrorCodes,
loc: Position loc: Position

View File

@ -1,11 +1,35 @@
import { parse, ParserOptions } from './parse'
import { transform, TransformOptions } from './transform'
import { generate, CodegenOptions, CodegenResult } from './codegen'
export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
export function compile(
template: string,
options: CompilerOptions = {}
): CodegenResult {
const ast = parse(template, options)
transform(ast, {
...options,
transforms: [
// TODO include built-in core transforms
...(options.transforms || []) // user transforms
]
})
return generate(ast, options)
}
// Also expose lower level APIs & types
export { parse, ParserOptions, TextModes } from './parse' export { parse, ParserOptions, TextModes } from './parse'
export { export {
transform, transform,
createDirectiveTransform, createDirectiveTransform,
TransformOptions, TransformOptions,
Transform Transform,
DirectiveTransform
} from './transform' } from './transform'
export { generate, CodegenOptions, CodegenResult } from './codegen' export { generate, CodegenOptions, CodegenResult } from './codegen'
export { ErrorCodes, CompilerError, createCompilerError } from './errors' export { ErrorCodes, CompilerError, createCompilerError } from './errors'
export * from './ast' export * from './ast'

View File

@ -1,4 +1,9 @@
import { ErrorCodes, CompilerError, createCompilerError } from './errors' import {
ErrorCodes,
CompilerError,
createCompilerError,
defaultOnError
} from './errors'
import { import {
assert, assert,
advancePositionWithMutation, advancePositionWithMutation,
@ -48,9 +53,7 @@ export const defaultParserOptions: Required<ParserOptions> = {
'apos;': "'", 'apos;': "'",
'quot;': '"' 'quot;': '"'
}, },
onError(error: CompilerError): void { onError: defaultOnError
throw error
}
} }
export const enum TextModes { export const enum TextModes {
@ -62,7 +65,8 @@ export const enum TextModes {
ATTRIBUTE_VALUE ATTRIBUTE_VALUE
} }
interface ParserContext extends Required<ParserOptions> { interface ParserContext {
options: Required<ParserOptions>
readonly originalSource: string readonly originalSource: string
source: string source: string
offset: number offset: number
@ -87,8 +91,10 @@ function createParserContext(
options: ParserOptions options: ParserOptions
): ParserContext { ): ParserContext {
return { return {
options: {
...defaultParserOptions, ...defaultParserOptions,
...options, ...options
},
column: 1, column: 1,
line: 1, line: 1,
offset: 0, offset: 0,
@ -115,7 +121,7 @@ function parseChildren(
const s = context.source const s = context.source
let node: any = null let node: any = null
if (startsWith(s, context.delimiters[0])) { if (startsWith(s, context.options.delimiters[0])) {
// '{{' // '{{'
node = parseInterpolation(context, mode) node = parseInterpolation(context, mode)
} else if (mode === TextModes.DATA && s[0] === '<') { } else if (mode === TextModes.DATA && s[0] === '<') {
@ -194,7 +200,11 @@ function pushNode(
if (!__DEV__ && node.type === NodeTypes.COMMENT) { if (!__DEV__ && node.type === NodeTypes.COMMENT) {
return return
} }
if (context.ignoreSpaces && node.type === NodeTypes.TEXT && node.isEmpty) { if (
context.options.ignoreSpaces &&
node.type === NodeTypes.TEXT &&
node.isEmpty
) {
return return
} }
@ -311,13 +321,13 @@ function parseElement(
const parent = last(ancestors) const parent = last(ancestors)
const element = parseTag(context, TagType.Start, parent) const element = parseTag(context, TagType.Start, parent)
if (element.isSelfClosing || context.isVoidTag(element.tag)) { if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
return element return element
} }
// Children. // Children.
ancestors.push(element) ancestors.push(element)
const mode = (context.getTextMode( const mode = (context.options.getTextMode(
element.tag, element.tag,
element.ns element.ns
) as unknown) as TextModes ) as unknown) as TextModes
@ -368,7 +378,7 @@ function parseTag(
const tag = match[1] const tag = match[1]
const attrs = [] const attrs = []
const directives = [] const directives = []
const ns = context.getNamespace(tag, parent) const ns = context.options.getNamespace(tag, parent)
advanceBy(context, match[0].length) advanceBy(context, match[0].length)
advanceSpaces(context) advanceSpaces(context)
@ -601,7 +611,7 @@ function parseInterpolation(
context: ParserContext, context: ParserContext,
mode: TextModes mode: TextModes
): ExpressionNode | undefined { ): ExpressionNode | undefined {
const [open, close] = context.delimiters const [open, close] = context.options.delimiters
__DEV__ && assert(startsWith(context.source, open)) __DEV__ && assert(startsWith(context.source, open))
const closeIndex = context.source.indexOf(close, open.length) const closeIndex = context.source.indexOf(close, open.length)
@ -626,7 +636,7 @@ function parseInterpolation(
function parseText(context: ParserContext, mode: TextModes): TextNode { function parseText(context: ParserContext, mode: TextModes): TextNode {
__DEV__ && assert(context.source.length > 0) __DEV__ && assert(context.source.length > 0)
const [open] = context.delimiters const [open] = context.options.delimiters
const endIndex = Math.min( const endIndex = Math.min(
...[ ...[
context.source.indexOf('<', 1), context.source.indexOf('<', 1),
@ -691,7 +701,7 @@ function parseTextData(
--length --length
) { ) {
name = context.source.substr(1, length) name = context.source.substr(1, length)
value = context.namedCharacterReferences[name] value = context.options.namedCharacterReferences[name]
} }
if (value) { if (value) {
const semi = name.endsWith(';') const semi = name.endsWith(';')
@ -837,7 +847,7 @@ function emitError(
loc.offset += offset loc.offset += offset
loc.column += offset loc.column += offset
} }
context.onError(createCompilerError(code, loc)) context.options.onError(createCompilerError(code, loc))
} }
function isEnd( function isEnd(

View File

@ -7,7 +7,7 @@ import {
DirectiveNode DirectiveNode
} from './ast' } from './ast'
import { isString } from '@vue/shared' import { isString } from '@vue/shared'
import { CompilerError } from './errors' import { CompilerError, defaultOnError } from './errors'
export type Transform = (node: ChildNode, context: TransformContext) => void export type Transform = (node: ChildNode, context: TransformContext) => void
@ -18,11 +18,13 @@ export type DirectiveTransform = (
) => false | void ) => false | void
export interface TransformOptions { export interface TransformOptions {
transforms: Transform[] transforms?: Transform[]
onError?: (error: CompilerError) => void onError?: (error: CompilerError) => void
} }
interface TransformContext extends Required<TransformOptions> { interface TransformContext {
transforms: Transform[]
emitError: (error: CompilerError) => void
parent: ParentNode parent: ParentNode
ancestors: ParentNode[] ancestors: ParentNode[]
childIndex: number childIndex: number
@ -42,10 +44,8 @@ function createTransformContext(
options: TransformOptions options: TransformOptions
): TransformContext { ): TransformContext {
const context: TransformContext = { const context: TransformContext = {
onError(error: CompilerError) { transforms: options.transforms || [],
throw error emitError: options.onError || defaultOnError,
},
...options,
parent: root, parent: root,
ancestors: [], ancestors: [],
childIndex: 0, childIndex: 0,
@ -109,7 +109,7 @@ function traverseNode(
ancestors: ParentNode[] ancestors: ParentNode[]
) { ) {
// apply transform plugins // apply transform plugins
const transforms = context.transforms const { transforms } = context
for (let i = 0; i < transforms.length; i++) { for (let i = 0; i < transforms.length; i++) {
const plugin = transforms[i] const plugin = transforms[i]
plugin(node, context) plugin(node, context)

View File

@ -1,46 +1,23 @@
import { import {
parse, compile as baseCompile,
transform, CompilerOptions,
generate,
CompilerError,
Transform,
CodegenResult CodegenResult
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { parserOptionsMinimal } from './parserOptionsMinimal' import { parserOptionsMinimal } from './parserOptionsMinimal'
import { parserOptionsStandard } from './parserOptionsStandard' import { parserOptionsStandard } from './parserOptionsStandard'
const parserOptions = __BROWSER__ ? parserOptionsMinimal : parserOptionsStandard
export interface CompilerOptions {
module?: boolean
onError?(err: CompilerError): void
transforms?: Transform[]
}
export function compile( export function compile(
template: string, template: string,
options: CompilerOptions = {} options: CompilerOptions = {}
): CodegenResult { ): CodegenResult {
const { return baseCompile(template, {
module = false, ...options,
onError = (err: CompilerError) => { ...(__BROWSER__ ? parserOptionsMinimal : parserOptionsStandard),
throw err
},
transforms = []
} = options
const ast = parse(template, {
...parserOptions,
onError
})
transform(ast, {
transforms: [ transforms: [
// TODO include core transforms // TODO include DOM-specific transforms
// TODO include DOM transforms ...(options.transforms || []) // extra user transforms
...transforms // user transforms ]
],
onError
}) })
return generate(ast, { module })
} }
export * from '@vue/compiler-core' export * from '@vue/compiler-core'

View File

@ -24,6 +24,7 @@ import {
isObject isObject
} from '@vue/shared' } from '@vue/shared'
import { SuspenseBoundary } from './suspense' import { SuspenseBoundary } from './suspense'
import { CompilerOptions } from '@vue/compiler-dom'
export type Data = { [key: string]: unknown } export type Data = { [key: string]: unknown }
@ -61,7 +62,7 @@ export interface SetupContext {
emit: Emit emit: Emit
} }
type RenderFunction = () => VNodeChild export type RenderFunction = () => VNodeChild
export interface ComponentInternalInstance { export interface ComponentInternalInstance {
type: FunctionalComponent | ComponentOptions type: FunctionalComponent | ComponentOptions
@ -298,8 +299,14 @@ export function handleSetupResult(
finishComponentSetup(instance, parentSuspense) finishComponentSetup(instance, parentSuspense)
} }
let compile: Function | undefined type CompileFunction = (
export function registerCompiler(_compile: Function) { template: string,
options?: CompilerOptions
) => RenderFunction
let compile: CompileFunction | undefined
export function registerRuntimeCompiler(_compile: CompileFunction) {
compile = _compile compile = _compile
} }
@ -311,7 +318,9 @@ function finishComponentSetup(
if (!instance.render) { if (!instance.render) {
if (Component.template && !Component.render) { if (Component.template && !Component.render) {
if (compile) { if (compile) {
Component.render = compile(Component.template) Component.render = compile(Component.template, {
onError(err) {}
})
} else if (__DEV__) { } else if (__DEV__) {
warn( warn(
`Component provides template but the build of Vue you are running ` + `Component provides template but the build of Vue you are running ` +

View File

@ -40,7 +40,7 @@ export { applyDirectives } from './directives'
export { resolveComponent, resolveDirective } from './componentOptions' export { resolveComponent, resolveDirective } from './componentOptions'
// Internal, for integration with runtime compiler // Internal, for integration with runtime compiler
export { registerCompiler } from './component' export { registerRuntimeCompiler } from './component'
// Types ----------------------------------------------------------------------- // Types -----------------------------------------------------------------------
@ -50,7 +50,8 @@ export { VNode, VNodeTypes } from './vnode'
export { export {
Component, Component,
FunctionalComponent, FunctionalComponent,
ComponentInternalInstance ComponentInternalInstance,
RenderFunction
} from './component' } from './component'
export { export {
ComponentOptions, ComponentOptions,
@ -58,6 +59,7 @@ export {
ComponentOptionsWithProps, ComponentOptionsWithProps,
ComponentOptionsWithArrayProps ComponentOptionsWithArrayProps
} from './componentOptions' } from './componentOptions'
export { ComponentPublicInstance } from './componentPublicInstanceProxy' export { ComponentPublicInstance } from './componentPublicInstanceProxy'
export { RendererOptions } from './createRenderer' export { RendererOptions } from './createRenderer'
export { Slot, Slots } from './componentSlots' export { Slot, Slots } from './componentSlots'

View File

@ -1,15 +1,19 @@
// This package is the "full-build" that includes both the runtime // This package is the "full-build" that includes both the runtime
// and the compiler, and supports on-the-fly compilation of the template option. // and the compiler, and supports on-the-fly compilation of the template option.
import { compile as baseCompile, CompilerOptions } from '@vue/compiler-dom' import { compile, CompilerOptions } from '@vue/compiler-dom'
import { registerCompiler } from '@vue/runtime-dom' import { registerRuntimeCompiler, RenderFunction } from '@vue/runtime-dom'
export function compile(template: string, options?: CompilerOptions): Function { function compileToFunction(
const { code } = baseCompile(template, options) template: string,
return new Function(`with(this){return ${code}}`) options?: CompilerOptions
): RenderFunction {
const { code } = compile(template, options)
return new Function(`with(this){return ${code}}`) as RenderFunction
} }
registerCompiler(compile) registerRuntimeCompiler(compileToFunction)
export { compileToFunction as compile }
export * from '@vue/runtime-dom' export * from '@vue/runtime-dom'
if (__BROWSER__ && __DEV__) { if (__BROWSER__ && __DEV__) {