1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const path = require("path");
|
4 | const fs = require("fs");
|
5 | const _ = require("lodash");
|
6 | const changeCase = require("change-case");
|
7 | const babel = require("babel-core");
|
8 | const class_1 = require("../class");
|
9 | const declare_1 = require("../declare");
|
10 | const util_1 = require("../util");
|
11 | var t = babel.types;
|
12 | const GLOBAL_MIN_KEY = 'globalMin';
|
13 | const CONFIG_KEY = 'config';
|
14 | const MIXINS_KEY = 'mixins';
|
15 | const DATA_KEY = 'data';
|
16 | const PATH_SEP = path.sep;
|
17 | let $path = path;
|
18 | /**
|
19 | * SCRIPT 模块类
|
20 | *
|
21 | * @export
|
22 | * @class WxSFMScript
|
23 | * @extends {WxSFM}
|
24 | */
|
25 | class 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 | }
|
832 | exports.WxSFMScript = WxSFMScript;
|
833 | /**
|
834 | * Get key or value field name By t.Expression
|
835 | *
|
836 | * @param {t.Expression} keyOrValue
|
837 | * @returns {(string | undefined)}
|
838 | */
|
839 | function 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 | */
|
857 | function 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 | // }
|