1 | const hash = require('hash-sum')
|
2 | const path = require('path')
|
3 | const parseRequest = require('./utils/parse-request')
|
4 | const loaderUtils = require('loader-utils')
|
5 | const config = require('./config')
|
6 | const createHelpers = require('./helpers')
|
7 | const InjectDependency = require('./dependency/InjectDependency')
|
8 | const stringifyQuery = require('./utils/stringify-query')
|
9 | const mpxJSON = require('./utils/mpx-json')
|
10 | const async = require('async')
|
11 | const matchCondition = require('./utils/match-condition')
|
12 | const fixUsingComponent = require('./utils/fix-using-component')
|
13 |
|
14 | module.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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 | }
|