1/* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16const WebSocket = require('ws'); 17const ts = require('typescript'); 18const path = require('path'); 19const fs = require('fs'); 20const _ = require('lodash'); 21 22const { processComponentChild } = require('../lib/process_component_build'); 23const { createWatchCompilerHost } = require('../lib/ets_checker'); 24const { writeFileSync } = require('../lib/utils'); 25const { projectConfig } = require('../main'); 26const { props } = require('../lib/compile_info'); 27 28const WebSocketServer = WebSocket.Server; 29 30let pluginSocket = ''; 31 32let supplement = { 33 isAcceleratePreview: false, 34 line: 0, 35 column: 0, 36 fileName: '' 37} 38 39const pluginCommandChannelMessageHandlers = { 40 'compileComponent': handlePluginCompileComponent, 41 'default': () => {} 42}; 43 44let previewCacheFilePath; 45const messages = []; 46let start = false; 47let checkStatus = false; 48let compileStatus = false; 49let receivedMsg_; 50let errorInfo; 51let compileWithCheck; 52let connectNum = 0; 53const maxConnectNum = 8; 54 55function init(port) { 56 previewCacheFilePath = 57 path.join(projectConfig.cachePath || projectConfig.buildPath, 'preview.ets'); 58 const rootFileNames = []; 59 if (!fs.existsSync(previewCacheFilePath)) { 60 writeFileSync(previewCacheFilePath, ''); 61 } 62 rootFileNames.push(previewCacheFilePath); 63 ts.createWatchProgram( 64 createWatchCompilerHost(rootFileNames, resolveDiagnostic, delayPrintLogCount, ()=>{}, true)); 65 const wss = new WebSocketServer({ 66 port: port, 67 host: '127.0.0.1' 68 }); 69 wss.on('connection', function(ws) { 70 if (connectNum < maxConnectNum) { 71 connectNum++; 72 handlePluginConnect(ws); 73 } else { 74 ws.terminate(); 75 } 76 }); 77} 78 79function handlePluginConnect(ws) { 80 ws.on('message', function(message) { 81 pluginSocket = ws; 82 try { 83 const jsonData = JSON.parse(message); 84 handlePluginCommand(jsonData); 85 } catch(e) { 86 } 87 }); 88} 89 90function handlePluginCommand(jsonData) { 91 pluginCommandChannelMessageHandlers[jsonData.command] 92 ? pluginCommandChannelMessageHandlers[jsonData.command](jsonData) 93 : pluginCommandChannelMessageHandlers['default'](jsonData); 94} 95 96function handlePluginCompileComponent(jsonData) { 97 if (jsonData) { 98 messages.push(jsonData); 99 if (receivedMsg_) { 100 return; 101 } 102 } else if (messages.length > 0){ 103 jsonData = messages[0]; 104 } else { 105 return 106 } 107 start = true; 108 const receivedMsg = _.cloneDeep(jsonData); 109 const compilerOptions = ts.readConfigFile( 110 path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions; 111 Object.assign(compilerOptions, { 112 "sourceMap": false, 113 }); 114 const sourceNode = ts.createSourceFile('preview.ets', 115 'struct preview{build(){' + receivedMsg.data.script + '}}', 116 ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS, compilerOptions); 117 compileWithCheck = jsonData.data.compileWithCheck || 'true'; 118 if (previewCacheFilePath && fs.existsSync(previewCacheFilePath) 119 && compileWithCheck === 'true') { 120 writeFileSync(previewCacheFilePath, 'struct preview{build(){' + receivedMsg.data.script + '}}'); 121 } 122 const previewStatements = []; 123 const log = []; 124 supplement = { 125 isAcceleratePreview: true, 126 line: parseInt(JSON.parse(receivedMsg.data.offset).line), 127 column: parseInt(JSON.parse(receivedMsg.data.offset).column), 128 fileName: receivedMsg.data.filePath || '' 129 } 130 processComponentChild(sourceNode.statements[0].members[1].body, previewStatements, log, supplement); 131 supplement.isAcceleratePreview = false; 132 const newSource = ts.factory.updateSourceFile(sourceNode, previewStatements); 133 const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); 134 const result = printer.printNode(ts.EmitHint.Unspecified, newSource, newSource); 135 receivedMsg.data.script = ts.transpileModule(result, {}).outputText; 136 receivedMsg.data.log = log; 137 if (pluginSocket.readyState === WebSocket.OPEN){ 138 compileStatus = true; 139 receivedMsg_ = receivedMsg; 140 responseToPlugin(); 141 } 142} 143 144function resolveDiagnostic(diagnostic) { 145 errorInfo = []; 146 const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); 147 if (validateError(message)) { 148 if (diagnostic.file) { 149 const { line, character } = 150 diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); 151 errorInfo.push( 152 `ArkTS:ERROR File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}\n ${message}\n`); 153 } else { 154 errorInfo.push(`ArkTS:ERROR: ${message}`); 155 } 156 } 157} 158 159function delayPrintLogCount() { 160 if (start == true) { 161 checkStatus = true; 162 responseToPlugin(); 163 } 164} 165 166function responseToPlugin() { 167 if ((compileWithCheck !== "true" && compileStatus == true) || 168 (compileWithCheck === "true" && compileStatus == true && checkStatus == true) ) { 169 if (receivedMsg_) { 170 if (errorInfo) { 171 receivedMsg_.data.log = receivedMsg_.data.log || []; 172 receivedMsg_.data.log.push(...errorInfo); 173 } 174 pluginSocket.send(JSON.stringify(receivedMsg_), (err) => { 175 start = false; 176 checkStatus = false; 177 compileStatus = false; 178 errorInfo = undefined; 179 receivedMsg_ = undefined; 180 messages.shift(); 181 if (messages.length > 0) { 182 handlePluginCompileComponent(); 183 } 184 }); 185 } 186 } 187} 188 189function validateError(message) { 190 const propInfoReg = /Cannot find name\s*'(\$?\$?[_a-zA-Z0-9]+)'/; 191 const stateInfoReg = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/; 192 if (matchMessage(message, props, propInfoReg) || 193 matchMessage(message, props, stateInfoReg)) { 194 return false; 195 } 196 return true; 197} 198 199function matchMessage(message, nameArr, reg) { 200 if (reg.test(message)) { 201 const match = message.match(reg); 202 if (match[1] && nameArr.includes(match[1])) { 203 return true; 204 } 205 } 206 return false; 207} 208 209module.exports = { 210 init 211};