UNPKG

13.5 kBJavaScriptView Raw
1const hash = require('hash-sum')
2const parseComponent = require('./parser')
3const createHelpers = require('./helpers')
4const loaderUtils = require('loader-utils')
5const InjectDependency = require('./dependency/InjectDependency')
6const parseRequest = require('./utils/parse-request')
7const matchCondition = require('./utils/match-condition')
8const fixUsingComponent = require('./utils/fix-using-component')
9const addQuery = require('./utils/add-query')
10const async = require('async')
11const processJSON = require('./web/processJSON')
12const processScript = require('./web/processScript')
13const processStyles = require('./web/processStyles')
14const processTemplate = require('./web/processTemplate')
15const readJsonForSrc = require('./utils/read-json-for-src')
16const normalize = require('./utils/normalize')
17
18module.exports = function (content) {
19 this.cacheable()
20
21 const mpx = this._compilation.__mpx__
22 if (!mpx) {
23 return content
24 }
25 const packageName = mpx.currentPackageRoot || 'main'
26 const pagesMap = mpx.pagesMap
27 const componentsMap = mpx.componentsMap[packageName]
28 const resolveMode = mpx.resolveMode
29 const projectRoot = mpx.projectRoot
30 const mode = mpx.mode
31 const defs = mpx.defs
32 const i18n = mpx.i18n
33 const globalSrcMode = mpx.srcMode
34 const localSrcMode = loaderUtils.parseQuery(this.resourceQuery || '?').mode
35 const resourcePath = parseRequest(this.resource).resourcePath
36 const srcMode = localSrcMode || globalSrcMode
37 const vueContentCache = mpx.vueContentCache
38 const autoScope = matchCondition(resourcePath, mpx.autoScopeRules)
39
40 const resourceQueryObj = loaderUtils.parseQuery(this.resourceQuery || '?')
41
42 // 支持资源query传入page或component支持页面/组件单独编译
43 if ((resourceQueryObj.component && !componentsMap[resourcePath]) || (resourceQueryObj.page && !pagesMap[resourcePath])) {
44 let entryChunkName
45 const rawRequest = this._module.rawRequest
46 const _preparedEntrypoints = this._compilation._preparedEntrypoints
47 for (let i = 0; i < _preparedEntrypoints.length; i++) {
48 if (rawRequest === _preparedEntrypoints[i].request) {
49 entryChunkName = _preparedEntrypoints[i].name
50 break
51 }
52 }
53 if (resourceQueryObj.component) {
54 componentsMap[resourcePath] = entryChunkName || 'noEntryComponent'
55 } else {
56 pagesMap[resourcePath] = entryChunkName || 'noEntryPage'
57 }
58 }
59
60 let ctorType = 'app'
61 if (pagesMap[resourcePath]) {
62 // page
63 ctorType = 'page'
64 } else if (componentsMap[resourcePath]) {
65 // component
66 ctorType = 'component'
67 }
68
69 const loaderContext = this
70 const stringifyRequest = r => loaderUtils.stringifyRequest(loaderContext, r)
71 const isProduction = this.minimize || process.env.NODE_ENV === 'production'
72 const options = loaderUtils.getOptions(this) || {}
73
74 const filePath = this.resourcePath
75
76 const moduleId = 'm' + hash(this._module.identifier())
77
78 const needCssSourceMap = (
79 !isProduction &&
80 this.sourceMap &&
81 options.cssSourceMap !== false
82 )
83
84 const parts = parseComponent(content, filePath, this.sourceMap, mode, defs)
85
86 let output = ''
87 const callback = this.async()
88
89 async.waterfall([
90 (callback) => {
91 const json = parts.json || {}
92 if (json.src) {
93 readJsonForSrc(json.src, loaderContext, (err, result) => {
94 if (err) return callback(err)
95 json.content = result
96 callback()
97 })
98 } else {
99 callback()
100 }
101 },
102 (callback) => {
103 // web输出模式下没有任何inject,可以通过cache直接返回,由于读取src json可能会新增模块依赖,需要在之后返回缓存内容
104 if (vueContentCache.has(filePath)) {
105 return callback(null, vueContentCache.get(filePath))
106 }
107 // 只有ali才可能需要scoped
108 const hasScoped = (parts.styles.some(({ scoped }) => scoped) || autoScope) && mode === 'ali'
109 const templateAttrs = parts.template && parts.template.attrs
110 const hasComment = templateAttrs && templateAttrs.comments
111 const isNative = false
112
113 let usingComponents = [].concat(Object.keys(mpx.usingComponents))
114
115 if (parts.json && parts.json.content) {
116 try {
117 let ret = JSON.parse(parts.json.content)
118 if (ret.usingComponents) {
119 fixUsingComponent({ usingComponents: ret.usingComponents, mode })
120 usingComponents = usingComponents.concat(Object.keys(ret.usingComponents))
121 }
122 } catch (e) {
123 return callback(e)
124 }
125 }
126
127 const {
128 getRequire,
129 getNamedExports,
130 getRequireForSrc,
131 getNamedExportsForSrc
132 } = createHelpers(
133 loaderContext,
134 options,
135 moduleId,
136 isProduction,
137 hasScoped,
138 hasComment,
139 usingComponents,
140 needCssSourceMap,
141 srcMode,
142 isNative,
143 projectRoot
144 )
145
146 // 处理mode为web时输出vue格式文件
147 if (mode === 'web') {
148 if (ctorType === 'app' && !resourceQueryObj.app) {
149 const request = addQuery(this.resource, { app: true })
150 output += `
151 import App from ${stringifyRequest(request)}
152 import Vue from 'vue'
153 new Vue({
154 el: '#app',
155 render: function(h){
156 return h(App)
157 }
158 })\n
159 `
160 // 直接结束loader进入parse
161 this.loaderIndex = -1
162 return callback(null, output)
163 }
164
165 return async.waterfall([
166 (callback) => {
167 async.parallel([
168 (callback) => {
169 processTemplate(parts.template, {
170 mode,
171 srcMode,
172 defs,
173 loaderContext,
174 ctorType
175 }, callback)
176 },
177 (callback) => {
178 processStyles(parts.styles, {
179 ctorType
180 }, callback)
181 },
182 (callback) => {
183 processJSON(parts.json, {
184 mode,
185 defs,
186 resolveMode,
187 loaderContext,
188 pagesMap,
189 pagesEntryMap: mpx.pagesEntryMap,
190 componentsMap,
191 projectRoot
192 }, callback)
193 }
194 ], (err, res) => {
195 callback(err, res)
196 })
197 },
198 ([templateRes, stylesRes, jsonRes], callback) => {
199 output += templateRes.output
200 output += stylesRes.output
201 output += jsonRes.output
202 if (ctorType === 'app' && jsonRes.jsonObj.window && jsonRes.jsonObj.window.navigationBarTitleText) {
203 mpx.appTitle = jsonRes.jsonObj.window.navigationBarTitleText
204 }
205
206 let pageTitle = ''
207 if (ctorType === 'page' && jsonRes.jsonObj.navigationBarTitleText) {
208 pageTitle = jsonRes.jsonObj.navigationBarTitleText
209 }
210
211 processScript(parts.script, {
212 ctorType,
213 srcMode,
214 loaderContext,
215 isProduction,
216 getRequireForSrc,
217 i18n,
218 pageTitle,
219 mpxCid: resourceQueryObj.mpxCid,
220 builtInComponentsMap: templateRes.builtInComponentsMap,
221 localComponentsMap: jsonRes.localComponentsMap,
222 localPagesMap: jsonRes.localPagesMap
223 }, callback)
224 }
225 ], (err, scriptRes) => {
226 if (err) return callback(err)
227 output += scriptRes.output
228 vueContentCache.set(filePath, output)
229 callback(null, output)
230 })
231 }
232
233 // 触发webpack global var 注入
234 output += 'global.currentModuleId\n'
235
236 // todo loader中inject dep比较危险,watch模式下不一定靠谱,可考虑将import改为require然后通过修改loader内容注入
237 // 注入模块id及资源路径
238 let globalInjectCode = `global.currentModuleId = ${JSON.stringify(moduleId)}\n`
239 if (!isProduction) {
240 globalInjectCode += `global.currentResource = ${JSON.stringify(filePath)}\n`
241 }
242 if (ctorType === 'app' && i18n) {
243 globalInjectCode += `global.i18n = ${JSON.stringify({ locale: i18n.locale })}\n`
244
245 const i18nMethodsVar = 'i18nMethods'
246 const i18nWxsPath = normalize.lib('runtime/i18n.wxs')
247 const i18nWxsLoaderPath = normalize.lib('wxs/wxs-i18n-loader.js')
248 const i18nWxsRequest = i18nWxsLoaderPath + '!' + i18nWxsPath
249 const expression = `require(${loaderUtils.stringifyRequest(loaderContext, i18nWxsRequest)})`
250 const deps = []
251 this._module.parser.parse(expression, {
252 current: {
253 addDependency: dep => {
254 dep.userRequest = i18nMethodsVar
255 deps.push(dep)
256 }
257 },
258 module: this._module
259 })
260 this._module.addVariable(i18nMethodsVar, expression, deps)
261
262 globalInjectCode += `global.i18nMethods = ${i18nMethodsVar}\n`
263 }
264 // 注入构造函数
265 let ctor = 'App'
266 if (ctorType === 'page') {
267 if (mpx.forceUsePageCtor || mode === 'ali') {
268 ctor = 'Page'
269 } else {
270 ctor = 'Component'
271 }
272 } else if (ctorType === 'component') {
273 ctor = 'Component'
274 }
275 globalInjectCode += `global.currentCtor = ${ctor}\n`
276 globalInjectCode += `global.currentCtorType = ${JSON.stringify(ctor.replace(/^./, (match) => {
277 return match.toLowerCase()
278 }))}\n`
279
280 //
281 // <script>
282 output += '/* script */\n'
283 let scriptSrcMode = srcMode
284 const script = parts.script
285 if (script) {
286 scriptSrcMode = script.mode || scriptSrcMode
287 if (script.src) {
288 // 传入resourcePath以确保后续处理中能够识别src引入的资源为组件主资源
289 script.src = addQuery(script.src, { resourcePath })
290 output += getNamedExportsForSrc('script', script) + '\n\n'
291 } else {
292 output += getNamedExports('script', script) + '\n\n'
293 }
294 } else {
295 switch (ctorType) {
296 case 'app':
297 output += 'import {createApp} from "@mpxjs/core"\n' +
298 'createApp({})\n'
299 break
300 case 'page':
301 output += 'import {createPage} from "@mpxjs/core"\n' +
302 'createPage({})\n'
303 break
304 case 'component':
305 output += 'import {createComponent} from "@mpxjs/core"\n' +
306 'createComponent({})\n'
307 }
308 output += '\n'
309 }
310
311 if (scriptSrcMode) {
312 globalInjectCode += `global.currentSrcMode = ${JSON.stringify(scriptSrcMode)}\n`
313 }
314
315 // styles
316 output += '/* styles */\n'
317 let cssModules
318 if (parts.styles.length) {
319 let styleInjectionCode = ''
320 parts.styles.forEach((style, i) => {
321 let scoped = hasScoped ? (style.scoped || autoScope) : false
322 // require style
323 // todo style src会被特殊处理为全局复用样式,暂时不添加resourcePath,理论上在当前支持了@import样式复用后这里是可以添加resourcePath视为组件主资源的,后续待优化
324 let requireString = style.src
325 ? getRequireForSrc('styles', style, -1, scoped, undefined, true)
326 : getRequire('styles', style, i, scoped)
327
328 const hasStyleLoader = requireString.indexOf('style-loader') > -1
329 const invokeStyle = code => `${code}\n`
330
331 const moduleName = style.module === true ? '$style' : style.module
332 // setCssModule
333 if (moduleName) {
334 if (!cssModules) {
335 cssModules = {}
336 }
337 if (moduleName in cssModules) {
338 loaderContext.emitError(
339 'CSS module name "' + moduleName + '" is not unique!'
340 )
341 styleInjectionCode += invokeStyle(requireString)
342 } else {
343 cssModules[moduleName] = true
344
345 if (!hasStyleLoader) {
346 requireString += '.locals'
347 }
348
349 styleInjectionCode += invokeStyle(
350 'this["' + moduleName + '"] = ' + requireString
351 )
352 }
353 } else {
354 styleInjectionCode += invokeStyle(requireString)
355 }
356 })
357 output += styleInjectionCode + '\n'
358 }
359
360 // json
361 output += '/* json */\n'
362 // 给予json默认值, 确保生成json request以自动补全json
363 const json = parts.json || {}
364 if (json.src) {
365 json.src = addQuery(json.src, { resourcePath, __component: true })
366 output += getRequireForSrc('json', json) + '\n\n'
367 } else {
368 output += getRequire('json', json) + '\n\n'
369 }
370
371 // template
372 output += '/* template */\n'
373 const template = parts.template
374
375 if (template) {
376 if (template.src) {
377 template.src = addQuery(template.src, { resourcePath })
378 output += getRequireForSrc('template', template) + '\n\n'
379 } else {
380 output += getRequire('template', template) + '\n\n'
381 }
382 }
383
384 if (!mpx.forceDisableInject) {
385 const dep = new InjectDependency({
386 content: globalInjectCode,
387 index: -3
388 })
389 this._module.addDependency(dep)
390 }
391
392 callback(null, output)
393 }
394 ], callback)
395}