UNPKG

@mpxjs/webpack-plugin

Version:

mpx compile core

298 lines (267 loc) 10.4 kB
const parseComponent = require('./parser') const createHelpers = require('./helpers') const parseRequest = require('./utils/parse-request') const { matchCondition } = require('./utils/match-condition') const addQuery = require('./utils/add-query') const async = require('async') const normalize = require('./utils/normalize') const getEntryName = require('./utils/get-entry-name') const AppEntryDependency = require('./dependencies/AppEntryDependency') const RecordResourceMapDependency = require('./dependencies/RecordResourceMapDependency') const CommonJsVariableDependency = require('./dependencies/CommonJsVariableDependency') const DynamicEntryDependency = require('./dependencies/DynamicEntryDependency') const tsWatchRunLoaderFilter = require('./utils/ts-loader-watch-run-loader-filter') const { isReact } = require('./utils/env') const preProcessJson = require('./utils/pre-process-json') const path = require('path') const processWeb = require('./web') const processReact = require('./react') const genMpxCustomElement = require('./runtime-render/gen-mpx-custom-element') module.exports = function (content) { this.cacheable() // 兼容处理处理ts-loader中watch-run/updateFile逻辑,直接跳过当前loader及后续的loader返回内容 const pathExtname = path.extname(this.resourcePath) if (!['.vue', '.mpx'].includes(pathExtname)) { this.loaderIndex = tsWatchRunLoaderFilter(this.loaders, this.loaderIndex) return content } const mpx = this.getMpx() if (!mpx) { return content } const { resourcePath, queryObj } = parseRequest(this.resource) const packageRoot = queryObj.packageRoot || mpx.currentPackageRoot const packageName = packageRoot || 'main' const independent = queryObj.independent const pagesMap = mpx.pagesMap const componentsMap = mpx.componentsMap[packageName] const mode = mpx.mode const env = mpx.env const i18n = mpx.i18n const globalSrcMode = mpx.srcMode const localSrcMode = queryObj.mode const srcMode = localSrcMode || globalSrcMode const autoScope = matchCondition(resourcePath, mpx.autoScopeRules) const isRuntimeMode = queryObj.isDynamic const emitWarning = (msg) => { this.emitWarning( new Error('[mpx-loader][' + this.resource + ']: ' + msg) ) } const emitError = (msg) => { this.emitError( new Error('[mpx-loader][' + this.resource + ']: ' + msg) ) } let ctorType = pagesMap[resourcePath] ? 'page' : componentsMap[resourcePath] ? 'component' : 'app' // 支持资源query传入isPage或isComponent支持页面/组件单独编译 if (ctorType === 'app' && (queryObj.isComponent || queryObj.isPage)) { const entryName = getEntryName(this) || mpx.getOutputPath(resourcePath, queryObj.isComponent ? 'component' : 'page') ctorType = queryObj.isComponent ? 'component' : 'page' this._module.addPresentationalDependency(new RecordResourceMapDependency(resourcePath, ctorType, entryName, packageRoot)) } if (ctorType === 'app') { const appName = getEntryName(this) if (appName) this._module.addPresentationalDependency(new AppEntryDependency(resourcePath, appName)) } if (isRuntimeMode) { const { request, outputPath } = genMpxCustomElement(packageName) this._module.addPresentationalDependency(new DynamicEntryDependency([0, 0], request, 'component', outputPath, packageRoot, '', '', { replaceContent: '', postSubpackageEntry: true })) } const loaderContext = this const isProduction = this.minimize || process.env.NODE_ENV === 'production' const filePath = this.resourcePath const moduleId = mpx.getModuleId(resourcePath, ctorType === 'app') const parts = parseComponent(content, { filePath, needMap: this.sourceMap, mode, env }) const { getRequire } = createHelpers(loaderContext) const callback = this.async() async.waterfall([ (callback) => { preProcessJson({ json: parts.json || {}, srcMode, emitWarning, emitError, ctorType, resourcePath, loaderContext }, (err, jsonInfo) => { if (err) return callback(err) callback(null, jsonInfo) }) }, (jsonInfo, callback) => { const { componentPlaceholder, componentGenerics, usingComponentsInfo, jsonContent } = jsonInfo const hasScoped = parts.styles.some(({ scoped }) => scoped) || autoScope const templateAttrs = parts.template && parts.template.attrs const hasComment = templateAttrs && templateAttrs.comments const isNative = false // 处理mode为web时输出vue格式文件 if (mode === 'web') { return processWeb({ parts, jsonContent, loaderContext, pagesMap, componentsMap, queryObj, ctorType, srcMode, moduleId, isProduction, hasScoped, hasComment, isNative, usingComponentsInfo: JSON.stringify(usingComponentsInfo), componentGenerics, autoScope, callback }) } // 处理mode为react时输出js格式文件 if (isReact(mode)) { return processReact({ parts, jsonContent, loaderContext, pagesMap, componentsMap, queryObj, ctorType, srcMode, moduleId, isProduction, hasScoped, hasComment, isNative, usingComponentsInfo: JSON.stringify(usingComponentsInfo), componentGenerics, autoScope, callback }) } const moduleGraph = this._compilation.moduleGraph const issuer = moduleGraph.getIssuer(this._module) if (issuer) { return callback(new Error(`Current ${ctorType} [${this.resourcePath}] is issued by [${issuer.resource}], which is not allowed!`)) } let output = '' // 注入模块id及资源路径 output += `global.currentModuleId = ${JSON.stringify(moduleId)}\n` if (!isProduction) { output += `global.currentResource = ${JSON.stringify(filePath)}\n` } // 为app注入i18n if (i18n && ctorType === 'app') { const i18nWxsPath = normalize.lib('runtime/i18n.wxs') const i18nWxsLoaderPath = normalize.lib('wxs/i18n-loader.js') const i18nWxsRequest = i18nWxsLoaderPath + '!' + i18nWxsPath this._module.addDependency(new CommonJsVariableDependency(i18nWxsRequest)) // 避免该模块被concatenate导致注入的i18n没有最先执行 this._module.buildInfo.moduleConcatenationBailout = 'i18n' } // 为独立分包注入init module if (independent && typeof independent === 'string') { const independentLoader = normalize.lib('independent-loader.js') const independentInitRequest = `!!${independentLoader}!${independent}` this._module.addDependency(new CommonJsVariableDependency(independentInitRequest)) // 避免该模块被concatenate导致注入的independent init没有最先执行 this._module.buildInfo.moduleConcatenationBailout = 'independent init' } // 注入构造函数 const ctor = ctorType === 'page' ? (mpx.forceUsePageCtor || mode === 'ali') ? 'Page' : 'Component' : ctorType === 'component' ? 'Component' : 'App' output += `global.currentCtor = ${ctor}\n` output += `global.currentCtorType = ${JSON.stringify(ctor.replace(/^./, (match) => { return match.toLowerCase() }))}\n` output += `global.currentResourceType = ${JSON.stringify(ctorType)}\n` // template output += '/* template */\n' const template = parts.template if (template) { const extraOptions = { ...template.src ? { ...queryObj, resourcePath } : null, hasScoped, hasComment, isNative, ctorType, moduleId, usingComponentsInfo: JSON.stringify(usingComponentsInfo), componentPlaceholder // 添加babel处理渲染函数中可能包含的...展开运算符 // 由于...运算符应用范围极小以及babel成本极高,先关闭此特性后续看情况打开 // needBabel: true } if (template.src) extraOptions.resourcePath = resourcePath // 基于global.currentInject来注入模板渲染函数和refs等信息 output += getRequire('template', template, extraOptions) + '\n' } // styles output += '/* styles */\n' if (parts.styles.length) { parts.styles.forEach((style, i) => { const scoped = style.scoped || autoScope const extraOptions = { // style src会被特殊处理为全局复用样式,不添加resourcePath,添加isStatic及issuerFile ...style.src ? { ...queryObj, isStatic: true, issuerResource: addQuery(this.resource, { type: 'styles' }, true) } : null, moduleId, scoped } // require style output += getRequire('styles', style, extraOptions, i) + '\n' }) } if (parts.styles.filter(style => !style.src).length === 0 && ctorType === 'app' && mode === 'ali') { output += getRequire('styles', {}, {}, parts.styles.length) + '\n' } // json output += '/* json */\n' // 给予json默认值, 确保生成json request以自动补全json const json = parts.json || {} output += getRequire('json', json, json.src && { ...queryObj, resourcePath }) + '\n' // script output += '/* script */\n' let scriptSrcMode = srcMode // 给予script默认值, 确保生成js request以自动补全js const script = parts.script || {} if (script) { scriptSrcMode = script.mode || scriptSrcMode if (scriptSrcMode) output += `global.currentSrcMode = ${JSON.stringify(scriptSrcMode)}\n` // 传递ctorType以补全js内容 const extraOptions = { ...script.src ? { ...queryObj, resourcePath } : null, ctorType, lang: script.lang || 'js' } output += getRequire('script', script, extraOptions) + '\n' } callback(null, output) } ], callback) }