提交 76e85b8b 编写于 作者: fxy060608's avatar fxy060608

wip(mp): support global registration

上级 ae7ec06d
......@@ -2,7 +2,7 @@ import path from 'path'
import { ResolvedId } from 'rollup'
import { transformVueComponentImports } from '../src/mp/transformImports'
const root = '/usr/xxx/projects/test/src'
const importer = '/usr/xxx/projects/test/src/pages/index/index.vue'
async function resolve(id: string, importer?: string) {
return {
id: importer ? path.resolve(path.dirname(importer), id) : id,
......@@ -14,181 +14,222 @@ function dynamicImport(name: string, source: string) {
}
describe('transformVueComponentImports', () => {
test(`basic`, async () => {
const source = `import test1 from "${root}/components/test1.vue";
const _sfc_main = {
components: {
test1
}
};
const __BINDING_COMPONENTS__ = '{"test1":{"name":"_component_test1","type":"unknown"}}';
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {};
}
import "${importer}?vue&type=style&index=0&lang.css";
import _export_sfc from "plugin-vue:export-helper";
export default /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
describe('global', () => {
const importer = '/usr/xxx/projects/test/src/main.js'
test(`basic`, async () => {
const source = `
import { createSSRApp } from 'vue'
import ComponentA from './components/component-a.vue'
import ComponentB from './components/component-b.vue'
export function createApp() {
const app = createSSRApp(App)
app.component('component-a',ComponentA)
app.component('component-b',ComponentB)
return {
app
}
}
`
const { code, usingComponents } = await transformVueComponentImports(
source,
importer,
{
root,
resolve,
dynamicImport,
}
)
expect(code).toContain(
`const test1 = ()=>import('${root}/components/test1.vue')`
)
expect(usingComponents).toMatchObject({ test1: '/components/test1' })
const { code, usingComponents } = await transformVueComponentImports(
source,
importer,
{
root,
global: true,
resolve,
dynamicImport,
}
)
expect(code).toContain(
`const ComponentA = ()=>import('${root}/components/component-a.vue')`
)
expect(code).toContain(
`const ComponentB = ()=>import('${root}/components/component-b.vue')`
)
expect(usingComponents).toMatchObject({
'component-a': '/components/component-a',
'component-b': '/components/component-b',
})
})
})
test(`easycom`, async () => {
const source = `import test1 from "../../components/test1.vue";
import MyComponentName from "../../components/test1.vue";
const _sfc_main = {
components: {
test1,
MyComponentName
}
};
const __BINDING_COMPONENTS__ = '{"test":{"name":"_easycom_test","type":"unknown"},"test1":{"name":"_component_test1","type":"unknown"},"MyComponentName":{"name":"_component_MyComponentName","type":"unknown"},"my-component-name":{"name":"_component_my_component_name","type":"unknown"}}';
import _easycom_test from "${root}/components/test/test.vue";
if (!Math) {
Math.max.call(Max, _easycom_test);
}
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {};
}
import "${root}/pages/index/index.vue?vue&type=style&index=0&lang.css";
import _export_sfc from "plugin-vue:export-helper";
export default /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
`
const { code, usingComponents } = await transformVueComponentImports(
source,
importer,
{
root,
resolve,
dynamicImport,
describe('local', () => {
const importer = '/usr/xxx/projects/test/src/pages/index/index.vue'
test(`basic`, async () => {
const source = `import test1 from "${root}/components/test1.vue";
const _sfc_main = {
components: {
test1
}
};
const __BINDING_COMPONENTS__ = '{"test1":{"name":"_component_test1","type":"unknown"}}';
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {};
}
)
expect(code).toContain(
`const _easycom_test = ()=>import('${root}/components/test/test.vue')`
)
expect(usingComponents).toMatchObject({
test: '/components/test/test',
test1: '/components/test1',
'my-component-name': '/components/test1',
import "${importer}?vue&type=style&index=0&lang.css";
import _export_sfc from "plugin-vue:export-helper";
export default /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
`
const { code, usingComponents } = await transformVueComponentImports(
source,
importer,
{
root,
resolve,
dynamicImport,
}
)
expect(code).toContain(
`const test1 = ()=>import('${root}/components/test1.vue')`
)
expect(usingComponents).toMatchObject({ test1: '/components/test1' })
})
})
test(`PascalCase`, async () => {
const source = `import test1 from "../../components/test1.vue";
import MyComponentName from "../../components/test1.vue";
const _sfc_main = {
components: {
test1,
MyComponentName
test(`easycom`, async () => {
const source = `import test1 from "../../components/test1.vue";
import MyComponentName from "../../components/test1.vue";
const _sfc_main = {
components: {
test1,
MyComponentName
}
};
const __BINDING_COMPONENTS__ = '{"test":{"name":"_easycom_test","type":"unknown"},"test1":{"name":"_component_test1","type":"unknown"},"MyComponentName":{"name":"_component_MyComponentName","type":"unknown"},"my-component-name":{"name":"_component_my_component_name","type":"unknown"}}';
import _easycom_test from "${root}/components/test/test.vue";
if (!Math) {
Math.max.call(Max, _easycom_test);
}
};
const __BINDING_COMPONENTS__ = '{"test1":{"name":"_component_test1","type":"unknown"},"MyComponentName":{"name":"_component_MyComponentName","type":"unknown"},"my-component-name":{"name":"_component_my_component_name","type":"unknown"}}';
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {};
}
import "${root}/pages/index/index.vue?vue&type=style&index=0&lang.css";
import _export_sfc from "plugin-vue:export-helper";
export default /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
`
const { code, usingComponents } = await transformVueComponentImports(
source,
importer,
{
root,
resolve,
dynamicImport,
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {};
}
)
expect(code).toContain(
`const MyComponentName = ()=>import('${root}/components/test1.vue')`
)
expect(usingComponents).toMatchObject({
test1: '/components/test1',
'my-component-name': '/components/test1',
import "${root}/pages/index/index.vue?vue&type=style&index=0&lang.css";
import _export_sfc from "plugin-vue:export-helper";
export default /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
`
const { code, usingComponents } = await transformVueComponentImports(
source,
importer,
{
root,
resolve,
dynamicImport,
}
)
expect(code).toContain(
`const _easycom_test = ()=>import('${root}/components/test/test.vue')`
)
expect(usingComponents).toMatchObject({
test: '/components/test/test',
test1: '/components/test1',
'my-component-name': '/components/test1',
})
})
})
test(`setup`, async () => {
const source = `import { defineComponent as _defineComponent } from "vue";
const __BINDING_COMPONENTS__ = '{"test1":{"name":"test1","type":"setup"},"MyComponentName":{"name":"MyComponentName","type":"setup"},"my-component-name":{"name":"MyComponentName","type":"setup"}}';
if (!Math) {
Math.max.call(Max, test1, MyComponentName, MyComponentName);
}
import test1 from "../../components/test1.vue";
import MyComponentName from "../../components/test1.vue";
const _sfc_main = /* @__PURE__ */ _defineComponent({
setup(__props) {
return (_ctx, _cache) => {
return {};
};
}
});
import "${root}/pages/index/index.vue?vue&type=style&index=0&lang.css";
export default _sfc_main;
`
const { code, usingComponents } = await transformVueComponentImports(
source,
importer,
{
root,
resolve,
dynamicImport,
test(`PascalCase`, async () => {
const source = `import test1 from "../../components/test1.vue";
import MyComponentName from "../../components/test1.vue";
const _sfc_main = {
components: {
test1,
MyComponentName
}
};
const __BINDING_COMPONENTS__ = '{"test1":{"name":"_component_test1","type":"unknown"},"MyComponentName":{"name":"_component_MyComponentName","type":"unknown"},"my-component-name":{"name":"_component_my_component_name","type":"unknown"}}';
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {};
}
)
expect(code).toContain(
`const MyComponentName = ()=>import('${root}/components/test1.vue')`
)
expect(usingComponents).toMatchObject({
test1: '/components/test1',
'my-component-name': '/components/test1',
import "${root}/pages/index/index.vue?vue&type=style&index=0&lang.css";
import _export_sfc from "plugin-vue:export-helper";
export default /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]);
`
const { code, usingComponents } = await transformVueComponentImports(
source,
importer,
{
root,
resolve,
dynamicImport,
}
)
expect(code).toContain(
`const MyComponentName = ()=>import('${root}/components/test1.vue')`
)
expect(usingComponents).toMatchObject({
test1: '/components/test1',
'my-component-name': '/components/test1',
})
})
})
test(`setup with easycom`, async () => {
const source = `import { defineComponent as _defineComponent } from "vue";
const __BINDING_COMPONENTS__ = '{"test":{"name":"_easycom_test","type":"unknown"},"test1":{"name":"test1","type":"setup"},"MyComponentName":{"name":"MyComponentName","type":"setup"},"my-component-name":{"name":"MyComponentName","type":"setup"}}';
import _easycom_test from "${root}/components/test/test.vue";
if (!Math) {
Math.max.call(Max, _easycom_test, test1, MyComponentName, MyComponentName);
}
import test1 from "../../components/test1.vue";
import MyComponentName from "../../components/test1.vue";
const _sfc_main = /* @__PURE__ */ _defineComponent({
setup(__props) {
return (_ctx, _cache) => {
return {};
};
test(`setup`, async () => {
const source = `import { defineComponent as _defineComponent } from "vue";
const __BINDING_COMPONENTS__ = '{"test1":{"name":"test1","type":"setup"},"MyComponentName":{"name":"MyComponentName","type":"setup"},"my-component-name":{"name":"MyComponentName","type":"setup"}}';
if (!Math) {
Math.max.call(Max, test1, MyComponentName, MyComponentName);
}
});
import "${root}/pages/index/index.vue?vue&type=style&index=0&lang.css";
export default _sfc_main;
`
const { code, usingComponents } = await transformVueComponentImports(
source,
importer,
{
root,
resolve,
dynamicImport,
import test1 from "../../components/test1.vue";
import MyComponentName from "../../components/test1.vue";
const _sfc_main = /* @__PURE__ */ _defineComponent({
setup(__props) {
return (_ctx, _cache) => {
return {};
};
}
});
import "${root}/pages/index/index.vue?vue&type=style&index=0&lang.css";
export default _sfc_main;
`
const { code, usingComponents } = await transformVueComponentImports(
source,
importer,
{
root,
resolve,
dynamicImport,
}
)
expect(code).toContain(
`const MyComponentName = ()=>import('${root}/components/test1.vue')`
)
expect(usingComponents).toMatchObject({
test1: '/components/test1',
'my-component-name': '/components/test1',
})
})
test(`setup with easycom`, async () => {
const source = `import { defineComponent as _defineComponent } from "vue";
const __BINDING_COMPONENTS__ = '{"test":{"name":"_easycom_test","type":"unknown"},"test1":{"name":"test1","type":"setup"},"MyComponentName":{"name":"MyComponentName","type":"setup"},"my-component-name":{"name":"MyComponentName","type":"setup"}}';
import _easycom_test from "${root}/components/test/test.vue";
if (!Math) {
Math.max.call(Max, _easycom_test, test1, MyComponentName, MyComponentName);
}
)
expect(code).toContain(
`const _easycom_test = ()=>import('${root}/components/test/test.vue')`
)
expect(usingComponents).toMatchObject({
test: '/components/test/test',
test1: '/components/test1',
'my-component-name': '/components/test1',
import test1 from "../../components/test1.vue";
import MyComponentName from "../../components/test1.vue";
const _sfc_main = /* @__PURE__ */ _defineComponent({
setup(__props) {
return (_ctx, _cache) => {
return {};
};
}
});
import "${root}/pages/index/index.vue?vue&type=style&index=0&lang.css";
export default _sfc_main;
`
const { code, usingComponents } = await transformVueComponentImports(
source,
importer,
{
root,
resolve,
dynamicImport,
}
)
expect(code).toContain(
`const _easycom_test = ()=>import('${root}/components/test/test.vue')`
)
expect(usingComponents).toMatchObject({
test: '/components/test/test',
test1: '/components/test1',
'my-component-name': '/components/test1',
})
})
})
})
......@@ -18,4 +18,6 @@ export const M = {
'i18n.fallbackLocale.missing':
'当前应用配置的 fallbackLocale 或 locale 为:{locale},但 locale 目录缺少该语言文件',
'easycom.conflict': 'easycom组件冲突:',
'mp.component.args[0]': '{0}的第一个参数必须为静态字符串',
'mp.component.args[1]': '{0}需要两个参数',
}
import { parse, ParserPlugin } from '@babel/parser'
import {
ImportDeclaration,
isCallExpression,
isIdentifier,
isImportDeclaration,
isMemberExpression,
isObjectExpression,
isObjectProperty,
isStringLiteral,
......@@ -16,11 +18,13 @@ import { camelize, capitalize, hyphenate } from '@vue/shared'
import { walk } from 'estree-walker'
import MagicString from 'magic-string'
import { PluginContext } from 'rollup'
import { M } from '../messages'
import { BINDING_COMPONENTS } from '../constants'
import { normalizeMiniProgramFilename, removeExt } from '../utils'
interface TransformVueComponentImportsOptions {
root: string
global?: boolean
resolve: PluginContext['resolve']
dynamicImport: (name: string, source: string) => string
babelParserPlugins?: ParserPlugin[]
......@@ -31,6 +35,7 @@ export async function transformVueComponentImports(
{
root,
resolve,
global,
dynamicImport,
babelParserPlugins,
}: TransformVueComponentImportsOptions
......@@ -38,7 +43,7 @@ export async function transformVueComponentImports(
code: string
usingComponents: Record<string, string>
}> {
if (!code.includes(BINDING_COMPONENTS)) {
if (!global && !code.includes(BINDING_COMPONENTS)) {
return { code, usingComponents: {} }
}
const s = new MagicString(code)
......@@ -49,7 +54,9 @@ export async function transformVueComponentImports(
const imports = findVueComponentImports(
scriptAst.body,
parseComponents(scriptAst, findBindingComponents(scriptAst.body))
global
? parseGlobalComponents(scriptAst)
: parseComponents(scriptAst, findBindingComponents(scriptAst.body))
)
const usingComponents: Record<string, string> = {}
for (let i = 0; i < imports.length; i++) {
......@@ -116,6 +123,47 @@ function findBindingComponents(ast: Statement[]): BindingComponents {
}
return {}
}
/**
* 查找全局组件定义:app.component('component-a',{})
* @param ast
* @returns
*/
function parseGlobalComponents(ast: Program) {
const bindingComponents: BindingComponents = {}
;(walk as any)(ast, {
enter(child: Node) {
if (!isCallExpression(child)) {
return
}
const { callee } = child
// .component
if (
!isMemberExpression(callee) ||
!isIdentifier(callee.property) ||
callee.property.name !== 'component'
) {
return
}
// .component('component-a',{})
const args = child.arguments
if (args.length !== 2) {
return
}
const [name, value] = args
if (!isStringLiteral(name)) {
return console.warn(M['mp.component.args[0]'])
}
if (!isIdentifier(value)) {
return console.warn(M['mp.component.args[1]'])
}
bindingComponents[value.name] = {
tag: name.value,
type: 'unknown',
}
},
})
return bindingComponents
}
/**
* 从 components 中查找定义的组件,修改 bindingComponents
* @param ast
......
......@@ -13,6 +13,9 @@ function assert(
filename: 'foo.vue',
prefixIdentifiers: true,
inline: true,
generatorOpts: {
concise: true,
},
miniProgram: {
slot: {
fallback: false,
......
......@@ -26,6 +26,9 @@ export function assert(
inline: true,
isNativeTag,
isCustomElement,
generatorOpts: {
concise: true,
},
miniProgram: {
slot: {
fallback: false,
......
......@@ -16,7 +16,12 @@ function parseWithElementTransform(
root: RootNode
node: ElementNode
} {
const { ast, code, preamble } = compile(`<div>${template}</div>`, options)
const { ast, code, preamble } = compile(`<div>${template}</div>`, {
generatorOpts: {
concise: true,
},
...options,
})
const node = (ast as any).children[0].children[0]
return {
code,
......@@ -32,10 +37,14 @@ describe('compiler: element transform', () => {
source: `<image src="/static/logo.png"/>`,
filename: 'foo.vue',
id: 'foo',
compiler: MPCompiler as unknown as TemplateCompiler,
compilerOptions: {
mode: 'module',
},
generatorOpts: {
concise: true,
},
} as any,
transformAssetUrls: {
includeAbsolute: true,
...(createUniVueTransformAssetUrls('/') as Record<string, any>),
......
......@@ -5,7 +5,12 @@ import { CompilerOptions } from '../src/options'
import { assert, miniProgram } from './testUtils'
function parseWithVBind(template: string, options: CompilerOptions = {}) {
const { ast, code } = compile(template, options)
const { ast, code } = compile(template, {
generatorOpts: {
concise: true,
},
...options,
})
return {
code,
node: ast.children[0] as ElementNode,
......
......@@ -14,7 +14,12 @@ function parseWithForTransform(
template: string,
options: CompilerOptions = {}
) {
const { ast } = compile(template, options)
const { ast } = compile(template, {
generatorOpts: {
concise: true,
},
...options,
})
return {
root: ast,
......
......@@ -9,7 +9,12 @@ function compileWithIfTransform(
returnIndex: number = 0,
childrenLen: number = 1
) {
const { ast } = compile(template, options)
const { ast } = compile(template, {
generatorOpts: {
concise: true,
},
...options,
})
if (!options.onError) {
expect(ast.children.length).toBe(childrenLen)
for (let i = 0; i < childrenLen; i++) {
......
......@@ -5,7 +5,12 @@ import { CompilerOptions } from '../src/options'
import { assert } from './testUtils'
function parseWithVOn(template: string, options: CompilerOptions = {}) {
const { ast } = compile(template, options)
const { ast } = compile(template, {
generatorOpts: {
concise: true,
},
...options,
})
return {
root: ast,
node: ast.children[0] as ElementNode,
......
......@@ -4,7 +4,12 @@ import { CompilerOptions } from '../src/options'
import { assert } from './testUtils'
function parseWithVOn(template: string, options: CompilerOptions = {}) {
const { ast } = compile(template, options)
const { ast } = compile(template, {
generatorOpts: {
concise: true,
},
...options,
})
return {
root: ast,
node: ast.children[0] as ElementNode,
......
......@@ -94,7 +94,9 @@ export function parseExpr(
code = genExpr(code)
}
try {
return parseExpression(code)
return parseExpression(code, {
plugins: context.expressionPlugins,
})
} catch (e: any) {
context.onError(
createCompilerError(
......
import { isString, isSymbol } from '@vue/shared'
import { isString, isSymbol, hasOwn } from '@vue/shared'
import {
CodegenResult,
CompoundExpressionNode,
......@@ -11,7 +11,7 @@ import {
TO_DISPLAY_STRING,
} from '@vue/compiler-core'
import { Expression } from '@babel/types'
import { default as babelGenerate } from '@babel/generator'
import { default as babelGenerate, GeneratorOptions } from '@babel/generator'
import { addImportDeclaration, matchEasycom } from '@dcloudio/uni-cli-shared'
import { CodegenOptions, CodegenRootNode } from './options'
......@@ -88,7 +88,7 @@ export function generate(
}
push(`return `)
push(genBabelExpr(ast.renderData))
push(genBabelExpr(ast.renderData, options.generatorOpts))
if (useWithBlock) {
deindent()
push(`}`)
......@@ -283,13 +283,12 @@ function createGenNodeContext() {
return context
}
export function genBabelExpr(expr: Expression) {
return babelGenerate(expr, {
concise: true,
jsescOption: {
quotes: 'single',
},
}).code
export function genBabelExpr(expr: Expression, opts: GeneratorOptions = {}) {
if (!hasOwn(opts, 'jsescOption')) {
opts.jsescOption = {}
}
opts.jsescOption!.quotes = 'single'
return babelGenerate(expr, opts).code
}
export function genExpr(
......
import { ParserPlugin } from '@babel/parser'
import { GeneratorOptions } from '@babel/generator'
import {
CallExpression,
Expression,
......@@ -90,6 +91,7 @@ export interface CodegenOptions extends SharedTransformCodegenOptions {
scopeId?: string | null
runtimeModuleName?: string
runtimeGlobalName?: string
generatorOpts?: GeneratorOptions
miniProgram?: MiniProgramCompilerOptions
}
......
......@@ -12,7 +12,11 @@ import { uniRenderjsPlugin } from './plugins/renderjs'
export { UniMiniProgramPluginOptions } from './plugin'
export default (options: UniMiniProgramPluginOptions) => {
return [
uniMainJsPlugin(options),
(options: {
vueOptions?: { script?: Partial<SFCScriptCompileOptions> }
}) => {
return uniMainJsPlugin(options.vueOptions?.script)
},
uniManifestJsonPlugin(options),
uniPagesJsonPlugin(options),
uniEntryPlugin(options),
......
import { defineUniMainJsPlugin } from '@dcloudio/uni-cli-shared'
import { UniMiniProgramPluginOptions } from '../plugin'
import {
addMiniProgramUsingComponents,
defineUniMainJsPlugin,
transformVueComponentImports,
} from '@dcloudio/uni-cli-shared'
import { SFCScriptCompileOptions } from '@vue/compiler-sfc'
import { dynamicImport } from './usingComponents'
export function uniMainJsPlugin(options: UniMiniProgramPluginOptions) {
export function uniMainJsPlugin(
options: Partial<SFCScriptCompileOptions> = {}
) {
return defineUniMainJsPlugin((opts) => {
return {
name: 'vite:uni-mp-main-js',
enforce: 'pre',
transform(code, id) {
async transform(source, id) {
if (opts.filter(id)) {
code = code.includes('createSSRApp')
? createApp(code)
: createLegacyApp(code)
source = source.includes('createSSRApp')
? createApp(source)
: createLegacyApp(source)
const inputDir = process.env.UNI_INPUT_DIR
const { code, usingComponents } = await transformVueComponentImports(
source,
id,
{
root: inputDir,
global: true,
resolve: this.resolve,
dynamicImport,
babelParserPlugins: options.babelParserPlugins,
}
)
addMiniProgramUsingComponents('app', usingComponents)
return {
code:
`import 'plugin-vue:export-helper';import 'uni-mp-runtime';import './pages.json.js';` +
......
......@@ -49,6 +49,6 @@ export function uniUsingComponentsPlugin(
}
}
function dynamicImport(name: string, value: string) {
export function dynamicImport(name: string, value: string) {
return `const ${name} = ()=>import('${virtualComponentPath(value)}')`
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册