"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.transformDynamicImports = exports.parseScriptDescriptor = exports.parseTemplateDescriptor = exports.updateMiniProgramComponentsByMainFilename = exports.updateMiniProgramGlobalComponents = exports.updateMiniProgramComponentsByTemplateFilename = exports.updateMiniProgramComponentsByScriptFilename = exports.parseMainDescriptor = void 0; const types_1 = require("@babel/types"); const estree_walker_1 = require("estree-walker"); const magic_string_1 = __importDefault(require("magic-string")); const shared_1 = require("@vue/shared"); const uni_shared_1 = require("@dcloudio/uni-shared"); const messages_1 = require("../messages"); const constants_1 = require("../constants"); const utils_1 = require("../utils"); const utils_2 = require("../vite/utils"); const jsonFile_1 = require("../json/mp/jsonFile"); const mainDescriptors = new Map(); const scriptDescriptors = new Map(); const templateDescriptors = new Map(); function findImportTemplateSource(ast) { const importDeclaration = ast.body.find((node) => (0, types_1.isImportDeclaration)(node) && node.source.value.includes('vue&type=template')); if (importDeclaration) { return importDeclaration.source.value; } } function findImportScriptSource(ast) { const importDeclaration = ast.body.find((node) => (0, types_1.isImportDeclaration)(node) && node.source.value.includes('vue&type=script')); if (importDeclaration) { return importDeclaration.source.value; } } async function resolveSource(filename, source, resolve) { if (!source) { return; } const resolveId = await resolve(source, filename); if (resolveId) { return resolveId.id; } } async function parseMainDescriptor(filename, ast, resolve) { const script = await resolveSource(filename, findImportScriptSource(ast), resolve); const template = await resolveSource(filename, findImportTemplateSource(ast), resolve); const imports = await parseVueComponentImports(filename, ast.body.filter((node) => (0, types_1.isImportDeclaration)(node)), resolve); if (!script) { // inline script await parseScriptDescriptor(filename, ast, { resolve, isExternal: false }); } if (!template) { // inline template await parseTemplateDescriptor(filename, ast, { resolve, isExternal: false }); } const descriptor = { imports, script: script ? (0, utils_2.parseVueRequest)(script).filename : filename, template: template ? (0, utils_2.parseVueRequest)(template).filename : filename, }; mainDescriptors.set(filename, descriptor); return descriptor; } exports.parseMainDescriptor = parseMainDescriptor; function updateMiniProgramComponentsByScriptFilename(scriptFilename, inputDir, normalizeComponentName) { const mainFilename = findMainFilenameByScriptFilename(scriptFilename); if (mainFilename) { updateMiniProgramComponentsByMainFilename(mainFilename, inputDir, normalizeComponentName); } } exports.updateMiniProgramComponentsByScriptFilename = updateMiniProgramComponentsByScriptFilename; function updateMiniProgramComponentsByTemplateFilename(templateFilename, inputDir, normalizeComponentName) { const mainFilename = findMainFilenameByTemplateFilename(templateFilename); if (mainFilename) { updateMiniProgramComponentsByMainFilename(mainFilename, inputDir, normalizeComponentName); } } exports.updateMiniProgramComponentsByTemplateFilename = updateMiniProgramComponentsByTemplateFilename; function findMainFilenameByScriptFilename(scriptFilename) { const keys = [...mainDescriptors.keys()]; return keys.find((key) => mainDescriptors.get(key).script === scriptFilename); } function findMainFilenameByTemplateFilename(templateFilename) { const keys = [...mainDescriptors.keys()]; return keys.find((key) => mainDescriptors.get(key).template === templateFilename); } async function updateMiniProgramGlobalComponents(filename, ast, { inputDir, resolve, normalizeComponentName, }) { const { bindingComponents, imports } = await parseGlobalDescriptor(filename, ast, resolve); (0, jsonFile_1.addMiniProgramUsingComponents)('app', createUsingComponents(bindingComponents, imports, inputDir, normalizeComponentName)); return { imports, }; } exports.updateMiniProgramGlobalComponents = updateMiniProgramGlobalComponents; function createUsingComponents(bindingComponents, imports, inputDir, normalizeComponentName) { const usingComponents = {}; imports.forEach(({ source: { value }, specifiers: [specifier] }) => { const { name } = specifier.local; if (!bindingComponents[name]) { return; } const componentName = normalizeComponentName((0, shared_1.hyphenate)(bindingComponents[name].tag)); if (!usingComponents[componentName]) { usingComponents[componentName] = (0, uni_shared_1.addLeadingSlash)((0, utils_1.removeExt)((0, utils_1.normalizeMiniProgramFilename)(value, inputDir))); } }); return usingComponents; } function updateMiniProgramComponentsByMainFilename(mainFilename, inputDir, normalizeComponentName) { const mainDescriptor = mainDescriptors.get(mainFilename); if (!mainDescriptor) { return; } const templateDescriptor = templateDescriptors.get(mainDescriptor.template); if (!templateDescriptor) { return; } const scriptDescriptor = scriptDescriptors.get(mainDescriptor.script); if (!scriptDescriptor) { return; } const bindingComponents = parseBindingComponents({ ...templateDescriptor.bindingComponents, ...scriptDescriptor.setupBindingComponents, }, scriptDescriptor.bindingComponents); const imports = parseImports(mainDescriptor.imports, scriptDescriptor.imports, templateDescriptor.imports); (0, jsonFile_1.addMiniProgramUsingComponents)((0, utils_1.removeExt)((0, utils_1.normalizeMiniProgramFilename)(mainFilename, inputDir)), createUsingComponents(bindingComponents, imports, inputDir, normalizeComponentName)); } exports.updateMiniProgramComponentsByMainFilename = updateMiniProgramComponentsByMainFilename; function findBindingComponent(tag, bindingComponents) { return Object.keys(bindingComponents).find((name) => { const componentTag = bindingComponents[name].tag; const camelName = (0, shared_1.camelize)(componentTag); const PascalName = (0, shared_1.capitalize)(camelName); return tag === componentTag || tag === camelName || tag === PascalName; }); } function normalizeComponentId(id) { // _unref(test) => test if (id.includes('_unref(')) { return id.replace('_unref(', '').replace(')', ''); } // $setup["test"] => test if (id.includes('$setup[')) { return id.replace('$setup["', '').replace('"', ''); } return id; } function parseBindingComponents(templateBindingComponents, scriptBindingComponents) { const bindingComponents = {}; Object.keys(templateBindingComponents).forEach((id) => { bindingComponents[normalizeComponentId(id)] = templateBindingComponents[id]; }); Object.keys(scriptBindingComponents).forEach((id) => { const { tag } = scriptBindingComponents[id]; const name = findBindingComponent(tag, templateBindingComponents); if (name) { bindingComponents[id] = bindingComponents[name]; } }); return bindingComponents; } function parseImports(mainImports, scriptImports, templateImports) { const imports = [...mainImports, ...templateImports, ...scriptImports]; return imports; } /** * 解析 template * @param filename * @param code * @param ast * @param options * @returns */ async function parseTemplateDescriptor(filename, ast, options) { // 外置时查找所有 vue component import const imports = options.isExternal ? await parseVueComponentImports(filename, ast.body.filter((node) => (0, types_1.isImportDeclaration)(node)), options.resolve) : []; const descriptor = { bindingComponents: findBindingComponents(ast.body), imports, }; templateDescriptors.set(filename, descriptor); return descriptor; } exports.parseTemplateDescriptor = parseTemplateDescriptor; async function parseGlobalDescriptor(filename, ast, resolve) { // 外置时查找所有 vue component import const imports = (await parseVueComponentImports(filename, ast.body.filter((node) => (0, types_1.isImportDeclaration)(node)), resolve)).filter((item) => !(0, utils_1.isAppVue)((0, utils_2.cleanUrl)(item.source.value))); return { bindingComponents: parseGlobalComponents(ast), imports, }; } /** * 解析 script * @param filename * @param code * @param ast * @param options * @returns */ async function parseScriptDescriptor(filename, ast, options) { // 外置时查找所有 vue component import const imports = options.isExternal ? await parseVueComponentImports(filename, ast.body.filter((node) => (0, types_1.isImportDeclaration)(node)), options.resolve) : []; const descriptor = { bindingComponents: parseComponents(ast), setupBindingComponents: findBindingComponents(ast.body), imports, }; scriptDescriptors.set(filename, descriptor); return descriptor; } exports.parseScriptDescriptor = parseScriptDescriptor; /** * 解析编译器生成的 bindingComponents * @param ast * @returns */ function findBindingComponents(ast) { const mapping = findUnpluginComponents(ast); for (const node of ast) { if (!(0, types_1.isVariableDeclaration)(node)) { continue; } const declarator = node.declarations[0]; if ((0, types_1.isIdentifier)(declarator.id) && declarator.id.name === constants_1.BINDING_COMPONENTS) { const bindingComponents = JSON.parse(declarator.init.value); return Object.keys(bindingComponents).reduce((bindings, tag) => { const { name, type } = bindingComponents[tag]; bindings[mapping[name] || name] = { tag, type: type, }; return bindings; }, {}); } } return {}; } /** * 兼容:unplugin_components * https://github.com/dcloudio/uni-app/issues/3057 * @param ast * @returns */ function findUnpluginComponents(ast) { const res = Object.create(null); // if(!Array){} const ifStatement = ast.find((statement) => (0, types_1.isIfStatement)(statement) && (0, types_1.isUnaryExpression)(statement.test) && statement.test.operator === '!' && (0, types_1.isIdentifier)(statement.test.argument) && statement.test.argument.name === 'Array'); if (!ifStatement) { return res; } if (!(0, types_1.isBlockStatement)(ifStatement.consequent)) { return res; } for (const node of ifStatement.consequent.body) { if (!(0, types_1.isVariableDeclaration)(node)) { continue; } const { id, init } = node.declarations[0]; if ((0, types_1.isIdentifier)(id) && (0, types_1.isIdentifier)(init) && init.name.includes('unplugin_components')) { res[id.name] = init.name; } } return res; } /** * 查找全局组件定义:app.component('component-a',{}) * @param ast * @returns */ function parseGlobalComponents(ast) { const bindingComponents = {}; estree_walker_1.walk(ast, { enter(child) { if (!(0, types_1.isCallExpression)(child)) { return; } const { callee } = child; // .component if (!(0, types_1.isMemberExpression)(callee) || !(0, types_1.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 (!(0, types_1.isStringLiteral)(name)) { return console.warn(messages_1.M['mp.component.args[0]']); } if (!(0, types_1.isIdentifier)(value)) { return console.warn(messages_1.M['mp.component.args[1]']); } bindingComponents[value.name] = { tag: name.value, type: 'unknown', }; }, }); return bindingComponents; } /** * 从 components 中查找定义的组件 * @param ast * @param bindingComponents */ function parseComponents(ast) { const bindingComponents = {}; estree_walker_1.walk(ast, { enter(child) { if (!(0, types_1.isObjectExpression)(child)) { return; } const componentsProp = child.properties.find((prop) => (0, types_1.isObjectProperty)(prop) && (0, types_1.isIdentifier)(prop.key) && prop.key.name === 'components'); if (!componentsProp) { return; } const componentsExpr = componentsProp.value; if (!(0, types_1.isObjectExpression)(componentsExpr)) { return; } componentsExpr.properties.forEach((prop) => { if (!(0, types_1.isObjectProperty)(prop)) { return; } if (!(0, types_1.isIdentifier)(prop.key) && !(0, types_1.isStringLiteral)(prop.key)) { return; } if (!(0, types_1.isIdentifier)(prop.value)) { return; } bindingComponents[prop.value.name] = { tag: (0, types_1.isIdentifier)(prop.key) ? prop.key.name : prop.key.value, type: 'unknown', }; }); }, }); return bindingComponents; } /** * vue component imports * @param filename * @param imports * @param resolve * @returns */ async function parseVueComponentImports(importer, imports, resolve) { const vueComponentImports = []; for (let i = 0; i < imports.length; i++) { const { source } = imports[i]; if ((0, utils_2.parseVueRequest)(source.value).query.vue) { continue; } const resolveId = await resolve(source.value, importer); if (!resolveId) { continue; } const { filename } = (0, utils_2.parseVueRequest)(resolveId.id); if (constants_1.EXTNAME_VUE_RE.test(filename)) { source.value = resolveId.id; vueComponentImports.push(imports[i]); } } return vueComponentImports; } /** * static import => dynamic import * @param code * @param imports * @param dynamicImport * @returns */ async function transformDynamicImports(code, imports, { id, sourceMap, dynamicImport, }) { if (!imports.length) { return { code, map: null, }; } const s = new magic_string_1.default(code); for (let i = 0; i < imports.length; i++) { const { start, end, specifiers: [specifier], source, } = imports[i]; s.overwrite(start, end, dynamicImport(specifier.local.name, source.value) + ';'); } return { code: s.toString(), map: null, }; } exports.transformDynamicImports = transformDynamicImports;