diff --git a/lib/mp-compiler/index.js b/lib/mp-compiler/index.js index 2938960..c8750bc 100644 --- a/lib/mp-compiler/index.js +++ b/lib/mp-compiler/index.js @@ -6,14 +6,16 @@ const path = require('path') const fs = require('fs') const deepEqual = require('deep-equal') -const { parseConfig, parseComponentsDeps, parseGlobalComponents, clearGlobalComponents } = require('./parse') +const { parseConfig, parseComponentsDeps, parseGlobalComponents, clearGlobalComponents, parseMixinsDeps } = require('./parse') const { parseComponentsDeps: parseComponentsDepsTs } = require('./parse-ts') const { genPageWxml } = require('./templates') +const { extractScriptFilters, combineScriptAndMixinsFilters } = require('../script-compiler') const { cacheFileInfo, getFileInfo, getCompNameAndSrc, + getFiltersOutputSrc, resolveTarget, covertCCVar, cacheSlots, @@ -57,7 +59,7 @@ const cacheCreateWxmlFns = {} function createWxml ({ emitWarning, emitError, emitFile, resourcePath, context, compiled }) { cacheCreateWxmlFns[resourcePath] = arguments - const { pageType, moduleId, components } = getFileInfo(resourcePath) || {} + const { pageType, moduleId, components, filters } = getFileInfo(resourcePath) || {} // TODO, 这儿传 options 进去 // { @@ -69,7 +71,7 @@ function createWxml ({ emitWarning, emitError, emitFile, resourcePath, context, // moduleId: 'moduleId' // } const { name, filePath: wxmlSrc } = getCompNameAndSrc(context, resourcePath) - const options = { components, pageType, name, moduleId } + const options = { components, pageType, name, moduleId, filters } const wxmlContent = genComponentWxml(compiled, options, emitFile, emitError, emitWarning) emitFile(wxmlSrc, wxmlContent) } @@ -91,8 +93,8 @@ function compileWxml (compiled, html) { } return new Promise(resolve => { const pollComponentsStatus = () => { - const { pageType, components } = getFileInfo(this.resourcePath) || {} - if (!pageType || (components && !components.isCompleted)) { + const { pageType, components, filters } = getFileInfo(this.resourcePath) || {} + if (!pageType || (components && !components.isCompleted) || (filters && !filters.isCompleted)) { setTimeout(pollComponentsStatus, 20) } else { resolve() @@ -116,11 +118,11 @@ function compileWxml (compiled, html) { // 针对 .vue 单文件的脚本逻辑的处理 // 处理出当前单文件组件的子组件依赖 function compileMPScript (script, mpOptioins, moduleId) { - const { resourcePath, options, resolve, context } = this + const { resourcePath, options, resolve, context, emitFile } = this const babelrc = getBabelrc(mpOptioins.globalBabelrc) let result, metadata let scriptContent = script.content - const babelOptions = { extends: babelrc, plugins: [parseComponentsDeps] } + const babelOptions = { extends: babelrc, plugins: [parseComponentsDeps, parseMixinsDeps] } if (script.src) { // 处理src const scriptpath = path.join(path.dirname(resourcePath), script.src) scriptContent = fs.readFileSync(scriptpath).toString() @@ -133,11 +135,12 @@ function compileMPScript (script, mpOptioins, moduleId) { } // metadata: importsMap, components - const { importsMap, components: originComponents } = metadata + const { importsMap, components: originComponents, mixins } = metadata + + const fileInfo = resolveTarget(resourcePath, options.entry) // 处理子组件的信息 const components = {} - const fileInfo = resolveTarget(resourcePath, options.entry) if (originComponents) { resolveSrc(originComponents, components, resolve, context, options.context).then(() => { resolveComponent(resourcePath, fileInfo, importsMap, components, moduleId) @@ -149,6 +152,21 @@ function compileMPScript (script, mpOptioins, moduleId) { resolveComponent(resourcePath, fileInfo, importsMap, components, moduleId) } + // 处理filters信息 + const mixinsFilters = [] + if (mixins) { + // 包含外部mixins引用 + resolveMixinsFilters(mixins, mixinsFilters, resolve, context, options.context, babelOptions, emitFile).then(() => { + resolveFilters(resourcePath, fileInfo, scriptContent, babelOptions, mixinsFilters, context, options.context, emitFile) + }).catch(err => { + console.error(err) + resolveFilters(resourcePath, fileInfo, scriptContent, babelOptions, mixinsFilters, context, options.context, emitFile) + }) + } else { + // 不包外部mixins引用,则直接获取脚本中的filters信息 + resolveFilters(resourcePath, fileInfo, scriptContent, babelOptions, mixinsFilters, context, options.context, emitFile) + } + return script } @@ -247,4 +265,72 @@ function resolveComponent (resourcePath, fileInfo, importsMap, localComponents, } } +// 创建wxs文件 +function createWxs (resourcePath, wxsContent, context, emitFile) { + const { filePath: wxsSrc } = getFiltersOutputSrc(context, resourcePath) + emitFile(wxsSrc, wxsContent) +} + +// 分析mixins解析外部引用Filters +const cacheResolveMixinsFilters = {} + +function resolveMixinsFilters (mixins, mixinsFilters, resolveFn, context, projectRoot, babelOptions, emitFile) { + return Promise.all(Object.keys(mixins).map(k => { + return new Promise((resolve, reject) => { + resolveFn(context, mixins[k], (err, realSrc) => { + if (err) return reject(err) + if (cacheResolveMixinsFilters[realSrc]) { + // 之前解析过该组件 + mixinsFilters.push(realSrc) + } else { + // 之前未解析过该组件 + // 读取文件 + const mixinsFileSource = fs.readFileSync(realSrc).toString() + // 提取filters + const mixinsFileFilter = extractScriptFilters(mixinsFileSource, babelOptions) + if (mixinsFileFilter) { + mixinsFilters.push(realSrc) + cacheResolveMixinsFilters[realSrc] = mixinsFileFilter + // 创建引用的wxs文件 + createWxs(realSrc, mixinsFileFilter.code, projectRoot, emitFile) + } + } + resolve() + }) + }) + })) +} + +// 解析Filters +function resolveFilters (resourcePath, fileInfo, scriptContent, babelOptions, mixinsFilters, context, projectRoot, emitFile) { + const resourceSrcPath = getFiltersOutputSrc(projectRoot, resourcePath) + const mixinsFiltersArray = [] + mixinsFilters.forEach((item) => { + // 计算相对路径,由于wxs里require只支持相对路径,微信真渣! + const itemSrcPath = getFiltersOutputSrc(projectRoot, item) + const realtivePath = path.join(path.relative(path.dirname(resourceSrcPath.filePath), path.dirname(itemSrcPath.filePath)), path.basename(itemSrcPath.filePath)) + mixinsFiltersArray.push({ + filePath: item, + name: itemSrcPath.name, + extractFilter: cacheResolveMixinsFilters[item], + realtivePath: realtivePath + }) + }) + // 提取脚本内的filters + const scriptFilter = extractScriptFilters(scriptContent, babelOptions) + // 合并外部引用和脚本内的filters + const combineFilter = combineScriptAndMixinsFilters(scriptFilter, mixinsFiltersArray, babelOptions) + // 保存文件信息 + if (combineFilter) { + const filters = { + isCompleted: true, + src: path.join('./', path.basename(resourceSrcPath.filePath)), + module: resourceSrcPath.name + } + cacheFileInfo(resourcePath, fileInfo, { filters }) + // 创建引用的wxs文件 + createWxs(resourcePath, combineFilter.code, projectRoot, emitFile) + } +} + module.exports = { compileWxml, compileMPScript, compileMP } diff --git a/lib/mp-compiler/parse.js b/lib/mp-compiler/parse.js index c15cd7e..7a92de2 100644 --- a/lib/mp-compiler/parse.js +++ b/lib/mp-compiler/parse.js @@ -133,4 +133,32 @@ function parseGlobalComponents (babel) { function clearGlobalComponents () { globalComponents = {} } -module.exports = { parseConfig, parseComponentsDeps, parseGlobalComponents, clearGlobalComponents } + +function parseMixinsDeps (babel) { + return { + visitor: { + ExportDefaultDeclaration (path) { + path.traverse({ + Property: function (path) { + if (path.node.key.name !== 'mixins') { + return + } + path.stop() + const { metadata } = path.hub.file + const { importsMap } = getImportsMap(metadata) + // 找到所有的 imports + const { elements } = path.node.value + const mixins = {} + elements.forEach(p => { + const k = p.name + mixins[k] = importsMap[k] + }) + metadata.mixins = mixins + } + }) + } + } + } +} + +module.exports = { parseConfig, parseComponentsDeps, parseGlobalComponents, clearGlobalComponents, parseMixinsDeps } diff --git a/lib/mp-compiler/util.js b/lib/mp-compiler/util.js index 27726f5..762d365 100644 --- a/lib/mp-compiler/util.js +++ b/lib/mp-compiler/util.js @@ -26,6 +26,15 @@ function getCompNameAndSrc (context, file) { } } +function getFiltersOutputSrc (context, file) { + const filePath = `/${resolveSrc(context, file)}.wxs` + const name = (path.basename(file) + hash(file)).replace(/[^\d|\w]/g, '') + return { + filePath, + name: name + } +} + // 根据路径获得组件名 function getNameByFile (dir) { // const arr = dir.match(/[pages?/components?]\/(.*?)(\/)/) @@ -134,6 +143,7 @@ const defaultPart = type => { names: [], mappings: '', sourcesContent: [] + } } } @@ -142,6 +152,7 @@ module.exports = { cacheFileInfo, getFileInfo, getCompNameAndSrc, + getFiltersOutputSrc, resolveTarget, covertCCVar, cacheSlots, diff --git a/lib/script-compiler/index.js b/lib/script-compiler/index.js new file mode 100644 index 0000000..4fd15de --- /dev/null +++ b/lib/script-compiler/index.js @@ -0,0 +1,54 @@ +const babel = require('babel-core') +const extractFiltersBabelPlugins = require('./plugins/babel-plugin-extract-filters') +const combineFiltersBabelPlugins = require('./plugins/babel-plugin-combine-filters') + +function extractScriptFilters (scriptContent, babelOptions) { + babelOptions.plugins = [] + babelOptions.plugins.push(extractFiltersBabelPlugins) + babelOptions.comments = false + const result = babel.transform(scriptContent, babelOptions) + result.code = result.code.trim() + if (result.code) { + // 遍历解析处的filter,复制filter包含哪些函数 + const filtersFuncArray = [] + babel.traverse(result.ast, { + Program: function (path) { + path.stop() + const filters = path.get('body.0.expression.right.properties') + if (filters.length && filters.length > 0) { + filters.forEach((filter) => { + filtersFuncArray.push(filter.get('key').node.name) + }) + } + } + }) + result.filtersFuncArray = filtersFuncArray + return result + } else { + return null + } +} + +function combineScriptAndMixinsFilters (scriptFilter, mixinsFiltersArray, babelOptions) { + if (scriptFilter === null && (mixinsFiltersArray === null || mixinsFiltersArray.length === 0)) { + return null + } + if (scriptFilter === null && mixinsFiltersArray.length > 0) { + scriptFilter = { + code: 'module.exports={\n_empty:null\n};' + } + } + // 包含脚本内的filters + babelOptions.plugins = [] + babelOptions.plugins.push([combineFiltersBabelPlugins, { mixinsFiltersArray: mixinsFiltersArray }]) + babelOptions.comments = false + const result = babel.transform(scriptFilter.code || '', babelOptions) + result.code = result.code.trim() + if (result.code) { + return result + } else { + return null + } +} + +module.exports = { extractScriptFilters, combineScriptAndMixinsFilters } diff --git a/lib/script-compiler/plugins/babel-plugin-combine-filters.js b/lib/script-compiler/plugins/babel-plugin-combine-filters.js new file mode 100644 index 0000000..ad79475 --- /dev/null +++ b/lib/script-compiler/plugins/babel-plugin-combine-filters.js @@ -0,0 +1,58 @@ +const t = require('babel-types') + +module.exports = () => ({ + visitor: { + Program: { + enter (path, options) { + const { mixinsFiltersArray } = options.opts + if (mixinsFiltersArray && mixinsFiltersArray.length > 0) { + mixinsFiltersArray.forEach((filters) => { + if (path.get('body.0')) { + const insert = t.variableDeclaration('var', [t.variableDeclarator(t.identifier(filters.name), t.callExpression(t.identifier('require'), [t.stringLiteral(filters.realtivePath)]))]) + path.get('body.0').insertBefore(insert) + } + }) + } + }, + exit (path) { + // 清理use strict + var list = path.node.directives + for (var i = list.length - 1, it; i >= 0; i--) { + it = list[i] + if (it.value.value === 'use strict') { + list.splice(i, 1) + } + } + } + }, + AssignmentExpression (path, options) { + if (path.get('left').isMemberExpression() && + path.get('left.object').node.name === 'module' && + path.get('left.property').node.name === 'exports') { + // 暂停搜索 + path.stop() + const { mixinsFiltersArray } = options.opts + if (mixinsFiltersArray && mixinsFiltersArray.length > 0) { + const currentFunArray = path.get('right.properties') + const currentFunNameArray = [] + let lastChild + currentFunArray.forEach((func, index) => { + currentFunNameArray.push(func.get('key.name').node) + if (index === currentFunArray.length - 1) { + lastChild = func + } + }) + mixinsFiltersArray.forEach((filters) => { + const { extractFilter } = filters + extractFilter && extractFilter.filtersFuncArray.forEach((func) => { + if (currentFunNameArray.indexOf(func) <= 0 && lastChild) { + const insert = t.objectProperty(t.identifier(func), t.identifier(filters.name + '.' + func)) + lastChild.insertAfter(insert) + } + }) + }) + } + } + } + } +}) diff --git a/lib/script-compiler/plugins/babel-plugin-extract-filters.js b/lib/script-compiler/plugins/babel-plugin-extract-filters.js new file mode 100644 index 0000000..af50669 --- /dev/null +++ b/lib/script-compiler/plugins/babel-plugin-extract-filters.js @@ -0,0 +1,60 @@ +const types = require('babel-types') + +module.exports = () => ({ + visitor: { + Program: { + enter (path) { + path.get('body').forEach((path) => { + if (!types.isImportDeclaration(path.node) && !types.isExportDefaultDeclaration(path.node)) { + path.remove() + } + }) + }, + exit (path) { + // 清理use strict + var list = path.node.directives + for (var i = list.length - 1, it; i >= 0; i--) { + it = list[i] + if (it.value.value === 'use strict') { + list.splice(i, 1) + } + } + } + }, + ImportDeclaration (path) { + // 处理import + path.remove() + }, + ExportDefaultDeclaration: { + enter (path, options) { + // 判断export内部是否有filter,没有则删除所有节点 + path.traverse({ + ObjectProperty (subpath) { + if (subpath.parentPath.parentPath.type !== 'ExportDefaultDeclaration') { + return + } + if (subpath.node.key.name !== 'filters') { + subpath.remove() + } + }, + ObjectMethod (subpath) { + if (subpath.parentPath.parentPath.type !== 'ExportDefaultDeclaration') { + return + } + subpath.remove() + } + }) + }, + exit (path) { + if (path.get('declaration').node.properties.length === 0) { + path.remove() + } else { + path.replaceWith( + types.assignmentExpression('=', types.Identifier('module.exports'), path.get('declaration').node.properties[0].value) + ) + } + } + } + } +}) +