added two apps, and better implementation

This commit is contained in:
David Meincke
2022-03-22 18:20:26 +01:00
parent bb44e0e92d
commit 33b3f2e5cc
47 changed files with 486 additions and 151 deletions
+2 -2
View File
@@ -11,8 +11,8 @@
"type": "node-terminal" "type": "node-terminal"
}, },
{ {
"command": "npm run server", "command": "node run-servers.js",
"name": "2. Start server (w. hot)", "name": "2. Start servers (w. hot)",
"request": "launch", "request": "launch",
"type": "node-terminal" "type": "node-terminal"
}, },
@@ -1 +0,0 @@
console.log("hello");
@@ -1,16 +0,0 @@
import { BaseElement } from "../_base";
import { NotificationBubble } from "./notification-bubble";
import './notification-bubbles.less';
export class NotificationBubbles extends BaseElement {
add(text: string, type: 'log' | 'info' | 'warn' | 'error' | 'syntax-error') {
var bubble = <NotificationBubble>document.createElement("notification-bubble");
bubble.innerText = text;
bubble.classList.add(type);
this.appendChild(bubble);
}
reset() {
this.innerHTML = "";
}
}
-3
View File
@@ -1,3 +0,0 @@
export function hello() {
}
@@ -1,3 +0,0 @@
window.onerror = function (error) {
parent.document.body.getElementsByTagName("notification-bubbles")[0].add(error, 'syntax-error');
}
@@ -1,9 +0,0 @@
try {
setTimeout(() => {
[replace]
});
}
catch (ex) {
parent.document.body.getElementsByTagName("notification-bubbles")[0].add(ex, 'syntax-error');
}
@@ -1,24 +0,0 @@
// define a new console
var console=(function(oldCons){
return {
log: function(text){
oldCons.log(text);
parent.document.body.getElementsByTagName("notification-bubbles")[0].add(text, 'log');
},
info: function (text) {
oldCons.info(text);
parent.document.body.getElementsByTagName("notification-bubbles")[0].add(text, 'info');
},
warn: function (text) {
oldCons.warn(text);
parent.document.body.getElementsByTagName("notification-bubbles")[0].add(text, 'warn');
},
error: function (text) {
oldCons.error(text);
parent.document.body.getElementsByTagName("notification-bubbles")[0].add(text, 'error');
}
};
}(window.console));
//Then redefine the old console
window.console = console;
-68
View File
@@ -1,68 +0,0 @@
import { BaseElement } from "../_base";
import './output-frame.less';
import watchConsole from './injects/watch-console.txt';
import tryCatch from './injects/try-catch.txt';
import onError from './injects/on-error.txt';
import windowStopScript from './injects/window-stop.txt';
import { NotificationBubbles } from "../notification-bubbles/notification-bubbles";
export class OutputFrame extends BaseElement {
public Iframe: HTMLIFrameElement | null = null;
private watchConsoleScript: string;
constructor() {
super();
this.watchConsoleScript = watchConsole;
}
onInit(): void {
this.reset();
}
onUpdate(): void {
}
reset() {
if (this.Iframe && this.hasChild(this.Iframe)) {
this.removeChild(this.Iframe);
}
this.Iframe = document.createElement("iframe");
this.Iframe.setAttribute("sandbox", "allow-pointer-lock allow-same-origin allow-scripts");
this.appendChild(this.Iframe);
var bubbles = <NotificationBubbles>this.find("notification-bubbles");
bubbles.reset();
this.setInitScript(windowStopScript);
this.setInitScript(onError);
this.setInitScript(watchConsole);
}
preventInfiniteLoop(value: string) {
return value.replace("while (true)", "while (false)");
}
private setInitScript(value: string) {
var script: HTMLScriptElement = document.createElement("script");
script.setAttribute("async", "");
script.innerHTML = this.preventInfiniteLoop(value);
this.Iframe?.contentWindow?.document.body.appendChild(script);
}
setScript(value: string) {
this.reset();
var script: HTMLScriptElement = document.createElement("script");
script.setAttribute("async", "");
script.innerHTML = this.preventInfiniteLoop(value);
this.Iframe?.contentWindow?.document.body.appendChild(script);
}
surroundTryCatch(value: string) {
return tryCatch.replace("[replace]", value);
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
code editor code editor
</div> </div>
<div class="flex-horiz"> <div class="flex-horiz">
<code-editor></code-editor> <code-editor style="min-width: 50%; max-width: 50%;"></code-editor>
<output-frame></output-frame> <output-frame></output-frame>
</div> </div>
<div class="flex-shrink section bottom">methods: rect, box, center, width, height</div> <div class="flex-shrink section bottom">methods: rect, box, center, width, height</div>
+11 -1
View File
@@ -1,4 +1,4 @@
html, body, body > * { html, body {
margin: 0; margin: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -22,6 +22,7 @@ html, body, body > * {
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
flex-basis: 1px; flex-basis: 1px;
position: relative;
} }
.flex-horiz .flex-shrink, .flex-vert .flex-shrink { .flex-horiz .flex-shrink, .flex-vert .flex-shrink {
@@ -34,6 +35,15 @@ html, body, body > * {
flex-shrink: 0; flex-shrink: 0;
} }
.flex-reset {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
}
body { body {
font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif; font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif;
+1 -4
View File
@@ -13,7 +13,4 @@ window.customElements.define('notification-bubbles', NotificationBubbles);
window.customElements.define('notification-bubble', NotificationBubble); window.customElements.define('notification-bubble', NotificationBubble);
var appEle = document.createElement("div"); document.body.innerHTML += bootHtml;
appEle.innerHTML = bootHtml;
document.body.appendChild(appEle);
@@ -13,6 +13,7 @@ export class CodeEditor extends BaseElement {
this.initWorkers(); this.initWorkers();
var editor = monaco.editor.create(this, { var editor = monaco.editor.create(this, {
value: this.input, value: this.input,
language: 'javascript', language: 'javascript',
@@ -20,9 +21,35 @@ export class CodeEditor extends BaseElement {
contextmenu: false, contextmenu: false,
minimap: { minimap: {
enabled: false enabled: false
} },
autoIndent: "full"
}); });
monaco.languages.registerCompletionItemProvider("javascript", {
triggerCharacters: ["."],
provideCompletionItems: (model, position, context, token) => (
{
suggestions: [
{
label: 'for',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'for (let i = 0; i < array.length; i++) {\n\n}',
range: <any>null,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
},
{
label: 'forr',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'for (var i = array.length - 1; i >= 0; i--) {\n\n}',
range: <any>null,
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet
}
]
}
)
});
editor.onDidChangeModelContent((e) => { editor.onDidChangeModelContent((e) => {
this.input = editor.getValue(); this.input = editor.getValue();
this.outputFrame.setScript(this.input); this.outputFrame.setScript(this.input);
@@ -0,0 +1,16 @@
var myDiv = document.createElement("div");
myDiv.innerText = "hello moto";
document.body.appendChild(myDiv);
var i = 0;
while (i < 10) {
console.log("hello " + i);
i++;
}
var obj = { hello: true, moto: "enabled" };
console.log(obj);
@@ -3,6 +3,7 @@ notification-bubble {
padding: 10px; padding: 10px;
color: black; color: black;
background-color: #a0ffa7; background-color: #a0ffa7;
border: 1px solid #8ddf92;
display: block; display: block;
position: relative; position: relative;
box-shadow: 1px 1px 1px 1px rgba(0,0,0,0.19); box-shadow: 1px 1px 1px 1px rgba(0,0,0,0.19);
@@ -11,22 +12,27 @@ notification-bubble {
notification-bubble.log { notification-bubble.log {
background-color: #ffffff; background-color: #ffffff;
border-color: #dfdfdf;
} }
notification-bubble.info { notification-bubble.info {
background-color: #e7e7b3; background-color: #e7e7b3;
border-color: #d4d4a9;
} }
notification-bubble.warn { notification-bubble.warn {
background-color: #fff9a0; background-color: #fff9a0;
border-color: #e6e094;
} }
notification-bubble.error { notification-bubble.error {
background-color: #ffa0a0; background-color: #ffa0a0;
border-color: #df8e8e;
} }
notification-bubble.syntax-error { notification-bubble.syntax-error {
background-color: #ff6262; background-color: #ff6262;
background-color: #d15353;
font-size: 22px; font-size: 22px;
padding: 30px; padding: 30px;
} }
@@ -0,0 +1,31 @@
import { messageListener } from "../../../shared/message-listener";
import { BaseElement } from "../_base";
import { NotificationBubble } from "./notification-bubble";
import './notification-bubbles.less';
export class NotificationBubbles extends BaseElement {
add(text: string, type: 'log' | 'info' | 'warn' | 'error' | 'syntax-error' | 'script' | 'time' | 'response') {
if (text && type) {
var bubble = <NotificationBubble>document.createElement("notification-bubble");
bubble.innerText = text;
bubble.classList.add(type);
this.appendChild(bubble);
if (this.children.length > 50) {
this.removeChild(this.children[0]);
}
}
}
reset() {
this.innerHTML = "";
}
onInit(): void {
messageListener((type, value) => {
if (type != 'script' && type != 'response')
this.add(value, type);
});
}
}
@@ -0,0 +1,84 @@
import { BaseElement } from "../_base";
import './output-frame.less';
import { NotificationBubbles } from "../notification-bubbles/notification-bubbles";
import { GetOutputFrameUrl } from "../../../shared/url-helpers";
import { GetNotificationBubbles } from "../../../shared/getdom";
import { postMessage } from "../../../shared/post-message";
export class OutputFrame extends BaseElement {
public Iframe: HTMLIFrameElement | null = null;
public CurrentIframeScriptRemoveResponseListener: Function | null = null;
constructor() {
super();
}
onInit(): void {
this.reset();
}
onUpdate(): void {
}
reset(resetNotificationBubbles: boolean = true) {
if (this.CurrentIframeScriptRemoveResponseListener != null) {
this.CurrentIframeScriptRemoveResponseListener();
this.CurrentIframeScriptRemoveResponseListener = null;
}
if (this.Iframe && this.hasChild(this.Iframe)) {
this.removeChild(this.Iframe);
}
this.Iframe = document.createElement("iframe");
this.Iframe.setAttribute("sandbox", "allow-pointer-lock allow-same-origin allow-scripts");
this.Iframe.src = GetOutputFrameUrl();
this.appendChild(this.Iframe);
if (resetNotificationBubbles) {
var bubbles = GetNotificationBubbles();
if (bubbles && bubbles.reset) {
bubbles.reset();
}
}
}
setError() {
this.reset();
if (this.Iframe) {
this.Iframe.src = GetOutputFrameUrl() + "execution-time-error.html";
}
}
preventInfiniteLoop(value: string) {
return value.replace("while (true)", "while (false)");
}
setScript(value: string) {
this.reset();
this.onIframeLoaded(() => {
this.CurrentIframeScriptRemoveResponseListener = postMessage(this.Iframe?.contentWindow, "script", value, (executeTimeInMs, exceedTimeInMs) => {
if (executeTimeInMs === -1) {
this.setError();
}
else {
var bubbles = GetNotificationBubbles();
bubbles.add("code executed in: " + executeTimeInMs + "ms", "info");
}
});
});
}
onIframeLoaded(fn: Function) {
if (this.Iframe) {
this.Iframe.onload = () => {
fn();
}
}
else {
this.waitFor(this.Iframe, () => {
this.onIframeLoaded(fn);
});
}
}
}

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 166 KiB

View File
+11
View File
@@ -0,0 +1,11 @@
html, body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
body {
font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif;
padding: 10px;
}
+22
View File
@@ -0,0 +1,22 @@
import './app.less';
import bootHtml from './app.html';
import { watchConsole } from './injects/watch-console';
import { setOnErrorListener } from './injects/on-error';
import { createScript } from '../shared/create-script';
import { messageListener } from '../shared/message-listener';
import { postMessage, postResponseMessage } from '../shared/post-message';
document.body.innerHTML += bootHtml;
watchConsole((type, value) => {
postMessage(parent, type, value);
});
messageListener((type, value, id) => {
if (type == "script") {
createScript(value);
postResponseMessage(parent, id, value);
}
});
setOnErrorListener((type, value) => {
postMessage(parent, type, value);
});
+7
View File
@@ -0,0 +1,7 @@
import { NotificationBubbles } from "../../codeeditor-app/elements/notification-bubbles/notification-bubbles";
export function setOnErrorListener(fn: (type: 'syntax-error', value: any) => void) {
window.onerror = function (error) {
fn('syntax-error', error);
}
}
+12
View File
@@ -0,0 +1,12 @@
import { NotificationBubbles } from "../../codeeditor-app/elements/notification-bubbles/notification-bubbles";
export function tryCatch(fn: Function) {
try {
fn();
}
catch (err) {
var bubbles = <NotificationBubbles>parent.document.body.getElementsByTagName("notification-bubbles")[0];
//@ts-ignore
bubbles.add(err, 'syntax-error');
}
}
+28
View File
@@ -0,0 +1,28 @@
import { NotificationBubbles } from "../../codeeditor-app/elements/notification-bubbles/notification-bubbles";
export function watchConsole(fn: (type: 'log' | 'info' | 'warn' | 'error', value: any) => void) {
// define a new console
var console: any = (function (oldCons) {
return {
log: function (text: any) {
oldCons.log(text);
fn('log', text);
},
info: function (text: any) {
oldCons.info(text);
fn('info', text);
},
warn: function (text: any) {
oldCons.warn(text);
fn('warn', text);
},
error: function (text: any) {
oldCons.error(text);
fn('error', text);
}
};
}(window.console));
//Then redefine the old console
window.console = console;
}
@@ -0,0 +1,36 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" id="master-viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>output error</title>
<base href="/" />
<style>
body {
font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif;
}
.center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 22px;
text-align: center;
}
small {
font-size: 0.6em;
}
</style>
</head>
<body>
<div class="center">
took too long to run?<br />
<small>why you infinite loopin?</small>
</div>
</body>
</html>
+16
View File
@@ -0,0 +1,16 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" id="master-viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>output</title>
<base href="/" />
</head>
<body>
<script type="text/javascript" src="/output.frame.bundle.js?v=1"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
const WebpackDevServer = require("webpack-dev-server");
const webpack = require("webpack");
const codeeditorConfig = require("./webpack.dev.js");
const outputframeConfig = require("./webpack.dev-outputframe.js");
const codeeditorCompiler = webpack(codeeditorConfig);
const outputframeCompiler = webpack(outputframeConfig);
const server1 = new WebpackDevServer(codeeditorCompiler, {
hot: true,
historyApiFallback: true
})
const server2 = new WebpackDevServer(outputframeCompiler, {
hot: true,
historyApiFallback: true
})
server1.listen(8020, "localhost", function() { console.log("server 1 rdy"); })
server2.listen(8021, "localhost", function() { console.log("server 2 rdy"); })
+7
View File
@@ -0,0 +1,7 @@
export function createScript(value: string) {
var script: HTMLScriptElement = document.createElement("script");
script.setAttribute("async", "");
script.innerHTML = value;
document.body.appendChild(script);
}
+10
View File
@@ -0,0 +1,10 @@
import { NotificationBubbles } from "../codeeditor-app/elements/notification-bubbles/notification-bubbles";
import { OutputFrame } from "../codeeditor-app/elements/output-frame/output-frame";
export function GetNotificationBubbles(): NotificationBubbles {
return <NotificationBubbles>document.getElementsByTagName("notification-bubbles")[0];
}
export function GetOutputFrame(): OutputFrame {
return <OutputFrame>document.getElementsByTagName("output-frame")[0];
}
+16
View File
@@ -0,0 +1,16 @@
export function messageListener(fn: (type: 'script' | 'log' | 'info' | 'warn' | 'error' | 'syntax-error' | 'time' | 'response', value: string, id: number) => void): Function {
var evt = function(e: any) {
if (!e.data || e.data === '' || e.data.type === 'webpackOk') {
return;
}
fn(e.data.type, e.data.value, e.data.id);
};
window.addEventListener('message', evt);
return () => {
window.removeEventListener('message', evt);
}
}
+48
View File
@@ -0,0 +1,48 @@
import { messageListener } from "./message-listener";
export function postMessage(window: Window | null | undefined, type: string, value: any, onResponse?: (executeTimeInMs: number, exceedTimeInMs: number) => void): Function {
var postId = +new Date();
var responseTimeout = 5000;
var execResponse = true;
if (window && type && value) {
if (onResponse) {
var timeoutHandle: number | null = null;
var remListener = messageListener((type, value, id) => {
if (type == "response" && id == postId) {
if (timeoutHandle != null)
clearTimeout(timeoutHandle);
remListener();
if (execResponse)
onResponse(+new Date() - id, responseTimeout);
}
});
timeoutHandle = setTimeout(() => {
remListener();
if (timeoutHandle != null) {
clearTimeout(timeoutHandle);
if (execResponse)
onResponse(-1, responseTimeout);
}
}, responseTimeout);
}
window.postMessage({ type: type, value: (typeof value === 'string' || value instanceof String) ? value : JSON.stringify(value), id: postId }, "*");
}
return () => {
if (onResponse) {
execResponse = false;
}
};
}
export function postResponseMessage(window: Window | null | undefined, id: number, value: any) {
if (window && value) {
window.postMessage({ type: "response", value: JSON.stringify(value), id: id }, "*");
}
}
+16
View File
@@ -0,0 +1,16 @@
export function GetUrl(): string {
return location.protocol + "//" + location.host + "/";
}
export function IsLocalhost(): boolean {
return location.href.indexOf("://localhost") != -1;
}
export function GetOutputFrameUrl(): string {
if (IsLocalhost()) {
return "http://127.0.0.1:8021/";
}
else {
return "https://outputframe.davidmeincke.dk/";
}
}
+3 -2
View File
@@ -1,12 +1,13 @@
const path = require('path'); const path = require('path');
module.exports = { module.exports = {
entry: { entry: {
'main': path.resolve(__dirname, 'App', 'boot.ts'), 'main': path.resolve(__dirname, 'codeeditor-app', 'boot.ts'),
'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js', 'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
'json.worker': 'monaco-editor/esm/vs/language/json/json.worker', 'json.worker': 'monaco-editor/esm/vs/language/json/json.worker',
'css.worker': 'monaco-editor/esm/vs/language/css/css.worker', 'css.worker': 'monaco-editor/esm/vs/language/css/css.worker',
'html.worker': 'monaco-editor/esm/vs/language/html/html.worker', 'html.worker': 'monaco-editor/esm/vs/language/html/html.worker',
'ts.worker': 'monaco-editor/esm/vs/language/typescript/ts.worker' 'ts.worker': 'monaco-editor/esm/vs/language/typescript/ts.worker',
'../outputframe/output.frame': path.resolve(__dirname, 'outputframe-app', 'boot.ts')
}, },
resolve: { resolve: {
extensions: [".ts", ".js", ".json"] extensions: [".ts", ".js", ".json"]
+35
View File
@@ -0,0 +1,35 @@
const path = require('path');
const { merge } = require('webpack-merge');
const copyPlugin = require('copy-webpack-plugin');
const common = require('./webpack.common.js');
const devFolderName = "dev-dist";
module.exports = merge(common, {
mode: 'development',
output: {
globalObject: 'self',
filename: '[name].bundle.js',
path: path.resolve(__dirname, devFolderName, 'outputframe'),
publicPath: "/"
},
devServer: {
historyApiFallback: true
},
plugins: [
new copyPlugin(
{
patterns: [
{
from: path.resolve(__dirname, 'codeeditor-app/static'),
to: path.resolve(__dirname, devFolderName, 'codeeditor', '[name][ext]')
},
{
from: path.resolve(__dirname, 'outputframe-app/static'),
to: path.resolve(__dirname, devFolderName, 'outputframe', '[name][ext]')
}
]
}
)
]
});
+7 -7
View File
@@ -2,7 +2,6 @@ const path = require('path');
const { merge } = require('webpack-merge'); const { merge } = require('webpack-merge');
const copyPlugin = require('copy-webpack-plugin'); const copyPlugin = require('copy-webpack-plugin');
const common = require('./webpack.common.js'); const common = require('./webpack.common.js');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const devFolderName = "dev-dist"; const devFolderName = "dev-dist";
@@ -11,13 +10,10 @@ module.exports = merge(common, {
output: { output: {
globalObject: 'self', globalObject: 'self',
filename: '[name].bundle.js', filename: '[name].bundle.js',
path: path.resolve(__dirname, devFolderName), path: path.resolve(__dirname, devFolderName, 'codeeditor'),
publicPath: "/" publicPath: "/"
}, },
devServer: { devServer: {
static: {
directory: path.join(__dirname, devFolderName),
},
historyApiFallback: true historyApiFallback: true
}, },
plugins: [ plugins: [
@@ -25,8 +21,12 @@ module.exports = merge(common, {
{ {
patterns: [ patterns: [
{ {
from: path.resolve(__dirname, 'static'), from: path.resolve(__dirname, 'codeeditor-app/static'),
to: path.resolve(__dirname, devFolderName, '[name][ext]') to: path.resolve(__dirname, devFolderName, 'codeeditor', '[name][ext]')
},
{
from: path.resolve(__dirname, 'outputframe-app/static'),
to: path.resolve(__dirname, devFolderName, 'outputframe', '[name][ext]')
} }
] ]
} }
+9 -9
View File
@@ -3,7 +3,6 @@ const common = require('./webpack.common.js');
const path = require('path'); const path = require('path');
const ReplaceInFileWebpackPlugin = require('replace-in-file-webpack-plugin'); const ReplaceInFileWebpackPlugin = require('replace-in-file-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const prodFolderName = "prod-dist"; const prodFolderName = "prod-dist";
@@ -18,28 +17,29 @@ const replaceVersionInHtmlOption = {
return match.toString().substr(0, 3) + return match.toString().substr(0, 3) +
+new Date(); +new Date();
} }
} }
] ]
}; };
module.exports = merge(common, { module.exports = merge(common, {
mode: "production", mode: "production",
output: { output: {
path: path.resolve(__dirname, prodFolderName), globalObject: 'self',
publicPath: "/", filename: '[name].bundle.js',
filename: 'main.js' path: path.resolve(__dirname, prodFolderName, "codeeditor"),
publicPath: "/"
}, },
plugins: [ plugins: [
new CopyWebpackPlugin( new CopyWebpackPlugin(
{ {
patterns: [ patterns: [
{ {
from: path.resolve(__dirname, 'index.html'), from: path.resolve(__dirname, 'codeeditor-app/static'),
to: path.resolve(__dirname, prodFolderName, '[name][ext]') to: path.resolve(__dirname, prodFolderName, 'codeeditor', '[name][ext]')
}, },
{ {
from: path.resolve(__dirname, 'manifest.json'), from: path.resolve(__dirname, 'outputframe-app/static'),
to: path.resolve(__dirname, devFolderName, '[name][ext]') to: path.resolve(__dirname, prodFolderName, 'outputframe', '[name][ext]')
} }
] ]
} }