diff --git a/packages/taro-mini-runner/src/plugins/MiniPlugin.ts b/packages/taro-mini-runner/src/plugins/MiniPlugin.ts index 406080113965..12172380577a 100644 --- a/packages/taro-mini-runner/src/plugins/MiniPlugin.ts +++ b/packages/taro-mini-runner/src/plugins/MiniPlugin.ts @@ -13,11 +13,14 @@ import { merge, defaults, kebabCase } from 'lodash' import * as t from 'babel-types' import traverse from 'babel-traverse' import { Config as IConfig } from '@tarojs/taro' +import * as _ from 'lodash' -import { REG_TYPESCRIPT, BUILD_TYPES, PARSE_AST_TYPE, MINI_APP_FILES, NODE_MODULES_REG, CONFIG_MAP, taroJsFramework } from '../utils/constants' +import { REG_TYPESCRIPT, BUILD_TYPES, PARSE_AST_TYPE, MINI_APP_FILES, NODE_MODULES_REG, CONFIG_MAP, taroJsFramework, taroJsComponents, QUICKAPP_SPECIAL_COMPONENTS, taroJsQuickAppComponents } from '../utils/constants' import { IComponentObj } from '../utils/types' -import { traverseObjectNode, resolveScriptPath, buildUsingComponents, isNpmPkg, resolveNpmSync, isEmptyObject } from '../utils' +import { resolveScriptPath, buildUsingComponents, isNpmPkg, resolveNpmSync, isEmptyObject, promoteRelativePath } from '../utils' import TaroSingleEntryDependency from '../dependencies/TaroSingleEntryDependency' +import { getTaroJsQuickAppComponentsPath, generateQuickAppUx, getImportTaroSelfComponents } from '../utils/helper' +import parseAst from '../utils/parseAst' import TaroLoadChunksPlugin from './TaroLoadChunksPlugin' import TaroNormalModulesPlugin from './TaroNormalModulesPlugin' @@ -26,6 +29,9 @@ import VirtualModulePlugin from './VirtualModulePlugin/VirtualModulePlugin' interface IMiniPluginOptions { appEntry?: string, buildAdapter: BUILD_TYPES, + nodeModulesPath: string, + sourceDir: string, + outputDir: string, commonChunks: string[] } @@ -34,7 +40,11 @@ export interface ITaroFileInfo { type: PARSE_AST_TYPE, config: IConfig, template?: string, - code?: string + code?: string, + taroSelfComponents?: Set<{ + name: string, + path: string + }> } } @@ -120,14 +130,20 @@ export default class MiniPlugin { pages: Set components: Set sourceDir: string + outputDir: string context: string appConfig: IConfig constructor (options = {}) { this.options = defaults(options || {}, { buildAdapter: BUILD_TYPES.WEAPP, + nodeModulesPath: '', + sourceDir: '', + outputDir: '', commonChunks: ['runtime', 'vendors'] }) + this.sourceDir = this.options.sourceDir + this.outputDir = this.options.outputDir this.pages = new Set() this.components = new Set() @@ -192,80 +208,10 @@ export default class MiniPlugin { return app } const appEntryPath = getEntryPath(entry) - this.sourceDir = path.dirname(appEntryPath) compiler.options.entry = {} return appEntryPath } - parseAst ( - ast: t.File, - buildAdapter: BUILD_TYPES - ): { - configObj: IConfig, - hasEnablePageScroll: boolean - } { - let configObj = {} - let hasEnablePageScroll - traverse(ast, { - ClassDeclaration (astPath) { - const node = astPath.node - let hasCreateData = false - if (node.superClass) { - astPath.traverse({ - ClassMethod (astPath) { - if (astPath.get('key').isIdentifier({ name: '_createData' })) { - hasCreateData = true - } - } - }) - if (hasCreateData) { - astPath.traverse({ - ClassMethod (astPath) { - const node = astPath.node - if (node.kind === 'constructor') { - astPath.traverse({ - ExpressionStatement (astPath) { - const node = astPath.node - if (node.expression && - node.expression.type === 'AssignmentExpression' && - node.expression.operator === '=') { - const left = node.expression.left - if (left.type === 'MemberExpression' && - left.object.type === 'ThisExpression' && - left.property.type === 'Identifier' && - left.property.name === 'config') { - configObj = traverseObjectNode(node.expression.right, buildAdapter) - } - } - } - }) - } - } - }) - } - } - }, - ClassMethod (astPath) { - const keyName = (astPath.get('key').node as t.Identifier).name - if (keyName === 'onPageScroll' || keyName === 'onReachBottom') { - hasEnablePageScroll = true - } - }, - ClassProperty (astPath) { - const node = astPath.node - const keyName = node.key.name - if (keyName === 'config') { - configObj = traverseObjectNode(node, buildAdapter) - } - } - }) - - return { - configObj, - hasEnablePageScroll - } - } - getNpmComponentRealPath (code: string, component: IComponentObj, adapter: BUILD_TYPES): string | null { let componentRealPath: string | null = null let importExportName @@ -420,7 +366,7 @@ export default class MiniPlugin { isApp: true, adapter: buildAdapter }) - const { configObj } = this.parseAst(transformResult.ast, buildAdapter) + const { configObj } = parseAst(transformResult.ast, buildAdapter) const appPages = configObj.pages this.appConfig = configObj if (!appPages || appPages.length === 0) { @@ -448,6 +394,18 @@ export default class MiniPlugin { return fs.existsSync(templatePath) && jsContent.indexOf(taroJsFramework) < 0 } + getComponentName (componentPath) { + let componentName + if (NODE_MODULES_REG.test(componentPath)) { + componentName = componentPath.replace(this.context, '').replace(/\\/g, '/').replace(path.extname(componentPath), '') + componentName = componentName.replace(/node_modules/gi, 'npm') + } else { + componentName = componentPath.replace(this.sourceDir, '').replace(/\\/g, '/').replace(path.extname(componentPath), '') + } + + return componentName + } + getComponents (fileList: Set, isRoot: boolean) { const { buildAdapter } = this.options const isQuickApp = buildAdapter === BUILD_TYPES.QUICKAPP @@ -456,6 +414,7 @@ export default class MiniPlugin { const isComponentConfig = isRoot ? {} : { component: true } let configObj + let taroSelfComponents let depComponents let template let code = fs.readFileSync(file.path).toString() @@ -484,7 +443,7 @@ export default class MiniPlugin { isTyped: REG_TYPESCRIPT.test(file.path), adapter: buildAdapter }) - const res = this.parseAst(aheadTransformResult.ast, buildAdapter) + const res = parseAst(aheadTransformResult.ast, buildAdapter) if (res.configObj.enablePullDownRefresh || (this.appConfig.window && this.appConfig.window.enablePullDownRefresh)) { rootProps.enablePullDownRefresh = true } @@ -504,7 +463,9 @@ export default class MiniPlugin { rootProps: isEmptyObject(rootProps) || rootProps, adapter: buildAdapter }) - configObj = this.parseAst(transformResult.ast, buildAdapter).configObj + let parseAstRes = parseAst(transformResult.ast, buildAdapter) + configObj = parseAstRes.configObj + taroSelfComponents = parseAstRes.taroSelfComponents const usingComponents = configObj.usingComponents if (usingComponents) { Object.keys(usingComponents).forEach(item => { @@ -514,30 +475,68 @@ export default class MiniPlugin { }) }) } + if (isRoot) { + taroSelfComponents.add('taro-page') + } depComponents = transformResult.components template = transformResult.template code = transformResult.code } depComponents = depComponents.filter(item => !/^plugin:\/\//.test(item.path)) this.transfromComponentsPath(depComponents) + if (isQuickApp) { + const scriptPath = file.path + const outputScriptPath = scriptPath.replace(this.sourceDir, this.outputDir).replace(path.extname(scriptPath), MINI_APP_FILES[buildAdapter].SCRIPT) + const stylePath = outputScriptPath.replace(path.extname(outputScriptPath), MINI_APP_FILES[buildAdapter].STYLE) + const templPath = outputScriptPath.replace(path.extname(outputScriptPath), MINI_APP_FILES[buildAdapter].TEMPL) + const styleRelativePath = promoteRelativePath(path.relative(outputScriptPath, stylePath)) + const scriptRelativePath = promoteRelativePath(path.relative(templPath, outputScriptPath)) + const importTaroSelfComponents = getImportTaroSelfComponents(outputScriptPath, this.options.nodeModulesPath, taroSelfComponents) + const usingComponents = configObj.usingComponents + let importUsingComponent: any = new Set([]) + if (usingComponents) { + importUsingComponent = new Set(Object.keys(usingComponents).map(item => { + return { + name: item, + path: usingComponents[item] + } + })) + } + const importCustomComponents = new Set(depComponents.map(item => { + return { + path: item.path, + name: item.name as string + } + })) + template = generateQuickAppUx({ + template, + script: scriptRelativePath, + style: styleRelativePath, + imports: new Set([...importTaroSelfComponents, ...importUsingComponent, ...importCustomComponents]) + }) + } taroFileTypeMap[file.path] = { type: isRoot ? PARSE_AST_TYPE.PAGE : PARSE_AST_TYPE.COMPONENT, config: merge({}, isComponentConfig, buildUsingComponents(file.path, this.sourceDir, {}, depComponents), configObj), template, code } + if (taroSelfComponents) { + taroFileTypeMap[file.path].taroSelfComponents = new Set(Array.from(taroSelfComponents).map(item => { + const taroJsQuickAppComponentsPath = getTaroJsQuickAppComponentsPath(this.options.nodeModulesPath) + const componentPath = path.join(taroJsQuickAppComponentsPath, item as string, `index${MINI_APP_FILES[buildAdapter].TEMPL}`) + return { + name: item as string, + path: componentPath + } + })) + } if (depComponents && depComponents.length) { depComponents.forEach(item => { const componentPath = resolveScriptPath(path.resolve(path.dirname(file.path), item.path)) if (fs.existsSync(componentPath) && !Array.from(this.components).some(item => item.path === componentPath)) { - let componentName - if (NODE_MODULES_REG.test(componentPath)) { - componentName = componentPath.replace(this.context, '').replace(/\\/g, '/').replace(path.extname(componentPath), '') - componentName = componentName.replace(/node_modules/gi, 'npm') - } else { - componentName = componentPath.replace(this.sourceDir, '').replace(/\\/g, '/').replace(path.extname(componentPath), '') - } + const componentName = this.getComponentName(componentPath) const componentTempPath = this.getTemplatePath(componentPath) const isNative = this.isNativePageORComponent(componentTempPath, fs.readFileSync(componentPath).toString()) const componentObj = { name: componentName, path: componentPath, isNative } @@ -601,6 +600,23 @@ export default class MiniPlugin { source: () => jsonStr } } + if (itemInfo.taroSelfComponents) { + itemInfo.taroSelfComponents.forEach(item => { + if (fs.existsSync(item.path)) { + const content = fs.readFileSync(item.path).toString() + let relativePath + if (NODE_MODULES_REG.test(item.path)) { + relativePath = item.path.replace(this.context, '').replace(/node_modules/gi, 'npm') + } else { + relativePath = item.path.replace(this.sourceDir, '') + } + compilation.assets[relativePath] = { + size: () => content.length, + source: () => content + } + } + }) + } }) } diff --git a/packages/taro-mini-runner/src/utils/helper.ts b/packages/taro-mini-runner/src/utils/helper.ts index b8c3f4bfb016..c58c3419bc76 100644 --- a/packages/taro-mini-runner/src/utils/helper.ts +++ b/packages/taro-mini-runner/src/utils/helper.ts @@ -1,6 +1,7 @@ import * as path from 'path' import * as fs from 'fs' -import { isEmptyObject } from '.' +import { isEmptyObject, getInstalledNpmPkgPath, promoteRelativePath } from '.' +import { taroJsQuickAppComponents, REG_STYLE, REG_SCRIPT } from './constants' let quickappConfig = {} @@ -16,3 +17,67 @@ export function getQuickappConfig (appPath) { } return quickappConfig } + +export function getTaroJsQuickAppComponentsPath (nodeModulesPath: string): string { + const taroJsQuickAppComponentsPkg = getInstalledNpmPkgPath(taroJsQuickAppComponents, nodeModulesPath) + if (!taroJsQuickAppComponentsPkg) { + // printLog(processTypeEnum.ERROR, '包安装', `缺少包 ${taroJsQuickAppComponents},请安装!`) + process.exit(0) + } + return path.join(path.dirname(taroJsQuickAppComponentsPkg as string), 'src/components') +} + +export function getImportTaroSelfComponents (filePath, nodeModulesPath, taroSelfComponents) { + const importTaroSelfComponents = new Set<{ path: string, name: string }>() + const taroJsQuickAppComponentsPath = getTaroJsQuickAppComponentsPath(nodeModulesPath) + taroSelfComponents.forEach(c => { + const cPath = path.join(taroJsQuickAppComponentsPath, c) + const cMainPath = path.join(cPath, 'index') + const cRelativePath = promoteRelativePath(path.relative(filePath, cMainPath.replace(nodeModulesPath, 'npm'))) + importTaroSelfComponents.add({ + path: cRelativePath, + name: c + }) + }) + return importTaroSelfComponents +} + +export function generateQuickAppUx ({ + script, + template, + style, + imports +}: { + script?: string, + template?: string, + style?: string, + imports?: Set<{ + path: string, + name: string + }> +}) { + let uxTxt = '' + if (imports && imports.size) { + imports.forEach(item => { + uxTxt += `\n` + }) + } + if (style) { + if (REG_STYLE.test(style)) { + uxTxt += `\n` + } else { + uxTxt += `\n` + } + } + if (template) { + uxTxt += `\n` + } + if (script) { + if (REG_SCRIPT.test(script)) { + uxTxt += `\n` + } else { + uxTxt += `\n` + } + } + return uxTxt +} diff --git a/packages/taro-mini-runner/src/utils/index.ts b/packages/taro-mini-runner/src/utils/index.ts index 1b3b5cdf8964..b5d40c4ad190 100644 --- a/packages/taro-mini-runner/src/utils/index.ts +++ b/packages/taro-mini-runner/src/utils/index.ts @@ -202,3 +202,12 @@ export function recursiveMerge (src, ...args) { } }) } + +export function getInstalledNpmPkgPath (pkgName: string, basedir: string): string | null { + const resolvePath = require('resolve') + try { + return resolvePath.sync(`${pkgName}/package.json`, { basedir }) + } catch (err) { + return null + } +} diff --git a/packages/taro-mini-runner/src/utils/parseAst.ts b/packages/taro-mini-runner/src/utils/parseAst.ts new file mode 100644 index 000000000000..ce8a1bd9c8b6 --- /dev/null +++ b/packages/taro-mini-runner/src/utils/parseAst.ts @@ -0,0 +1,119 @@ +import { Config as IConfig } from '@tarojs/taro' +import * as t from 'babel-types' +import traverse from 'babel-traverse' + +import { BUILD_TYPES, taroJsComponents, QUICKAPP_SPECIAL_COMPONENTS } from './constants' +import { traverseObjectNode, isNpmPkg } from '../utils' +import * as _ from 'lodash' + +export default function parseAst ( + ast: t.File, + buildAdapter: BUILD_TYPES +): { + configObj: IConfig, + hasEnablePageScroll: boolean, + taroSelfComponents: Set, +} { + let configObj = {} + let hasEnablePageScroll + const taroSelfComponents = new Set() + const isQuickApp = buildAdapter === BUILD_TYPES.QUICKAPP + + traverse(ast, { + ClassDeclaration (astPath) { + const node = astPath.node + let hasCreateData = false + if (node.superClass) { + astPath.traverse({ + ClassMethod (astPath) { + if (astPath.get('key').isIdentifier({ name: '_createData' })) { + hasCreateData = true + } + } + }) + if (hasCreateData) { + astPath.traverse({ + ClassMethod (astPath) { + const node = astPath.node + if (node.kind === 'constructor') { + astPath.traverse({ + ExpressionStatement (astPath) { + const node = astPath.node + if (node.expression && + node.expression.type === 'AssignmentExpression' && + node.expression.operator === '=') { + const left = node.expression.left + if (left.type === 'MemberExpression' && + left.object.type === 'ThisExpression' && + left.property.type === 'Identifier' && + left.property.name === 'config') { + configObj = traverseObjectNode(node.expression.right, buildAdapter) + } + } + } + }) + } + } + }) + } + } + }, + ClassMethod (astPath) { + const keyName = (astPath.get('key').node as t.Identifier).name + if (keyName === 'onPageScroll' || keyName === 'onReachBottom') { + hasEnablePageScroll = true + } + }, + ClassProperty (astPath) { + const node = astPath.node + const keyName = node.key.name + if (keyName === 'config') { + configObj = traverseObjectNode(node, buildAdapter) + } + }, + ImportDeclaration (astPath) { + const node = astPath.node + const source = node.source + let value = source.value + const specifiers = node.specifiers + if (isNpmPkg(value) && isQuickApp && value === taroJsComponents) { + specifiers.forEach(specifier => { + const name = specifier.local.name + if (!QUICKAPP_SPECIAL_COMPONENTS.has(name)) { + taroSelfComponents.add(_.kebabCase(name)) + } + }) + astPath.remove() + } + }, + CallExpression (astPath) { + const node = astPath.node + const callee = node.callee as t.Identifier + if (callee.name === 'require') { + const args = node.arguments as t.StringLiteral[] + let value = args[0].value + const parentNode = astPath.parentPath.parentPath.node as t.VariableDeclaration + if (isNpmPkg(value) && isQuickApp && value === taroJsComponents) { + if (parentNode.declarations.length === 1 && parentNode.declarations[0].init) { + const id = parentNode.declarations[0].id + if (id.type === 'ObjectPattern') { + const properties = id.properties as any + properties.forEach(p => { + if (p.type === 'ObjectProperty' && p.value.type === 'Identifier') { + taroSelfComponents.add(_.kebabCase(p.value.name)) + } + }) + } + } + astPath.remove() + } + } + } + }) + + return { + configObj, + hasEnablePageScroll, + taroSelfComponents + } +} diff --git a/packages/taro-mini-runner/src/utils/types.ts b/packages/taro-mini-runner/src/utils/types.ts index e5060d7ef4c6..478eb3ad0ca9 100644 --- a/packages/taro-mini-runner/src/utils/types.ts +++ b/packages/taro-mini-runner/src/utils/types.ts @@ -22,5 +22,6 @@ export interface IChain { export interface IBuildConfig extends IProjectBaseConfig, IMiniAppConfig { isWatch: boolean, port?: number, - buildAdapter: BUILD_TYPES + buildAdapter: BUILD_TYPES, + nodeModulesPath: string } diff --git a/packages/taro-mini-runner/src/webpack/build.conf.ts b/packages/taro-mini-runner/src/webpack/build.conf.ts index 48478f9f9d8d..6519ac3cda7d 100644 --- a/packages/taro-mini-runner/src/webpack/build.conf.ts +++ b/packages/taro-mini-runner/src/webpack/build.conf.ts @@ -48,6 +48,7 @@ export default (appPath: string, mode, config: Partial): any => { miniCssExtractPluginOption = emptyObj, postcss = emptyObj, + nodeModulesPath, babel, csso, @@ -57,6 +58,7 @@ export default (appPath: string, mode, config: Partial): any => { const plugin: any = {} const minimizer: any[] = [] const sourceDir = path.join(appPath, sourceRoot) + const outputDir = path.join(appPath, outputRoot) const isQuickapp = buildAdapter === BUILD_TYPES.QUICKAPP if (copy) { @@ -64,7 +66,7 @@ export default (appPath: string, mode, config: Partial): any => { } const constantsReplaceList = mergeOption([processEnvOption(env), defineConstants]) plugin.definePlugin = getDefinePlugin([constantsReplaceList]) - plugin.miniPlugin = getMiniPlugin({ buildAdapter, constantsReplaceList }) + plugin.miniPlugin = getMiniPlugin({ sourceDir, outputDir, buildAdapter, constantsReplaceList, nodeModulesPath }) plugin.miniCssExtractPlugin = getMiniCssExtractPlugin([{ filename: `[name]${MINI_APP_FILES[buildAdapter].STYLE}`, diff --git a/packages/taro-mini-runner/src/webpack/chain.ts b/packages/taro-mini-runner/src/webpack/chain.ts index 75592f6c8657..5c2c13ff0d73 100644 --- a/packages/taro-mini-runner/src/webpack/chain.ts +++ b/packages/taro-mini-runner/src/webpack/chain.ts @@ -15,7 +15,7 @@ import { getPostcssPlugins } from './postcss.conf' import MiniPlugin from '../plugins/MiniPlugin' import { IOption } from '../utils/types' import { recursiveMerge, isNodeModule } from '../utils' -import { REG_SASS, REG_LESS, REG_STYLUS, REG_STYLE, REG_MEDIA, REG_FONT, REG_IMAGE, BUILD_TYPES, REG_SCRIPTS, MINI_APP_FILES } from '../utils/constants' +import { REG_SASS, REG_LESS, REG_STYLUS, REG_STYLE, REG_MEDIA, REG_FONT, REG_IMAGE, BUILD_TYPES, REG_SCRIPTS, MINI_APP_FILES, REG_UX } from '../utils/constants' const globalObjectMap = { [BUILD_TYPES.WEAPP]: 'wx', @@ -257,7 +257,7 @@ export const getModule = (appPath: string, { buildAdapter }]) - const rule = { + const rule: any = { sass: { test: REG_SASS, enforce: 'pre',