/* * Copyright (c) 2021-2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const WebSocket = require('ws'); const ts = require('typescript'); const path = require('path'); const fs = require('fs'); const { spawn, execSync } = require('child_process'); const _ = require('lodash'); const { processComponentChild } = require('../lib/process_component_build'); const { createWatchCompilerHost } = require('../lib/ets_checker'); const { writeFileSync } = require('../lib/utils'); const { projectConfig } = require('../main'); const { props } = require('../lib/ets_checker'); const { isResource, processResourceData, isAnimateToOrImmediately, processAnimateToOrImmediately } = require('../lib/process_ui_syntax'); const { dollarCollection } = require('../lib/ets_checker'); const WebSocketServer = WebSocket.Server; let pluginSocket = ''; let supplement = { isAcceleratePreview: false, line: 0, column: 0, fileName: '' }; const pluginCommandChannelMessageHandlers = { 'compileComponent': handlePluginCompileComponent, 'default': () => {} }; let es2abcFilePath = path.join(__dirname, '../bin/ark/build-win/bin/es2abc'); let previewCacheFilePath; let previewJsFilePath; let previewAbcFilePath; const messages = []; let start = false; let checkStatus = false; let compileStatus = false; let receivedMsg_; let errorInfo = []; let compileWithCheck; let globalVariable = []; let propertyVariable = []; let globalDeclaration = new Map(); let connectNum = 0; const maxConnectNum = 8; let codeOverMaxlength = false; let callback = undefined; function buildPipeServer() { return { init(cachePath, buildPath, cb) { previewCacheFilePath = path.join(cachePath || buildPath, 'preview.ets'); const rootFileNames = []; writeFileSync(previewCacheFilePath, ''); rootFileNames.push(previewCacheFilePath); ts.createWatchProgram( createWatchCompilerHost(rootFileNames, resolveDiagnostic, delayPrintLogCount, ()=>{}, true)); callback = cb; }, compileComponent(jsonData) { handlePluginCompileComponent(jsonData); } }; } function init(port) { previewCacheFilePath = path.join(projectConfig.cachePath || projectConfig.buildPath, 'preview.ets'); previewJsFilePath = path.join(projectConfig.cachePath || projectConfig.buildPath, 'preview.js'); previewAbcFilePath = path.join(projectConfig.cachePath || projectConfig.buildPath, 'preview.abc'); const rootFileNames = []; writeFileSync(previewCacheFilePath, ''); rootFileNames.push(previewCacheFilePath); ts.createWatchProgram( createWatchCompilerHost(rootFileNames, resolveDiagnostic, delayPrintLogCount, ()=>{}, true)); const wss = new WebSocketServer({ port: port, host: '127.0.0.1' }); wss.on('connection', function(ws) { if (connectNum < maxConnectNum) { connectNum++; handlePluginConnect(ws); } else { ws.terminate(); } }); } function handlePluginConnect(ws) { ws.on('message', function(message) { pluginSocket = ws; try { const jsonData = JSON.parse(message); handlePluginCommand(jsonData); } catch (e) { } }); } function handlePluginCommand(jsonData) { pluginCommandChannelMessageHandlers[jsonData.command] ? pluginCommandChannelMessageHandlers[jsonData.command](jsonData) : pluginCommandChannelMessageHandlers['default'](jsonData); } function handlePluginCompileComponent(jsonData) { if (jsonData) { messages.push(jsonData); if (receivedMsg_) { return; } } else if (messages.length > 0) { jsonData = messages[0]; } else { return; } start = true; const receivedMsg = _.cloneDeep(jsonData); const compilerOptions = ts.readConfigFile( path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions; Object.assign(compilerOptions, { 'sourceMap': false }); const sourceNode = ts.createSourceFile('preview.ets', 'struct preview{build(){' + receivedMsg.data.script.replace(/new\s+\b(Circle|Ellipse|Rect|Path)\b/g, (item, item1) => { return 'special' + item1 + 'ForPreview'; }) + '}}', ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS, compilerOptions); compileWithCheck = jsonData.data.compileWithCheck || 'true'; receivedMsg.data.variableScript = ''; checkPreparation(receivedMsg, sourceNode); const previewStatements = []; const log = []; supplement = { isAcceleratePreview: true, line: parseInt(JSON.parse(receivedMsg.data.offset).line), column: parseInt(JSON.parse(receivedMsg.data.offset).column), fileName: receivedMsg.data.filePath || '' }; try { processComponentChild(sourceNode.statements[0].members[1].body, previewStatements, log, supplement); } catch (e) { log.push({ message: e.stack }); } supplement.isAcceleratePreview = false; const newSource = ts.factory.updateSourceFile(sourceNode, previewStatements); const transformedSourceFile = transformResourceNode(newSource, log); const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); const result = printer.printNode(ts.EmitHint.Unspecified, transformedSourceFile, transformedSourceFile); receivedMsg.data.script = ts.transpileModule(result, {}).outputText.replace( /\bspecial(Circle|Ellipse|Rect|Path)ForPreview\b/g, (item, item1) => { return 'new ' + item1; }); receivedMsg.data.log = log; if (receivedMsg.data.viewID) { receivedMsg.data.script = receivedMsg.data.variableScript + `function quickPreview(context) { const fastPreview = function build(){ ${receivedMsg.data.script} }.bind(context); fastPreview(); } quickPreview(GetRootView().findChildByIdForPreview(${receivedMsg.data.viewID}))`; } callEs2abc(receivedMsg); } function transformResourceNode(newSource, log) { const transformerFactory = (context) => { return (rootNode) => { function visit(node) { node = ts.visitEachChild(node, visit, context); return processResourceNode(node, log); } return ts.visitNode(rootNode, visit); }; }; const transformationResult = ts.transform(newSource, [transformerFactory]); return transformationResult.transformed[0]; } function processResourceNode(node, log) { if (isResource(node)) { return processResourceData(node, '', {isAcceleratePreview: true, log: log}); } else if (isAnimateToOrImmediately(node)) { return processAnimateToOrImmediately(node); } else { return node; } } function checkPreparation(receivedMsg, sourceNode) { let variableScript = ''; if (previewCacheFilePath && fs.existsSync(previewCacheFilePath) && compileWithCheck === 'true') { globalVariable = receivedMsg.data.globalVariable || globalVariable; globalVariable = globalVariable.map((item) => { globalDeclaration.set(item.identifier, item.declaration); return item.identifier; }); for (const [key, value] of sourceNode.identifiers) { if (globalVariable.includes(key)) { variableScript += globalDeclaration.get(key) + '\n'; } else if (key.startsWith('$$') && globalVariable.includes(key.substring(2))) { variableScript += globalDeclaration.get(key.substring(2)) + '\n'; } } propertyVariable = receivedMsg.data.propertyVariable || propertyVariable; receivedMsg.data.variableScript = ts.transpileModule(variableScript, {}).outputText; writeFileSync(previewCacheFilePath, variableScript + 'struct preview{build(){' + receivedMsg.data.script + '}}'); } } function callEs2abc(receivedMsg) { if (fs.existsSync(es2abcFilePath + '.exe') || fs.existsSync(es2abcFilePath)) { es2abc(receivedMsg); } else { es2abcFilePath = path.join(__dirname, '../bin/ark/build-mac/bin/es2abc'); if (fs.existsSync(es2abcFilePath)) { es2abc(receivedMsg); } } } function es2abc(receivedMsg) { try { const transCode = spawn(es2abcFilePath, ['--base64Input', Buffer.from(receivedMsg.data.script).toString('base64'), '--base64Output'], {windowsHide: true}); transCode.stdout.on('data', (data) => { receivedMsg.data.script = data.toString(); nextResponse(receivedMsg); }); transCode.stderr.on('data', (data) => { receivedMsg.data.script = ''; nextResponse(receivedMsg); }); } catch (e) { if (checkStatus) { getOverLengthCode(receivedMsg); } else { codeOverMaxlength = true; receivedMsg_ = receivedMsg; } } } function getOverLengthCode(receivedMsg) { writeFileSync(previewJsFilePath, receivedMsg.data.script); const cmd = '"' + es2abcFilePath + '" ' + previewJsFilePath + ' --output ' + previewAbcFilePath; execSync(cmd, {windowsHide: true}); if (fs.existsSync(previewAbcFilePath)) { receivedMsg.data.script = fs.readFileSync(previewAbcFilePath).toString('base64'); } else { receivedMsg.data.script = ''; } nextResponse(receivedMsg); } function nextResponse(receivedMsg) { compileStatus = true; receivedMsg_ = receivedMsg; responseToPlugin(); } function resolveDiagnostic(diagnostic) { const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); if (validateError(message)) { if (diagnostic.file) { const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); errorInfo.push( `ArkTS:ERROR File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}\n ${message}\n`); } else { errorInfo.push(`ArkTS:ERROR: ${message}`); } } } function delayPrintLogCount() { if (start == true) { checkStatus = true; if (codeOverMaxlength && !errorInfo.length && !receivedMsg_.data.log.length) { getOverLengthCode(receivedMsg_); } else { if (codeOverMaxlength) { compileStatus = true; } responseToPlugin(); } } } function responseToPlugin() { if ((compileWithCheck !== 'true' && compileStatus == true) || (compileWithCheck === 'true' && compileStatus == true && checkStatus == true)) { if (receivedMsg_) { if (errorInfo && errorInfo.length) { receivedMsg_.data.log = receivedMsg_.data.log || []; receivedMsg_.data.log.push(...errorInfo); } if (callback) { callback(JSON.stringify(receivedMsg_)); afterResponse(); } else { pluginSocket.send(JSON.stringify(receivedMsg_), (err) => { afterResponse(); }); } } } } function afterResponse() { start = false; checkStatus = false; compileStatus = false; codeOverMaxlength = false; errorInfo = []; receivedMsg_ = undefined; globalDeclaration.clear(); messages.shift(); if (messages.length > 0) { handlePluginCompileComponent(); } } function validateError(message) { const stateInfoReg = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/; const $$InfoReg = /Cannot find name\s*'(\$\$[_a-zA-Z0-9]+)'/; if (matchMessage(message, [...propertyVariable, ...props], stateInfoReg) || matchMessage(message, [...dollarCollection], $$InfoReg)) { return false; } return true; } function matchMessage(message, nameArr, reg) { if (reg.test(message)) { const match = message.match(reg); if (match[1] && nameArr.includes(match[1])) { return true; } } return false; } module.exports = { init, buildPipeServer };