UNPKG

32.4 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const path = require("path");
4const fs = require("fs");
5const _ = require("lodash");
6const changeCase = require("change-case");
7const babel = require("babel-core");
8const class_1 = require("../class");
9const declare_1 = require("../declare");
10const util_1 = require("../util");
11var t = babel.types;
12const GLOBAL_MIN_KEY = 'globalMin';
13const CONFIG_KEY = 'config';
14const MIXINS_KEY = 'mixins';
15const DATA_KEY = 'data';
16const PATH_SEP = path.sep;
17let $path = path;
18/**
19 * SCRIPT 模块类
20 *
21 * @export
22 * @class WxSFMScript
23 * @extends {WxSFM}
24 */
25class WxSFMScript extends class_1.WxSFM {
26 /**
27 * Creates an instance of WxSFMScript.
28 * @param {string} source
29 * @param {Request} request
30 * @param {CompileType} compileType
31 * @memberof WxSFMScript
32 */
33 constructor(source, request, options) {
34 super(source, request, {
35 destExt: request.ext === util_1.config.ext.wxs ? util_1.config.ext.wxs : util_1.config.ext.js
36 });
37 this.options = options;
38 /**
39 * 是否包含 export default
40 *
41 * @type {boolean}
42 * @memberof WxSFMScript
43 */
44 // private hasExportDefault: boolean
45 /**
46 * config 配置
47 *
48 * @type {WxSFMScript.Config}
49 * @memberof WxSFMScript
50 */
51 this.config = Object.create(null);
52 this.globalMin = {
53 config: {
54 usingComponents: {}
55 },
56 mixins: [],
57 requestDeclaration: []
58 };
59 /**
60 * 依赖列表
61 *
62 * @private
63 * @type {Depend[]}
64 * @memberof WxSFMScript
65 */
66 this.depends = [];
67 this.initConfig();
68 this.initNode();
69 this.traverse();
70 }
71 /**
72 * 返回 wxa wxp wxa 单文件中 script 模块的 config 属性
73 *
74 * @returns
75 * @memberof WxSFMScript
76 */
77 getConfig() {
78 return this.config;
79 }
80 /**
81 * 返回 wxa wxp wxa 单文件中 script 模块所引用的 wxc 组件
82 *
83 * @returns
84 * @memberof WxSFMScript
85 */
86 getUsingComponents() {
87 let { usingComponents = {} } = this.config;
88 return usingComponents;
89 }
90 getGlobalMin() {
91 if (!this.isWxa)
92 return;
93 let { config } = this.globalMin;
94 let { usingComponents = {} } = config;
95 let { usingComponents: usingComponents2 = {} } = this.config;
96 _.merge(usingComponents, _.cloneDeep(usingComponents2));
97 return this.globalMin;
98 }
99 /**
100 * 获取依赖列表
101 *
102 * @returns {Depend[]}
103 * @memberof WxSFMScript
104 */
105 getDepends() {
106 return this.depends;
107 }
108 /**
109 * 更新依赖列表
110 *
111 * @param {Request.Core[]} useRequests 可用的请求列表
112 * @memberof WxSFMScript
113 */
114 updateDepends(useRequests) {
115 let depends = this.getDepends();
116 useRequests.forEach(useRequest => {
117 depends
118 .filter(depend => {
119 return depend.requestType === useRequest.requestType && depend.request === useRequest.request;
120 })
121 .forEach(depend => {
122 let request = '';
123 request = path.relative(path.dirname(this.dest), path.dirname(useRequest.dest));
124 request = path.join(request, path.basename(useRequest.dest, useRequest.ext));
125 request = request.charAt(0) !== '.' ? `./${request}` : request;
126 request = request.split(path.sep).join('/');
127 switch (depend.requestType) {
128 case declare_1.RequestType.SCRIPT:
129 depend.$node.value = request + util_1.config.ext.js;
130 break;
131 case declare_1.RequestType.JSON:
132 // *.json => *.json.js
133 depend.$node.value = request + useRequest.ext + util_1.config.ext.js;
134 break;
135 case declare_1.RequestType.WXS:
136 if (depend.$node) {
137 depend.$node.value = request + useRequest.ext;
138 }
139 break;
140 case declare_1.RequestType.WXC:
141 case declare_1.RequestType.WXP:
142 this.config.usingComponents = Object.assign(this.config.usingComponents || {}, {
143 [depend.usingKey]: request
144 });
145 break;
146 }
147 });
148 });
149 }
150 /**
151 * 将 AST 节点树生成 code 代码
152 *
153 * @returns {string}
154 * @memberof WxSFMScript
155 */
156 generator() {
157 let { isThreeNpm, ext } = this.request;
158 // for @mindev/min-compiler-babel
159 // 第三方NPM包,不使用babel编译
160 let transformOptions = isThreeNpm ? {} : (util_1.config.compilers['babel'] || {});
161 // TODO BUG
162 // wxs文件 或者 build编译情况下,关闭sourceMaps
163 if (ext === util_1.config.ext.wxs) {
164 transformOptions = _.omit(transformOptions, 'sourceMaps');
165 }
166 let result = babel.transformFromAst(this.node, this.source, Object.assign({ ast: false, babelrc: false, filename: this.request.src }, transformOptions));
167 let { code = '' } = result;
168 return code;
169 }
170 /**
171 * 保存文件
172 *
173 * @memberof WxSFMScript
174 */
175 save() {
176 super.save();
177 }
178 /**
179 * 移除文件
180 *
181 * @memberof WxSFMScript
182 */
183 remove() {
184 super.remove();
185 }
186 /**
187 * 保存文件后的处理函数
188 *
189 * @memberof WxSFMScript
190 */
191 afterSave() {
192 this.saveConfigFile();
193 }
194 initConfig() {
195 if (!this.isWxp)
196 return;
197 let { globalMin } = util_1.Global.layout.app;
198 let { config } = globalMin;
199 let { usingComponents } = config;
200 this.config = _.merge({}, this.config, {
201 usingComponents
202 });
203 this.addWXCDepends(this.config.usingComponents);
204 }
205 /**
206 * 初始化 AST 节点树
207 *
208 * @private
209 * @memberof WxSFMScript
210 */
211 initNode() {
212 let result = babel.transform(this.source, {
213 ast: true,
214 babelrc: false
215 });
216 let { ast = t.emptyStatement() } = result;
217 this.node = ast;
218 }
219 /**
220 * AST 节点树转换器
221 *
222 * @private
223 * @memberof WxSFMScript
224 */
225 traverse() {
226 let visitor = {
227 Program: (path) => {
228 this.createMixinsDeclaration(path);
229 },
230 // import hello from './hello
231 ImportDeclaration: (path) => {
232 this.visitDepend(path);
233 },
234 CallExpression: (path) => {
235 this.visitDepend(path);
236 this.createMixinsProperties(path);
237 },
238 ExportDefaultDeclaration: (path) => {
239 // this.hasExportDefault = true
240 },
241 ObjectExpression: (path) => {
242 this.visitStructure(path);
243 },
244 ObjectProperty: (path) => {
245 this.visitMarkdown(path);
246 this.visitConfig(path);
247 this.visitGlobalMin(path);
248 }
249 };
250 babel.traverse(this.node, visitor);
251 }
252 checkUseModuleExports(path) {
253 if (!this.isSFC) {
254 return undefined;
255 }
256 // the parent is module.exports = {}; exports.default = {}
257 if (!t.isAssignmentExpression(path.parent)) {
258 return undefined;
259 }
260 let { left, operator } = path.parent;
261 if (operator !== '=') {
262 return undefined;
263 }
264 // left => module.exports or exports.default
265 // operator => =
266 // right => { ... }
267 if (!t.isMemberExpression(left)) {
268 return undefined;
269 }
270 if (!t.isIdentifier(left.object) || !t.isIdentifier(left.property)) {
271 return undefined;
272 }
273 let expression = `${left.object.name}.${left.property.name}`;
274 if (expression !== 'module.exports' && expression !== 'exports.default') {
275 return undefined;
276 }
277 return true;
278 }
279 /**
280 * babel.traverse 转换访问器方法,用于在 export default 增加一个构造函数
281 *
282 * @private
283 * @param {NodePath<t.ObjectExpression>} path 节点路径
284 * @memberof WxSFMScript
285 */
286 checkUseExportDefault(path) {
287 if (!this.isSFC) {
288 return undefined;
289 }
290 // the parent is export default
291 if (!t.isExportDefaultDeclaration(path.parent)) {
292 return undefined;
293 }
294 return true;
295 }
296 visitStructure(path) {
297 // export default {...} => export default Component({...})
298 // export default {...} => export default Page({...})
299 // export default {...} => export default App({...})
300 // module.exports = {...} => export default App({...})
301 if (!this.checkUseExportDefault(path) && !this.checkUseModuleExports(path)) {
302 return;
303 }
304 // .wxc => wxc => Component
305 // .wxp => wxc => Page
306 // .wxa => wxa => App
307 let extKey = _.findKey(util_1.config.ext, (ext) => ext === this.request.ext) || '';
308 let structure = util_1.config.structure[extKey];
309 if (!structure) {
310 util_1.log.error('没找到构造器');
311 return;
312 }
313 path.replaceWith(t.callExpression(t.identifier(structure), [t.objectExpression(path.node.properties)]));
314 }
315 /**
316 * babel.traverse 转换访问器方法,用于将 docs 和 demos 目录下文件md内容转换成 html 并写入到 data 属性 中
317 *
318 * @private
319 * @param {NodePath<t.ObjectProperty>} path 节点路径
320 * @memberof WxSFMScript
321 */
322 visitMarkdown(path) {
323 if (!this.isWxp) {
324 return;
325 }
326 let { key, value } = path.node;
327 let dataKey = '';
328 if (t.isIdentifier(key)) {
329 dataKey = key.name;
330 }
331 else if (t.isStringLiteral(key)) {
332 dataKey = key.value;
333 }
334 if (DATA_KEY !== dataKey) {
335 return;
336 }
337 if (!value) {
338 util_1.log.warn('data 属性没有值');
339 return;
340 }
341 if (!t.isObjectExpression(value)) {
342 util_1.log.warn('data 属性不是一个ObjectExpression');
343 return;
344 }
345 let properties = [];
346 // [['src', 'pages'], ['abnor', 'index.wxp']] => ['src', 'pages', 'abnor', 'index.wxp'] => 'src\/pages\/abnor\/index.wxp'
347 let pattern = Array.prototype.concat.apply([], [util_1.config.pages.split('/'), ['([a-z-]+)', `index${util_1.config.ext.wxp}`]]).join(`\\${PATH_SEP}`);
348 // src/pages/abnor/index.wxp => ['src/pages/abnor/index.wxp', 'abnor']
349 let matchs = this.request.srcRelative.match(new RegExp(`^${pattern}$`));
350 if (!matchs || matchs.length < 2) {
351 return;
352 }
353 // abnor => wxc-abnor
354 let pkgDirName = `${util_1.config.prefixStr}${matchs[1]}`;
355 // ~/you_project_path/src/packages/wxc-abnor/README.md
356 let readmeFile = util_1.config.getPath('packages', pkgDirName, 'README.md');
357 properties.push(t.objectProperty(t.identifier('readme'), // readme
358 t.stringLiteral(this.md2htmlFromFile(readmeFile))));
359 // let docIntroFile = 'docs/intro.md'
360 // let docApiFile = 'docs/api.md'
361 // let docChangeLogFile = 'docs/changelog.md'
362 // properties.push(
363 // t.objectProperty(
364 // t.identifier('docIntro'), // docIntro
365 // t.stringLiteral(this.md2htmlFromFile(docIntroFile)) // <h1></h1>
366 // )
367 // )
368 // properties.push(
369 // t.objectProperty(
370 // t.identifier('docApi'), // docApi
371 // t.stringLiteral(this.md2htmlFromFile(docApiFile))
372 // )
373 // )
374 // properties.push(
375 // t.objectProperty(
376 // t.identifier('docChangeLog'), // docChangeLog
377 // t.stringLiteral(this.md2htmlFromFile(docChangeLogFile))
378 // )
379 // )
380 // 前提条件,需要将config字段写在js模块最前面
381 let dependWxcs = this.depends.filter(depend => {
382 return depend.requestType === declare_1.RequestType.WXC && /^demo-/.test(depend.usingKey);
383 });
384 _.forEach(dependWxcs, (dependWxc, index) => {
385 let name = dependWxc.usingKey;
386 let file = `${dependWxc.request}${util_1.config.ext.wxc}`;
387 properties.push(t.objectProperty(t.identifier(changeCase.camelCase(name)), // demoDefault
388 t.stringLiteral(this.md2htmlFromFile(file)) // <template><wxc-hello></wxc-hello><template>
389 ));
390 });
391 let mdObjectProperty = t.objectProperty(t.stringLiteral('__code__'), t.objectExpression(properties));
392 value.properties = [mdObjectProperty, ...value.properties];
393 }
394 /**
395 * babel.traverse 转换访问器方法,用于将import 或 require 依赖的路径提取到 this.depends 中
396 *
397 * @private
398 * @param {(NodePath<t.ImportDeclaration | t.CallExpression>)} path 节点路径
399 * @memberof WxSFMScript
400 */
401 visitDepend(path) {
402 // Extract import declaration
403 let extractImport = (node) => {
404 let { source: $node } = node;
405 // Add a dependency.
406 this.addNativeDepends($node);
407 return true;
408 };
409 // Extract require declaration
410 let extractRequire = (node) => {
411 let { callee, arguments: args } = node;
412 // It must be the require function, with parameters.
413 if (!(t.isIdentifier(callee) && callee.name === 'require' && args.length > 0)) {
414 return false;
415 }
416 // For example, from the first parameter of require('xxx').
417 let $node = args[0];
418 // Must be a string type.
419 if (!t.isStringLiteral($node))
420 return false;
421 // Add a dependency.
422 this.addNativeDepends($node);
423 return true;
424 };
425 // Add request declaration
426 let addRequestDeclaration = () => {
427 let { requestDeclaration } = this.globalMin;
428 let { node, parent } = path;
429 let $node = null;
430 if (t.isImportDeclaration(node)) {
431 $node = node;
432 }
433 if (t.isCallExpression(node) && t.isVariableDeclarator(parent)) {
434 $node = parent;
435 }
436 if (!$node)
437 return;
438 // Add a request declaration
439 requestDeclaration.push($node);
440 };
441 let { node } = path;
442 // For import
443 if (t.isImportDeclaration(node)) {
444 let isContinue = extractImport(node);
445 if (!isContinue)
446 return;
447 // Add request declaration
448 addRequestDeclaration();
449 return;
450 }
451 // For require
452 if (t.isCallExpression(node)) {
453 let isContinue = extractRequire(node);
454 if (!isContinue)
455 return;
456 // Add request declaration
457 addRequestDeclaration();
458 return;
459 }
460 }
461 /**
462 * Create or attach the mixins properties.
463 * For WXP
464 *
465 * @private
466 * @param {NodePath<t.CallExpression>} path
467 * @memberof WxSFMScript
468 */
469 createMixinsProperties(path) {
470 if (!this.isWxp)
471 return;
472 let { node: { callee, arguments: args } } = path;
473 if (!t.isMemberExpression(callee))
474 return;
475 if (!args || args.length === 0)
476 return;
477 // For Example:
478 // object.name is min
479 // property.name is Page
480 let { object, property } = callee;
481 if (!t.isIdentifier(object) || !t.isIdentifier(property))
482 return;
483 let caller = `${object.name}.${property.name}`;
484 // The mixins function is valid only in min.Page.
485 if (caller !== 'min.Page')
486 return;
487 let arg = args[0];
488 // The first argument must be the ObjectExpression.
489 if (!t.isObjectExpression(arg))
490 return;
491 let { properties } = arg;
492 // Get the mixins properties.
493 let prop = properties.find(prop => {
494 if (!t.isObjectProperty(prop))
495 return false;
496 let keyField = getKeyOrValueFieldByExpression(prop.key);
497 if (keyField === MIXINS_KEY)
498 return true;
499 });
500 let { mixins } = util_1.Global.layout.app.globalMin;
501 // Create an arrayExpression.
502 // For example:[mixin1, mixin2]
503 let arrExp = t.arrayExpression(mixins.map(mixin => {
504 return t.identifier(mixin);
505 }));
506 // The mixins property already exists.
507 if (prop && t.isObjectProperty(prop)) {
508 let { value } = prop;
509 if (!t.isArrayExpression(value))
510 return;
511 // Extend the new value from the existing mixins attribute.
512 // For example:[newMixin1, newMixin2, oldMixin1, oldMixin2]
513 value.elements = [
514 ...arrExp.elements,
515 ...value.elements
516 ];
517 }
518 else {
519 // Create a mixins attribute.
520 // For example:{mixins: [mixin1, mixin2]}
521 prop = t.objectProperty(t.identifier(MIXINS_KEY), arrExp);
522 properties.push(prop);
523 }
524 }
525 /**
526 * Create or attach the mixins declaration.
527 * For WXP
528 *
529 * @private
530 * @param {NodePath<t.Program>} path
531 * @memberof WxSFMScript
532 */
533 createMixinsDeclaration(path) {
534 if (!this.isWxp)
535 return;
536 // For import Declaration
537 // Example:
538 // 1. import mixin from 'mixins/xxx'
539 // 2. import { mixin1, mixin2 } from 'mixins/xxx'
540 let importDecl = (mixin, decl) => {
541 // specifiers => [mixin, mixin1, mixin2]
542 // source => mixins/xxx
543 let { specifiers, source } = decl;
544 // Find a name that is the same as specifiers.
545 let spe = specifiers.find(spe => {
546 let { local: { name } } = spe;
547 return name === mixin;
548 });
549 if (!spe)
550 return;
551 let newSpecifiers = [spe];
552 let newSource = resolvePath(source.value);
553 let newImportDeclaration = t.importDeclaration(newSpecifiers, newSource);
554 // Insert the top of the body.
555 body.unshift(newImportDeclaration);
556 };
557 // For require Declaration
558 // Example:
559 // 1. const mixn = require('mixins/xxx')
560 // 2. const { mixin1, mixin2 } = require('mixins/xxx')
561 // 3. const { mixin2: mixin22 } = require('mixins/xxx')
562 let requireDecl = (mixin, decl) => {
563 let { id, init } = decl;
564 if (!t.isCallExpression(init))
565 return;
566 if (!init.arguments.length)
567 return;
568 // Get first argument,Ignore other arguments
569 // For example: 'mixins/xxx'
570 let fistArgument = init.arguments[0];
571 if (!t.isStringLiteral(fistArgument))
572 return;
573 let newDeclarations = [];
574 // Get the resolved require path.
575 // For example: ['~/mixins/xxx']
576 let newArguments = [resolvePath(fistArgument.value)];
577 // For example: require('~/mixns/xxx')
578 let newInit = t.callExpression(init.callee, newArguments);
579 // ①
580 // For example:
581 // id => { mixin1 }
582 // id => { mixin2: mixin22 }
583 if (t.isObjectPattern(id)) {
584 let { properties } = id;
585 // Find a name that is the same a properties.
586 let prop = properties.find(prop => {
587 if (!t.isObjectProperty(prop))
588 return false;
589 // Get mixin22 from { mixin2: mixin22 }
590 let valueField = getKeyOrValueFieldByExpression(prop.value);
591 return valueField === mixin;
592 });
593 if (!prop)
594 return;
595 // Create an objectPattern
596 let newId = t.objectPattern([prop]);
597 newDeclarations = [t.variableDeclarator(newId, newInit)];
598 }
599 // ②
600 // For example:
601 // id => mixin
602 if (t.isIdentifier(id) && id.name === mixin) {
603 // Use the original id
604 let newId = id;
605 newDeclarations = [t.variableDeclarator(newId, newInit)];
606 }
607 if (newDeclarations.length === 0)
608 return;
609 let newVariableDeclaration = t.variableDeclaration('const', newDeclarations);
610 // Insert the top of the body.
611 body.unshift(newVariableDeclaration);
612 };
613 // The require path of the mixins is resolved.
614 let resolvePath = (requirePath) => {
615 if (requirePath.charAt(0) === '.') {
616 let { request: appRequest } = util_1.Global.layout.app;
617 let { src: appFilePath } = appRequest;
618 let { src: curFilePath } = this.request;
619 // The relative path from the current file to the app file.
620 // For example:
621 // from ~/src/pages/home/index.wxp
622 // to ~/src/app.wxa
623 let relativePath = $path.relative($path.dirname(curFilePath), $path.dirname(appFilePath));
624 // For example: ../../
625 requirePath = $path.join(relativePath, requirePath);
626 }
627 return t.stringLiteral(requirePath);
628 };
629 let { node: { body } } = path;
630 let { globalMin } = util_1.Global.layout.app;
631 let { mixins, requestDeclaration } = globalMin;
632 mixins.forEach(mixin => {
633 requestDeclaration.forEach(decl => {
634 if (t.isImportDeclaration(decl)) {
635 importDecl(mixin, decl);
636 }
637 if (t.isVariableDeclarator(decl)) {
638 requireDecl(mixin, decl);
639 }
640 });
641 });
642 }
643 /**
644 * babel.traverse 转换访问器方法,用于设置 this.config 配置对象
645 *
646 * @private
647 * @param {NodePath<t.ObjectProperty>} path
648 * @memberof WxSFMScript
649 */
650 visitConfig(path) {
651 if (!this.isSFC)
652 return;
653 let { node, parent } = path;
654 let $config = getConfigObjectByNode(node);
655 if (!$config)
656 return;
657 this.config = _.merge({}, this.config, $config);
658 this.addWXCDepends(this.config.usingComponents);
659 path.remove();
660 // value.properties.forEach(prop => {
661 // // key => 'navigationBarTitleText'
662 // // value => 'Title'
663 // if (!t.isObjectProperty(prop))
664 // return
665 // let key = ''
666 // if (t.isStringLiteral(prop.key)) { // 'navigationBarTitleText' || 'usingComponents'
667 // key = prop.key.value
668 // } else if (t.isIdentifier(prop.key)) { // navigationBarTitleText || usingComponents
669 // key = prop.key.name
670 // }
671 // if (!key)
672 // return
673 // this.setConfigUsing(key, prop.value)
674 // this.setConfigProp(key, prop.value)
675 // })
676 // path.remove()
677 }
678 visitGlobalMin(path) {
679 let { node } = path;
680 if (!this.isWxa)
681 return;
682 if (!node)
683 return;
684 // Extract config from globalMix.
685 let extractConfig = (prop) => {
686 let $config = getConfigObjectByNode(prop);
687 let { config } = this.globalMin;
688 // Merge the config properties to globalMin.
689 _.merge(config, $config);
690 };
691 // Extract mixins from globalMix.
692 let extractMixins = (prop) => {
693 if (!t.isArrayExpression(prop.value)) {
694 util_1.log.warn('mixins 属性不是一个 ArrayExpression 类型');
695 return;
696 }
697 // Register the list of elements for mixins.
698 let { elements } = prop.value;
699 let { mixins } = this.globalMin;
700 let $mixins = elements.map(elem => {
701 if (!t.isIdentifier(elem)) {
702 util_1.log.warn(`mixins 中包含非 Identifier 类型的元素`);
703 return;
704 }
705 return elem.name;
706 }).filter(elem => !!elem);
707 $mixins.forEach(mixin => mixins.push(mixin));
708 };
709 let { key, value } = node;
710 let keyField = getKeyOrValueFieldByExpression(key);
711 if (GLOBAL_MIN_KEY !== keyField) {
712 return undefined;
713 }
714 if (!value || value.type !== 'ObjectExpression') {
715 return undefined;
716 }
717 // { config: {}, mixins: []}
718 let { properties } = value;
719 properties.forEach(prop => {
720 if (!t.isObjectProperty(prop))
721 return;
722 // Get the key field name from globalMix.
723 let keyField = getKeyOrValueFieldByExpression(prop.key);
724 switch (keyField) {
725 case CONFIG_KEY:
726 extractConfig(prop);
727 break;
728 case MIXINS_KEY:
729 extractMixins(prop);
730 break;
731 }
732 });
733 _.merge(this.globalMin, {
734 config: {
735 usingComponents: {}
736 },
737 mixins: [],
738 requestDeclaration: []
739 });
740 path.remove();
741 }
742 /**
743 * 添加WXC依赖
744 *
745 * @private
746 * @param {WxSFMScript.UsingComponents} [usingComponents]
747 * @memberof WxSFMScript
748 */
749 addWXCDepends(usingComponents) {
750 if (!usingComponents)
751 return;
752 if (this.isWxc || this.isWxp) { // 组件 & 页面
753 // TODO There is duplication of dependency.
754 _.forIn(usingComponents, (value, key) => {
755 this.depends.push({
756 request: value,
757 requestType: declare_1.RequestType.WXC,
758 usingKey: key
759 });
760 });
761 }
762 }
763 addNativeDepends($node) {
764 let request = $node.value;
765 let isJsonExt = path.extname(request) === util_1.config.ext.json;
766 let isWxsExt = path.extname(request) === util_1.config.ext.wxs;
767 if (isJsonExt) {
768 this.depends.push({
769 request,
770 requestType: declare_1.RequestType.JSON,
771 $node
772 });
773 }
774 else if (isWxsExt) {
775 this.depends.push({
776 request,
777 requestType: declare_1.RequestType.WXS,
778 $node
779 });
780 }
781 else {
782 let isVirtual = !!util_1.config.resolveVirtual[request];
783 this.depends.push({
784 request,
785 requestType: declare_1.RequestType.SCRIPT,
786 $node,
787 isVirtual
788 });
789 }
790 }
791 /**
792 * 将文件的MD内容转换成HTML
793 *
794 * @private
795 * @param {string} file 文件地址
796 * @returns
797 * @memberof WxSFMScript
798 */
799 md2htmlFromFile(file) {
800 if (!path.isAbsolute(file)) {
801 file = path.join(path.dirname(this.request.src), file);
802 }
803 if (fs.existsSync(file)) {
804 let source = fs.readFileSync(file, 'utf-8');
805 let isWxc = path.extname(file) === util_1.config.ext.wxc;
806 if (isWxc) {
807 source = '``` html\n' + source + '\n```';
808 }
809 return `${util_1.md.md2html(source, isWxc)}`;
810 }
811 return '';
812 }
813 /**
814 * 将 wxp wxa 单文件中 script 模块的 config 属性值提取并过滤 并保存到 file.json 中
815 *
816 * @private
817 * @memberof WxSFMScript
818 */
819 saveConfigFile() {
820 if (!this.isWxp && !this.isWxc)
821 return;
822 let configCopy = _.cloneDeep(this.config);
823 if (this.isWxc) {
824 configCopy.component = true;
825 }
826 // save config
827 let dester = this.getDester(util_1.config.ext.json);
828 util_1.log.msg(util_1.LogType.WRITE, dester.destRelative);
829 util_1.default.writeFile(dester.dest, JSON.stringify(configCopy, null, 2));
830 }
831}
832exports.WxSFMScript = WxSFMScript;
833/**
834 * Get key or value field name By t.Expression
835 *
836 * @param {t.Expression} keyOrValue
837 * @returns {(string | undefined)}
838 */
839function getKeyOrValueFieldByExpression(keyOrValue) {
840 // Example {config: {key, value}}
841 if (t.isIdentifier(keyOrValue)) {
842 return keyOrValue.name;
843 }
844 // Example {'config': {key, value}}
845 if (t.isStringLiteral(keyOrValue)) {
846 return keyOrValue.value;
847 }
848 return '';
849}
850/**
851 * Get the config object through the node of Babel.
852 *
853 * @private
854 * @param {t.ObjectProperty} prop
855 * @returns {(WxSFMScript.Config | undefined)}
856 */
857function getConfigObjectByNode(prop) {
858 // if (!t.isObjectProperty(node)) {
859 // return undefined
860 // }
861 let { key, value } = prop;
862 let keyField = getKeyOrValueFieldByExpression(key);
863 if (CONFIG_KEY !== keyField) {
864 return undefined;
865 }
866 if (!value) {
867 return undefined;
868 }
869 if (!t.isObjectExpression(value)) {
870 util_1.log.warn('config 属性不是一个 ObjectExpression 类型');
871 return undefined;
872 }
873 let $config = {};
874 // Create ast
875 let configProgram = t.program([
876 t.expressionStatement(t.assignmentExpression('=', t.identifier('$config'), value) // config = value
877 )
878 ]);
879 let { code: configCode = '' } = babel.transformFromAst(configProgram, '', {
880 code: true,
881 ast: false,
882 babelrc: false
883 });
884 // Execute the code and export a $config object.
885 eval(configCode);
886 return _.merge($config, {
887 usingComponents: {}
888 });
889}
890// 设置 config 的 usingComponents 的属性
891// private setConfigUsing (propKey: string, propValue: t.Expression) {
892// if (propKey !== USING_KEY)
893// return
894// if (!this.isWxc && !this.isWxp)
895// return
896// if (!t.isObjectExpression(propValue)) {
897// log.warn('config.usingComponents 属性不是一个ObjectExpression')
898// return
899// }
900// // {'value': {'properties': [{'wx-loading': '@scope/wxc-loading'}]}}
901// propValue.properties.forEach(prop => {
902// // key => 'wxc-loading'
903// // value => '@scope/wxc-loading'
904// if (!t.isObjectProperty(prop))
905// return
906// let key = ''
907// let value = ''
908// if (t.isStringLiteral(prop.key)) { // 'wxc-loading'
909// key = prop.key.value
910// } else if (t.isIdentifier(prop.key)) { // loading
911// key = prop.key.name
912// }
913// if (t.isStringLiteral(prop.value)) { // '@scope/wxc-loading'
914// value = prop.value.value
915// }
916// if (!key || !value)
917// return
918// this.config.usingComponents = this.config.usingComponents || {}
919// // key => 'wxc-loading'
920// // value => '@scope/wxc-loading'
921// this.config.usingComponents[key] = value
922// // 'wxc-loading' => '@scope/wxc-loading'
923// this.depends.push({
924// request: value,
925// requestType: RequestType.WXC,
926// usingKey: key
927// })
928// })
929// }
930// 设置 config 的属性
931// private setConfigProp (propKey: string, propValue: t.Expression) {
932// if (propKey === USING_KEY)
933// return
934// let key = propKey
935// let value: string | boolean | undefined = undefined
936// if (t.isStringLiteral(propValue)) { // 'Title'
937// value = propValue.value
938// } else if (t.isIdentifier(propValue)) { // 100
939// value = propValue.name
940// } else if (t.isBooleanLiteral(propValue)) { // true
941// value = propValue.value
942// }
943// if (_.isUndefined(value))
944// return
945// this.config[key] = value
946// }