compile in memory

editorcontextDummy
filip mertens 3 years ago
parent a7d31c8970
commit 59f9d087b6
  1. 2
      apps/remix-ide/src/app/files/fileManager.ts
  2. 4
      apps/remix-ide/src/app/tabs/compile-tab.js
  3. 251
      apps/remix-ide/src/assets/js/parser/Solidity-EZVQ6AE4.tokens
  4. 40864
      apps/remix-ide/src/assets/js/parser/index.iife.js
  5. 7
      apps/remix-ide/src/assets/js/parser/index.iife.js.map
  6. 1
      apps/remix-ide/src/index.html
  7. 8
      apps/solidity-compiler/src/app/compiler-api.ts
  8. 47
      libs/remix-core-plugin/src/lib/compiler-content-imports.ts
  9. 63
      libs/remix-core-plugin/src/lib/editor-context-listener.ts
  10. 1
      libs/remix-lib/src/types/ICompilerApi.ts
  11. 1
      libs/remix-solidity/src/compiler/compiler.ts
  12. 14
      libs/remix-ui/editor/src/lib/providers/completionProvider.ts
  13. 9
      libs/remix-ui/solidity-compiler/src/lib/logic/compileTabLogic.ts

@ -219,6 +219,7 @@ class FileManager extends Plugin {
* @returns {string} content of the file * @returns {string} content of the file
*/ */
async readFile(path) { async readFile(path) {
try { try {
path = this.normalize(path) path = this.normalize(path)
path = this.limitPluginScope(path) path = this.limitPluginScope(path)
@ -226,6 +227,7 @@ class FileManager extends Plugin {
await this._handleIsFile(path, `Cannot read file ${path}`) await this._handleIsFile(path, `Cannot read file ${path}`)
return this.getFileContent(path) return this.getFileContent(path)
} catch (e) { } catch (e) {
console.trace(e)
throw new Error(e) throw new Error(e)
} }
} }

@ -98,10 +98,10 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA
this.call('notification', 'toast', compilerConfigChangedToastMsg(this.currentRequest.from, value)) this.call('notification', 'toast', compilerConfigChangedToastMsg(this.currentRequest.from, value))
} }
compile (fileName) { compile (fileName, settings = {}) {
if(this.currentRequest.from !== 'contextualListener') if(this.currentRequest.from !== 'contextualListener')
this.call('notification', 'toast', compileToastMsg(this.currentRequest.from, fileName)) this.call('notification', 'toast', compileToastMsg(this.currentRequest.from, fileName))
super.compile(fileName) super.compile(fileName, settings)
} }
compileFile (event) { compileFile (event) {

@ -0,0 +1,251 @@
T__0=1
T__1=2
T__2=3
T__3=4
T__4=5
T__5=6
T__6=7
T__7=8
T__8=9
T__9=10
T__10=11
T__11=12
T__12=13
T__13=14
T__14=15
T__15=16
T__16=17
T__17=18
T__18=19
T__19=20
T__20=21
T__21=22
T__22=23
T__23=24
T__24=25
T__25=26
T__26=27
T__27=28
T__28=29
T__29=30
T__30=31
T__31=32
T__32=33
T__33=34
T__34=35
T__35=36
T__36=37
T__37=38
T__38=39
T__39=40
T__40=41
T__41=42
T__42=43
T__43=44
T__44=45
T__45=46
T__46=47
T__47=48
T__48=49
T__49=50
T__50=51
T__51=52
T__52=53
T__53=54
T__54=55
T__55=56
T__56=57
T__57=58
T__58=59
T__59=60
T__60=61
T__61=62
T__62=63
T__63=64
T__64=65
T__65=66
T__66=67
T__67=68
T__68=69
T__69=70
T__70=71
T__71=72
T__72=73
T__73=74
T__74=75
T__75=76
T__76=77
T__77=78
T__78=79
T__79=80
T__80=81
T__81=82
T__82=83
T__83=84
T__84=85
T__85=86
T__86=87
T__87=88
T__88=89
T__89=90
T__90=91
T__91=92
T__92=93
T__93=94
T__94=95
T__95=96
T__96=97
Int=98
Uint=99
Byte=100
Fixed=101
Ufixed=102
BooleanLiteral=103
DecimalNumber=104
HexNumber=105
NumberUnit=106
HexLiteralFragment=107
ReservedKeyword=108
AnonymousKeyword=109
BreakKeyword=110
ConstantKeyword=111
ImmutableKeyword=112
ContinueKeyword=113
LeaveKeyword=114
ExternalKeyword=115
IndexedKeyword=116
InternalKeyword=117
PayableKeyword=118
PrivateKeyword=119
PublicKeyword=120
VirtualKeyword=121
PureKeyword=122
TypeKeyword=123
ViewKeyword=124
GlobalKeyword=125
ConstructorKeyword=126
FallbackKeyword=127
ReceiveKeyword=128
Identifier=129
StringLiteralFragment=130
VersionLiteral=131
WS=132
COMMENT=133
LINE_COMMENT=134
'pragma'=1
';'=2
'*'=3
'||'=4
'^'=5
'~'=6
'>='=7
'>'=8
'<'=9
'<='=10
'='=11
'as'=12
'import'=13
'from'=14
'{'=15
','=16
'}'=17
'abstract'=18
'contract'=19
'interface'=20
'library'=21
'is'=22
'('=23
')'=24
'error'=25
'using'=26
'for'=27
'struct'=28
'modifier'=29
'function'=30
'returns'=31
'event'=32
'enum'=33
'['=34
']'=35
'address'=36
'.'=37
'mapping'=38
'=>'=39
'memory'=40
'storage'=41
'calldata'=42
'if'=43
'else'=44
'try'=45
'catch'=46
'while'=47
'unchecked'=48
'assembly'=49
'do'=50
'return'=51
'throw'=52
'emit'=53
'revert'=54
'var'=55
'bool'=56
'string'=57
'byte'=58
'++'=59
'--'=60
'new'=61
':'=62
'+'=63
'-'=64
'after'=65
'delete'=66
'!'=67
'**'=68
'/'=69
'%'=70
'<<'=71
'>>'=72
'&'=73
'|'=74
'=='=75
'!='=76
'&&'=77
'?'=78
'|='=79
'^='=80
'&='=81
'<<='=82
'>>='=83
'+='=84
'-='=85
'*='=86
'/='=87
'%='=88
'let'=89
':='=90
'=:'=91
'switch'=92
'case'=93
'default'=94
'->'=95
'callback'=96
'override'=97
'anonymous'=109
'break'=110
'constant'=111
'immutable'=112
'continue'=113
'leave'=114
'external'=115
'indexed'=116
'internal'=117
'payable'=118
'private'=119
'public'=120
'virtual'=121
'pure'=122
'type'=123
'view'=124
'global'=125
'constructor'=126
'fallback'=127
'receive'=128

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -41,6 +41,7 @@
<script type="text/javascript" src="assets/js/loader.js"></script> <script type="text/javascript" src="assets/js/loader.js"></script>
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script> <script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script>
<script type="text/javascript" src="assets/js/intro.min.js"></script> <script type="text/javascript" src="assets/js/intro.min.js"></script>
<script type="text/javascript" src="assets/js/parser/index.iife.js"></script>
</body> </body>
</html> </html>

@ -97,6 +97,10 @@ export const CompilerApiMixin = (Base) => class extends Base {
return this.call('contentImport', 'resolveAndSave', url) return this.call('contentImport', 'resolveAndSave', url)
} }
resolveContent (url) {
return this.call('contentImport', 'resolveAndSave', url, undefined, false)
}
runScriptAfterCompilation (fileName: string) { runScriptAfterCompilation (fileName: string) {
this.call('compileAndRun', 'runScriptAfterCompilation', fileName) this.call('compileAndRun', 'runScriptAfterCompilation', fileName)
} }
@ -127,9 +131,9 @@ export const CompilerApiMixin = (Base) => class extends Base {
* This function is used by remix-plugin compiler API. * This function is used by remix-plugin compiler API.
* @param {string} fileName to compile * @param {string} fileName to compile
*/ */
compile (fileName) { compile (fileName, settings = {}) {
this.currentFile = fileName this.currentFile = fileName
return this.compileTabLogic.compileFile(fileName) return this.compileTabLogic.compileFile(fileName, settings)
} }
compileFile (event) { compileFile (event) {

@ -18,13 +18,13 @@ export type ResolvedImport = {
export class CompilerImports extends Plugin { export class CompilerImports extends Plugin {
previouslyHandled: Record<string, ResolvedImport> previouslyHandled: Record<string, ResolvedImport>
urlResolver: any urlResolver: any
constructor () { constructor() {
super(profile) super(profile)
this.urlResolver = new RemixURLResolver() this.urlResolver = new RemixURLResolver()
this.previouslyHandled = {} // cache import so we don't make the request at each compilation. this.previouslyHandled = {} // cache import so we don't make the request at each compilation.
} }
async setToken () { async setToken() {
try { try {
const protocol = typeof window !== 'undefined' && window.location.protocol const protocol = typeof window !== 'undefined' && window.location.protocol
const token = await this.call('settings', 'get', 'settings/gist-access-token') const token = await this.call('settings', 'get', 'settings/gist-access-token')
@ -35,11 +35,11 @@ export class CompilerImports extends Plugin {
} }
} }
isRelativeImport (url) { isRelativeImport(url) {
return /^([^/]+)/.exec(url) return /^([^/]+)/.exec(url)
} }
isExternalUrl (url) { isExternalUrl(url) {
const handlers = this.urlResolver.getHandlers() const handlers = this.urlResolver.getHandlers()
// we filter out "npm" because this will be recognized as internal url although it's not the case. // we filter out "npm" because this will be recognized as internal url although it's not the case.
return handlers.filter((handler) => handler.type !== 'npm').some(handler => handler.match(url)) return handlers.filter((handler) => handler.type !== 'npm').some(handler => handler.match(url))
@ -51,7 +51,7 @@ export class CompilerImports extends Plugin {
* @param {String} url - external URL of the content. can be basically anything like raw HTTP, ipfs URL, github address etc... * @param {String} url - external URL of the content. can be basically anything like raw HTTP, ipfs URL, github address etc...
* @returns {Promise} - { content, cleanUrl, type, url } * @returns {Promise} - { content, cleanUrl, type, url }
*/ */
resolve (url) { resolve(url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.import(url, null, (error, content, cleanUrl, type, url) => { this.import(url, null, (error, content, cleanUrl, type, url) => {
if (error) return reject(error) if (error) return reject(error)
@ -60,15 +60,15 @@ export class CompilerImports extends Plugin {
}) })
} }
async import (url, force, loadingCb, cb) { async import(url, force, loadingCb, cb) {
if (typeof force !== 'boolean') { if (typeof force !== 'boolean') {
const temp = loadingCb const temp = loadingCb
loadingCb = force loadingCb = force
cb = temp cb = temp
force = false force = false
} }
if (!loadingCb) loadingCb = () => {} if (!loadingCb) loadingCb = () => { }
if (!cb) cb = () => {} if (!cb) cb = () => { }
const self = this const self = this
if (force) delete this.previouslyHandled[url] if (force) delete this.previouslyHandled[url]
@ -93,19 +93,21 @@ export class CompilerImports extends Plugin {
} }
} }
importExternal (url, targetPath) { importExternal(url: string, targetPath: string, save: boolean = true) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.import(url, this.import(url,
// TODO: handle this event // TODO: handle this event
(loadingMsg) => { this.emit('message', loadingMsg) }, (loadingMsg) => { this.emit('message', loadingMsg) },
async (error, content, cleanUrl, type, url) => { async (error, content, cleanUrl, type, url) => {
if (error) return reject(error) if (error) return reject(error)
try { if (save) {
const provider = await this.call('fileManager', 'getProviderOf', null) try {
const path = targetPath || type + '/' + cleanUrl const provider = await this.call('fileManager', 'getProviderOf', null)
if (provider) await provider.addExternal('.deps/' + path, content, url) const path = targetPath || type + '/' + cleanUrl
} catch (err) { if (provider) await provider.addExternal('.deps/' + path, content, url)
console.error(err) } catch (err) {
console.error(err)
}
} }
resolve(content) resolve(content)
}, null) }, null)
@ -122,12 +124,13 @@ export class CompilerImports extends Plugin {
* @param {String} targetPath - (optional) internal path where the content should be saved to * @param {String} targetPath - (optional) internal path where the content should be saved to
* @returns {Promise} - string content * @returns {Promise} - string content
*/ */
async resolveAndSave (url, targetPath) { async resolveAndSave(url: string, targetPath: string, save: boolean = true) {
console.log(url, targetPath, save)
try { try {
if (targetPath && this.currentRequest) { if (targetPath && this.currentRequest) {
const canCall = await this.askUserPermission('resolveAndSave', 'This action will update the path ' + targetPath) const canCall = await this.askUserPermission('resolveAndSave', 'This action will update the path ' + targetPath)
if (!canCall) throw new Error('No permission to update ' + targetPath) if (!canCall) throw new Error('No permission to update ' + targetPath)
} }
const provider = await this.call('fileManager', 'getProviderOf', url) const provider = await this.call('fileManager', 'getProviderOf', url)
if (provider) { if (provider) {
if (provider.type === 'localhost' && !provider.isConnected()) { if (provider.type === 'localhost' && !provider.isConnected()) {
@ -139,8 +142,8 @@ export class CompilerImports extends Plugin {
Doesn't make sense to try to resolve "localhost/node_modules/localhost/node_modules/<path>" and we'll end in an infinite loop. Doesn't make sense to try to resolve "localhost/node_modules/localhost/node_modules/<path>" and we'll end in an infinite loop.
*/ */
if (!exist && (url === 'remix_tests.sol' || url === 'remix_accounts.sol')) { if (!exist && (url === 'remix_tests.sol' || url === 'remix_accounts.sol')) {
await this.call('solidityUnitTesting', 'createTestLibs') await this.call('solidityUnitTesting', 'createTestLibs')
exist = await provider.exists(url) exist = await provider.exists(url)
} }
if (!exist && url.startsWith('browser/')) throw new Error(`not found ${url}`) if (!exist && url.startsWith('browser/')) throw new Error(`not found ${url}`)
if (!exist && url.startsWith('localhost/')) throw new Error(`not found ${url}`) if (!exist && url.startsWith('localhost/')) throw new Error(`not found ${url}`)
@ -173,11 +176,11 @@ export class CompilerImports extends Plugin {
localhostProvider.addNormalizedName(path.replace('localhost/', ''), url) localhostProvider.addNormalizedName(path.replace('localhost/', ''), url)
return content return content
} }
} catch (e) {} } catch (e) { }
} }
return await this.importExternal(url, targetPath) return await this.importExternal(url, targetPath, save)
} }
return await this.importExternal(url, targetPath) return await this.importExternal(url, targetPath, save)
} }
} }
} catch (e) { } catch (e) {

@ -1,13 +1,11 @@
'use strict' 'use strict'
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import { sourceMappingDecoder } from '@remix-project/remix-debug' import { sourceMappingDecoder } from '@remix-project/remix-debug'
import { compile } from '@remix-project/remix-solidity'
import { CompilerAbstract } from '@remix-project/remix-solidity' import { CompilerAbstract } from '@remix-project/remix-solidity'
const profile = { const profile = {
name: 'contextualListener', name: 'contextualListener',
methods: ['nodesWithScope', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'nodesAtEditorPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'jumpToPosition'], methods: ['getBlockName', 'getAST', 'nodesWithScope', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'nodesAtEditorPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'jumpToPosition'],
events: [], events: [],
version: '0.0.1' version: '0.0.1'
} }
@ -21,6 +19,8 @@ export function isDefinition(node: any) {
node.nodeType === 'EventDefinition' node.nodeType === 'EventDefinition'
} }
const SolidityParser = (window as any).SolidityParser = (window as any).SolidityParser || []
/* /*
trigger contextChanged(nodes) trigger contextChanged(nodes)
*/ */
@ -40,6 +40,8 @@ export class EditorContextListener extends Plugin {
lastCompilationResult: any lastCompilationResult: any
lastAST: any
constructor(astWalker) { constructor(astWalker) {
super(profile) super(profile)
this.activated = false this.activated = false
@ -53,7 +55,10 @@ export class EditorContextListener extends Plugin {
} }
onActivation() { onActivation() {
this.on('editor', 'contentChanged', () => { this._stopHighlighting() }) this.on('editor', 'contentChanged', async () => {
await this.getAST()
this._stopHighlighting()
})
this.on('solidity', 'astFinished', async (file, source, languageVersion, data, input, version) => { this.on('solidity', 'astFinished', async (file, source, languageVersion, data, input, version) => {
// console.log('compilation result', Object.keys(data.sources)) // console.log('compilation result', Object.keys(data.sources))
@ -71,7 +76,11 @@ export class EditorContextListener extends Plugin {
}) })
setInterval(async () => { setInterval(async () => {
await this.compile()
}, 5000)
setInterval(async () => {
const compilationResult = this.lastCompilationResult // await this.call('compilerArtefacts', 'getLastCompilationResult') const compilationResult = this.lastCompilationResult // await this.call('compilerArtefacts', 'getLastCompilationResult')
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) { if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) {
@ -96,7 +105,44 @@ export class EditorContextListener extends Plugin {
async compile() { async compile() {
this.currentFile = await this.call('fileManager', 'file') this.currentFile = await this.call('fileManager', 'file')
return await this.call('solidity', 'compile', this.currentFile) if(!this.currentFile) return
return await this.call('solidity', 'compile', this.currentFile, { save: false })
}
async getBlockName(position: any) {
await this.getAST()
const allowedTypes = ['SourceUnit','ContractDefinition','FunctionDefinition']
const walkAst = (node) => {
console.log(node)
if (node.loc.start.line <= position.lineNumber && node.loc.end.line >= position.lineNumber) {
const children = node.children || node.subNodes
if (children && allowedTypes.indexOf(node.type) !== -1) {
for (const child of children) {
const result = walkAst(child)
if (result) return result
}
}
return node
}
return null
}
return walkAst(this.lastAST)
}
async getAST() {
this.currentFile = await this.call('fileManager', 'file')
if(!this.currentFile) return
let fileContent = await this.call('fileManager', 'readFile', this.currentFile)
try {
const ast = (SolidityParser as any).parse(fileContent, { loc: true, range: true, tolerant: true })
this.lastAST = ast
} catch (e) {
}
console.log('LAST AST', this.lastAST)
return this.lastAST
} }
getActiveHighlights() { getActiveHighlights() {
@ -164,12 +210,12 @@ export class EditorContextListener extends Plugin {
async nodesWithScope(scope: any) { async nodesWithScope(scope: any) {
const nodes = [] const nodes = []
for(const node of Object.values(this._index.FlatReferences) as any[]){ for (const node of Object.values(this._index.FlatReferences) as any[]) {
if(node.scope === scope) nodes.push(node) if (node.scope === scope) nodes.push(node)
} }
return nodes return nodes
} }
async definitionAtPosition(position: any) { async definitionAtPosition(position: any) {
const nodes = await this.nodesAtEditorPosition(position) const nodes = await this.nodesAtEditorPosition(position)
console.log('nodes at position', nodes) console.log('nodes at position', nodes)
@ -227,6 +273,7 @@ export class EditorContextListener extends Plugin {
const jumpToLine = async (fileName: string, lineColumn: any) => { const jumpToLine = async (fileName: string, lineColumn: any) => {
if (fileName !== await this.call('fileManager', 'file')) { if (fileName !== await this.call('fileManager', 'file')) {
console.log('jump to file', fileName) console.log('jump to file', fileName)
await this.call('contentImport', 'resolveAndSave', fileName, null, true)
await this.call('fileManager', 'open', fileName) await this.call('fileManager', 'open', fileName)
} }
if (lineColumn.start && lineColumn.start.line >= 0 && lineColumn.start.column >= 0) { if (lineColumn.start && lineColumn.start.line >= 0 && lineColumn.start.column >= 0) {

@ -32,6 +32,7 @@ export interface ICompilerApi {
onFileClosed: (name: string) => void onFileClosed: (name: string) => void
resolveContentAndSave: (url: string) => Promise<string> resolveContentAndSave: (url: string) => Promise<string>
resolveContent: (url: string) => Promise<string>
fileExists: (file: string) => Promise<boolean> fileExists: (file: string) => Promise<boolean>
writeFile: (file: string, content: string) => Promise<void> writeFile: (file: string, content: string) => Promise<void>
readFile: (file: string) => Promise<string> readFile: (file: string) => Promise<string>

@ -84,6 +84,7 @@ export class Compiler {
*/ */
compile (files: Source, target: string): void { compile (files: Source, target: string): void {
console.log('compiler', files, target)
this.state.target = target this.state.target = target
this.event.trigger('compilationStarted', []) this.event.trigger('compilationStarted', [])
this.internalCompile(files) this.internalCompile(files)

@ -10,7 +10,19 @@ export class RemixCompletionProvider {
triggerCharacters = ['.', ''] triggerCharacters = ['.', '']
async provideCompletionItems(model: any, position: any, context: any) { async provideCompletionItems(model: any, position: any, context: any) {
console.log('AUTOCOMPLETE', context) console.log('AUTOCOMPLETE', context)
console.log(position)
//await this.props.plugin.call('contextualListener', 'compile')
//const block = await this.props.plugin.call('contextualListener', 'getBlockName', position)
//console.log('BLOCK', block)
//return null
return await this.run(model, position, context) return await this.run(model, position, context)
const word = model.getWordUntilPosition(position);
const wordAt = model.getWordAtPosition(position);
console.log('WORD', word)
console.log('WORDAT', wordAt)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.props.plugin.once('contextualListener', 'astFinished', async () => { this.props.plugin.once('contextualListener', 'astFinished', async () => {
console.log('AST FINISHED') console.log('AST FINISHED')
@ -37,7 +49,7 @@ export class RemixCompletionProvider {
endColumn: word.endColumn endColumn: word.endColumn
}; };
console.log('WORD', word)
const getlinearizedBaseContracts = async (node: any) => { const getlinearizedBaseContracts = async (node: any) => {
let params = [] let params = []
if (node.linearizedBaseContracts) { if (node.linearizedBaseContracts) {

@ -99,7 +99,8 @@ export class CompileTabLogic {
* Compile a specific file of the file manager * Compile a specific file of the file manager
* @param {string} target the path to the file to compile * @param {string} target the path to the file to compile
*/ */
compileFile (target) { compileFile (target: string, settings: any = {save: true}) {
console.log(settings)
if (!target) throw new Error('No target provided for compiliation') if (!target) throw new Error('No target provided for compiliation')
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.api.readFile(target).then((content) => { this.api.readFile(target).then((content) => {
@ -109,6 +110,12 @@ export class CompileTabLogic {
this.api.readFile(this.configFilePath).then( contentConfig => { this.api.readFile(this.configFilePath).then( contentConfig => {
this.compiler.set('configFileContent', contentConfig) this.compiler.set('configFileContent', contentConfig)
}) })
if(settings.save) {
this.compiler.handleImportCall = (url, cb) => this.api.resolveContentAndSave(url).then((result) => cb(null, result)).catch((error) => cb(error.message))
}else{
this.compiler.handleImportCall = (url, cb) => this.api.resolveContent(url).then((result) => cb(null, result)).catch((error) => cb(error.message))
}
console.log(this.compiler)
// setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation') // setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation')
setTimeout(() => { this.compiler.compile(sources, target); resolve(true) }, 100) setTimeout(() => { this.compiler.compile(sources, target); resolve(true) }, 100)
}).catch((error) => { }).catch((error) => {

Loading…
Cancel
Save