Skip to content

Commit

Permalink
fix(cli): 支持引用 node_modules 中组件
Browse files Browse the repository at this point in the history
  • Loading branch information
luckyadam committed Dec 26, 2019
1 parent 2322c01 commit 9d07a89
Show file tree
Hide file tree
Showing 12 changed files with 324 additions and 30 deletions.
1 change: 1 addition & 0 deletions packages/taro-mini-runner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"lodash": "^4.17.11",
"node-sass": "^4.12.0",
"ora": "^3.4.0",
"resolve": "^1.11.1",
"sass-loader": "^7.1.0",
"virtual-module-webpack-plugin": "^0.4.1",
"webpack": "^4.31.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as ModuleDependency from 'webpack/lib/dependencies/ModuleDependency'
import { PARSE_AST_TYPE } from '../utils/constants'

export default class TaroSingleEntryDependency extends ModuleDependency {
name: string
miniType: PARSE_AST_TYPE
constructor (request, name, loc, miniType) {
super(request)
this.name = name
this.loc = loc
this.miniType = miniType
}

get type () {
return 'single entry'
}
}
1 change: 0 additions & 1 deletion packages/taro-mini-runner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export default function build (config: IBuildConfig) {
rules: [
{
test: /\.(tsx?|jsx?)$/,
exclude: /node_modules/,
use: [{
loader: path.resolve(__dirname, './loaders/fileParseLoader'),
options: {
Expand Down
8 changes: 3 additions & 5 deletions packages/taro-mini-runner/src/loaders/fileParseLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,8 +449,7 @@ export default function fileParseLoader (source, ast) {
constantsReplaceList,
buildAdapter,
designWidth,
deviceRatio,
fileTypeMap
deviceRatio
} = getOptions(this)
const filePath = this.resourcePath
const newAst = transformFromAst(ast, '', {
Expand All @@ -459,9 +458,8 @@ export default function fileParseLoader (source, ast) {
[require('babel-plugin-transform-define').default, constantsReplaceList]
]
}).ast as t.File
const fileTypeInfo = fileTypeMap[filePath] || { type: PARSE_AST_TYPE.NORMAL, config: { } }
const configObj = fileTypeInfo.config
const result = processAst(newAst, buildAdapter, fileTypeInfo.type, designWidth, deviceRatio, filePath, this.context)
const miniType = this._module.miniType || PARSE_AST_TYPE.NORMAL
const result = processAst(newAst, buildAdapter, miniType, designWidth, deviceRatio, filePath, this.context)
const code = generate(result).code
const res = transform(code, babelConfig)
return res.code
Expand Down
232 changes: 214 additions & 18 deletions packages/taro-mini-runner/src/plugins/MiniPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ import * as JsonpTemplatePlugin from 'webpack/lib/web/JsonpTemplatePlugin'
import * as NodeSourcePlugin from 'webpack/lib/node/NodeSourcePlugin'
import * as LoaderTargetPlugin from 'webpack/lib/LoaderTargetPlugin'
import * as VirtualModulePlugin from 'virtual-module-webpack-plugin'
import { merge, defaults } from 'lodash'
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 { REG_TYPESCRIPT, BUILD_TYPES, PARSE_AST_TYPE, MINI_APP_FILES } from '../utils/constants'
import { traverseObjectNode, resolveScriptPath, buildUsingComponents } from '../utils'
import { REG_TYPESCRIPT, BUILD_TYPES, PARSE_AST_TYPE, MINI_APP_FILES, NODE_MODULES_REG } from '../utils/constants'
import { IComponentObj } from '../utils/types'
import { traverseObjectNode, resolveScriptPath, buildUsingComponents, isNpmPkg, resolveNpmSync } from '../utils'
import TaroSingleEntryDependency from '../dependencies/TaroSingleEntryDependency'

import TaroLoadChunksPlugin from './TaroLoadChunksPlugin'
import TaroNormalModulesPlugin from './TaroNormalModulesPlugin'
import ResolverPlugin from './ResolverPlugin'

interface IMiniPluginOptions {
appEntry?: string,
Expand Down Expand Up @@ -59,20 +63,72 @@ export const Targets = {
[BUILD_TYPES.QQ]: createTarget(BUILD_TYPES.QQ),
}

export function isFileToBeTaroComponent (
code: string,
sourcePath: string,
buildAdapter: BUILD_TYPES
) {
const transformResult = wxTransformer({
code,
sourcePath: sourcePath,
isTyped: REG_TYPESCRIPT.test(sourcePath),
adapter: buildAdapter,
isNormal: true
})
const { ast } = transformResult
let isTaroComponent = false

traverse(ast, {
ClassDeclaration (astPath) {
astPath.traverse({
ClassMethod (astPath) {
if (astPath.get('key').isIdentifier({ name: 'render' })) {
astPath.traverse({
JSXElement () {
isTaroComponent = true
}
})
}
}
})
},

ClassExpression (astPath) {
astPath.traverse({
ClassMethod (astPath) {
if (astPath.get('key').isIdentifier({ name: 'render' })) {
astPath.traverse({
JSXElement () {
isTaroComponent = true
}
})
}
}
})
}
})

return {
isTaroComponent,
transformResult
}
}

export default class MiniPlugin {
options: IMiniPluginOptions
appEntry: string
pages: Set<IComponent>
components: Set<IComponent>
sourceDir: string
context: string

constructor (options = {}) {
this.options = defaults(options || {}, {
buildAdapter: BUILD_TYPES.WEAPP,
commonChunks: ['runtime', 'vendors']
})

this.pages = new Set()
this.pages = new Set()
this.components = new Set()
}

Expand All @@ -86,24 +142,60 @@ export default class MiniPlugin {
}

apply (compiler: webpack.Compiler) {
this.context = compiler.context

compiler.hooks.run.tapAsync(
PLUGIN_NAME,
this.tryAsync(async (compiler: webpack.Compiler) => {
await this.run(compiler)
})
)

compiler.hooks.emit.tapAsync(
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(TaroSingleEntryDependency, normalModuleFactory)
// compilation.hooks.afterOptimizeChunks.tap(PLUGIN_NAME, (modules) => {
// console.log(modules)
// // modules.forEach(mod => {
// // console.log(mod.name)
// // console.log(mod._source.source())
// // })
// // callback()
// })
})

compiler.hooks.emit.tapAsync(
PLUGIN_NAME,
this.tryAsync(async compilation => {
await this.generateMiniFiles(compilation)
})
)

// compiler.resolverFactory.hooks.resolver.for('normal').tap(PLUGIN_NAME, resolver => {
// console.log('sdsdsdsdsd')
// new ResolverPlugin('resolve', 'parsedResolve').apply(resolver)
// })
// compiler.hooks.normalModuleFactory.tap(PLUGIN_NAME, nmf => {
// nmf.hooks.afterResolve.tapAsync(PLUGIN_NAME, (result, callback) => {
// // console.log(result)
// if (result.resource.indexOf('node_modules') >= 0) {
// const issuerArr = result.resource.split(path.sep)
// const lastNodeModulesIndex = issuerArr.lastIndexOf('node_modules')
// const pkgName = result.resourceResolveData.descriptionFileData.name
// issuerArr.splice(lastNodeModulesIndex + 1, pkgName.split('/').length, pkgName.replace(/\//g, path.sep))
// const newIssuer = issuerArr.join(path.sep)
// result.userRequest = newIssuer
// result.resource = newIssuer
// }
// return callback(null, result)
// })
// })

new TaroLoadChunksPlugin({
commonChunks: this.options.commonChunks,
taroFileTypeMap
}).apply(compiler)

new TaroNormalModulesPlugin().apply(compiler)
}

getAppEntry (compiler) {
Expand Down Expand Up @@ -185,6 +277,95 @@ export default class MiniPlugin {
}
}

getNpmComponentRealPath (code: string, component: IComponentObj, adapter: BUILD_TYPES): string | null {
let componentRealPath: string | null = null
let importExportName
const { isTaroComponent, transformResult } = isFileToBeTaroComponent(code, component.path, adapter)
if (isTaroComponent) {
return component.path
}
const { ast } = transformResult
traverse(ast, {
ExportNamedDeclaration (astPath) {
const node = astPath.node
const specifiers = node.specifiers
const source = node.source
if (source && source.type === 'StringLiteral') {
specifiers.forEach(specifier => {
const exported = specifier.exported
if (kebabCase(exported.name) === component.name) {
componentRealPath = resolveScriptPath(path.resolve(path.dirname(component.path), source.value))
}
})
} else {
specifiers.forEach(specifier => {
const exported = specifier.exported
if (kebabCase(exported.name) === component.name) {
importExportName = exported.name
}
})
}
},

ExportDefaultDeclaration (astPath) {
const node = astPath.node
const declaration = node.declaration as t.Identifier
if (component.type === 'default') {
importExportName = declaration.name
}
},

CallExpression (astPath) {
if (astPath.get('callee').isIdentifier({ name: 'require' })) {
const arg = astPath.get('arguments')[0]
if (t.isStringLiteral(arg.node)) {
componentRealPath = resolveScriptPath(path.resolve(path.dirname(component.path), arg.node.value))
}
}
},

Program: {
exit (astPath) {
astPath.traverse({
ImportDeclaration (astPath) {
const node = astPath.node
const specifiers = node.specifiers
const source = node.source
if (importExportName) {
specifiers.forEach(specifier => {
const local = specifier.local
if (local.name === importExportName) {
componentRealPath = resolveScriptPath(path.resolve(path.dirname(component.path), source.value))
}
})
}
}
})
}
}
})
if (componentRealPath) {
component.path = componentRealPath
code = fs.readFileSync(componentRealPath).toString()
componentRealPath = this.getNpmComponentRealPath(code, component, adapter)
}
return componentRealPath
}

transfromComponentsPath (components: IComponentObj[]) {
const { buildAdapter } = this.options
components.forEach(component => {
const componentPath = component.path
if (componentPath && isNpmPkg(componentPath)) {
const res = resolveNpmSync(componentPath, this.context)
const code = fs.readFileSync(res).toString()
const newComponent = Object.assign({}, component, { path: res })
const realComponentPath = this.getNpmComponentRealPath(code, newComponent, buildAdapter)
component.path = realComponentPath
}
})
}

getPages () {
const { buildAdapter } = this.options
const appEntry = this.appEntry
Expand Down Expand Up @@ -228,18 +409,25 @@ export default class MiniPlugin {
})
const { configObj } = this.parseAst(transformResult.ast, buildAdapter)
const isComponentConfig = isRoot ? {} : { component: true }
let depComponents = transformResult.components
this.transfromComponentsPath(depComponents)
taroFileTypeMap[file.path] = {
type: isRoot ? PARSE_AST_TYPE.PAGE : PARSE_AST_TYPE.COMPONENT,
config: merge({}, isComponentConfig, buildUsingComponents(file.path, {}, transformResult.components),configObj),
config: merge({}, isComponentConfig, buildUsingComponents(file.path, this.sourceDir, {}, depComponents),configObj),
template: transformResult.template,
code: transformResult.code
}
let depComponents = transformResult.components
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)) {
const componentName = componentPath.replace(this.sourceDir, '').replace(/\\/g, '/').replace(path.extname(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 componentObj = { name: componentName, path: componentPath }
this.components.add(componentObj)
this.getComponents(new Set([componentObj]), false)
Expand All @@ -249,24 +437,32 @@ export default class MiniPlugin {
})
}

addEntry (compiler: webpack.Compiler, entryPath, entryName, entryType) {
compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation: webpack.compilation.Compilation, callback) => {
const dep = new TaroSingleEntryDependency(entryPath, entryName, { name: entryName }, entryType)
compilation.addEntry(this.sourceDir, dep, entryName, callback)
})
}

addEntries (compiler: webpack.Compiler) {
const mainFiles = new Set([ ...this.pages, ...this.components ])
mainFiles.add({
name: 'app',
path: this.appEntry
this.addEntry(compiler, this.appEntry, 'app', PARSE_AST_TYPE.ENTRY)
this.pages.forEach(item => {
this.addEntry(compiler, item.path, item.name, PARSE_AST_TYPE.PAGE)
})
mainFiles.forEach(item => {
compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation: webpack.compilation.Compilation, callback) => {
const dep = SingleEntryPlugin.createDependency(item.path, item.name)
compilation.addEntry(this.sourceDir, dep, item.name, callback)
})
this.components.forEach(item => {
this.addEntry(compiler, item.path, item.name, PARSE_AST_TYPE.COMPONENT)
})
}

generateMiniFiles (compilation: webpack.compilation.Compilation) {
const { buildAdapter } = this.options
Object.keys(taroFileTypeMap).forEach(item => {
const relativePath = item.replace(this.sourceDir, '')
let relativePath
if (NODE_MODULES_REG.test(item)) {
relativePath = item.replace(this.context, '').replace(/node_modules/gi, 'npm')
} else {
relativePath = item.replace(this.sourceDir, '')
}
const extname = path.extname(item)
const templatePath = relativePath.replace(extname, MINI_APP_FILES[buildAdapter].TEMPL)
const jsonPath = relativePath.replace(extname, MINI_APP_FILES[buildAdapter].CONFIG)
Expand Down
Loading

0 comments on commit 9d07a89

Please sign in to comment.