UNPKG

12.1 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const fs = require("fs-extra");
4const path = require("path");
5const glob = require("glob");
6const _ = require("lodash");
7const chokidar = require("chokidar");
8const class_1 = require("../class");
9const util_1 = require("../util");
10const MINI_PROGRAM_CONFIG_FILE_NAME = 'project.config.json';
11/**
12 * 小程序类,用于转换,编译和实时监听
13 *
14 * @export
15 * @class Xcx
16 */
17class Xcx {
18 /**
19 * Creates an instance of Xcx.
20 * @param {Xcx.Options} options
21 * @memberof Xcx
22 */
23 constructor(options) {
24 this.options = options;
25 }
26 /**
27 * 解析节点树
28 *
29 * @param {(Xcx.Entry | Xcx.Entry[])} entry
30 * @returns {XcxNode[]}
31 * @memberof Xcx
32 */
33 parser(entry) {
34 let xcxNodes = [];
35 if (_.isArray(entry)) {
36 entry.forEach(item => {
37 xcxNodes = [...xcxNodes, ...this.parser(item)];
38 });
39 }
40 else {
41 if (entry.isGlob) {
42 // 搜索文件结果
43 let requests = glob.sync(entry.request, {
44 cwd: entry.parent || util_1.config.cwd
45 });
46 // 创建入口文件配置
47 let list = requests.map(request => {
48 return Object.assign({}, _.omit(entry, 'isGlob'), { request });
49 });
50 // 遍历
51 xcxNodes = [...xcxNodes, ...this.parser(list)];
52 }
53 else {
54 // 创建节点树
55 let xcxNode = class_1.XcxNode.create(entry);
56 if (xcxNode) {
57 xcxNodes.push(xcxNode);
58 }
59 }
60 }
61 return xcxNodes;
62 }
63 /**
64 * 通过 Entry 入口生成节点树并进行转换
65 *
66 * @param {(Xcx.Entry | Xcx.Entry[])} entry
67 * @memberof Xcx
68 */
69 transfromFromEntry(entry) {
70 let xcxNodes = this.parser(entry);
71 this.transfrom(xcxNodes);
72 }
73 /**
74 * 转换节点树
75 *
76 * @param {(XcxNode | XcxNode [])} xcxNode
77 * @memberof Xcx
78 */
79 transfrom(xcxNode) {
80 class_1.XcxTraverse.traverse(xcxNode, this.options.traverse);
81 }
82 /**
83 * 编译
84 *
85 * @memberof Xcx
86 */
87 compile(isFromWatch) {
88 util_1.log.newline();
89 this.clear(isFromWatch);
90 // this.copyProjectConfig()
91 this.appCompile();
92 this.pagesCompile();
93 this.imagesCompile();
94 }
95 /**
96 * 清空 packages 目录下 dest 下的文件
97 *
98 * @memberof Xcx
99 */
100 clearPackages() {
101 let { isClear, packageNames = [] } = this.options;
102 if (!isClear)
103 return;
104 packageNames.forEach(packageName => {
105 fs.emptyDirSync(util_1.config.getPath('packages', packageName, util_1.config.package.dest));
106 });
107 }
108 /**
109 * 编译 packages 下的组件,仅用于 min publish
110 *
111 * @memberof Xcx
112 */
113 compilePackages() {
114 this.clearPackages();
115 let { packageNames = [] } = this.options;
116 let glob = '';
117 if (packageNames.length === 0) {
118 glob = '**';
119 }
120 else if (packageNames.length === 1) {
121 glob = packageNames[0];
122 }
123 else {
124 glob = `{${packageNames.join(',')}}`;
125 }
126 // ./**/src/index.wxc
127 glob = `./${glob}/${util_1.config.package.src}/*${util_1.config.ext.wxc}`;
128 let xcxEntry = {
129 request: glob,
130 parent: util_1.config.getPath('packages'),
131 isMain: true,
132 isGlob: true,
133 isPublish: true
134 };
135 this.transfromFromEntry(xcxEntry);
136 }
137 /**
138 * 监听文件新增、修改、删除
139 *
140 * @memberof Xcx
141 */
142 watch() {
143 let watcher = chokidar.watch([util_1.config.src, util_1.config.packages, util_1.config.filename, MINI_PROGRAM_CONFIG_FILE_NAME], {
144 cwd: util_1.config.cwd,
145 ignored: /node_modules|\.git|\.txt|\.log|\.DS_Store|\.npmignore|package\.json/i,
146 persistent: true,
147 ignoreInitial: true
148 });
149 watcher
150 .on('add', this.watchAdd.bind(this))
151 .on('change', this.watchChange.bind(this))
152 .on('unlink', this.watchDelete.bind(this))
153 .on('error', (err) => {
154 util_1.log.fatal(err);
155 })
156 .on('ready', () => {
157 if (!this.isWatched) {
158 this.isWatched = true;
159 util_1.log.msg(util_1.LogType.WATCH, '开始监听文件改动。');
160 }
161 });
162 return watcher;
163 }
164 /**
165 * 编译 从Next里获取监听变更文件,或者是之前存在缺失文件
166 *
167 * @memberof Xcx
168 */
169 next() {
170 let requests = util_1.xcxNext.get();
171 if (!requests.length) {
172 return;
173 }
174 let xcxEntry = requests.map(request => {
175 return {
176 request,
177 parent: util_1.config.cwd,
178 isMain: true,
179 isForce: true
180 };
181 });
182 util_1.log.newline();
183 this.transfromFromEntry(xcxEntry);
184 util_1.xcxNext.reset();
185 }
186 /**
187 * 拷贝小程序项目配置文件
188 *
189 * @private
190 * @memberof Xcx
191 */
192 // private copyProjectConfig () {
193 // let src = path.join(config.cwd, MINI_PROGRAM_CONFIG_FILE_NAME)
194 // let dest = config.getPath('dest', MINI_PROGRAM_CONFIG_FILE_NAME)
195 // if (!fs.existsSync(src)) {
196 // return
197 // }
198 // log.newline()
199 // log.msg(LogType.COPY, MINI_PROGRAM_CONFIG_FILE_NAME)
200 // fs.copySync(src, dest)
201 // }
202 /**
203 * 删除小程序项目配置文件
204 *
205 * @private
206 * @memberof Xcx
207 */
208 // private deleteProjectConfig () {
209 // let dest = config.getPath('dest', MINI_PROGRAM_CONFIG_FILE_NAME)
210 // if (!fs.existsSync(dest)) {
211 // return
212 // }
213 // log.newline()
214 // log.msg(LogType.DELETE, MINI_PROGRAM_CONFIG_FILE_NAME)
215 // fs.unlinkSync(dest)
216 // }
217 /**
218 * 编译 APP 应用层
219 *
220 * @memberof Xcx
221 */
222 appCompile() {
223 let { app = {} } = this.options;
224 let { isSFC } = app;
225 let xcxEntry = {
226 request: isSFC ? `./app${util_1.config.ext.wxa}` : './app.{js,wxss}',
227 parent: util_1.config.getPath('src'),
228 isMain: true,
229 isGlob: isSFC ? false : true
230 };
231 this.transfromFromEntry(xcxEntry);
232 }
233 /**
234 * 编译 Pages 页面列表
235 *
236 * @private
237 * @param {string[]} [pages]
238 * @memberof Xcx
239 */
240 pagesCompile() {
241 const tabBarList = util_1.Global.appTabBarList;
242 /**
243 * [
244 * pages/hello/index,
245 * pages/world/index
246 * ]
247 */
248 let pages = this.options.pages || [];
249 if (pages.length === 0) {
250 pages = util_1.Global.appPages || [];
251 }
252 let xcxEntry = [];
253 if (pages.length > 0) { // 优先编译命令行带过来的名称 或 app.json 里的 pages 字段
254 let pageFiles = [];
255 /**
256 * [
257 * pages/hello/index.wxp,
258 * pages/world/index.wxp
259 * ]
260 */
261 pageFiles = [...pageFiles, ...pages.map(page => `${page}${util_1.config.ext.wxp}`)];
262 // 合并 tabBar.List 的选项卡页面
263 pageFiles = [...pageFiles, ...tabBarList.map(tabBarItem => `${tabBarItem.pagePath}${util_1.config.ext.wxp}`)];
264 // 去重
265 pageFiles = _.uniq(pageFiles);
266 xcxEntry = [...xcxEntry, ...pageFiles.map(pageFile => {
267 return {
268 request: pageFile,
269 parent: util_1.config.getPath('src'),
270 isMain: true
271 };
272 })];
273 }
274 else { // 最后按照 模糊匹配所有的 .wxp 文件编译
275 xcxEntry = [...xcxEntry, {
276 request: `**/*${util_1.config.ext.wxp}`,
277 parent: util_1.config.getPath('pages'),
278 isMain: true,
279 isGlob: true
280 }];
281 }
282 this.transfromFromEntry(xcxEntry);
283 }
284 /**
285 * 编译 Image 图片,只支持app.json里的 tabar 选项卡的 icon 图片路径
286 *
287 * @memberof Xcx
288 */
289 imagesCompile() {
290 const tabBarList = util_1.Global.appTabBarList;
291 // Array.prototype.concat.apply([], [[], [], []])
292 const images = Array.prototype.concat.apply([], tabBarList.map(tabBarItem => {
293 let map = [];
294 if (tabBarItem.iconPath) {
295 map.push({
296 origin: util_1.config.getPath('src', tabBarItem.iconPath),
297 target: util_1.config.getPath('dest', tabBarItem.iconPath)
298 });
299 }
300 if (tabBarItem.selectedIconPath) {
301 map.push({
302 origin: util_1.config.getPath('src', tabBarItem.selectedIconPath),
303 target: util_1.config.getPath('dest', tabBarItem.selectedIconPath)
304 });
305 }
306 return map;
307 }));
308 images.forEach(image => {
309 if (!fs.existsSync(image.origin)) {
310 util_1.log.fatal(`找不到文件:${image.origin}`);
311 return;
312 }
313 fs.copySync(image.origin, image.target);
314 });
315 }
316 /**
317 * 监听新增
318 *
319 * @private
320 * @param {string} file
321 * @memberof Xcx
322 */
323 watchAdd(file) {
324 let isProjectConfig = file === MINI_PROGRAM_CONFIG_FILE_NAME;
325 // if (isProjectConfig) { // 拷贝小程序项目配置文件
326 // this.copyProjectConfig()
327 // } else {
328 // }
329 util_1.xcxNext.watchNewFile(file);
330 this.next();
331 }
332 /**
333 * 监听变更
334 *
335 * @private
336 * @param {string} file
337 * @memberof Xcx
338 */
339 watchChange(file) {
340 let isApp = file === path.join(util_1.config.src, `app${util_1.config.ext.wxa}`);
341 let isMinConfig = file === util_1.config.filename;
342 let isProjectConfig = file === MINI_PROGRAM_CONFIG_FILE_NAME;
343 // if (isProjectConfig) { // 拷贝小程序项目配置文件
344 // this.copyProjectConfig()
345 // } else
346 if (isApp || isMinConfig) { // 重新编译
347 this.compile(true);
348 }
349 else {
350 util_1.xcxNext.watchChangeFile(file);
351 this.next();
352 }
353 }
354 /**
355 * 监听删除
356 *
357 * @private
358 * @param {string} file
359 * @memberof Xcx
360 */
361 watchDelete(file) {
362 let isMinConfig = file === util_1.config.filename;
363 let isProjectConfig = file === MINI_PROGRAM_CONFIG_FILE_NAME;
364 // if (isProjectConfig) { // 删除小程序项目配置文件
365 // this.deleteProjectConfig()
366 // } else
367 if (isMinConfig) { // 重新编译
368 this.compile(true);
369 }
370 else {
371 util_1.xcxNext.watchDeleteFile(file);
372 this.next();
373 }
374 }
375 /**
376 * Clear cache and dest dir
377 *
378 * @private
379 * @param {Boolean} [isFromWatch] Is from the watch of chokidar
380 * @memberof Xcx
381 */
382 clear(isFromWatch) {
383 this.clearCache();
384 if (isFromWatch) {
385 return;
386 }
387 this.clearDest();
388 }
389 /**
390 * Clear cache
391 *
392 * @private
393 * @memberof Xcx
394 */
395 clearCache() {
396 let { isClear } = this.options;
397 if (!isClear)
398 return;
399 util_1.Global.clear();
400 util_1.xcxNext.clear();
401 util_1.xcxNodeCache.clear();
402 }
403 /**
404 * Clear dest dir
405 *
406 * @private
407 * @memberof Xcx
408 */
409 clearDest() {
410 let { isClear } = this.options;
411 if (!isClear)
412 return;
413 fs.emptyDirSync(util_1.config.getPath('dest'));
414 }
415}
416exports.Xcx = Xcx;