wip: initial compat build setup

This commit is contained in:
Evan You 2021-04-03 11:55:44 -04:00
parent 870f2a7ba3
commit 24850a99c6
14 changed files with 431 additions and 18 deletions

View File

@ -41,7 +41,7 @@ module.exports = {
},
// Packages targeting DOM
{
files: ['packages/{vue,runtime-dom}/**'],
files: ['packages/{vue,vue-compat,runtime-dom}/**'],
rules: {
'no-restricted-globals': ['error', ...NodeGlobals]
}

View File

@ -8,6 +8,7 @@ declare var __ESM_BROWSER__: boolean
declare var __NODE_JS__: boolean
declare var __COMMIT__: string
declare var __VERSION__: string
declare var __COMPAT__: boolean
// Feature flags
declare var __FEATURE_OPTIONS_API__: boolean

View File

@ -13,7 +13,15 @@ import {
import { nodeOps } from './nodeOps'
import { patchProp, forcePatchProp } from './patchProp'
// Importing from the compiler, will be tree-shaken in prod
import { isFunction, isString, isHTMLTag, isSVGTag, extend } from '@vue/shared'
import {
isFunction,
isString,
isHTMLTag,
isSVGTag,
extend,
warnDeprecation,
DeprecationTypes
} from '@vue/shared'
declare module '@vue/reactivity' {
export interface RefUnwrapBailTypes {
@ -63,8 +71,24 @@ export const createApp = ((...args) => {
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
const container = normalizeContainer(containerOrSelector)
if (!container) return
// 2.x compat check
if (__COMPAT__ && __DEV__) {
for (let i = 0; i < container.attributes.length; i++) {
const attr = container.attributes[i]
if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
warnDeprecation(DeprecationTypes.DOM_TEMPLATE_MOUNT)
break
}
}
}
const component = app._component
if (!isFunction(component) && !component.render && !component.template) {
// __UNSAFE__
// Reason: potential execution of JS expressions in in-DOM template.
// The user must make sure the in-DOM template is trusted. If it's
// rendered by the server, the template should not contain any user data.
component.template = container.innerHTML
}
// clear content before mounting

View File

@ -0,0 +1,25 @@
export const enum DeprecationTypes {
DOM_TEMPLATE_MOUNT
}
type DeprecationData = {
message: string
link?: string
}
const deprecations: Record<DeprecationTypes, DeprecationData> = {
[DeprecationTypes.DOM_TEMPLATE_MOUNT]: {
message:
`Vue detected directives on the mount container. ` +
`In Vue 3, the container is no longer considered part of the template ` +
`and will not be processed/replaced.`,
link: `https://v3.vuejs.org/guide/migration/mount-changes.html`
}
}
export function warnDeprecation(key: DeprecationTypes) {
const { message, link } = deprecations[key]
console.warn(
`[Deprecation]: ${message}${link ? `\nFor more details, see ${link}` : ``}`
)
}

View File

@ -12,6 +12,7 @@ export * from './domAttrConfig'
export * from './escapeHtml'
export * from './looseEqual'
export * from './toDisplayString'
export * from './deprecations'
/**
* List of @babel/parser plugins that are used for template expression

View File

@ -0,0 +1 @@
# @vue/compat

View File

@ -0,0 +1,7 @@
{
"extends": "../../api-extractor.json",
"mainEntryPointFilePath": "./dist/packages/<unscopedPackageName>/src/index.d.ts",
"dtsRollup": {
"publicTrimmedFilePath": "./dist/<unscopedPackageName>.d.ts"
}
}

View File

@ -0,0 +1,7 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./dist/compat.cjs.prod.js')
} else {
module.exports = require('./dist/compat.cjs.js')
}

View File

@ -0,0 +1,44 @@
{
"name": "@vue/compat",
"version": "3.0.11",
"description": "@vue/compat",
"main": "index.js",
"module": "dist/vue.esm-bundler.js",
"types": "dist/vue.d.ts",
"unpkg": "dist/vue.global.js",
"jsdelivr": "dist/vue.global.js",
"files": [
"index.js",
"dist"
],
"buildOptions": {
"name": "Vue",
"filename": "vue",
"compat": true,
"formats": [
"esm-bundler",
"esm-bundler-runtime",
"cjs",
"global",
"global-runtime",
"esm-browser",
"esm-browser-runtime"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue/issues"
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/vue-compat#readme",
"peerDependencies": {
"vue": "3.0.11"
}
}

View File

@ -0,0 +1,157 @@
import { reactive } from '@vue/reactivity'
import {
createApp,
defineComponent,
nextTick,
App,
AppConfig,
Plugin,
Component,
ComponentOptions,
ComponentPublicInstance,
Directive,
RenderFunction,
isRuntimeOnly
} from '@vue/runtime-dom'
// TODO make these getter/setters and trigger deprecation warnings
export type LegacyConfig = AppConfig & {
/**
* @deprecated `config.silent` option has been removed
*/
silent?: boolean
/**
* @deprecated use __VUE_PROD_DEVTOOLS__ compile-time feature flag instead
* https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags
*/
devtools?: boolean
/**
* @deprecated use `config.isCustomElement` instead
* https://v3.vuejs.org/guide/migration/global-api.html#config-ignoredelements-is-now-config-iscustomelement
*/
ignoredElements?: (string | RegExp)[]
/**
* @deprecated
* https://v3.vuejs.org/guide/migration/keycode-modifiers.html
*/
keyCodes?: Record<string, number | number[]>
/**
* @deprecated
* https://v3.vuejs.org/guide/migration/global-api.html#config-productiontip-removed
*/
productionTip?: boolean
}
/**
* @deprecated the default `Vue` export has been removed in Vue 3. The type for
* the default export is provided only for migration purposes. Please use
* named imports instead - e.g. `import { createApp } from 'vue'`.
*/
export type GlobalVue = Pick<App, 'version' | 'component' | 'directive'> & {
// no inference here since these types are not meant for actual use - they
// are merely here to provide type checks for internal implementation and
// information for migration.
new (options?: ComponentOptions): ComponentPublicInstance
version: string
config: LegacyConfig
extend: typeof defineComponent
nextTick: typeof nextTick
use(plugin: Plugin, ...options: any[]): GlobalVue
mixin(mixin: ComponentOptions): GlobalVue
component(name: string): Component | undefined
component(name: string, component: Component): GlobalVue
directive(name: string): Directive | undefined
directive(name: string, directive: Directive): GlobalVue
compile(template: string): RenderFunction
/**
* @deprecated Vue 3 no longer needs set() for adding new properties.
*/
set(target: any, key: string | number | symbol, value: any): void
/**
* @deprecated Vue 3 no longer needs delete() for property deletions.
*/
delete(target: any, key: string | number | symbol): void
/**
* @deprecated use `reactive` instead.
*/
observable: typeof reactive
/**
* @deprecated filters have been removed from Vue 3.
*/
filter(name: string, arg: any): null
}
export const Vue: GlobalVue = function Vue(options: ComponentOptions = {}) {
const app = createApp(options)
// copy over global config mutations
for (const key in singletonApp.config) {
if (
key !== 'isNativeTag' &&
!(key === 'isCustomElement' && isRuntimeOnly())
) {
// @ts-ignore
app.config[key] = singletonApp.config[key]
}
}
if (options.el) {
return app.mount(options.el)
}
} as any
const singletonApp = createApp({})
Vue.version = __VERSION__
Vue.config = singletonApp.config
Vue.extend = defineComponent
Vue.nextTick = nextTick
Vue.set = (target, key, value) => {
// TODO deprecation warnings
target[key] = value
}
Vue.delete = (target, key) => {
// TODO deprecation warnings
delete target[key]
}
// TODO wrap with deprecation warning
Vue.observable = reactive
Vue.use = (p, ...options) => {
singletonApp.use(p, ...options)
return Vue
}
Vue.mixin = m => {
singletonApp.mixin(m)
return Vue
}
Vue.component = ((name: string, comp: any) => {
if (comp) {
singletonApp.component(name, comp)
return Vue
} else {
return singletonApp.component(name)
}
}) as any
Vue.directive = ((name: string, dir: any) => {
if (dir) {
singletonApp.directive(name, dir)
return Vue
} else {
return singletonApp.directive(name)
}
}) as any
Vue.filter = ((name: string, filter: any) => {
// TODO deprecation warning
// TODO compiler warning for filters (maybe behavior compat?)
}) as any

View File

@ -0,0 +1,14 @@
import { initCustomFormatter } from '@vue/runtime-dom'
export function initDev() {
if (__BROWSER__) {
if (!__ESM_BUNDLER__) {
console.info(
`You are running a development build of Vue.\n` +
`Make sure to use the production build (*.prod.js) when deploying for production.`
)
}
initCustomFormatter()
}
}

View File

@ -0,0 +1,93 @@
// This entry is the "full-build" that includes both the runtime
// and the compiler, and supports on-the-fly compilation of the template option.
import { initDev } from './dev'
import { compile, CompilerOptions, CompilerError } from '@vue/compiler-dom'
import { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom'
import { isString, NOOP, generateCodeFrame, extend } from '@vue/shared'
import { InternalRenderFunction } from 'packages/runtime-core/src/component'
import * as runtimeDom from '@vue/runtime-dom'
import { Vue } from './apiGlobal'
if (__DEV__) {
initDev()
}
const compileCache: Record<string, RenderFunction> = Object.create(null)
function compileToFunction(
template: string | HTMLElement,
options?: CompilerOptions
): RenderFunction {
if (!isString(template)) {
if (template.nodeType) {
template = template.innerHTML
} else {
__DEV__ && warn(`invalid template option: `, template)
return NOOP
}
}
const key = template
const cached = compileCache[key]
if (cached) {
return cached
}
if (template[0] === '#') {
const el = document.querySelector(template)
if (__DEV__ && !el) {
warn(`Template element not found or is empty: ${template}`)
}
// __UNSAFE__
// Reason: potential execution of JS expressions in in-DOM template.
// The user must make sure the in-DOM template is trusted. If it's rendered
// by the server, the template should not contain any user data.
template = el ? el.innerHTML : ``
}
const { code } = compile(
template,
extend(
{
hoistStatic: true,
onError(err: CompilerError) {
if (__DEV__) {
const message = `Template compilation error: ${err.message}`
const codeFrame =
err.loc &&
generateCodeFrame(
template as string,
err.loc.start.offset,
err.loc.end.offset
)
warn(codeFrame ? `${message}\n${codeFrame}` : message)
} else {
/* istanbul ignore next */
throw err
}
}
},
options
)
)
// The wildcard import results in a huge object with every export
// with keys that cannot be mangled, and can be quite heavy size-wise.
// In the global build we know `Vue` is available globally so we can avoid
// the wildcard object.
const render = (__GLOBAL__
? new Function(code)()
: new Function('Vue', code)(runtimeDom)) as RenderFunction
// mark the function as runtime compiled
;(render as InternalRenderFunction)._rc = true
return (compileCache[key] = render)
}
registerRuntimeCompiler(compileToFunction)
Vue.compile = compileToFunction
extend(Vue, runtimeDom)
export default Vue

View File

@ -0,0 +1,25 @@
// This entry exports the runtime only, and is built as
// `dist/vue.esm-bundler.js` which is used by default for bundlers.
import { initDev } from './dev'
import { warn } from '@vue/runtime-dom'
if (__DEV__) {
initDev()
}
export * from '@vue/runtime-dom'
export const compile = () => {
if (__DEV__) {
warn(
`Runtime compilation is not supported in this build of Vue.` +
(__ESM_BUNDLER__
? ` Configure your bundler to alias "vue" to "@vue/compat/dist/vue.esm-bundler.js".`
: __ESM_BROWSER__
? ` Use "vue.esm-browser.js" instead.`
: __GLOBAL__
? ` Use "vue.global.js" instead.`
: ``) /* should not happen */
)
}
}

View File

@ -1,3 +1,4 @@
// @ts-check
import path from 'path'
import ts from 'rollup-plugin-typescript2'
import replace from '@rollup/plugin-replace'
@ -10,10 +11,10 @@ if (!process.env.TARGET) {
const masterVersion = require('./package.json').version
const packagesDir = path.resolve(__dirname, 'packages')
const packageDir = path.resolve(packagesDir, process.env.TARGET)
const name = path.basename(packageDir)
const resolve = p => path.resolve(packageDir, p)
const pkg = require(resolve(`package.json`))
const packageOptions = pkg.buildOptions || {}
const name = packageOptions.filename || path.basename(packageDir)
// ensure TS checks only once for each build
let hasTSChecked = false
@ -89,6 +90,7 @@ function createConfig(format, output, plugins = []) {
const isBrowserESMBuild = /esm-browser/.test(format)
const isNodeBuild = format === 'cjs'
const isGlobalBuild = /global/.test(format)
const isCompatBuild = !!packageOptions.compat
if (isGlobalBuild) {
output.name = packageOptions.name
@ -116,19 +118,23 @@ function createConfig(format, output, plugins = []) {
const entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`
const external =
isGlobalBuild || isBrowserESMBuild
? packageOptions.enableNonBrowserBranches
? []
: // normal browser builds - non-browser only imports are tree-shaken,
let external = []
if (isGlobalBuild || isBrowserESMBuild) {
if (!packageOptions.enableNonBrowserBranches) {
// normal browser builds - non-browser only imports are tree-shaken,
// they are only listed here to suppress warnings.
['source-map', '@babel/parser', 'estree-walker']
: // Node / esm-bundler builds. Externalize everything.
[
external = ['source-map', '@babel/parser', 'estree-walker']
}
} else if (!isCompatBuild) {
// Node / esm-bundler builds.
// externalize all deps unless it's the compat build.
external = [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
...['path', 'url', 'stream'] // for @vue/compiler-sfc / server-renderer
]
}
// the browser builds of @vue/compiler-sfc requires postcss to be available
// as a global (e.g. http://wzrd.in/standalone/postcss)
@ -139,9 +145,11 @@ function createConfig(format, output, plugins = []) {
const nodePlugins =
packageOptions.enableNonBrowserBranches && format !== 'cjs'
? [
// @ts-ignore
require('@rollup/plugin-commonjs')({
sourceMap: false
}),
// @ts-ignore
require('rollup-plugin-polyfill-node')(),
require('@rollup/plugin-node-resolve').nodeResolve()
]
@ -165,7 +173,8 @@ function createConfig(format, output, plugins = []) {
(isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) &&
!packageOptions.enableNonBrowserBranches,
isGlobalBuild,
isNodeBuild
isNodeBuild,
isCompatBuild
),
...nodePlugins,
...plugins
@ -188,7 +197,8 @@ function createReplacePlugin(
isBrowserESMBuild,
isBrowserBuild,
isGlobalBuild,
isNodeBuild
isNodeBuild,
isCompatBuild
) {
const replacements = {
__COMMIT__: `"${process.env.COMMIT}"`,
@ -208,6 +218,9 @@ function createReplacePlugin(
// is targeting Node (SSR)?
__NODE_JS__: isNodeBuild,
// 2.x compat build
__COMPAT__: isCompatBuild,
// feature flags
__FEATURE_SUSPENSE__: true,
__FEATURE_OPTIONS_API__: isBundlerESMBuild ? `__VUE_OPTIONS_API__` : true,
@ -231,6 +244,7 @@ function createReplacePlugin(
}
})
return replace({
// @ts-ignore
values: replacements,
preventAssignment: true
})