diff --git a/packages/compiler-core/__tests__/transform.spec.ts b/packages/compiler-core/__tests__/transform.spec.ts index 87cb9326..e6d8ef05 100644 --- a/packages/compiler-core/__tests__/transform.spec.ts +++ b/packages/compiler-core/__tests__/transform.spec.ts @@ -164,7 +164,7 @@ describe('compiler: transform', () => { const ast = parse(`
`) const loc = ast.children[0].loc.start const plugin: Transform = (node, context) => { - context.onError( + context.emitError( createCompilerError(ErrorCodes.X_INVALID_END_TAG, node.loc.start) ) } diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 52b348b5..857aa92a 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -66,12 +66,11 @@ export function generate( function createCodegenContext( ast: RootNode, - options: CodegenOptions + { module = false, filename = `template.vue.html` }: CodegenOptions ): CodegenContext { const context: CodegenContext = { - module: false, - filename: `template.vue.html`, - ...options, + module, + filename, source: ast.loc.source, code: ``, column: 1, diff --git a/packages/compiler-core/src/directives/vFor.ts b/packages/compiler-core/src/directives/vFor.ts index 095d201c..668fbd12 100644 --- a/packages/compiler-core/src/directives/vFor.ts +++ b/packages/compiler-core/src/directives/vFor.ts @@ -27,7 +27,7 @@ export const transformFor = createDirectiveTransform( children: [node] }) } else { - context.onError( + context.emitError( createCompilerError( ErrorCodes.X_FOR_MALFORMED_EXPRESSION, dir.loc.start @@ -35,7 +35,7 @@ export const transformFor = createDirectiveTransform( ) } } else { - context.onError( + context.emitError( createCompilerError(ErrorCodes.X_FOR_NO_EXPRESSION, dir.loc.start) ) } diff --git a/packages/compiler-core/src/directives/vIf.ts b/packages/compiler-core/src/directives/vIf.ts index 09898997..cbdf8be9 100644 --- a/packages/compiler-core/src/directives/vIf.ts +++ b/packages/compiler-core/src/directives/vIf.ts @@ -38,7 +38,7 @@ export const transformIf = createDirectiveTransform( } sibling.branches.push(branch) } else { - context.onError( + context.emitError( createCompilerError( dir.name === 'else' ? ErrorCodes.X_ELSE_NO_ADJACENT_IF diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts index e6288ac6..5467715f 100644 --- a/packages/compiler-core/src/errors.ts +++ b/packages/compiler-core/src/errors.ts @@ -5,6 +5,10 @@ export interface CompilerError extends SyntaxError { loc: Position } +export function defaultOnError(error: CompilerError) { + throw error +} + export function createCompilerError( code: ErrorCodes, loc: Position diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 250bebcf..495933ac 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -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 { transform, createDirectiveTransform, TransformOptions, - Transform + Transform, + DirectiveTransform } from './transform' export { generate, CodegenOptions, CodegenResult } from './codegen' export { ErrorCodes, CompilerError, createCompilerError } from './errors' - export * from './ast' diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 0012e379..0a9521ad 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -1,4 +1,9 @@ -import { ErrorCodes, CompilerError, createCompilerError } from './errors' +import { + ErrorCodes, + CompilerError, + createCompilerError, + defaultOnError +} from './errors' import { assert, advancePositionWithMutation, @@ -48,9 +53,7 @@ export const defaultParserOptions: Required = { 'apos;': "'", 'quot;': '"' }, - onError(error: CompilerError): void { - throw error - } + onError: defaultOnError } export const enum TextModes { @@ -62,7 +65,8 @@ export const enum TextModes { ATTRIBUTE_VALUE } -interface ParserContext extends Required { +interface ParserContext { + options: Required readonly originalSource: string source: string offset: number @@ -87,8 +91,10 @@ function createParserContext( options: ParserOptions ): ParserContext { return { - ...defaultParserOptions, - ...options, + options: { + ...defaultParserOptions, + ...options + }, column: 1, line: 1, offset: 0, @@ -115,7 +121,7 @@ function parseChildren( const s = context.source let node: any = null - if (startsWith(s, context.delimiters[0])) { + if (startsWith(s, context.options.delimiters[0])) { // '{{' node = parseInterpolation(context, mode) } else if (mode === TextModes.DATA && s[0] === '<') { @@ -194,7 +200,11 @@ function pushNode( if (!__DEV__ && node.type === NodeTypes.COMMENT) { return } - if (context.ignoreSpaces && node.type === NodeTypes.TEXT && node.isEmpty) { + if ( + context.options.ignoreSpaces && + node.type === NodeTypes.TEXT && + node.isEmpty + ) { return } @@ -311,13 +321,13 @@ function parseElement( const parent = last(ancestors) const element = parseTag(context, TagType.Start, parent) - if (element.isSelfClosing || context.isVoidTag(element.tag)) { + if (element.isSelfClosing || context.options.isVoidTag(element.tag)) { return element } // Children. ancestors.push(element) - const mode = (context.getTextMode( + const mode = (context.options.getTextMode( element.tag, element.ns ) as unknown) as TextModes @@ -368,7 +378,7 @@ function parseTag( const tag = match[1] const attrs = [] const directives = [] - const ns = context.getNamespace(tag, parent) + const ns = context.options.getNamespace(tag, parent) advanceBy(context, match[0].length) advanceSpaces(context) @@ -601,7 +611,7 @@ function parseInterpolation( context: ParserContext, mode: TextModes ): ExpressionNode | undefined { - const [open, close] = context.delimiters + const [open, close] = context.options.delimiters __DEV__ && assert(startsWith(context.source, open)) const closeIndex = context.source.indexOf(close, open.length) @@ -626,7 +636,7 @@ function parseInterpolation( function parseText(context: ParserContext, mode: TextModes): TextNode { __DEV__ && assert(context.source.length > 0) - const [open] = context.delimiters + const [open] = context.options.delimiters const endIndex = Math.min( ...[ context.source.indexOf('<', 1), @@ -691,7 +701,7 @@ function parseTextData( --length ) { name = context.source.substr(1, length) - value = context.namedCharacterReferences[name] + value = context.options.namedCharacterReferences[name] } if (value) { const semi = name.endsWith(';') @@ -837,7 +847,7 @@ function emitError( loc.offset += offset loc.column += offset } - context.onError(createCompilerError(code, loc)) + context.options.onError(createCompilerError(code, loc)) } function isEnd( diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 7db018a3..3f97359f 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -7,7 +7,7 @@ import { DirectiveNode } from './ast' import { isString } from '@vue/shared' -import { CompilerError } from './errors' +import { CompilerError, defaultOnError } from './errors' export type Transform = (node: ChildNode, context: TransformContext) => void @@ -18,11 +18,13 @@ export type DirectiveTransform = ( ) => false | void export interface TransformOptions { - transforms: Transform[] + transforms?: Transform[] onError?: (error: CompilerError) => void } -interface TransformContext extends Required { +interface TransformContext { + transforms: Transform[] + emitError: (error: CompilerError) => void parent: ParentNode ancestors: ParentNode[] childIndex: number @@ -42,10 +44,8 @@ function createTransformContext( options: TransformOptions ): TransformContext { const context: TransformContext = { - onError(error: CompilerError) { - throw error - }, - ...options, + transforms: options.transforms || [], + emitError: options.onError || defaultOnError, parent: root, ancestors: [], childIndex: 0, @@ -109,7 +109,7 @@ function traverseNode( ancestors: ParentNode[] ) { // apply transform plugins - const transforms = context.transforms + const { transforms } = context for (let i = 0; i < transforms.length; i++) { const plugin = transforms[i] plugin(node, context) diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts index 47c58e09..7b636355 100644 --- a/packages/compiler-dom/src/index.ts +++ b/packages/compiler-dom/src/index.ts @@ -1,46 +1,23 @@ import { - parse, - transform, - generate, - CompilerError, - Transform, + compile as baseCompile, + CompilerOptions, CodegenResult } from '@vue/compiler-core' import { parserOptionsMinimal } from './parserOptionsMinimal' import { parserOptionsStandard } from './parserOptionsStandard' -const parserOptions = __BROWSER__ ? parserOptionsMinimal : parserOptionsStandard - -export interface CompilerOptions { - module?: boolean - onError?(err: CompilerError): void - transforms?: Transform[] -} - export function compile( template: string, options: CompilerOptions = {} ): CodegenResult { - const { - module = false, - onError = (err: CompilerError) => { - throw err - }, - transforms = [] - } = options - const ast = parse(template, { - ...parserOptions, - onError - }) - transform(ast, { + return baseCompile(template, { + ...options, + ...(__BROWSER__ ? parserOptionsMinimal : parserOptionsStandard), transforms: [ - // TODO include core transforms - // TODO include DOM transforms - ...transforms // user transforms - ], - onError + // TODO include DOM-specific transforms + ...(options.transforms || []) // extra user transforms + ] }) - return generate(ast, { module }) } export * from '@vue/compiler-core' diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index cf79af41..d99a636f 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -24,6 +24,7 @@ import { isObject } from '@vue/shared' import { SuspenseBoundary } from './suspense' +import { CompilerOptions } from '@vue/compiler-dom' export type Data = { [key: string]: unknown } @@ -61,7 +62,7 @@ export interface SetupContext { emit: Emit } -type RenderFunction = () => VNodeChild +export type RenderFunction = () => VNodeChild export interface ComponentInternalInstance { type: FunctionalComponent | ComponentOptions @@ -298,8 +299,14 @@ export function handleSetupResult( finishComponentSetup(instance, parentSuspense) } -let compile: Function | undefined -export function registerCompiler(_compile: Function) { +type CompileFunction = ( + template: string, + options?: CompilerOptions +) => RenderFunction + +let compile: CompileFunction | undefined + +export function registerRuntimeCompiler(_compile: CompileFunction) { compile = _compile } @@ -311,7 +318,9 @@ function finishComponentSetup( if (!instance.render) { if (Component.template && !Component.render) { if (compile) { - Component.render = compile(Component.template) + Component.render = compile(Component.template, { + onError(err) {} + }) } else if (__DEV__) { warn( `Component provides template but the build of Vue you are running ` + diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index cd81043f..006ecf64 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -40,7 +40,7 @@ export { applyDirectives } from './directives' export { resolveComponent, resolveDirective } from './componentOptions' // Internal, for integration with runtime compiler -export { registerCompiler } from './component' +export { registerRuntimeCompiler } from './component' // Types ----------------------------------------------------------------------- @@ -50,7 +50,8 @@ export { VNode, VNodeTypes } from './vnode' export { Component, FunctionalComponent, - ComponentInternalInstance + ComponentInternalInstance, + RenderFunction } from './component' export { ComponentOptions, @@ -58,6 +59,7 @@ export { ComponentOptionsWithProps, ComponentOptionsWithArrayProps } from './componentOptions' + export { ComponentPublicInstance } from './componentPublicInstanceProxy' export { RendererOptions } from './createRenderer' export { Slot, Slots } from './componentSlots' diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 7635080a..1c89caba 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -1,15 +1,19 @@ // This package is the "full-build" that includes both the runtime // and the compiler, and supports on-the-fly compilation of the template option. -import { compile as baseCompile, CompilerOptions } from '@vue/compiler-dom' -import { registerCompiler } from '@vue/runtime-dom' +import { compile, CompilerOptions } from '@vue/compiler-dom' +import { registerRuntimeCompiler, RenderFunction } from '@vue/runtime-dom' -export function compile(template: string, options?: CompilerOptions): Function { - const { code } = baseCompile(template, options) - return new Function(`with(this){return ${code}}`) +function compileToFunction( + template: string, + 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' if (__BROWSER__ && __DEV__) {