UNPKG

12.2 kBJavaScriptView Raw
1const webpack = require('webpack');
2const devServer = require('./web/dev-server.js');
3const getConfig = require('../configs/index.js');
4const {startServer: startWeexLiveLoad, broadcast} = require('./weex/socket-server.js');
5const previewSocket = require('./web/web-socket.js');
6const cmlLinter = require('chameleon-linter');
7const watch = require('glob-watcher');
8const fse = require('fs-extra');
9const path = require('path');
10const fs = require('fs');
11const crypto = require('crypto');
12const cmlUtils = require('chameleon-tool-utils');
13const {getEntryName} = require('../configs/utils.js');
14
15/**
16 * 非web端构建
17 * @param {*} media dev or build ...
18 * @param {*} type wx web weex
19 */
20exports.getBuildPromise = async function (media, type) {
21
22 let options = exports.getOptions(media, type);
23 let webpackConfig = await getConfig(options);
24
25 // 非web和weex 并且非增量
26 if (!~['web', 'weex'].indexOf(type) && options.increase !== true) {
27 // 异步删除output目录
28 var outputpath = webpackConfig.output.path;
29 if (outputpath) {
30 await new Promise(function(resolve, reject) {
31 fse.remove(outputpath, function(err) {
32 if (err) {
33 reject(err);
34 }
35 resolve();
36 })
37 })['catch'](e => {
38 let message = `clear file error! please remove direction ${outputpath} by yourself!`
39 cml.log.error(message);
40 throw new Error(e)
41 })
42 }
43
44 }
45 // 工程配置输出
46 let projectConfig = options && options.projectConfig;
47 let destDir = path.resolve(cml.projectRoot, `dist/${type}`);
48 // 支付宝小程序没有工程配置
49 if (['wx', 'qq', 'tt'].includes(type) && projectConfig) {
50 fse.outputJsonSync(path.join(destDir, 'project.config.json'), projectConfig, {spaces: 4})
51 }
52 if (type === 'baidu' && projectConfig) {
53 fse.outputJsonSync(path.join(destDir, 'project.swan.json'), projectConfig, {spaces: 4})
54 }
55 // sitemap配置输出
56 let siteMap = options && options.siteMap;
57 if (type === 'wx' && siteMap) {
58 fse.outputJsonSync(path.join(destDir, 'sitemap.json'), siteMap, {spaces: 4})
59 }
60 return new Promise(function(resolve, reject) {
61 // watch模式
62 if (media === 'dev') {
63 const compiler = webpack(webpackConfig);
64 if (type === 'weex') {
65 startWeexLiveLoad(options);
66 }
67 let optimizeCML = cml.config.get().optimize;
68 let watchOptions = {
69 aggregateTimeout: 300,
70 poll: undefined
71 }
72 if (optimizeCML && !optimizeCML.watchNodeModules) {
73 watchOptions.ignored = /node_modules/;
74 }
75 compiler.watch(watchOptions, (err, stats) => {
76
77 if (type === 'weex') {
78 if (!(stats && stats.compilation && stats.compilation.errors && stats.compilation.errors.length > 0)) {
79 broadcast('weex_refresh');
80 }
81 previewSocket.broadcast('weex_refresh');
82 }
83 if (err) {
84 reject(err);
85 }
86 resolve();
87 });
88 } else {
89 // build模式
90 webpack(webpackConfig, (err, stats) => {
91 if (err) {
92 reject(err);
93 }
94 resolve();
95 });
96 }
97 })
98}
99
100/**
101 * 获取webpack配置
102 * @param {*} media dev or build ...
103 * @param {*} type wx web weex
104 */
105exports.getOptions = function (media, type) {
106 let chameleonConfig = (cml.config.get() && cml.config.get()[type] && cml.config.get()[type][media]) || {};
107
108 if (!chameleonConfig) {
109 cml.log.error(`在chameleon的config中未找到 ${media}的配置参数`);
110 return;
111 }
112
113 let options = cml.utils.merge(
114 chameleonConfig,
115 {
116 type: type,
117 media,
118 root: cml.projectRoot,
119 buildType: 'weex' // 传递给dev-server,判断启动devserver的类型 暂时固定为weex
120 }
121 )
122
123 return options;
124}
125
126/**
127 * web端构建
128 * @param {*} type dev or build or 其他
129 * @param {*} isCompile 是否要编译web端 不编译web时也要启动web端服务
130 */
131exports.getWebBuildPromise = async function (media, isCompile) {
132 if (media === 'dev') {
133 let options = exports.getOptions(media, 'web');
134 let webpackConfig = await getConfig(options)
135 let compiler;
136 if (isCompile) {
137 compiler = webpack(webpackConfig);
138 }
139 return devServer({webpackConfig, options, compiler});
140 } else {
141 if (isCompile) {
142 return exports.getBuildPromise(media, 'web');
143 } else {
144 return Promise.resolve();
145 }
146 }
147}
148
149/**
150 * 构建所有端
151 * @param {*} media dev or build ...
152 * @param {*} type wx web weex
153 */
154exports.startReleaseAll = async function (media) {
155 if (media === 'build') {
156 process.env.NODE_ENV = 'production';
157 }
158 let cmlConfig = cml.config.get();
159 let allPlatform = cmlConfig.platforms;
160 let offPlatform = [];
161 let activePlatform = []; // 启动编译的platform
162 if (media === 'dev') {
163 offPlatform = cmlConfig.devOffPlatform;
164 } else if (media === 'build') {
165 offPlatform = cmlConfig.buildOffPlatform;
166 }
167 // 获取激活平台
168 for (let i = 0, j = allPlatform.length; i < j; i++) {
169 let platform = allPlatform[i];
170 if (!~offPlatform.indexOf(platform)) {
171 activePlatform.push(platform)
172 }
173 }
174
175 // 是否编译web端
176 let isCompile = !!~activePlatform.indexOf('web');
177 // 给preview使用
178 cml.activePlatform = activePlatform;
179
180 for (let i = 0, j = activePlatform.length; i < j; i++) {
181 let platform = activePlatform[i];
182 if (platform !== 'web') {
183 await exports.getBuildPromise(media, platform);
184 }
185 }
186
187 await exports.getWebBuildPromise(media, isCompile);
188 if (media === 'build') {
189 exports.createConfigJson()
190 }
191 startCmlLinter(media);
192}
193
194exports.startReleaseOne = async function(media, type) {
195 if (media === 'build') {
196 process.env.NODE_ENV = 'production';
197 }
198 // 给preview使用
199 cml.activePlatform = [type];
200 if (type === 'web') {
201 await exports.getWebBuildPromise(media, true);
202 } else {
203 let build = exports.getBuildPromise(media, type);
204 // 如果dev模式再启动web服务
205 if (media === 'dev') {
206 await build.then(res => {
207 exports.getWebBuildPromise(media, false);
208 })
209 } else {
210 await build;
211 }
212 }
213 if (media === 'build') {
214 exports.createConfigJson()
215 }
216 startCmlLinter(media);
217
218}
219
220// let lastLintTime = null;
221function startCmlLinter(media) {
222 if (cml.config.get().enableLinter === true && media === 'dev') {
223 cmlLinter(cml.projectRoot);
224
225 const watcher = watch([cml.projectRoot + '/src/**/**.**']);
226 watcher.on('change', function(path, stat) {
227 cmlLinter(cml.projectRoot);
228 });
229 }
230}
231exports.getMD5 = function(weexjs) {
232 if (!weexjs) {
233 return '';
234 }
235 const weexjsName = weexjs.split('/').pop();
236 const weexjsPath = path.resolve(cml.projectRoot, 'dist/weex/', weexjsName);
237
238 if (cml.utils.isFile(weexjsPath)) {
239 const md5sum = crypto.createHash('md5');
240 const buffer = fs.readFileSync(weexjsPath);
241 md5sum.update(buffer);
242 let md5str = md5sum.digest('hex').toUpperCase();
243 return md5str
244 }
245}
246exports.createConfigJson = function() {
247 let configJsonPath = path.join(cml.projectRoot, 'dist/config.json');
248 let configObj = {};
249 if (cml.utils.isFile(configJsonPath)) {
250 configObj = JSON.parse(fs.readFileSync(configJsonPath, {encoding: 'utf-8'}))
251 }
252 // 获取weex jsbundle地址
253
254 let config = cml.config.get();
255 config.buildInfo = config.buildInfo || {};
256 let {wxAppId = '', baiduAppId = '', alipayAppId = '', qqAppId = '', ttAppId = ''} = config.buildInfo;
257 let extPlatform = config.extPlatform ;
258 let extCommand = (typeof extPlatform === 'object') ? Object.keys(extPlatform)[0] : undefined;
259 let extAppId = ''
260 if (extCommand) { // extCommand 可能值为 tt quickApp
261 extAppId = config.buildInfo && config.buildInfo[`${extCommand}AppId`]
262 }
263 let {routerConfig, hasError} = cml.utils.getRouterConfig();
264 let entryName = getEntryName();
265 let weexjs = (configObj.entryName && configObj.entryName.js) || '';
266 let md5str = exports.getMD5(weexjs);
267 let mpa = routerConfig.mpa;
268
269
270 if (hasError) {
271 throw new Error('router.config.json格式不正确')
272 }
273
274 let result = [];
275 if (routerConfig) {
276 if (~cml.activePlatform.indexOf('web') && !routerConfig.domain) {
277 throw new Error('router.config.json 中未设置web端需要的domain字段');
278 }
279 let {domain, mode} = routerConfig;
280 routerConfig.routes.forEach(item => {
281 // 如果配置了weex多页面,那么每个路由都要重新计算对应的weexjs
282 if (mpa && mpa.weexMpa && Array.isArray(mpa.weexMpa)) { // 配置了weex多页面
283 let weexMpa = mpa.weexMpa;
284 for (let i = 0; i < weexMpa.length ; i++) {
285 if (Array.isArray(weexMpa[i].paths) && weexMpa[i].paths.includes(item.path)) {
286 weexjs = configObj[`${entryName}${i}`] && configObj[`${entryName}${i}`].js;
287 md5str = exports.getMD5(weexjs);
288 }
289 }
290 }
291 let webUrl = domain;
292 if (mode === 'history') {
293 webUrl += item.url;
294 } else if (mode === 'hash') {
295 webUrl += ('#' + item.url);
296 }
297 let route = {
298 wx: {
299 appId: wxAppId,
300 path: item.path
301 },
302 baidu: {
303 appId: baiduAppId,
304 path: item.path
305 },
306 alipay: {
307 appId: alipayAppId,
308 path: item.path
309 },
310 qq: {
311 appId: qqAppId,
312 path: item.path
313 },
314 tt: {
315 appId: ttAppId,
316 path: item.path
317 },
318 web: {
319 url: webUrl
320 },
321 weex: {
322 url: weexjs,
323 md5: md5str,
324 query: {
325 path: item.path
326 }
327 }
328 }
329 if (item.extra) {
330 route.extra = item.extra;
331 }
332 if (extCommand) {
333 route[extCommand] = {
334 appId: extAppId,
335 path: item.path
336 }
337 }
338 result.push(route);
339 })
340 // 处理subProject配置的npm包中cml项目的页面
341 let subProject = cml.config.get().subProject;
342 if (subProject && subProject.length > 0) {
343 subProject.forEach(function(item) {
344 let npmName = cmlUtils.isString(item) ? item : item.npmName;
345 let npmRouterConfig = cml.utils.readsubProjectRouterConfig(cml.projectRoot, npmName);
346 npmRouterConfig.routes && npmRouterConfig.routes.forEach(item => {
347 let cmlFilePath = path.join(cml.projectRoot, 'node_modules', npmName, 'src', item.path + '.cml');
348 let routePath = cml.utils.getPureEntryName(cmlFilePath, '', cml.projectRoot);
349 routePath = cml.utils.handleSpecialChar(routePath);
350 let webUrl = domain;
351 if (mode === 'history') {
352 webUrl += item.url;
353 } else if (mode === 'hash') {
354 webUrl += ('#' + item.url);
355 }
356 if (routePath[0] !== '/') {
357 routePath = '/' + routePath;
358 }
359 let route = {
360 wx: {
361 appId: wxAppId,
362 path: routePath
363 },
364 baidu: {
365 appId: baiduAppId,
366 path: routePath
367 },
368 alipay: {
369 appId: alipayAppId,
370 path: routePath
371 },
372 qq: {
373 appId: qqAppId,
374 path: routePath
375 },
376 tt: {
377 appId: ttAppId,
378 path: item.path
379 },
380 web: {
381 url: webUrl
382 },
383 weex: {
384 url: weexjs,
385 query: {
386 path: routePath
387 }
388 }
389 }
390 if (item.extra) {
391 route.extra = item.extra;
392 }
393 if (extCommand) {
394 route[extCommand] = {
395 appId: extAppId,
396 path: item.path
397 }
398 }
399 result.push(route);
400 })
401 })
402 }
403 }
404
405 result.forEach(item => {
406 Object.keys(item).forEach(key => {
407 if (!~cml.activePlatform.indexOf(key) && key !== 'extra') {
408 delete item[key]
409 }
410 })
411 })
412
413 cml.event.emit('config-json', result);
414 /* eslint-disable-next-line */
415 fse.outputFileSync(configJsonPath, JSON.stringify(result, '', 4))
416}