UNPKG

8.12 kBJavaScriptView Raw
1const hash = require('hash-sum')
2const path = require('path')
3const parseRequest = require('./utils/parse-request')
4const loaderUtils = require('loader-utils')
5const config = require('./config')
6const createHelpers = require('./helpers')
7const InjectDependency = require('./dependency/InjectDependency')
8const stringifyQuery = require('./utils/stringify-query')
9const mpxJSON = require('./utils/mpx-json')
10const async = require('async')
11const matchCondition = require('./utils/match-condition')
12const fixUsingComponent = require('./utils/fix-using-component')
13
14module.exports = function (content) {
15 this.cacheable()
16
17 const mpx = this._compilation.__mpx__
18 if (!mpx) {
19 return content
20 }
21
22 const nativeCallback = this.async()
23
24 const loaderContext = this
25 const isProduction = this.minimize || process.env.NODE_ENV === 'production'
26 const options = loaderUtils.getOptions(this) || {}
27
28 const filePath = this.resourcePath
29
30 const moduleId = 'm' + hash(this._module.identifier())
31
32 const projectRoot = mpx.projectRoot
33 const mode = mpx.mode
34 const defs = mpx.defs
35 const globalSrcMode = mpx.srcMode
36 const queryObj = loaderUtils.parseQuery(this.resourceQuery || '?')
37 const localSrcMode = queryObj.mode
38 const packageName = mpx.currentPackageRoot || 'main'
39 const pagesMap = mpx.pagesMap
40 const componentsMap = mpx.componentsMap[packageName]
41 const resourcePath = parseRequest(this.resource).resourcePath
42 const parsed = path.parse(resourcePath)
43 const resourceName = path.join(parsed.dir, parsed.name)
44 const isApp = !pagesMap[resourcePath] && !componentsMap[resourcePath]
45 const srcMode = localSrcMode || globalSrcMode
46 const fs = this._compiler.inputFileSystem
47 const originTypeExtMap = config[srcMode].typeExtMap
48 const typeExtMap = Object.assign({}, originTypeExtMap)
49 const autoScope = matchCondition(resourcePath, mpx.autoScopeRules)
50
51 const EXT_MPX_JSON = '.json.js'
52 const CSS_LANG_EXT_MAP = {
53 less: '.less',
54 stylus: '.styl',
55 sass: '.sass',
56 scss: '.scss',
57 css: originTypeExtMap.styles
58 }
59
60 let useMPXJSON = false
61 let cssLang = ''
62
63 const needCssSourceMap = (
64 !isProduction &&
65 this.sourceMap &&
66 options.cssSourceMap !== false
67 )
68 const hasScoped = (queryObj.scoped || autoScope) && mode === 'ali'
69 const hasComment = false
70 const isNative = true
71
72 const tryEvalMPXJSON = (callback) => {
73 const _src = resourceName + EXT_MPX_JSON
74 this.addDependency(_src)
75 fs.readFile(_src, (err, raw) => {
76 if (err) {
77 callback(err)
78 } else {
79 try {
80 const source = raw.toString('utf-8')
81 const text = mpxJSON.compileMPXJSONText({ source, defs, filePath: _src })
82 callback(null, text)
83 } catch (e) {
84 callback(e)
85 }
86 }
87 })
88 }
89
90 function checkFileExists (extName, callback) {
91 fs.stat(resourceName + extName, (err) => {
92 callback(null, !err)
93 })
94 }
95
96 function checkCSSLangFiles (callback) {
97 const langs = mpx.nativeOptions.cssLangs || ['css', 'less', 'stylus', 'scss', 'sass']
98 const results = []
99 async.eachOf(langs, function (lang, i, callback) {
100 if (!CSS_LANG_EXT_MAP[lang]) {
101 return callback()
102 }
103 checkFileExists(CSS_LANG_EXT_MAP[lang], (err, result) => {
104 if (!err && result) {
105 results[i] = true
106 }
107 callback(err)
108 })
109 }, function (err) {
110 for (let i = 0; i < langs.length; i++) {
111 if (results[i]) {
112 cssLang = langs[i]
113 typeExtMap.styles = CSS_LANG_EXT_MAP[cssLang]
114 break
115 }
116 }
117 callback(err)
118 })
119 }
120
121 function checkMPXJSONFile (callback) {
122 // checkFileExists(EXT_MPX_JSON, (err, result) => {
123 checkFileExists(EXT_MPX_JSON, (err, result) => {
124 if (!err && result) {
125 useMPXJSON = true
126 typeExtMap.json = EXT_MPX_JSON
127 }
128 callback(err)
129 })
130 }
131
132 // 先读取json获取usingComponents信息
133 async.waterfall([
134 (callback) => {
135 async.parallel([
136 checkCSSLangFiles,
137 checkMPXJSONFile
138 ], (err) => {
139 callback(err)
140 })
141 },
142 (callback) => {
143 async.forEachOf(typeExtMap, (ext, key, callback) => {
144 // 检测到mpxJson或cssLang时跳过对应类型文件检测
145 if ((key === 'json' && useMPXJSON) || (key === 'styles' && cssLang)) {
146 return callback()
147 }
148 checkFileExists(ext, (err, result) => {
149 if (!err && !result) {
150 delete typeExtMap[key]
151 }
152 callback(err)
153 })
154 }, callback)
155 },
156 (callback) => {
157 // 对原生写法增强json写法,可以用js来写json,尝试找.json.js文件,找不到用回json的内容
158 if (useMPXJSON) {
159 tryEvalMPXJSON(callback)
160 } else {
161 if (typeExtMap['json']) {
162 const jsonSrc = resourceName + typeExtMap['json']
163 this.addDependency(jsonSrc)
164 fs.readFile(jsonSrc, (err, raw) => {
165 if (err) {
166 callback(err)
167 } else {
168 callback(null, raw.toString('utf-8'))
169 }
170 })
171 } else {
172 callback(null, '{}')
173 }
174 }
175 }, (content, callback) => {
176 let usingComponents = [].concat(Object.keys(mpx.usingComponents))
177 try {
178 let ret = JSON.parse(content)
179 if (ret.usingComponents) {
180 fixUsingComponent({ usingComponents: ret.usingComponents, mode })
181 usingComponents = usingComponents.concat(Object.keys(ret.usingComponents))
182 }
183 } catch (e) {
184 }
185 const {
186 getRequireForSrc,
187 getNamedExportsForSrc
188 } = createHelpers(
189 loaderContext,
190 options,
191 moduleId,
192 isProduction,
193 hasScoped,
194 hasComment,
195 usingComponents,
196 needCssSourceMap,
197 srcMode,
198 isNative,
199 projectRoot
200 )
201
202 const getRequire = (type) => {
203 let localQuery = Object.assign({}, queryObj)
204 let src = resourceName + typeExtMap[type]
205 localQuery.resourcePath = resourcePath
206 if (type !== 'script') {
207 this.addDependency(src)
208 }
209 if (type === 'template' && isApp) {
210 return ''
211 }
212 if (type === 'json' && !useMPXJSON) {
213 localQuery.__component = true
214 }
215 src += stringifyQuery(localQuery)
216
217 const partsOpts = { src }
218
219 if (type === 'script') {
220 return getNamedExportsForSrc(type, partsOpts)
221 }
222 if (type === 'styles') {
223 if (cssLang !== 'css') {
224 partsOpts.lang = cssLang
225 }
226 if (hasScoped) {
227 return getRequireForSrc(type, partsOpts, 0, true)
228 }
229 }
230 return getRequireForSrc(type, partsOpts)
231 }
232
233 // 注入模块id及资源路径
234 let globalInjectCode = `global.currentModuleId = ${JSON.stringify(moduleId)}\n`
235 if (!isProduction) {
236 globalInjectCode += `global.currentResource = ${JSON.stringify(filePath)}\n`
237 }
238
239 // 注入构造函数
240 let ctor = 'App'
241 if (pagesMap[resourcePath]) {
242 if (mpx.forceUsePageCtor || mode === 'ali') {
243 ctor = 'Page'
244 } else {
245 ctor = 'Component'
246 }
247 } else if (componentsMap[resourcePath]) {
248 ctor = 'Component'
249 }
250 globalInjectCode += `global.currentCtor = ${ctor}\n`
251 globalInjectCode += `global.currentCtorType = ${JSON.stringify(ctor.replace(/^./, (match) => {
252 return match.toLowerCase()
253 }))}\n`
254
255 if (srcMode) {
256 globalInjectCode += `global.currentSrcMode = ${JSON.stringify(srcMode)}\n`
257 }
258
259 if (!mpx.forceDisableInject) {
260 const dep = new InjectDependency({
261 content: globalInjectCode,
262 index: -3
263 })
264 this._module.addDependency(dep)
265 }
266
267 // 触发webpack global var 注入
268 let output = 'global.currentModuleId;\n'
269
270 for (let type in typeExtMap) {
271 output += `/* ${type} */\n${getRequire(type)}\n\n`
272 }
273
274 callback(null, output)
275 }
276 ], nativeCallback)
277}