UNPKG

12.4 kBJavaScriptView Raw
1const querystring = require('querystring')
2const loaderUtils = require('loader-utils')
3const normalize = require('./utils/normalize')
4const tryRequire = require('./utils/try-require')
5const styleCompilerPath = normalize.lib('style-compiler/index')
6const templateCompilerPath = normalize.lib('template-compiler/index')
7const jsonCompilerPath = normalize.lib('json-compiler/index')
8const templatePreprocessorPath = normalize.lib('template-compiler/preprocessor')
9const wxsLoaderPath = normalize.lib('wxs/wxs-loader')
10const wxmlLoaderPath = normalize.lib('wxml/wxml-loader')
11const wxssLoaderPath = normalize.lib('wxss/loader')
12const config = require('./config')
13const selectorPath = normalize.lib('selector')
14const extractorPath = normalize.lib('extractor')
15const addQuery = require('./utils/add-query')
16
17// check whether default js loader exists
18const hasBabel = !!tryRequire('babel-loader')
19
20const rewriterInjectRE = /\b(css(?:-loader)?(?:\?[^!]+)?)(?:!|$)/
21
22const defaultLang = {
23 template: 'html',
24 styles: 'css',
25 script: 'js',
26 json: 'json',
27 wxs: 'wxs'
28}
29
30const postcssExtensions = [
31 'postcss', 'pcss', 'sugarss', 'sss'
32]
33
34function getRawRequest ({ resource, loaderIndex, loaders }, excludedPreLoaders = /eslint-loader/) {
35 return loaderUtils.getRemainingRequest({
36 resource: resource,
37 loaderIndex: loaderIndex,
38 loaders: loaders.filter(loader => !excludedPreLoaders.test(loader.path))
39 })
40}
41
42// sass => sass-loader
43// sass-loader => sass-loader
44// sass?indentedSyntax!css => sass-loader?indentedSyntax&!css-loader
45function ensureLoader (lang) {
46 return lang
47 .split('!')
48 .map(loader =>
49 loader.replace(
50 /^([\w-]+)(\?.*)?/,
51 (_, name, query) =>
52 (/-loader$/.test(name) ? name : name + '-loader') + (query || '')
53 )
54 )
55 .join('!')
56}
57
58function ensureBang (loader) {
59 if (loader && loader.charAt(loader.length - 1) !== '!') {
60 return loader + '!'
61 } else {
62 return loader
63 }
64}
65
66function resolveLoaders (options, moduleId, isProduction, hasScoped, hasComment, usingComponents, needCssSourceMap, projectRoot = '') {
67 let cssLoaderOptions = ''
68 let wxmlLoaderOptions = ''
69 let jsonCompilerOptions = ''
70 if (needCssSourceMap) {
71 cssLoaderOptions += '?sourceMap'
72 }
73
74 wxmlLoaderOptions += '?root=' + projectRoot
75 jsonCompilerOptions += '?root=' + projectRoot
76 // 由于css-loader@1.0之后不再支持root,暂时不允许在css中使用/开头的路径,后续迁移至postcss-loader再进行支持
77 // 现在切回css-loader@0.28.11了,先加回来和原生小程序保持一致
78 cssLoaderOptions += (cssLoaderOptions ? '&' : '?') + 'root=' + projectRoot + '&importLoaders=1&extract=true'
79
80 const defaultLoaders = {
81 html: wxmlLoaderPath + wxmlLoaderOptions,
82 css: getCSSLoaderString(),
83 js: hasBabel ? 'babel-loader' : '',
84 json: jsonCompilerPath + jsonCompilerOptions,
85 wxs: wxsLoaderPath
86 }
87
88 function getCSSLoaderString (lang) {
89 const langLoader = lang ? ensureBang(ensureLoader(lang)) : ''
90 return ensureBang('css-loader' + cssLoaderOptions) + langLoader
91 }
92
93 return {
94 defaultLoaders,
95 getCSSLoaderString,
96 loaders: Object.assign({}, defaultLoaders, options.loaders),
97 preLoaders: options.preLoaders || {},
98 postLoaders: options.postLoaders || {}
99 }
100}
101
102module.exports = function createHelpers (loaderContext, options, moduleId, isProduction, hasScoped, hasComment, usingComponents, needCssSourceMap, srcMode, isNative, projectRoot) {
103 const rawRequest = getRawRequest(loaderContext, options.excludedPreLoaders)
104 const {
105 defaultLoaders,
106 getCSSLoaderString,
107 loaders,
108 preLoaders,
109 postLoaders
110 } = resolveLoaders(
111 options,
112 moduleId,
113 isProduction,
114 hasScoped,
115 hasComment,
116 usingComponents,
117 needCssSourceMap,
118 projectRoot
119 )
120
121 function getRequire (type, part, index, scoped) {
122 return 'require(' + getRequestString(type, part, index, scoped) + ')'
123 }
124
125 function getImport (type, part, index, scoped) {
126 return (
127 'import __' + type + '__ from ' +
128 getRequestString(type, part, index, scoped)
129 )
130 }
131
132 function getNamedExports (type, part, index, scoped) {
133 return (
134 'export * from ' +
135 getRequestString(type, part, index, scoped)
136 )
137 }
138
139 function processQuery (request, mode, type) {
140 let addQueryObj = {}
141 let removeKeys
142 if (mode) {
143 addQueryObj.mode = mode
144 }
145 // 为了使js模块全局唯一,避免闭包变量存在多份,删除js模块的分包标记
146 // 该逻辑有问题,模块复用后后续分包不会再执行component初始化函数,导致wx找不到组件
147 // if (type === 'script') {
148 // removeKeys = 'packageName'
149 // }
150 return addQuery(request, addQueryObj, removeKeys)
151 }
152
153 function getRequestString (type, part, index = 0, scoped) {
154 return loaderUtils.stringifyRequest(
155 loaderContext,
156 // disable all configuration loaders
157 '!!' +
158 // get loader string for pre-processors
159 getLoaderString(type, part, index, scoped) +
160 // select the corresponding part from the mpx file
161 getSelectorString(type, index) +
162 // the url to the actual mpx file, including remaining requests
163 processQuery(rawRequest, part.mode, type)
164 )
165 }
166
167 function getRequireForSrc (type, impt, index, scoped, prefix, withIssuer) {
168 return 'require(' + getSrcRequestString(type, impt, index, scoped, prefix, withIssuer) + ')'
169 }
170
171 function getImportForSrc (type, impt, index, scoped, prefix) {
172 return (
173 'import __' + type + '__ from ' +
174 getSrcRequestString(type, impt, index, scoped, prefix)
175 )
176 }
177
178 function getNamedExportsForSrc (type, impt, index, scoped, prefix) {
179 return (
180 'export * from ' +
181 getSrcRequestString(type, impt, index, scoped, prefix)
182 )
183 }
184
185 function getSrcRequestString (type, impt, index = 0, scoped, prefix = '!', withIssuer) {
186 let loaderString = type === 'script' ? '' : prefix + getLoaderString(type, impt, index, scoped, withIssuer)
187 let src = impt.src
188 return loaderUtils.stringifyRequest(
189 loaderContext,
190 loaderString + processQuery(src, impt.mode, type)
191 )
192 }
193
194 function addCssModulesToLoader (loader, part, index) {
195 if (!part.module) return loader
196 const option = options.cssModules || {}
197 const DEFAULT_OPTIONS = {
198 modules: true
199 }
200 const OPTIONS = {
201 localIdentName: '[hash:base64]'
202 }
203 return loader.replace(/((?:^|!)css(?:-loader)?)(\?[^!]*)?/, (m, $1, $2) => {
204 // $1: !css-loader
205 // $2: ?a=b
206 const query = loaderUtils.parseQuery($2 || '?')
207 Object.assign(query, OPTIONS, option, DEFAULT_OPTIONS)
208 if (index !== -1) {
209 query.localIdentName += '_' + index
210 }
211 return $1 + '?' + JSON.stringify(query)
212 })
213 }
214
215 function buildCustomBlockLoaderString (attrs) {
216 const noSrcAttrs = Object.assign({}, attrs)
217 delete noSrcAttrs.src
218 const qs = querystring.stringify(noSrcAttrs)
219 return qs ? '?' + qs : qs
220 }
221
222 // stringify an Array of loader objects
223 function stringifyLoaders (loaders) {
224 return loaders
225 .map(
226 obj =>
227 obj && typeof obj === 'object' && typeof obj.loader === 'string'
228 ? obj.loader +
229 (obj.options ? '?' + JSON.stringify(obj.options) : '')
230 : obj
231 )
232 .join('!')
233 }
234
235 function getLoaderString (type, part, index, scoped, withIssuer) {
236 let loader = getRawLoaderString(type, part, index, scoped)
237 const lang = getLangString(type, part)
238 if (type !== 'script' && type !== 'wxs') {
239 loader = getExtractorString(type, index, withIssuer) + loader
240 }
241 if (preLoaders[lang]) {
242 loader = loader + ensureBang(preLoaders[lang])
243 }
244 if (postLoaders[lang]) {
245 loader = ensureBang(postLoaders[lang]) + loader
246 }
247 return loader
248 }
249
250 function getLangString (type, { lang }) {
251 if (type === 'script' || type === 'template' || type === 'styles') {
252 return lang || defaultLang[type]
253 } else {
254 return type
255 }
256 }
257
258 function replaceCssLoader (rawLoader) {
259 return rawLoader.replace(/css(?:-loader)?/, wxssLoaderPath)
260 }
261
262 function getRawLoaderString (type, part, index, scoped) {
263 let lang = (part.lang && part.lang !== config[srcMode].typeExtMap.template.slice(1)) ? part.lang : defaultLang[type]
264
265 let styleCompiler = ''
266 if (type === 'styles') {
267 // style compiler that needs to be applied for all styles
268 styleCompiler = styleCompilerPath + '?' +
269 JSON.stringify({
270 moduleId,
271 scoped: !!scoped,
272 sourceMap: needCssSourceMap,
273 transRpx: options.transRpx,
274 comment: options.comment,
275 designWidth: options.designWidth
276 })
277 // normalize scss/sass/postcss if no specific loaders have been provided
278 if (!loaders[lang]) {
279 if (postcssExtensions.indexOf(lang) !== -1) {
280 lang = 'css'
281 } else if (lang === 'sass') {
282 lang = `sass?${JSON.stringify({
283 sassOptions: {
284 indentedSyntax: true
285 }
286 })}`
287 } else if (lang === 'scss') {
288 lang = 'sass'
289 }
290 }
291 }
292
293 let templateCompiler = ''
294
295 if (type === 'template') {
296 const templateCompilerOptions = {
297 usingComponents,
298 hasScoped,
299 hasComment,
300 isNative,
301 moduleId
302 }
303 templateCompiler = templateCompilerPath + '?' + JSON.stringify(templateCompilerOptions)
304 }
305
306 let loader = type === 'styles'
307 ? loaders[lang] || getCSSLoaderString(lang)
308 : loaders[lang]
309
310 if (loader != null) {
311 if (Array.isArray(loader)) {
312 loader = stringifyLoaders(loader)
313 } else if (typeof loader === 'object') {
314 loader = stringifyLoaders([loader])
315 }
316 if (type === 'styles') {
317 // add css modules
318 loader = addCssModulesToLoader(loader, part, index)
319 // inject rewriter before css loader for extractTextPlugin use cases
320 if (rewriterInjectRE.test(loader)) {
321 loader = loader.replace(
322 rewriterInjectRE,
323 (m, $1) => ensureBang($1) + ensureBang(styleCompiler)
324 )
325 } else {
326 loader = ensureBang(loader) + ensureBang(styleCompiler)
327 }
328 loader = replaceCssLoader(loader)
329 }
330
331 if (type === 'template') {
332 loader = ensureBang(loader) + ensureBang(templateCompiler)
333 }
334
335 return ensureBang(loader)
336 } else {
337 // unknown lang, infer the loader to be used
338 switch (type) {
339 case 'template':
340 // allow passing options to the template preprocessor via `templateOption` option
341 const preprocessorOption = { engine: lang, templateOption: options.templateOption || {} }
342 const templatePreprocessor = templatePreprocessorPath + '?' + JSON.stringify(preprocessorOption)
343 return ensureBang(defaultLoaders.html) + ensureBang(templateCompiler) + ensureBang(templatePreprocessor)
344 case 'styles':
345 loader = addCssModulesToLoader(defaultLoaders.css, part, index)
346 loader = replaceCssLoader(loader)
347 return ensureBang(loader) + ensureBang(styleCompiler) + ensureBang(ensureLoader(lang))
348 case 'script':
349 return ensureBang(defaultLoaders.js) + ensureBang(ensureLoader(lang))
350 default:
351 loader = loaders[type]
352 if (Array.isArray(loader)) {
353 loader = stringifyLoaders(loader)
354 }
355 return ensureBang(loader + buildCustomBlockLoaderString(part.attrs))
356 }
357 }
358 }
359
360 function getSelectorString (type, index) {
361 return ensureBang(
362 selectorPath +
363 '?type=' +
364 (type === 'script' || type === 'template' || type === 'styles' || type === 'json'
365 ? type
366 : 'customBlocks') +
367 '&index=' + index
368 )
369 }
370
371 function getExtractorString (type, index, withIssuer) {
372 return ensureBang(
373 extractorPath +
374 '?type=' +
375 (type === 'script' || type === 'template' || type === 'styles' || type === 'json'
376 ? type
377 : 'customBlocks') +
378 '&index=' + index +
379 (withIssuer ? '&issuerResource=' + loaderContext.resource : '')
380 )
381 }
382
383 return {
384 loaders,
385 getRequire,
386 getImport,
387 getNamedExports,
388 getRequireForSrc,
389 getImportForSrc,
390 getNamedExportsForSrc,
391 getRequestString,
392 getSrcRequestString
393 }
394}