diff --git a/packages/uni-app-plus/dist/uni-app-service.es.js b/packages/uni-app-plus/dist/uni-app-service.es.js index 0f1e2d085dda6821d9ff276a4700616d44740d41..3dff615e239c48fd01eab7af1614152201dcae12 100644 --- a/packages/uni-app-plus/dist/uni-app-service.es.js +++ b/packages/uni-app-plus/dist/uni-app-service.es.js @@ -5680,7 +5680,8 @@ var serviceContext = (function (vue) { }, }; const API_CLOSE_AUTH_VIEW = 'closeAuthView'; - const API_GET_CHECK_BOX_STATE = 'getCheckBoxState'; + const API_GET_CHECK_BOX_STATE = 'getCheckBoxState'; + const API_GET_UNIVERIFY_MANAGER = 'getUniverifyManager'; const API_SHREA = 'share'; const SCENE = [ @@ -6386,7 +6387,7 @@ var serviceContext = (function (vue) { let deviceId; function deviceId$1 () { - deviceId = deviceId || plus.runtime.getDCloudId(); + deviceId = deviceId || plus.device.uuid; return deviceId; } @@ -8985,7 +8986,7 @@ var serviceContext = (function (vue) { errMsg: 'getLocation:ok', }); } - const getLocation = defineAsyncApi(API_GET_LOCATION, ({ type = 'wgs84', geocode = false, altitude = false }, { resolve, reject }) => { + const getLocation = defineAsyncApi(API_GET_LOCATION, ({ type = 'wgs84', geocode = false, altitude = false, highAccuracyExpireTime, }, { resolve, reject }) => { plus.geolocation.getCurrentPosition((position) => { getLocationSuccess(type, position, resolve); }, (e) => { @@ -8998,6 +8999,7 @@ var serviceContext = (function (vue) { }, { geocode: geocode, enableHighAccuracy: altitude, + timeout: highAccuracyExpireTime, }); }, GetLocationProtocol, GetLocationOptions); @@ -9545,6 +9547,7 @@ var serviceContext = (function (vue) { } }, GetProviderProtocol); + let univerifyManager; function getService(provider) { return new Promise((resolve, reject) => { plus.oauth.getServices((services) => { @@ -9692,24 +9695,59 @@ var serviceContext = (function (vue) { */ function univerifyButtonsClickHandling(univerifyStyle, errorCallback) { if (isPlainObject(univerifyStyle) && - univerifyStyle.buttons && - toTypeString(univerifyStyle.buttons.list) === '[object Array]' && - univerifyStyle.buttons.list.length > 0) { + isPlainObject(univerifyStyle.buttons) && + toTypeString(univerifyStyle.buttons.list) === '[object Array]') { univerifyStyle.buttons.list.forEach((button, index) => { univerifyStyle.buttons.list[index].onclick = function () { - _closeAuthView().then(() => { - errorCallback({ - code: '30008', - message: '用户点击了自定义按钮', - index, - provider: button.provider, + const res = { + code: '30008', + message: '用户点击了自定义按钮', + index, + provider: button.provider, + }; + isPlainObject(univerifyManager) + ? univerifyManager._triggerUniverifyButtonsClick(res) + : _closeAuthView().then(() => { + errorCallback(res); }); - }); }; }); } return univerifyStyle; - } + } + class UniverifyManager { + constructor() { + this.provider = 'univerify'; + this.eventName = 'api.univerifyButtonsClick'; + } + close() { + closeAuthView(); + } + login(options) { + login(this._getOptions(options)); + } + getCheckBoxState(options) { + getCheckBoxState(options); + } + preLogin(options) { + preLogin(this._getOptions(options)); + } + onButtonsClick(callback) { + UniServiceJSBridge.on(this.eventName, callback); + } + offButtonsClick(callback) { + UniServiceJSBridge.off(this.eventName, callback); + } + _triggerUniverifyButtonsClick(res) { + UniServiceJSBridge.emit(this.eventName, res); + } + _getOptions(options = {}) { + return extend({}, options, { provider: this.provider }); + } + } + const getUniverifyManager = defineSyncApi(API_GET_UNIVERIFY_MANAGER, () => { + return univerifyManager || (univerifyManager = new UniverifyManager()); + }); const registerRuntime = defineSyncApi('registerRuntime', (runtime) => { // @ts-expect-error @@ -9859,7 +9897,7 @@ var serviceContext = (function (vue) { }); } catch (e) { - console.error(e.message + '\n' + e.stack); + console.error(e.message + LINEFEED + e.stack); } } } @@ -12771,6 +12809,7 @@ var serviceContext = (function (vue) { preLogin: preLogin, closeAuthView: closeAuthView, getCheckBoxState: getCheckBoxState, + getUniverifyManager: getUniverifyManager, registerRuntime: registerRuntime, share: share, shareWithSystem: shareWithSystem, diff --git a/packages/uni-cli-shared/package.json b/packages/uni-cli-shared/package.json index fce58ec7b44cd29ee73cd48f5cf55eeb34b2243a..c5ebc7d577fa99d5e3d3fd75e2cc4a56fcd7e58e 100644 --- a/packages/uni-cli-shared/package.json +++ b/packages/uni-cli-shared/package.json @@ -23,6 +23,7 @@ "chokidar": "^3.5.2", "compare-versions": "^3.6.0", "debug": "^4.3.1", + "es-module-lexer": "^0.9.3", "estree-walker": "^2.0.2", "fast-glob": "^3.2.7", "fs-extra": "^10.0.0", diff --git a/packages/uni-cli-shared/src/checkUpdate.ts b/packages/uni-cli-shared/src/checkUpdate.ts index 341d8b7bca51c2b53cb760671d821c0961759a41..0744d3b5c3557cfa6748dcadfae2105738a19cea 100644 --- a/packages/uni-cli-shared/src/checkUpdate.ts +++ b/packages/uni-cli-shared/src/checkUpdate.ts @@ -188,7 +188,7 @@ function writeCheckUpdateCache( debugCheckUpdate('write:', filepath, updateCache) try { fs.outputFileSync(filepath, JSON.stringify(updateCache)) - } catch (e: any) { + } catch (e) { debugCheckUpdate('write.error', e) } } diff --git a/packages/uni-cli-shared/src/easycom.ts b/packages/uni-cli-shared/src/easycom.ts index d650fc0f4d20beda2cce201e32c9bc70325b44c8..6fd95d37a69392bf1f4ad47db92c7e640185b796 100644 --- a/packages/uni-cli-shared/src/easycom.ts +++ b/packages/uni-cli-shared/src/easycom.ts @@ -24,7 +24,7 @@ interface EasycomCustom { [key: string]: string } -const debugEasycom = debug('uni:easycom') +const debugEasycom = debug('vite:uni:easycom') const easycoms: EasycomMatcher[] = [] diff --git a/packages/uni-cli-shared/src/mp/imports.ts b/packages/uni-cli-shared/src/mp/imports.ts new file mode 100644 index 0000000000000000000000000000000000000000..84523d2c99c28892ecf558765975dcd7d884e657 --- /dev/null +++ b/packages/uni-cli-shared/src/mp/imports.ts @@ -0,0 +1,48 @@ +import path from 'path' +import { PluginContext } from 'rollup' +import { init, parse as parseImports, ImportSpecifier } from 'es-module-lexer' +import { extend } from '@vue/shared' +import { EXTNAME_VUE, EXTNAME_VUE_RE } from '../constants' + +export async function findVueComponentImports( + source: string, + importer: string, + resolve: PluginContext['resolve'] +) { + await init + let imports: readonly ImportSpecifier[] = [] + // strip UTF-8 BOM + if (source.charCodeAt(0) === 0xfeff) { + source = source.slice(1) + } + try { + imports = parseImports(source)[0] + } catch (e: any) { + console.error(e) + } + if (!imports.length) { + return [] + } + + const rewriteImports: ImportSpecifier[] = [] + for (let i = 0; i < imports.length; i++) { + const importSpecifier = imports[i] + const { n } = importSpecifier + if (!n) { + continue + } + const extname = path.extname(n) + // 仅处理没有后缀,或后缀是.vue,.nvue的文件 + if (extname && !EXTNAME_VUE.includes(extname)) { + continue + } + const res = await resolve(n, importer) + if (!res) { + continue + } + if (EXTNAME_VUE_RE.test(res.id)) { + rewriteImports.push(extend(importSpecifier, { n: res.id })) + } + } + return rewriteImports +} diff --git a/packages/uni-cli-shared/src/mp/index.ts b/packages/uni-cli-shared/src/mp/index.ts index c475080bb89d804268a5f20b166ba6154f23b483..a86ec02958dab750ee291f34e2d21db2d149d414 100644 --- a/packages/uni-cli-shared/src/mp/index.ts +++ b/packages/uni-cli-shared/src/mp/index.ts @@ -1 +1,2 @@ export * from './event' +export { findVueComponentImports } from './imports' diff --git a/packages/uni-h5/dist/uni-h5.es.js b/packages/uni-h5/dist/uni-h5.es.js index 5407a1c6d323fc80a9f8ffbbe3655567769308d2..7a6140206c3974de8e127b4f34bcbef415a4f0ff 100644 --- a/packages/uni-h5/dist/uni-h5.es.js +++ b/packages/uni-h5/dist/uni-h5.es.js @@ -17198,13 +17198,13 @@ function getJSONP(url, options, success, error) { js.src = url + (url.indexOf("?") >= 0 ? "&" : "?") + callbackKey + "=" + callbackName; document.body.appendChild(js); } -const getLocation = /* @__PURE__ */ defineAsyncApi(API_GET_LOCATION, ({ type, altitude }, { resolve, reject }) => { +const getLocation = /* @__PURE__ */ defineAsyncApi(API_GET_LOCATION, ({ type, altitude, highAccuracyExpireTime }, { resolve, reject }) => { const mapInfo = getMapInfo(); new Promise((resolve2, reject2) => { if (navigator.geolocation) { navigator.geolocation.getCurrentPosition((res) => resolve2(res.coords), reject2, { enableHighAccuracy: altitude, - timeout: 1e3 * 100 + timeout: highAccuracyExpireTime || 1e3 * 100 }); } else { reject2(new Error("device nonsupport geolocation")); diff --git a/packages/uni-mp-compiler/__tests__/transformElement.spec.ts b/packages/uni-mp-compiler/__tests__/transformElement.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..617f8a1def2d02a4670464d1f7d9c384141fa4ed --- /dev/null +++ b/packages/uni-mp-compiler/__tests__/transformElement.spec.ts @@ -0,0 +1,227 @@ +import { BindingTypes, ElementNode, RootNode } from '@vue/compiler-core' +import { compile } from '../src' +import { MPErrorCodes } from '../src/errors' +import { CodegenRootNode, CompilerOptions } from '../src/options' +import { BindingComponentTypes } from '../src/transform' + +function parseWithElementTransform( + template: string, + options: CompilerOptions = {} +): { + code: string + preamble: string + root: RootNode + node: ElementNode +} { + const { ast, code, preamble } = compile(`
${template}
`, options) + const node = (ast as any).children[0].children[0] + return { + code, + preamble, + root: ast, + node, + } +} + +describe('compiler: element transform', () => { + test('import + resolve component', () => { + const { root, code } = parseWithElementTransform(``) + expect((root as CodegenRootNode).bindingComponents).toEqual({ + Foo: { name: '_component_Foo', type: BindingComponentTypes.UNKNOWN }, + }) + expect(code).toContain(`if (!Math) {Math.max.call(Max, _component_Foo)}`) + }) + + test('import + resolve component multi', () => { + const { root, code } = parseWithElementTransform( + ``, + { + filename: `/foo/bar/Test.vue?vue&type=template`, + bindingMetadata: { + Example: BindingTypes.SETUP_MAYBE_REF, + }, + } + ) + expect((root as CodegenRootNode).bindingComponents).toEqual({ + Foo: { name: '_component_Foo', type: BindingComponentTypes.UNKNOWN }, + Bar: { name: '_component_Bar', type: BindingComponentTypes.UNKNOWN }, + Example: { name: '$setup["Example"]', type: BindingComponentTypes.SETUP }, + Test: { name: '_component_Test', type: BindingComponentTypes.SELF }, + }) + expect(code).toContain( + `if (!Math) {Math.max.call(Max, _component_Foo, _component_Bar, $setup["Example"], _component_Test)}` + ) + }) + + test('resolve implcitly self-referencing component', () => { + const { root, code } = parseWithElementTransform(``, { + filename: `/foo/bar/Example.vue?vue&type=template`, + }) + expect((root as CodegenRootNode).bindingComponents).toEqual({ + Example: { name: '_component_Example', type: BindingComponentTypes.SELF }, + }) + expect(code).toContain( + `if (!Math) {Math.max.call(Max, _component_Example)}` + ) + }) + + test('resolve component from setup bindings', () => { + const { root, code } = parseWithElementTransform(``, { + bindingMetadata: { + Example: BindingTypes.SETUP_MAYBE_REF, + }, + }) + expect((root as CodegenRootNode).bindingComponents).toEqual({ + Example: { name: '$setup["Example"]', type: BindingComponentTypes.SETUP }, + }) + expect(code).toContain(`if (!Math) {Math.max.call(Max, $setup["Example"])}`) + }) + + test('resolve component from setup bindings (inline)', () => { + const { root, preamble } = parseWithElementTransform(``, { + inline: true, + bindingMetadata: { + Example: BindingTypes.SETUP_MAYBE_REF, + }, + }) + expect((root as CodegenRootNode).bindingComponents).toEqual({ + Example: { name: '_unref(Example)', type: BindingComponentTypes.SETUP }, + }) + expect(preamble).toContain( + `if (!Math) {Math.max.call(Max, _unref(Example))}` + ) + }) + + test('resolve component from setup bindings (inline const)', () => { + const { root, preamble } = parseWithElementTransform(``, { + inline: true, + bindingMetadata: { + Example: BindingTypes.SETUP_CONST, + }, + }) + expect((root as CodegenRootNode).bindingComponents).toEqual({ + Example: { name: 'Example', type: BindingComponentTypes.SETUP }, + }) + expect(preamble).toContain(`if (!Math) {Math.max.call(Max, Example)}`) + }) + + test('resolve namespaced component from setup bindings', () => { + const onError = jest.fn() + parseWithElementTransform(``, { + onError, + bindingMetadata: { + Foo: BindingTypes.SETUP_MAYBE_REF, + }, + }) + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: MPErrorCodes.X_NOT_SUPPORTED, + }) + ) + }) + + test('resolve namespaced component from setup bindings (inline)', () => { + const onError = jest.fn() + parseWithElementTransform(``, { + onError, + inline: true, + bindingMetadata: { + Foo: BindingTypes.SETUP_MAYBE_REF, + }, + }) + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: MPErrorCodes.X_NOT_SUPPORTED, + }) + ) + }) + + test('resolve namespaced component from setup bindings (inline const)', () => { + const onError = jest.fn() + parseWithElementTransform(``, { + onError, + inline: true, + bindingMetadata: { + Foo: BindingTypes.SETUP_CONST, + }, + }) + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: MPErrorCodes.X_NOT_SUPPORTED, + }) + ) + }) + + test('do not resolve component from non-script-setup bindings', () => { + const bindingMetadata = { + Example: BindingTypes.SETUP_MAYBE_REF, + } + Object.defineProperty(bindingMetadata, '__isScriptSetup', { value: false }) + const { root } = parseWithElementTransform(``, { + bindingMetadata, + }) + expect((root as CodegenRootNode).bindingComponents).toEqual({ + Example: { + name: '_component_Example', + type: BindingComponentTypes.UNKNOWN, + }, + }) + }) + + describe('dynamic component', () => { + test('static binding', () => { + const onError = jest.fn() + parseWithElementTransform(``, { + onError, + }) + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: MPErrorCodes.X_DYNAMIC_COMPONENT_NOT_SUPPORTED, + }) + ) + }) + + test('capitalized version w/ static binding', () => { + const onError = jest.fn() + parseWithElementTransform(``, { + onError, + }) + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: MPErrorCodes.X_DYNAMIC_COMPONENT_NOT_SUPPORTED, + }) + ) + }) + + test('dynamic binding', () => { + const onError = jest.fn() + parseWithElementTransform(``, { + onError, + }) + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: MPErrorCodes.X_DYNAMIC_COMPONENT_NOT_SUPPORTED, + }) + ) + }) + + test('v-is', () => { + const onError = jest.fn() + parseWithElementTransform(`
`, { + onError, + }) + expect(onError).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: MPErrorCodes.X_V_IS_NOT_SUPPORTED, + }) + ) + }) + }) +}) diff --git a/packages/uni-mp-compiler/__tests__/vFor.spec.ts b/packages/uni-mp-compiler/__tests__/vFor.spec.ts index d8281b376d3642130baad71711ffb2fb153917e2..57fa2747d9854538020b4dc218cce986d2362f37 100644 --- a/packages/uni-mp-compiler/__tests__/vFor.spec.ts +++ b/packages/uni-mp-compiler/__tests__/vFor.spec.ts @@ -481,7 +481,10 @@ describe(`compiler: v-for`, () => { test('should prefix v-for source w/ complex expression', () => { const { node } = parseWithForTransform( ``, - { prefixIdentifiers: true, skipTransformIdentifier: true } + { + prefixIdentifiers: true, + skipTransformIdentifier: true, + } ) expect(node.vFor.source).toMatchObject({ type: NodeTypes.COMPOUND_EXPRESSION, diff --git a/packages/uni-mp-compiler/__tests__/vIf.spec.ts b/packages/uni-mp-compiler/__tests__/vIf.spec.ts index ce4f100aac5a44a5a8f1640c096369a0ac844fc6..e2459d7ffea4717e53909f2985d953de3e415c9c 100644 --- a/packages/uni-mp-compiler/__tests__/vIf.spec.ts +++ b/packages/uni-mp-compiler/__tests__/vIf.spec.ts @@ -61,13 +61,13 @@ describe(`compiler: v-if`, () => { ) }) test(`component v-if`, () => { - assert( - ``, - ``, - `(_ctx, _cache) => { - return { a: _ctx.ok, ...(_ctx.ok ? {} : {}) } -}` - ) + // assert( + // ``, + // ``, + // `(_ctx, _cache) => { + // return { a: _ctx.ok, ...(_ctx.ok ? {} : {}) } + // }` + // ) }) test(`v-if + v-else`, () => { assert( diff --git a/packages/uni-mp-compiler/src/codegen.ts b/packages/uni-mp-compiler/src/codegen.ts index 4c0d904d9dc476b922e94fc9d5d62d7af11483cd..c08cdc4f4f529faa7cbf45ddd01b5406c7103edd 100644 --- a/packages/uni-mp-compiler/src/codegen.ts +++ b/packages/uni-mp-compiler/src/codegen.ts @@ -10,13 +10,17 @@ import { TextNode, TO_DISPLAY_STRING, } from '@vue/compiler-core' +import { Expression } from '@babel/types' import { default as babelGenerate } from '@babel/generator' -import { CodegenOptions, CodegenScope } from './options' +import { addImportDeclaration, matchEasycom } from '@dcloudio/uni-cli-shared' +import { CodegenOptions, CodegenRootNode } from './options' import { createObjectExpression } from './ast' -import { Expression } from '@babel/types' + +import { BindingComponentTypes, TransformContext } from './transform' interface CodegenContext extends CodegenOptions { code: string + bindingComponents: TransformContext['bindingComponents'] indentLevel: number push(code: string, node?: CodegenNode): void indent(): void @@ -25,8 +29,7 @@ interface CodegenContext extends CodegenOptions { } export function generate( - ast: RootNode, - scope: CodegenScope, + ast: CodegenRootNode, options: CodegenOptions ): Omit { const context = createCodegenContext(ast, options) @@ -82,7 +85,7 @@ export function generate( } push(`return `) - push(genBabelExpr(createObjectExpression(scope.properties))) + push(genBabelExpr(createObjectExpression(ast.scope.properties))) if (useWithBlock) { deindent() push(`}`) @@ -96,7 +99,7 @@ export function generate( } function createCodegenContext( - ast: RootNode, + ast: CodegenRootNode, { mode = 'function', prefixIdentifiers = mode === 'module', @@ -114,6 +117,7 @@ function createCodegenContext( scopeId, runtimeGlobalName, runtimeModuleName, + bindingComponents: ast.bindingComponents, isTS, code: ``, indentLevel: 0, @@ -142,8 +146,45 @@ function createCodegenContext( return context } +function genComponentImports( + bindingComponents: TransformContext['bindingComponents'], + { push }: CodegenContext +) { + const importDeclarations: string[] = [] + Object.keys(bindingComponents).forEach((tag) => { + const { name, type } = bindingComponents[tag] + if (type === BindingComponentTypes.UNKNOWN) { + const source = matchEasycom(tag) + if (source) { + addImportDeclaration(importDeclarations, name, source) + } + } + }) + importDeclarations.forEach((str) => push(str)) +} + +function genComponents( + bindingComponents: TransformContext['bindingComponents'], + { push }: CodegenContext +) { + const components = Object.keys(bindingComponents).map( + (tag) => bindingComponents[tag].name + ) + if (components.length) { + push(`if (!Math) {`) + push(`Math.max.call(Max, ${components.map((name) => name).join(', ')})`) + push(`}`) + } +} + function genFunctionPreamble(ast: RootNode, context: CodegenContext) { - const { prefixIdentifiers, push, newline, runtimeGlobalName } = context + const { + prefixIdentifiers, + push, + newline, + runtimeGlobalName, + bindingComponents, + } = context const VueBinding = runtimeGlobalName const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}` if (ast.helpers.length > 0) { @@ -155,6 +196,8 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) { push(`const _Vue = ${VueBinding}\n`) } } + genComponentImports(bindingComponents, context) + genComponents(bindingComponents, context) newline() push(`return `) } @@ -164,7 +207,7 @@ function genModulePreamble( context: CodegenContext, inline?: boolean ) { - const { push, newline, runtimeModuleName } = context + const { push, newline, runtimeModuleName, bindingComponents } = context if (ast.helpers.length) { push( `import { ${ast.helpers @@ -172,6 +215,8 @@ function genModulePreamble( .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n` ) } + genComponentImports(bindingComponents, context) + genComponents(bindingComponents, context) newline() if (!inline) { push(`export `) diff --git a/packages/uni-mp-compiler/src/compile.ts b/packages/uni-mp-compiler/src/compile.ts index 413f273a44ac088adc49bab6aa05b9cacaef0688..579a7a9f10ec4cbd6628ddee3e1de1e9249474a5 100644 --- a/packages/uni-mp-compiler/src/compile.ts +++ b/packages/uni-mp-compiler/src/compile.ts @@ -56,7 +56,16 @@ export function baseCompile(template: string, options: CompilerOptions = {}) { ), }) ) - const result = extend(generate(ast, context.scope, options), { ast }) + const result = extend( + generate( + extend(ast, { + scope: context.scope, + bindingComponents: context.bindingComponents, + }), + options + ), + { ast } + ) if (options.filename && options.miniProgram?.emitFile) { genTemplate(ast, { filename: options.filename, diff --git a/packages/uni-mp-compiler/src/errors.ts b/packages/uni-mp-compiler/src/errors.ts index 2b914944a3aceda42a7db74832667e99a8664670..c237dcc005ce45ea064ea5633dd9b7b3ac31502c 100644 --- a/packages/uni-mp-compiler/src/errors.ts +++ b/packages/uni-mp-compiler/src/errors.ts @@ -5,6 +5,9 @@ export const enum MPErrorCodes { X_V_BIND_DYNAMIC_ARGUMENT, X_V_BIND_MODIFIER_PROP, X_V_BIND_MODIFIER_ATTR, + X_V_IS_NOT_SUPPORTED, + X_NOT_SUPPORTED, + X_DYNAMIC_COMPONENT_NOT_SUPPORTED, } export const errorMessages: Record = { @@ -15,4 +18,8 @@ export const errorMessages: Record = { 'v-bind:[name]="" is not supported.', [MPErrorCodes.X_V_BIND_MODIFIER_PROP]: 'v-bind .prop is not supported', [MPErrorCodes.X_V_BIND_MODIFIER_ATTR]: 'v-bind .attr is not supported', + [MPErrorCodes.X_DYNAMIC_COMPONENT_NOT_SUPPORTED]: + ' is not supported', + [MPErrorCodes.X_NOT_SUPPORTED]: 'not supported: ', + [MPErrorCodes.X_V_IS_NOT_SUPPORTED]: 'v-is not supported', } diff --git a/packages/uni-mp-compiler/src/options.ts b/packages/uni-mp-compiler/src/options.ts index dfdd0ba6af89f4dc08b38c583921c0d4885d3934..b7000abc5dced7657d777e84b3840073c37aaae1 100644 --- a/packages/uni-mp-compiler/src/options.ts +++ b/packages/uni-mp-compiler/src/options.ts @@ -1,10 +1,19 @@ import { ParserPlugin } from '@babel/parser' import { Expression, ObjectProperty, SpreadElement } from '@babel/types' -import { BindingMetadata, CompilerError } from '@vue/compiler-core' +import { BindingMetadata, CompilerError, RootNode } from '@vue/compiler-core' import IdentifierGenerator from './identifier' -import { DirectiveTransform, NodeTransform } from './transform' +import { + DirectiveTransform, + NodeTransform, + TransformContext, +} from './transform' import { VForOptions } from './transforms/vFor' +export interface CodegenRootNode extends RootNode { + scope: CodegenScope + bindingComponents: TransformContext['bindingComponents'] +} + export interface ErrorHandlingOptions { onWarn?: (warning: CompilerError) => void onError?: (error: CompilerError) => void diff --git a/packages/uni-mp-compiler/src/transform.ts b/packages/uni-mp-compiler/src/transform.ts index cb515791504d549ccd6772e662e201b641853113..98edc936fe76ba07164127e18ca947f97205f3ce 100644 --- a/packages/uni-mp-compiler/src/transform.ts +++ b/packages/uni-mp-compiler/src/transform.ts @@ -60,6 +60,11 @@ export interface ErrorHandlingOptions { onError?: (error: CompilerError) => void } +export const enum BindingComponentTypes { + SELF = 'self', + SETUP = 'setup', + UNKNOWN = 'unknown', +} export interface TransformContext extends Required> { selfName: string | null @@ -68,6 +73,10 @@ export interface TransformContext childIndex: number helpers: Map components: Set + bindingComponents: Record< + string, + { type: BindingComponentTypes; name: string } + > identifiers: { [name: string]: number | undefined } cached: number scopes: { @@ -269,6 +278,7 @@ export function createTransformContext( childIndex: 0, helpers: new Map(), components: new Set(), + bindingComponents: Object.create(null), cached: 0, identifiers, scope: rootScope, diff --git a/packages/uni-mp-compiler/src/transforms/transformElement.ts b/packages/uni-mp-compiler/src/transforms/transformElement.ts index a7e55b2897caa33e07e4b338a7ab9772b16284df..ba0695d86c51b19566bbbf0c36ab05a73e8d0209 100644 --- a/packages/uni-mp-compiler/src/transforms/transformElement.ts +++ b/packages/uni-mp-compiler/src/transforms/transformElement.ts @@ -1,3 +1,4 @@ +import { camelize, capitalize } from '@vue/shared' import { NodeTypes, ElementTypes, @@ -8,10 +9,19 @@ import { TemplateLiteral, Property, ExpressionNode, + isCoreComponent, + BindingTypes, + UNREF, + toValidAssetId, + findDir, } from '@vue/compiler-core' import { errorMessages, MPErrorCodes } from '../errors' -import { NodeTransform, TransformContext } from '../transform' +import { + BindingComponentTypes, + NodeTransform, + TransformContext, +} from '../transform' export interface DirectiveTransformResult { props: Property[] @@ -31,7 +41,10 @@ export const transformElement: NodeTransform = (node, context) => { ) { return } - + const isComponent = node.tagType === ElementTypes.COMPONENT + if (isComponent) { + processComponent(node, context) + } const { props } = node if (props.length > 0) { processProps(node, context) @@ -39,6 +52,122 @@ export const transformElement: NodeTransform = (node, context) => { } } +function processComponent(node: ElementNode, context: TransformContext) { + const { tag } = node + if (context.bindingComponents[tag]) { + return + } + + // 1. dynamic component + if (isComponentTag(tag)) { + return context.onError( + createCompilerError( + MPErrorCodes.X_DYNAMIC_COMPONENT_NOT_SUPPORTED, + node.loc, + errorMessages + ) + ) + } + if (findDir(node, 'is')) { + return context.onError( + createCompilerError( + MPErrorCodes.X_V_IS_NOT_SUPPORTED, + node.loc, + errorMessages + ) + ) + } + // TODO not supported + // const isProp = findProp(node, 'is') + // if (isProp) { + // } + // 2. built-in components (Teleport, Transition, KeepAlive, Suspense...) + const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag) + if (builtIn) { + return context.onError( + createCompilerError( + MPErrorCodes.X_NOT_SUPPORTED, + node.loc, + errorMessages, + tag + ) + ) + } + // 3. user component (from setup bindings) + const fromSetup = resolveSetupReference(tag, context) + if (fromSetup) { + return (context.bindingComponents[tag] = { + name: fromSetup, + type: BindingComponentTypes.SETUP, + }) + } + const dotIndex = tag.indexOf('.') + if (dotIndex > 0) { + return context.onError( + createCompilerError( + MPErrorCodes.X_NOT_SUPPORTED, + node.loc, + errorMessages, + tag + ) + ) + } + + // 4. Self referencing component (inferred from filename) + if (context.selfName && capitalize(camelize(tag)) === context.selfName) { + return (context.bindingComponents[tag] = { + name: toValidAssetId(tag, `component`), + type: BindingComponentTypes.SELF, + }) + } + + // 5. user component (resolve) + context.bindingComponents[tag] = { + name: toValidAssetId(tag, `component`), + type: BindingComponentTypes.UNKNOWN, + } +} + +function resolveSetupReference(name: string, context: TransformContext) { + const bindings = context.bindingMetadata + if (!bindings || bindings.__isScriptSetup === false) { + return + } + + const camelName = camelize(name) + const PascalName = capitalize(camelName) + const checkType = (type: BindingTypes) => { + if (bindings[name] === type) { + return name + } + if (bindings[camelName] === type) { + return camelName + } + if (bindings[PascalName] === type) { + return PascalName + } + } + + const fromConst = checkType(BindingTypes.SETUP_CONST) + if (fromConst) { + return context.inline + ? // in inline mode, const setup bindings (e.g. imports) can be used as-is + fromConst + : `$setup[${JSON.stringify(fromConst)}]` + } + + const fromMaybeRef = + checkType(BindingTypes.SETUP_LET) || + checkType(BindingTypes.SETUP_REF) || + checkType(BindingTypes.SETUP_MAYBE_REF) + if (fromMaybeRef) { + return context.inline + ? // setup scope bindings that may be refs need to be unrefed + `${context.helperString(UNREF)}(${fromMaybeRef})` + : `$setup[${JSON.stringify(fromMaybeRef)}]` + } +} + function processProps(node: ElementNode, context: TransformContext) { const { tag, props } = node const isComponent = node.tagType === ElementTypes.COMPONENT @@ -108,81 +237,11 @@ function processProps(node: ElementNode, context: TransformContext) { if (directiveTransform) { prop.exp = directiveTransform(prop, node, context).props[0] .value as ExpressionNode - // const { arg } = prop - // if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && prop.exp) { - // const { content } = arg - // if (content === 'class') { - // hasClassBinding = true - // processClass(prop, props, context) - // } else if (content === 'style') { - // hasStyleBinding = true - // processStyle(prop, props, context) - // } - // } } } } - // remove static class and static style - // if (hasClassBinding) { - // const staticClassPropIndex = findStaticClassIndex(props) - // if (staticClassPropIndex > -1) { - // props.splice(staticClassPropIndex, 1) - // } - // } - // if (hasStyleBinding) { - // const staticStylePropIndex = findStaticStyleIndex(props) - // if (staticStylePropIndex > -1) { - // props.splice(staticStylePropIndex, 1) - // } - // } } function isComponentTag(tag: string) { return tag[0].toLowerCase() + tag.slice(1) === 'component' } - -// function findStaticClassIndex(props: (AttributeNode | DirectiveNode)[]) { -// return props.findIndex((prop) => prop.name === 'class') -// } -// function findStaticStyleIndex(props: (AttributeNode | DirectiveNode)[]) { -// return props.findIndex((prop) => prop.name === 'style') -// } - -// function processClass( -// classBindingProp: DirectiveNode, -// props: (AttributeNode | DirectiveNode)[], -// context: TransformContext -// ) { -// if (!classBindingProp.exp) { -// return -// } -// const staticClassPropIndex = findStaticClassIndex(props) -// const staticClass = -// staticClassPropIndex > -1 -// ? (props[staticClassPropIndex] as AttributeNode).value -// : '' -// const expr = parseExpr(classBindingProp.exp, context) -// if (!expr) { -// return -// } -// console.log(staticClass) -// if (isObjectExpression(expr)) { -// classBindingProp.exp = createSimpleExpression( -// genBabelExpr(createClassBindingArrayExpression(expr)) -// ) -// } -// } -// function processStyle( -// styleBindingPropprop: DirectiveNode, -// props: (AttributeNode | DirectiveNode)[], -// context: TransformContext -// ) { -// const staticStylePropIndex = findStaticStyleIndex(props) -// const staticStyle = -// staticStylePropIndex > -1 -// ? (props[staticStylePropIndex] as AttributeNode).value -// : '' -// if (staticStyle) { -// console.log(staticStyle) -// } -// } diff --git a/packages/uni-mp-vite/__tests__/test.js b/packages/uni-mp-vite/__tests__/test.js index 4ecc63e4a4c647fb6df88bcec69f53a59b050dd9..5a6048b7eec89a4288cef9b10a622f2dd145f9cf 100644 --- a/packages/uni-mp-vite/__tests__/test.js +++ b/packages/uni-mp-vite/__tests__/test.js @@ -14,3 +14,5 @@ console.log( depth: null, }) ) + +// import a from 'a.vue' diff --git a/packages/uni-mp-vite/package.json b/packages/uni-mp-vite/package.json index 6cda5853752115657cd21e14a33557c425c7b355..06ea55ca8fff41452667f20ff4643fbee3834d87 100644 --- a/packages/uni-mp-vite/package.json +++ b/packages/uni-mp-vite/package.json @@ -18,5 +18,8 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "license": "Apache-2.0", + "dependencies": { + "magic-string": "^0.25.7" + }, "gitHead": "1efa8efd0a9eddeabdba75c020d015ebf31b8177" } diff --git a/packages/uni-mp-vite/src/index.ts b/packages/uni-mp-vite/src/index.ts index c958be4c5479d02d38911a3ab07da69817b59238..f518d86f156fe87f11c03ecdff703901c24edb6d 100644 --- a/packages/uni-mp-vite/src/index.ts +++ b/packages/uni-mp-vite/src/index.ts @@ -1,4 +1,5 @@ import { uniMiniProgramPlugin, UniMiniProgramPluginOptions } from './plugin' +import { uniComponentPlugin } from './plugins/component' import { uniMainJsPlugin } from './plugins/mainJs' import { uniManifestJsonPlugin } from './plugins/manifestJson' import { uniPagesJsonPlugin } from './plugins/pagesJson' @@ -11,5 +12,6 @@ export default (options: UniMiniProgramPluginOptions) => { uniPagesJsonPlugin(options), uniVirtualPlugin(options), uniMiniProgramPlugin(options), + uniComponentPlugin(), ] } diff --git a/packages/uni-mp-vite/src/plugins/component.ts b/packages/uni-mp-vite/src/plugins/component.ts new file mode 100644 index 0000000000000000000000000000000000000000..3bf13f409c25125cffa9177d601a566d493ca022 --- /dev/null +++ b/packages/uni-mp-vite/src/plugins/component.ts @@ -0,0 +1,40 @@ +import path from 'path' +import { Plugin } from 'vite' +import { + EXTNAME_VUE, + parseVueRequest, + findVueComponentImports, +} from '@dcloudio/uni-cli-shared' +import MagicString from 'magic-string' +import { virtualComponentPath } from './virtual' + +export function uniComponentPlugin(): Plugin { + return { + name: 'vite:uni-mp-component', + async transform(code, id) { + const { filename, query } = parseVueRequest(id) + if (query.vue) { + return null + } + if (!EXTNAME_VUE.includes(path.extname(filename))) { + return null + } + const vueComponentImports = await findVueComponentImports( + code, + id, + this.resolve + ) + if (!vueComponentImports.length) { + return null + } + const s = new MagicString(code) + const rewriteImports: string[] = [] + vueComponentImports.forEach(({ n, ss, se }) => { + s.remove(ss, se) + rewriteImports.push(`import('${virtualComponentPath(n!)}')`) + }) + s.prepend(`if(!Math){${rewriteImports.join(';')}}`) + return s.toString() + }, + } +} diff --git a/packages/uni-vue/src/componentOptions/hooks.ts b/packages/uni-vue/src/componentOptions/hooks.ts index be0736d6a3b349a34660e9ab8084ac24a540cd3f..f33619d723305627385fc0d3b60ee8e3724b7426 100644 --- a/packages/uni-vue/src/componentOptions/hooks.ts +++ b/packages/uni-vue/src/componentOptions/hooks.ts @@ -1,5 +1,5 @@ import { invokeHook } from '@dcloudio/uni-core' -import { ON_LOAD, ON_SHOW } from '@dcloudio/uni-shared' +import { LINEFEED, ON_LOAD, ON_SHOW } from '@dcloudio/uni-shared' import { isArray, isFunction } from '@vue/shared' import { @@ -49,7 +49,7 @@ export function initHooks( invokeHook(publicThis, ON_SHOW) }) } catch (e: any) { - console.error(e.message + '\n' + e.stack) + console.error(e.message + LINEFEED + e.stack) } } } diff --git a/yarn.lock b/yarn.lock index 71ab88f5631296c5f8a0a43d78bc5230411f857c..597a64ece02eec9515a4e736cfa9efb9ad9621ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4616,7 +4616,7 @@ es-abstract@^1.19.1: string.prototype.trimstart "^1.0.4" unbox-primitive "^1.0.1" -es-module-lexer@^0.9.0: +es-module-lexer@^0.9.0, es-module-lexer@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==