Ways to detect node environment in electron - node.js

I would like to know how can I detect the NODE_ENV variable inside the electron main file.
What I'm trying to do is to set the loadURL to localhost:8080 if my NODE_ENV === 'dev' otherwise to /dist/index.html. Because I want to use some webpack-dev-server features such as the HMR and live reload during the development.
I set my npm script in this way but I'm not sure if it is correct.
package.json
"scripts": {
"start": "electron .",
"start:dev": "NODE_ENV=dev webpack-dev-server --config webpack.dev.js && electron .",
"build": "NODE_ENV=prod webpack --config webpack.prod.js && electron ."
},
Here is my electron main file.
main.js
const electron = require('electron');
const url = require('url');
const path = require('path');
const { app, BrowserWindow } = electron;
let mainWindow = null;
app.on('ready', function() { // eslint-disable-line
mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
},
});
// mainWindow.loadURL('http://localhost:8080/');
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, '/dist/index.html'),
protocol: 'file:',
slashes: true,
}));
mainWindow.on('closed', function(){
app.quit();
});
});

I will share some of the code from a project I am working on that deals with this issue and hopefully it will help clarify how you might be able to solve your issue.
Electron considers the environment to always be "development" if you are not loading the app from an executable file after packaging the app with something like electron-builder or electron-packager.
Distinguishing "development" from "production" #7714
This has made it time consuming for me to test other items that behave differently in prod vs. dev, but at least that seems to consistently work.
Once this is understood you can use electron.app.isPackaged to set a variable in your main.js file that can be used to control certain functionality.
Demo below...let me know if you have confusion that is not explained in the commented parts of the code.
Here are the npm scripts I am using in a project running Electron v7.0.1:
"main": "main.js",
"scripts": {
"test": "jest",
"prod": "webpack --mode production --config webpack.build.config.js && electron --noDevServer .",
"start": "webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js --mode development",
"build": "webpack --config webpack.build.config.js --mode production",
"package-mac": "electron-builder build --x64 --mac",
"package-all": "electron-builder build -mwl",
"package-linux": "electron-builder build --linux",
"gh-publish-mac": "electron-builder build --x64 --mac -p always",
"gh-publish": "electron-builder build -mwl -p always",
}
Here is code from the main.js file that is used to manage some functionality based on dev vs prod:
// Keep a reference for dev mode
let dev = false;
// this works if npm run build, followed by npm run package-(any of the scripts),
// and then open from executable file
dev = !app.isPackaged;
function createWindow() {
// Create the new browser window instance. devtools set to false in production
if (!dev) {
mainWindow = new BrowserWindow({
width: 2000,
height: 1000,
minWidth: 1304,
minHeight: 700,
backgroundColor: "-webkit-linear-gradient(top, #3dadc2 0%,#2f4858 100%)",
show: false,
title: "Swell",
allowRunningInsecureContent: true,
webPreferences: {
devTools: false,
nodeIntegration: true,
sandbox: false,
webSecurity: true,
},
icon: `${__dirname}/src/assets/icons/64x64.png`,
});
} else {
mainWindow = new BrowserWindow({
width: 2000,
height: 1000,
minWidth: 1304,
minHeight: 700,
backgroundColor: "-webkit-linear-gradient(top, #3dadc2 0%,#2f4858 100%)",
show: false,
title: "Swell",
allowRunningInsecureContent: true,
webPreferences: {
nodeIntegration: true,
sandbox: false,
webSecurity: true,
},
icon: `${__dirname}/src/assets/icons/64x64.png`,
});
}
if (dev) {
const {
default: installExtension,
REACT_DEVELOPER_TOOLS,
REDUX_DEVTOOLS,
} = require("electron-devtools-installer");
// If we are in developer mode Add React & Redux DevTools to Electon App
installExtension(REACT_DEVELOPER_TOOLS)
.then((name) => console.log(`Added Extension: ${name}`))
.catch((err) => console.log("An error occurred: ", err));
installExtension(REDUX_DEVTOOLS)
.then((name) => console.log(`Added Extension: ${name}`))
.catch((err) => console.log("An error occurred: ", err));
}
// and load the index.html of the app.
let indexPath;
if (dev && process.argv.indexOf("--noDevServer") === -1) {
// if we are in dev mode load up 'http://localhost:8080/index.html'
indexPath = url.format({
protocol: "http:",
host: "localhost:8080",
pathname: "index.html",
slashes: true,
});
} else {
indexPath = url.format({
// if we are not in dev mode load production build file
protocol: "file:",
pathname: path.join(__dirname, "dist", "index.html"),
slashes: true,
});
}
// our new app window will load content depending on the boolean value of the dev variable
mainWindow.loadURL(indexPath);
// give our new window the earlier created touchbar
mainWindow.setTouchBar(touchBar);
// prevent webpack-dev-server from setting new title
mainWindow.on("page-title-updated", (e) => e.preventDefault());
// Don't show until we are ready and loaded
mainWindow.once("ready-to-show", () => {
mainWindow.show();
// Open the DevTools automatically if developing
if (dev) {
mainWindow.webContents.openDevTools();
}
});
// Emitted when the window is closed.
mainWindow.on("closed", () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
//tldr: Remove the BrowserWindow instance that we created earlier by setting its value to null when we exit Swell
mainWindow = null;
});
//require menu file
require("./menu/mainMenu");
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", () => {
// createLoadingScreen();
createWindow();
if (!dev) {
autoUpdater.checkForUpdates();
}
});
ipcMain.on("check-for-update", () => {
//listens to ipcRenderer in UpdatePopUpContainer.jsx
if (!dev) autoUpdater.checkForUpdates();
});

Well, some quick solution would be if statement
if(process.env.NODE_ENV === 'dev') {
mainWindow.loadURL('http://localhost:8080/')
} else {
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, '/dist/index.html'),
protocol: 'file:',
slashes: true,
}));
}

Related

esBuild not creating type files when in watch mode

Have recently started using edBuild, have found it simple, fast and easy to onboard.
When i run my esBuild build command WITHOUT WATCH I can see type files are successfully created - .d.ts files.
When WATCH IS running, these files are not generate.
package.json:
"scripts": {
"ts-types": " tsc --emitDeclarationOnly --outDir dist",
"build": " node ./esbuild.js && npm run ts-types",
"postbuild": "npm run ts-types"
}
esbuild.js
.build({
entryPoints: ['/index.ts'],
outdir: 'dist',
format: 'cjs',
watch: {
onRebuild(err, result) {
if(err) log('error')
else log('succes')
}
}
})
.then(result => {
log('succes')
})
.catch(() => process.exit(1));
How can i run watch and recreate the .d.ts files on changes?
ESBuild doesn't support generating type declaration.
You are using tsc to actually generate the type declaration.
You should be able to start a process from inside node that runs this. Something similar to this:
const {exec} = require('child_process');
...
.build({
entryPoints: ['/index.ts'],
outdir: 'dist',
format: 'cjs',
watch: {
onRebuild(err, result) {
if(err) log('error')
else {
exec("npm run ts-types");
}
}
}
})
.then(result => {
log('succes')
})
.catch(() => process.exit(1));

Shopify node api context initalize errors out

I'm trying to make an app following these directions:
https://github.com/Shopify/shopify-node-api/blob/main/docs/getting_started.md
I have all the code configred and it looks like this:
// src/index.ts
import http from 'http';
import url from 'url';
import querystring from 'querystring';
import Shopify, { ApiVersion } from '#shopify/shopify-api';
require('dotenv').config();
const { API_KEY, API_SECRET_KEY, SCOPES, SHOP, HOST } = process.env
Shopify.Context.initialize({
API_KEY,
API_SECRET_KEY,
SCOPES: [SCOPES],
HOST_NAME: HOST.replace(/https?:\/\//, ""),
HOST_SCHEME: HOST.split("://")[0],
IS_EMBEDDED_APP: {boolean},
API_VERSION: ApiVersion.{version} // all supported versions are available, as well as "unstable" and "unversioned"
});
// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS: { [key: string]: string | undefined } = {};
async function onRequest(
request: http.IncomingMessage,
response: http.ServerResponse,
): Promise<void> {
const {headers, url: req_url} = request;
const pathName: string | null = url.parse(req_url).pathname;
const queryString: string = String(url.parse(req_url).query);
const query: Record<string, any> = querystring.parse(queryString);
switch (pathName) {
default:
// This shop hasn't been seen yet, go through OAuth to create a session
if (ACTIVE_SHOPIFY_SHOPS[SHOP] === undefined) {
// not logged in, redirect to login
response.writeHead(302, {Location: `/login`});
response.end();
} else {
response.write('Hello world!');
// Load your app skeleton page with App Bridge, and do something amazing!
}
return;
} // end of default path
} // end of onRequest()
http.createServer(onRequest).listen(3000);
Package JSON looks like this:
{
"name": "shopify-checkout-apit",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"#shopify/shopify-api": "^3.1.0"
},
"devDependencies": {
"#types/node": "^17.0.40",
"dotenv": "^16.0.1",
"typescript": "^4.7.3"
},
"scripts": {
"build": "npx tsc",
"prestart": "yarn run build",
"start": "node dist/index.js"
}
}
When I go to run the app with yarn start I get a ton of errors
PS C:\Users\kawnah\shopify-checkout-apit> yarn start yarn run v1.22.18
$ yarn run build $ npx tsc src/index.ts:17:27 - error TS1003:
Identifier expected.
17 API_VERSION: ApiVersion.{version} // all supported versions are
available, as well as "unstable" and "unversioned"
~
src/index.ts:18:1 - error TS1005: ',' expected.
18 }); ~
Found 2 errors in the same file, starting at: src/index.ts:17
error Command failed with exit code 2. info Visit
https://yarnpkg.com/en/docs/cli/run for documentation about this
command. error Command failed with exit code 2. info Visit
https://yarnpkg.com/en/docs/cli/run for documentation about this
command. PS C:\Users\kawnah\shopify-checkout-apit>
I have no idea what any of this means.
Typescript Error TS1003 when attempting to access object properties using bracket notation
Why does this trigger an Identifier Expected error in Typescript?
I tried deleting node modules and reinstalling but it didn't work.
How do you fix this?
the config needs to look like this
Shopify.Context.initialize({
API_KEY,
API_SECRET_KEY,
SCOPES: [SCOPES],
HOST_NAME: HOST.replace(/https?:\/\//, ""),
HOST_SCHEME: HOST.split("://")[0],
IS_EMBEDDED_APP: true,
API_VERSION: ApiVersion.October21 // all supported versions are available, as well as "unstable" and "unversioned"
});

White screen after building my electron-vue app for production

I am building an electron-vue app with multiple windows, I am using vue-router.
The app works well when running from Visual Studio Code terminal (dev mode), but after building it for production I am getting a white screen.
Here is my code
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>EmaFlow Work Sessiong Tracker</title>
</head>
<body>
<noscript>
<strong>We're sorry but statement-ts doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong>
</noscript>
<div id="app" class="h-100"></div>
<!-- built files will be auto injected -->
</body>
</html>
src/App.vue
<template>
<div id="app" class="h-100">
<router-view />
</div>
</template>
src/router.ts
import Vue from 'vue';
import Router from 'vue-router';
import LoginWindow from '#/views/LoginWindow.vue';
import MainWindow from '#/views/MainWindow.vue';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
name: 'login',
component: LoginWindow,
},
{
path: '/main',
name: 'main',
component: MainWindow,
},
],
});
src/main.ts
import Vue from 'vue';
import VeeValidate from 'vee-validate';
import VueTimers from 'vue-timers'
import App from './App.vue';
import router from './router';
Vue.use(VeeValidate);
Vue.use(VueTimers)
Vue.config.productionTip = false;
new Vue({
router,
render: (h) => h(App),
}).$mount('#app');
import $ from 'jquery'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.bundle.min.js'
import '#fortawesome/fontawesome-free/js/all.min.js';
src/background.ts
'use strict'
import { app, protocol, BrowserWindow, ipcMain, Event } from 'electron'
import {
createProtocol,
installVueDevtools
} from 'vue-cli-plugin-electron-builder/lib'
const isDevelopment = process.env.NODE_ENV !== 'production'
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([{ scheme: 'app', privileges: { secure: true, standard: true } }])
const appWindows: BrowserWindow[] = [];
function createWindow(slug: string, options?: object) {
const defaultOptions = {
width: 800,
height: 600,
frame: false,
webPreferences: {
nodeIntegration: true,
},
};
const windowOptions = Object.assign({}, defaultOptions, options);
const window = new BrowserWindow(windowOptions);
appWindows.push(window);
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
window.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string + '/#' + slug);
window.webContents.openDevTools();
} else {
createProtocol('app')
// Load the index.html when not in development
window.loadURL('app://./index.html' + '/#' + slug);
}
window.on('closed', () => {
appWindows.splice(appWindows.indexOf(window), 1);
});
}
function createLoginWindow() {
createWindow('/', {
width: 400,
height: 300,
resizable: isDevelopment,
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installVueDevtools();
} catch (e) {
// console.error('Vue Devtools failed to install:', e.toString());
}
}
createLoginWindow();
});
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (appWindows.length === 0) {
createLoginWindow();
}
})
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
ipcMain.on('open-window', (e: Event, arg: WindowParams) => {
createWindow(arg.route, arg.options);
});
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', data => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
package.json
{
"name": "emaflow-worksession-tracker",
"version": "0.1.0",
"private": true,
"scripts": {
"lint": "vue-cli-service lint",
"build": "vue-cli-service electron:build",
"serve": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"main": "background.js",
"dependencies": {
"axios": "^0.19.0",
"bootstrap": "^4.3.1",
"core-js": "^2.6.10",
"howler": "^2.1.2",
"jquery": "^3.4.1",
"popper.js": "^1.15.0",
"typescript": "^3.6.4",
"vee-validate": "^2.2.15",
"vue": "^2.6.10",
"vue-class-component": "^7.0.2",
"vue-property-decorator": "^8.2.2",
"vue-router": "^3.1.3",
"vue-timers": "^2.0.4"
},
"devDependencies": {
"#fortawesome/fontawesome-free": "^5.11.2",
"#vue/cli-plugin-babel": "^3.12.0",
"#vue/cli-plugin-typescript": "^3.12.0",
"#vue/cli-service": "^3.12.0",
"electron": "^5.0.11",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"vue-cli-plugin-electron-builder": "^1.4.0",
"vue-template-compiler": "^2.6.10"
}
}
On app launch, a login window will be shown, on success login, the login window will close and open another window.
To open a window, in background.ts I created the function createWindow which takes the router path as the first parameter. To create the login window for example I call createWindow('/', options) and to create the main app window after successful login I write createWindow('/main', options).
I think my problem is in window.loadUrl inside createWindow in background.ts, but I am not sure what the correct url should be for production mode.
please advise and thanks in advance.
finally I could make window.loadUrl working for the production version as follows:
createProtocol('app');
window.loadURL(formatUrl({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file',
slashes: true
}));
The above code is working but it only opens the login window which has the path '/' in the vue-router routes list.
To open a window for another route like '/main' I tried to append a hash and the route to the pathname like this:
window.loadURL(formatUrl({
pathname: path.join(__dirname, 'index.html#', slug),
protocol: 'file',
slashes: true
}));
but it did not work and on dev tools network tab I see this error:
Name: index.html%23/ Status: (blocked:other)
please advice
EDIT: all worked after adding the hash property to the options object passed to formatUrl instead of appending to the pathname manually:
window.loadURL(formatUrl({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file',
slashes: true,
hash: slug
}));

Docker + Webpack (Dev Server) + Yarnpkg incomplete builds

Problem
Converting a webpack project that runs locally right now to run inside docker containers. This work takes place in two git branches: develop, and containers.
Local (No Container)
develop is the stable base, which runs locally via
$ yarn install && npm run dev given the following in package.json
"scripts": {
"start": "node .",
"env:dev": "cross-env NODE_ENV=development",
"env:prod": "cross-env NODE_ENV=production",
"predev": "npm run prebuild",
"dev": "npm run env:dev -- webpack-dev-server",
//[...]
}
The branch develop does include yarn.lock, though FWIW, $ rm yarn.lock && yarn install --force && npm run dev does start up the server correctly, i.e. GET http://localhost:3000 gives me the homepage, as I expect to see it. The above all works the same after $ git checkout containers
Docker
After shutting down the local dev server, I run $ git checkout containers, and this branch does NOT contain the yarn.lock or package.lock. I then run $ docker-compose up --build web (in a separate terminal, in a sibling directory that contains the following in the docker-compose.yaml)
web:
build:
context: ../frontend/
dockerfile: Dockerfile
env_file: ../frontend/.env
volumes:
- ../frontend/src:/code/src
ports:
- "3001:3000"
depends_on:
- api
networks:
- backend
The frontend/Dockerfile for the service web is like so
# Dockerfile
FROM node:latest
RUN mkdir /code
ADD . /code/
WORKDIR /code/
RUN yarn cache clean && yarn install --non-interactive --force && npm rebuild node-sass
CMD npm run dev --verbose
given
#frontend/.dockerignore
node_modules
deploy
.circleci
stories
.storybook
All seems to go well, and the final line of the startup is web_1 | Server is running at http://localhost:3000/.
Yet when I GET http://localhost:3001 (note port mapping in docker-compose), the page that's returned does not contain the expected <style>...</style> tag in the <head> as is supposed to be injected (as far as I understand) by webpack, given the configuration below
// https://github.com/diegohaz/arc/wiki/Webpack
const path = require('path')
const devServer = require('#webpack-blocks/dev-server2')
const splitVendor = require('webpack-blocks-split-vendor')
const happypack = require('webpack-blocks-happypack')
const serverSourceMap = require('webpack-blocks-server-source-map')
const nodeExternals = require('webpack-node-externals')
const AssetsByTypePlugin = require('webpack-assets-by-type-plugin')
const ChildConfigPlugin = require('webpack-child-config-plugin')
const SpawnPlugin = require('webpack-spawn-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const {
addPlugins, createConfig, entryPoint, env, setOutput,
sourceMaps, defineConstants, webpack, group,
} = require('#webpack-blocks/webpack2')
const host = process.env.HOST || 'localhost'
const port = (+process.env.PORT + 1) || 3001
const sourceDir = process.env.SOURCE || 'src'
const publicPath = `/${process.env.PUBLIC_PATH || ''}/`.replace('//', '/')
const sourcePath = path.join(process.cwd(), sourceDir)
const outputPath = path.join(process.cwd(), 'dist/public')
const assetsPath = path.join(process.cwd(), 'dist/assets.json')
const clientEntryPath = path.join(sourcePath, 'client.js')
const serverEntryPath = path.join(sourcePath, 'server.js')
const devDomain = `http://${host}:${port}/`
//[...]
const sass = () => () => ({
module: {
rules: [
{
test: /\.(scss|sass)$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'sass-loader'},
],
},
],
},
})
const extractSass = new ExtractTextPlugin({
filename: 'style.css',
})
const prodSass = () => () => ({
module: {
rules: [
{ test: /\.(scss|sass)$/,
use: extractSass.extract({
use: [
{ loader: 'css-loader', options: { minimize: true } },
{ loader: 'sass-loader' },
],
fallback: 'style-loader',
}),
},
],
},
})
const babel = () => () => ({
module: {
rules: [
{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader' },
],
},
})
const assets = () => () => ({
module: {
rules: [
{ test: /\.(png|jpe?g|svg|woff2?|ttf|eot)$/, loader: 'url-loader?limit=8000' },
],
},
})
const resolveModules = modules => () => ({
resolve: {
modules: [].concat(modules, ['node_modules']),
},
})
const base = () => group([
setOutput({
filename: '[name].js',
path: outputPath,
publicPath,
}),
defineConstants({
'process.env.NODE_ENV': process.env.NODE_ENV,
'process.env.PUBLIC_PATH': publicPath.replace(/\/$/, ''),
}),
addPlugins([
new webpack.ProgressPlugin(),
extractSass,
]),
apiInsert(),
happypack([
babel(),
]),
assets(),
resolveModules(sourceDir),
env('development', [
setOutput({
publicPath: devDomain,
}),
sass(),
]),
env('production', [
prodSass(),
]),
])
const server = createConfig([
base(),
entryPoint({ server: serverEntryPath }),
setOutput({
filename: '../[name].js',
libraryTarget: 'commonjs2',
}),
addPlugins([
new webpack.BannerPlugin({
banner: 'global.assets = require("./assets.json");',
raw: true,
}),
]),
() => ({
target: 'node',
externals: [nodeExternals()],
stats: 'errors-only',
}),
env('development', [
serverSourceMap(),
addPlugins([
new SpawnPlugin('npm', ['start']),
]),
() => ({
watch: true,
}),
]),
])
const client = createConfig([
base(),
entryPoint({ client: clientEntryPath }),
addPlugins([
new AssetsByTypePlugin({ path: assetsPath }),
new ChildConfigPlugin(server),
]),
env('development', [
devServer({
contentBase: 'public',
stats: 'errors-only',
historyApiFallback: { index: publicPath },
headers: { 'Access-Control-Allow-Origin': '*' },
host,
port,
}),
sourceMaps(),
addPlugins([
new webpack.NamedModulesPlugin(),
]),
]),
env('production', [
splitVendor(),
addPlugins([
new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }),
]),
]),
])
module.exports = client
Interestingly, adding this line to package.json
"dev-docker": "npm run predev && npm run env:dev -- webpack --progress --watch --watch-poll",
and changing the last line of the Dockerfile to CMD npm run dev-docker does yield the desired effect...
Hypotheses
My current suspicion is that I am missing something about how the webpack dev server handles serving its loader output, and have not mapped some port properly, but that's a shot in the dark.
Alternatively, the webpack-dev-server version is a problem. Local is 4.4.2 where docker's shows 5.6.0, though this seems probably not the issue as the documentation for latest matches my own setup. I've confirmed that the package.json specification for the loader modules is the latest stable on each of them.
Apologia
Recognizing that this is a problem caused by the intersection of several technologies in a config-dependent and necessarily idiosyncratic way, I humbly ask your help in working through this dependency hell. If it seems like I do not understand how a given piece of the puzzle operates, I'm happy to hear it. Any ideas, leads, or suggestions, however tenuous, will be greatly appreciated and exploited to the best of my abilities.
Long shot here, but I was trying to run a grails-vue app in docker containers and had issues with the port mappings of webpack-dev-server not being exposed properly.
I found this issue on github https://github.com/webpack/webpack-dev-server/issues/547 which led to me adding --host 0.0.0.0 to my dev task in package.json like so:
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --host 0.0.0.0"
This solved my problem, maybe this will help you find your answer.
It's been a while, but coming back to this problem, I found the actual answer.
The webpack-dev-server uses two ports. Thus, in exposing only the one port (3000) I was not getting the built files, which are served in client.js on localhost:3001. The clue was right there the whole time in the JS console: a connection refused error on GET localhost:3001/client.js.
The solution is to expose both ports on the container, i.e.
docker run -it -p 3000:3000 -p 3001:3001 --rm --entrypoint "npm run env:dev -- webpack-dev-server" ${CONTAINER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
It could be possible that your locally installed packages differ from the packages in the docker container.
To be sure that you have the same packages installed, you should include yarn.lock and package.lock files. If you only use yarn yarn.lock should suffice. Even if this does not solve your specific problem, it can prevent others, because now you have a deterministic build.

Running node-inspector and node-debug with npm run

How to run two script at once with npm run?
First of all I know grunt or gulp but I want to make it without another js modules.
To make this I have this script:
"scripts": {
"start": "node ./node/www",
"nodemon": "./node_modules/.bin/nodemon node/www.js -i './e2e-tests/**' -i './node_modules/**' -i '.idea/**' -i './node/log/**' -w './node/server/**/*' -V -L",
"nodeInspector": "./node_modules/.bin/node-inspector --save-live-edit=true",
"debug": "node-debug ./node/www.js",
"ins": "npm run nodeInspector & npm run debug"
}
I want to run with npm run ins but it only fires node-inspector.
It is not possible to run both commands. They each need their own console
If node-debug comes from node-inspector package, then you don't need to start a new node-inspector instance, node-debug will start it automatically for you.
This is what node-debug performs under the hood (source code):
Run node-inspector.
Run the supplied script in debug mode
Open the user's browser, pointing it at the inspector.
In fact, running both node-inspector and node-debug will not work as intended, as the second node-inspector instance won't be able to attach to the same port where the first instance already listens.
I couldn't make it happen on one line that's why I changed to grunt with concurrent:
module.exports = function(grunt) {
grunt.initConfig({
concurrent: {
options: {
limit: 3,
logConcurrentOutput: true
},
debug: {
tasks: ['node-inspector', 'nodemon:debug'],
options: {
logConcurrentOutput: true
}
}
},
nodemon: {
dev: {
script: "node/www.js",
options: {
ignore: ['node/public/**'],
callback: function (nodemon) {
nodemon.on('log', function (event) {
console.log(JSON.stringify(event));
});
// opens browser on initial server start
nodemon.on('config:update', function () {
console.log("nodemon config:update oldu");
// Delay before server listens on port
setTimeout(function() {
require('open')('http://localhost:3000');
}, 1000);
});
// refreshes browser when server reboots
nodemon.on('restart', function () {
// Delay before server listens on port
setTimeout(function() {
//require('fs').writeFileSync('.rebooted', 'rebooted');
}, 1000);
});
}
}
},
debug: {
script: './node/www.js',
options: {
ignore: ['node/log/*.log','node/public/**','node/views/**','doc/**','e2e-tests/**','.idea'],
callback: function (nodemon) {
nodemon.on('log', function (event) {
console.log("Nodemon debug callback fonksiyonu nodemon.onLog olayı");
});
// opens browser on initial server start
nodemon.on('config:update', function () {
console.log("Nodemon debug callback fonksiyonu nodemon.onConfig:Update olayı");
// Delay before server listens on port
setTimeout(function() {
require('open')('http://localhost:3000');
require('open')('http://localhost:1337/debug?port=5858');
}, 1000);
});
// refreshes browser when server reboots
nodemon.on('restart', function () {
console.log("Nodemon debug callback fonksiyonu nodemon.onRestart olayı");
// Delay before server listens on port
setTimeout(function() {
//require('fs').writeFileSync('.rebooted', 'rebooted');
}, 1000);
});
},
nodeArgs: ['--debug'],
watch: ['Gruntfile.js', 'node/server', 'package.json']
}
}
},
'node-inspector': {
custom: {
options: {
'web-port': 1337,
'web-host': 'localhost',
'debug-port': 5857,
'save-live-edit': true,
'no-preload': true,
'stack-trace-limit': 4,
'hidden': ['node_modules']
}
}
}
});
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
// Çalışması test edildi ve iki pencerede hem test hem uygulama açıyor.
grunt.registerTask('nodemon-debug', ['concurrent:debug']);
};
I managed to run node-inspector and nodemon from a script using concurently
https://www.npmjs.com/package/concurrently
"dev": "npm install && concurrently \"node-inspector --web-port 9090\" \"nodemon --debug .\""

Resources