mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2024-12-26 07:05:56 -05:00
revamp webpack config
* simplify it. Go from six checked-in config files + one local one to three checked-in configs + commandline options. I find it less confusing to have the options plumbed through fewer layers. * support developing against a https production server, as described in guide/developing-ui.md. * fix the source map. The sourceMap parameter in prod.config.js as far as I can tell evaluated to false when run with production config, and anyway UglifyJS seems to be incompatible with the specified cheap-module-source-map. Use source-map instead.
This commit is contained in:
parent
92266612b5
commit
4b397670a4
@ -24,7 +24,9 @@ Checkout the branch you want to work on and type
|
|||||||
|
|
||||||
This will pack and prepare a development setup. By default the development
|
This will pack and prepare a development setup. By default the development
|
||||||
server that serves up the web page(s) will listen on
|
server that serves up the web page(s) will listen on
|
||||||
[http://localhost:3000](http://localhost:3000) so you can direct your browser
|
[http://localhost:3000/](http://localhost:3000/) so you can direct your browser
|
||||||
|
there. It assumes the Moonfire NVR server is running at
|
||||||
|
[http://localhost:8080/](http://localhost:8080/) and will proxy API requests
|
||||||
there.
|
there.
|
||||||
|
|
||||||
Make any changes to the source code as you desire (look at existing code
|
Make any changes to the source code as you desire (look at existing code
|
||||||
@ -34,113 +36,70 @@ you can use the browser's debugger), or compilation breaking Javascript errors.
|
|||||||
The latter will often be reported as errors during the webpack assembly
|
The latter will often be reported as errors during the webpack assembly
|
||||||
process, but some will show up in the browser console, or both.
|
process, but some will show up in the browser console, or both.
|
||||||
|
|
||||||
## Control and location of settings
|
## Overriding defaults
|
||||||
|
|
||||||
Much of the settings needed to put the UI together, run webpack etc. is
|
The configuration understands these environment variables:
|
||||||
located in a series of files that contain webpack configuration. These
|
|
||||||
files all live in the "webpack" subdirectory. We will not explain all
|
|
||||||
of them here, as you should rarely need to understand them, let alone
|
|
||||||
modify them.
|
|
||||||
|
|
||||||
What is worth mentioning is that the `package.json` file is configured
|
| variable | description | default |
|
||||||
to use a different webpack configuration for development vs. production
|
| :------------------ | :------------------------------------------ | :----------------------- |
|
||||||
builds. Both configurations depend on a shared configuration common to
|
| `MOONFIRE_URL` | base URL of the backing Moonfire NVR server | `http://localhost:8080/` |
|
||||||
both.
|
| `MOONFIRE_DEV_PORT` | port to listen on | 3000 |
|
||||||
|
| `MOONFIRE_DEV_HOST` | base URL of the backing Moonfire NVR server | `localhost` (1) |
|
||||||
|
|
||||||
There are also some settings that control aspects of the MoonFire UI
|
(1) Moonfire NVR's `webpack/dev.config.js` has no default value for
|
||||||
behavior, such as window titles etc. These settings are found in the
|
`MOONFIRE_DEV_HOST`. `webpack-dev-server` itself has a default of `localhost`,
|
||||||
`settings-nvr.js` file in the project root directory. They should be
|
as described
|
||||||
pretty self explanatory.
|
[here](https://webpack.js.org/configuration/dev-server/#devserverhost).
|
||||||
|
|
||||||
The webpack configuration for all webpack builds is able to load the
|
Thus one could connect to a remote Moonfire NVR by specifying its URL as
|
||||||
values from `settings-nvr.js` and then, if the file exists, load those
|
follows:
|
||||||
from `settings-nvr-local.js` and use them to add to the configuration,
|
|
||||||
or replace. You can take advantage of this to add your own configuration
|
|
||||||
values in the "local" file, which does not get checked in, but is used
|
|
||||||
to affect development, and production builds.
|
|
||||||
|
|
||||||
## Special considerations for API calls
|
$ MOONFIRE_URL=https://nvr.example.com/ yarn start
|
||||||
|
|
||||||
The UI code will make calls to the MoonFire NVR's server API, which is
|
This allows you to test a new UI against your stable, production Moonfire NVR
|
||||||
assumed to run on the same host as the MoonFire server. This makes sense
|
installation with real data.
|
||||||
because that server is also normally the one serving up the UI. For UI
|
|
||||||
development, however this is not always convenient, or even useful.
|
|
||||||
|
|
||||||
For one, you may be doing this development on a machine other than where
|
The default `MOONFIRE_DEV_HOST` is suitable for connecting to the proxy server
|
||||||
the main MoonFire server is running. That can work, but of course that
|
from a browser running on the same machine. If you want your server to be
|
||||||
machine will not be responding the the API calls. If the UI does not
|
externally accessible, you may want to bind to `0.0.0.0` instead:
|
||||||
gracefully handle API failure errors (it should but development on that
|
|
||||||
is ongoing), it may break your UI code.
|
|
||||||
|
|
||||||
Therefore, for practical purposes, you may want the API calls to go to
|
$ MOONFIRE_DEV_HOST=0.0.0.0 yarn start
|
||||||
a different server than where the localhost is. Rather than editing the
|
|
||||||
`webpack/dev.conf.js` file to make this happen, you should use a different
|
|
||||||
mechanism. The reason for not modifying this file (unless the change is
|
|
||||||
needed by all), is that the file is under source control and thus should
|
|
||||||
not reflect settings that are just for your personal use.
|
|
||||||
|
|
||||||
The manner in which you can achieve using a different server for API
|
Be careful, though: it's insecure to send your production credentials over a
|
||||||
calls is by telling the development server to use a "proxy" for
|
non-`https` connection, as described more below.
|
||||||
certain urls. All API calls start with "/api", so we'll take advantage
|
|
||||||
of that. Create a the file `settings-nvr-local.js` right next to the standard
|
|
||||||
`settings-nvr.js`. The file should look something like this:
|
|
||||||
|
|
||||||
module.exports.settings = {
|
## A note on `https`
|
||||||
devServer: {
|
|
||||||
proxy: {
|
|
||||||
'/api': 'http://192.168.1.232:8080'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
This tells the development server to proxy all urls that it encounters
|
Commonly production setups require credentials and run over `https`, as
|
||||||
requests for to the url specified. The way this is done is by taking the
|
described in [secure.md](secure.md). Furthermore, Moonfire NVR will set the
|
||||||
full URI component for the original request, and appending it to the string
|
`secure` attribute on cookies it receives over `https`, so that the browser
|
||||||
configured on the right side of the path to be rewritten above. In a standard
|
will only send them over a `https` connection.
|
||||||
MoonFire install, the server (and thus the API server as well), will be
|
|
||||||
listening on port 8080 a the IP address where the server lives. So adjust
|
|
||||||
the above for reach your own MoonFire instance where there is a server
|
|
||||||
running with some real data behind it.
|
|
||||||
|
|
||||||
### Issues with "older" MoonFire builds
|
This is great for security and somewhat inconvenient for proxying.
|
||||||
|
Fundamentally, there are three ways to make it work:
|
||||||
|
|
||||||
You might also have though to change the "/api" string in the source code
|
1. Configure the proxy server with valid credentials to supply on every
|
||||||
to include the IP address of the MoonFire server. This would use the
|
request, without requiring the browser to authenticate.
|
||||||
"right" (desirable) URLs, but the requests will fail due to a violation
|
2. Configure the proxy server to strip out the `secure` attribute from
|
||||||
of the Cross-Origin Resource Sharing (CORS) protocol. If you really
|
cookie response headers, so the browser will send them to the proxy
|
||||||
need to, you can add a configuration option to the MoonFire server
|
server.
|
||||||
by modifying its "service definition". We will not explain here how.
|
3. Configure the proxy server with a TLS certificate.
|
||||||
|
a. using a self-signed certificate manually added to the browser's
|
||||||
|
store.
|
||||||
|
b. using a certificate from a "real" Certificate Authority (such as
|
||||||
|
letsencrypt).
|
||||||
|
|
||||||
## Changing development server configuration
|
Currently the configuration only implements method 2. It's easy to configure
|
||||||
|
but has a couple caveats:
|
||||||
|
|
||||||
You can find our standard configuration for the development server inside
|
* if you alternate between proxying to a test Moonfire NVR
|
||||||
the `webpacks/dev.conf.js` file. Using the technique outlined above you
|
installation and a real one, your browser won't know the difference. It
|
||||||
can change ports, ip addresses etc. One example where this may come in
|
will supply whichever credentials were sent to it last.
|
||||||
useful is that you may want to "test" your new API code, running on
|
* if you connect via a host other than localhost (and set
|
||||||
machine "A" (from a development server), proxying API requests to machine
|
`MOONFIRE_DEV_HOST` to allow this), your browser will have a production
|
||||||
"B" (because it has real data), from a browser running on machine "C".
|
cookie that it's willing to send to a remote host over a non-`https`
|
||||||
|
connection. If you ever load this website using an untrustworthy DNS
|
||||||
The development server normally is configured to listing on port 3000
|
server, your credentials can be compromised.
|
||||||
on "localhost." (which would correspond to machine "A" in this example).
|
|
||||||
However, you cannot use "localhost" on another machine to refer to "A".
|
|
||||||
You may think that using the IP address of "A" works, but it doesn't
|
|
||||||
because "localhost" lives at an IP address local to machine "A".
|
|
||||||
|
|
||||||
To make this work you must tell the development server on host "A" to
|
|
||||||
listen differently. You need to configure it to listen on IP address
|
|
||||||
"0.0.0.0", which means "all available interfaces". Once that is in
|
|
||||||
place you can use the IP address to reach "A" from "C". "A" will then
|
|
||||||
send API requests on to "B", and present final UI using information
|
|
||||||
from "A" and "B" to the browser on "C".
|
|
||||||
|
|
||||||
Modify the local settings to something like this:
|
|
||||||
|
|
||||||
module.exports.settings = {
|
|
||||||
devServer: {
|
|
||||||
host: "0.0.0.0",
|
|
||||||
proxy: {
|
|
||||||
'/api': 'http://192.168.1.232:8080'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
We might add support for method 3 in the future. It's less convenient to
|
||||||
|
configure but can avoid these problems.
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
// vim: set et sw=2 ts=2:
|
|
||||||
//
|
|
||||||
// This file is part of Moonfire NVR, a security camera digital video recorder.
|
|
||||||
// Copyright (C) 2018 Dolf Starreveld <dolf@starreveld.com>
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// In addition, as a special exception, the copyright holders give
|
|
||||||
// permission to link the code of portions of this program with the
|
|
||||||
// OpenSSL library under certain conditions as described in each
|
|
||||||
// individual source file, and distribute linked combinations including
|
|
||||||
// the two.
|
|
||||||
//
|
|
||||||
// You must obey the GNU General Public License in all respects for all
|
|
||||||
// of the code used other than OpenSSL. If you modify file(s) with this
|
|
||||||
// exception, you may extend this exception to your version of the
|
|
||||||
// file(s), but you are not obligated to do so. If you do not wish to do
|
|
||||||
// so, delete this exception statement from your version. If you delete
|
|
||||||
// this exception statement from all source files in the program, then
|
|
||||||
// also delete it here.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This module must export a map, but can use a function with no arguments
|
|
||||||
* that returns a map, or a function that receives the "env" and "args"
|
|
||||||
* values from webpack.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
module.exports.settings = {
|
|
||||||
// Project related: use ./ in front of project root relative files!
|
|
||||||
app_src_dir: './ui-src',
|
|
||||||
dist_dir: './ui-dist',
|
|
||||||
|
|
||||||
// App related
|
|
||||||
app_title: 'Moonfire NVR',
|
|
||||||
|
|
||||||
// Where is the server to be found
|
|
||||||
moonfire: {
|
|
||||||
server: 'localhost',
|
|
||||||
port: 8080,
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* In settings override file you can add sections like below on this level.
|
|
||||||
* After processing, anything defined in mode.production or mode.development,
|
|
||||||
* as appropriate based on --mode argument to webpack, will be merged
|
|
||||||
* into the top level of this settings module. This allows you to add to, or
|
|
||||||
* override anything listed above.
|
|
||||||
*
|
|
||||||
* webpack_mode: {
|
|
||||||
* production: {},
|
|
||||||
* development: {},
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
};
|
|
@ -1,81 +0,0 @@
|
|||||||
// vim: set et sw=2 ts=2:
|
|
||||||
//
|
|
||||||
// This file is part of Moonfire NVR, a security camera digital video recorder.
|
|
||||||
// Copyright (C) 2018 Dolf Starreveld <dolf@starreveld.com>
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// In addition, as a special exception, the copyright holders give
|
|
||||||
// permission to link the code of portions of this program with the
|
|
||||||
// OpenSSL library under certain conditions as described in each
|
|
||||||
// individual source file, and distribute linked combinations including
|
|
||||||
// the two.
|
|
||||||
//
|
|
||||||
// You must obey the GNU General Public License in all respects for all
|
|
||||||
// of the code used other than OpenSSL. If you modify file(s) with this
|
|
||||||
// exception, you may extend this exception to your version of the
|
|
||||||
// file(s), but you are not obligated to do so. If you do not wish to do
|
|
||||||
// so, delete this exception statement from your version. If you delete
|
|
||||||
// this exception statement from all source files in the program, then
|
|
||||||
// also delete it here.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const Settings = require('./parts/Settings');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exports a sub-class of Settings specifically for the Moonfire NVR project.
|
|
||||||
*
|
|
||||||
* Gives us a simpler constructor that encapsulates the names of the expected
|
|
||||||
* settings files.
|
|
||||||
*
|
|
||||||
* Provide some convenience member variables:
|
|
||||||
* config {object} Map of the original settings configuration
|
|
||||||
* values {object} The values map of the settings that were configured
|
|
||||||
*
|
|
||||||
* @type {NVRSettings}
|
|
||||||
*/
|
|
||||||
module.exports = class NVRSettings extends Settings {
|
|
||||||
/**
|
|
||||||
* Construct an NVRSettings object.
|
|
||||||
*
|
|
||||||
* This object will be a subclass of Settings, with some extra functionality.
|
|
||||||
*
|
|
||||||
* Initializes the super Settings object with the proper project root
|
|
||||||
* and named settings files.
|
|
||||||
*
|
|
||||||
* @param {object} env "env" object passed to webpack config function
|
|
||||||
* @param {object} args "args" object passed to webpack config function
|
|
||||||
* @param {String} projectRoot Project root, defaults to '.' which is
|
|
||||||
* usually the directory from which you run
|
|
||||||
* npm or yarn.
|
|
||||||
*/
|
|
||||||
constructor(env, args, projectRoot = './') {
|
|
||||||
super({
|
|
||||||
projectRoot: path.resolve(projectRoot),
|
|
||||||
primaryFile: 'settings-nvr.js',
|
|
||||||
secondaryFile: 'settings-nvr-local.js',
|
|
||||||
env: env,
|
|
||||||
args: args,
|
|
||||||
});
|
|
||||||
const config = this.settings_config;
|
|
||||||
// Add some absolute paths that might be relevant
|
|
||||||
this.settings = Object.assign(this.settings, {
|
|
||||||
_paths: {
|
|
||||||
project_root: config.projectRoot,
|
|
||||||
app_src_dir: path.join(config.projectRoot, this.settings.app_src_dir),
|
|
||||||
dist_dir: path.join(config.projectRoot, this.settings.dist_dir),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
@ -33,72 +33,63 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
const NVRSettings = require('./NVRSettings');
|
|
||||||
|
|
||||||
module.exports = (env, args) => {
|
module.exports = {
|
||||||
const nvrSettings = new NVRSettings(env, args).settings;
|
entry: {
|
||||||
|
nvr: './ui-src/index.js',
|
||||||
return {
|
},
|
||||||
entry: {
|
output: {
|
||||||
nvr: path.join(nvrSettings._paths.app_src_dir, 'index.js'),
|
filename: '[name].bundle.js',
|
||||||
},
|
path: path.resolve('./ui-dist/'),
|
||||||
output: {
|
publicPath: '/',
|
||||||
filename: '[name].bundle.js',
|
},
|
||||||
path: nvrSettings._paths.dist_dir,
|
module: {
|
||||||
publicPath: '/',
|
rules: [
|
||||||
},
|
{
|
||||||
module: {
|
test: /\.js$/,
|
||||||
rules: [
|
loader: 'babel-loader',
|
||||||
{
|
query: {
|
||||||
test: /\.js$/,
|
presets: ['env', {modules: false}],
|
||||||
loader: 'babel-loader',
|
|
||||||
query: {
|
|
||||||
presets: ['env', {modules: false}],
|
|
||||||
},
|
|
||||||
exclude: /(node_modules|bower_components)/,
|
|
||||||
include: ['./ui-src'],
|
|
||||||
},
|
},
|
||||||
{
|
exclude: /(node_modules|bower_components)/,
|
||||||
test: /\.png$/,
|
include: ['./ui-src'],
|
||||||
use: ['file-loader'],
|
},
|
||||||
},
|
{
|
||||||
{
|
test: /\.png$/,
|
||||||
test: /\.ico$/,
|
use: ['file-loader'],
|
||||||
use: [
|
},
|
||||||
{
|
{
|
||||||
loader: 'file-loader',
|
test: /\.ico$/,
|
||||||
options: {
|
use: [
|
||||||
name: '[name].[ext]',
|
{
|
||||||
},
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: '[name].[ext]',
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
{
|
},
|
||||||
// Load css and then in-line in head
|
{
|
||||||
test: /\.css$/,
|
// Load css and then in-line in head
|
||||||
use: ['style-loader', 'css-loader'],
|
test: /\.css$/,
|
||||||
},
|
use: ['style-loader', 'css-loader'],
|
||||||
],
|
},
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.IgnorePlugin(/\.\/locale$/),
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
title: nvrSettings.app_title,
|
|
||||||
filename: 'index.html',
|
|
||||||
template: path.join(
|
|
||||||
nvrSettings._paths.app_src_dir,
|
|
||||||
'assets',
|
|
||||||
'index.html'
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
new webpack.NormalModuleReplacementPlugin(
|
|
||||||
/node_modules\/moment\/moment\.js$/,
|
|
||||||
'./min/moment.min.js'
|
|
||||||
),
|
|
||||||
new webpack.NormalModuleReplacementPlugin(
|
|
||||||
/node_modules\/moment-timezone\/index\.js$/,
|
|
||||||
'./builds/moment-timezone-with-data-2012-2022.min.js'
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
};
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.IgnorePlugin(/\.\/locale$/),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
title: 'Moonfire NVR',
|
||||||
|
filename: 'index.html',
|
||||||
|
template: './ui-src/assets/index.html',
|
||||||
|
}),
|
||||||
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
|
/node_modules\/moment\/moment\.js$/,
|
||||||
|
'./min/moment.min.js'
|
||||||
|
),
|
||||||
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
|
/node_modules\/moment-timezone\/index\.js$/,
|
||||||
|
'./builds/moment-timezone-with-data-2012-2022.min.js'
|
||||||
|
),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
@ -30,36 +30,57 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const merge = require('webpack-merge');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const NVRSettings = require('./NVRSettings');
|
|
||||||
const baseConfig = require('./base.config.js');
|
const baseConfig = require('./base.config.js');
|
||||||
|
|
||||||
module.exports = (env, args) => {
|
module.exports = merge(baseConfig, {
|
||||||
const settingsObject = new NVRSettings(env, args);
|
stats: {
|
||||||
const nvrSettings = settingsObject.settings;
|
warnings: true,
|
||||||
|
},
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
mode: 'development',
|
||||||
|
optimization: {
|
||||||
|
minimize: false,
|
||||||
|
namedChunks: true,
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
inline: true,
|
||||||
|
port: process.env.MOONFIRE_DEV_PORT || 3000,
|
||||||
|
host: process.env.MOONFIRE_DEV_HOST,
|
||||||
|
hot: true,
|
||||||
|
clientLogLevel: 'info',
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: process.env.MOONFIRE_URL || 'http://localhost:8080/',
|
||||||
|
|
||||||
return settingsObject.webpackMerge(baseConfig, {
|
|
||||||
stats: {
|
// The live stream URLs require WebSockets.
|
||||||
warnings: true,
|
ws: true,
|
||||||
},
|
|
||||||
devtool: 'inline-source-map',
|
// Change the Host: header so the name-based virtual hosts work
|
||||||
optimization: {
|
// properly.
|
||||||
minimize: false,
|
changeOrigin: true,
|
||||||
namedChunks: true,
|
|
||||||
},
|
// If the backing host is https, Moonfire NVR will set a 'secure'
|
||||||
devServer: {
|
// attribute on cookie responses, so that the browser will only send
|
||||||
contentBase: nvrSettings.app_src_dir,
|
// them over https connections. This is a good security practice, but
|
||||||
historyApiFallback: true,
|
// it means a non-https development proxy server won't work. Strip out
|
||||||
inline: true,
|
// this attribute in the proxy with code from here:
|
||||||
port: 3000,
|
// https://github.com/chimurai/http-proxy-middleware/issues/169#issuecomment-575027907
|
||||||
hot: true,
|
// See also discussion in guide/developing-ui.md.
|
||||||
clientLogLevel: 'info',
|
onProxyRes: (proxyRes, req, res) => {
|
||||||
proxy: {
|
const sc = proxyRes.headers['set-cookie'];
|
||||||
'/api': `http://${nvrSettings.moonfire.server}:${
|
if (Array.isArray(sc)) {
|
||||||
nvrSettings.moonfire.port
|
proxyRes.headers['set-cookie'] = sc.map(sc => {
|
||||||
}`,
|
return sc.split(';')
|
||||||
|
.filter(v => v.trim().toLowerCase() !== 'secure')
|
||||||
|
.join('; ')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [new webpack.HotModuleReplacementPlugin()],
|
},
|
||||||
});
|
plugins: [new webpack.HotModuleReplacementPlugin()],
|
||||||
};
|
});
|
||||||
|
@ -1,233 +0,0 @@
|
|||||||
// vim: set et sw=2 ts=2:
|
|
||||||
//
|
|
||||||
// This file is part of Moonfire NVR, a security camera digital video recorder.
|
|
||||||
// Copyright (C) 2018 Dolf Starreveld <dolf@starreveld.com>
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// In addition, as a special exception, the copyright holders give
|
|
||||||
// permission to link the code of portions of this program with the
|
|
||||||
// OpenSSL library under certain conditions as described in each
|
|
||||||
// individual source file, and distribute linked combinations including
|
|
||||||
// the two.
|
|
||||||
//
|
|
||||||
// You must obey the GNU General Public License in all respects for all
|
|
||||||
// of the code used other than OpenSSL. If you modify file(s) with this
|
|
||||||
// exception, you may extend this exception to your version of the
|
|
||||||
// file(s), but you are not obligated to do so. If you do not wish to do
|
|
||||||
// so, delete this exception statement from your version. If you delete
|
|
||||||
// this exception statement from all source files in the program, then
|
|
||||||
// also delete it here.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const merge = require('webpack-merge');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to require a file and catch errors so we can
|
|
||||||
* distinguish between failure to find the module and errors in the
|
|
||||||
* module.
|
|
||||||
*
|
|
||||||
* When a require results in errors (as opposed to the file not being
|
|
||||||
* found), we throw an exception.
|
|
||||||
*
|
|
||||||
* If the module that is require-d is a function, it will be executed,
|
|
||||||
* passing the "env" and "args" parameters to it.
|
|
||||||
* The function should return a map.
|
|
||||||
*
|
|
||||||
* @param {String} requiredPath Path to be passed to require()
|
|
||||||
* @param {object} env webpack's "env" on invocation
|
|
||||||
* @param {object} args webpack's "args" on invocation (options)
|
|
||||||
* @param {Boolean} optional True file not to exist
|
|
||||||
* @return {object} The module, or {} if not found (optional)
|
|
||||||
*/
|
|
||||||
function requireHelper(requiredPath, env, args, optional) {
|
|
||||||
let module = {};
|
|
||||||
try {
|
|
||||||
require.resolve(requiredPath); // Throws if not found
|
|
||||||
try {
|
|
||||||
module = require(requiredPath);
|
|
||||||
if (typeof module === 'function') {
|
|
||||||
module = module(env, args);
|
|
||||||
}
|
|
||||||
// Get owned properties only: now a literal map
|
|
||||||
module = Object.assign({}, require(requiredPath).settings);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error('Settings file (' + requiredPath + ') has errors.');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (!optional) {
|
|
||||||
throw new Error('Settings file (' + requiredPath + ') not found.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const webpackMode = (args ? args.mode : null) || 'none';
|
|
||||||
const modes = module.webpack_mode || {};
|
|
||||||
delete module.webpack_mode; // Not modifying original module. We have a copy!
|
|
||||||
if (webpackMode && modes) {
|
|
||||||
module = merge(module, modes[webpackMode]);
|
|
||||||
}
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* General purpose settings loading class.
|
|
||||||
*
|
|
||||||
* The class first reads a specified file extracting a map object with
|
|
||||||
* settings. It then attempts to read a second file which, if successfull,
|
|
||||||
* will be merged to override values from the first.
|
|
||||||
*
|
|
||||||
* The module exported in each file must either be a map, in which case
|
|
||||||
* it is used directly, or a function with no arguments. In the latter case
|
|
||||||
* it will be called in order to obtain the map.
|
|
||||||
*
|
|
||||||
* The intended use is that the first file contains project level settings
|
|
||||||
* that are checked into a repository. The second file should be for local
|
|
||||||
* (development) overrides and should not be checked in.
|
|
||||||
*
|
|
||||||
* If the primary file is allowed optional and is not found, we still
|
|
||||||
* attempt to read the secondary, but it is never an error if that file
|
|
||||||
* does not exist.
|
|
||||||
*
|
|
||||||
* Both primary and secondary files may contain a property called webpack_mode
|
|
||||||
* that, in turn, may contain properties named "development" and
|
|
||||||
* "production". During loading, if these properties are present, the whole
|
|
||||||
* "webpack_mode" property is *NOT* delivered in the final result, but the
|
|
||||||
* sub-property corresponding to webpack's "--mode" argument is merged
|
|
||||||
* with the configuration object at the top-level. This allows either
|
|
||||||
* sub-property to override defaults in the settings.
|
|
||||||
*
|
|
||||||
* Provide some convenience member variables in the Settings object:
|
|
||||||
* settings_config {object} object with the arguments to the constructor
|
|
||||||
* settings {object} The values map of the settings that were configured
|
|
||||||
*
|
|
||||||
* In many cases a user of this class will only be intersted in the values
|
|
||||||
* component. A typical usage patterns would the be:
|
|
||||||
* <pre><code>
|
|
||||||
* const Settings = require('Settings');
|
|
||||||
* const settings = (new Settings()).values;
|
|
||||||
* </code></pre>
|
|
||||||
*
|
|
||||||
* This does make the "config" component of the Settings instance unavailable.
|
|
||||||
* That can be remedied:
|
|
||||||
* <pre><code>
|
|
||||||
* const Settings = require('Settings');
|
|
||||||
* const _settings = new Settings();
|
|
||||||
* const settings = _settings.values;
|
|
||||||
* </code></pre>
|
|
||||||
*
|
|
||||||
* Now the config is available as "_settings.config".
|
|
||||||
*
|
|
||||||
* @type {NVRSettings}
|
|
||||||
*/
|
|
||||||
class Settings {
|
|
||||||
/**
|
|
||||||
* Construct the settings object by attempting to read and merge
|
|
||||||
* both files.
|
|
||||||
*
|
|
||||||
* Settings file and alternate or specified as filenames only. They
|
|
||||||
* are always looked for in the project root directory.
|
|
||||||
*
|
|
||||||
* "env", and "args" options are intended to be passed in like so:
|
|
||||||
* <pre><code>
|
|
||||||
* const Settings = require('./Settings');
|
|
||||||
*
|
|
||||||
* module.exports = (env, args) => {
|
|
||||||
* const settingsObject = new Settings({ env: env, args: args });
|
|
||||||
* const settings = settingsObject.settings;
|
|
||||||
*
|
|
||||||
* return {
|
|
||||||
* ... webpack config here, using things like
|
|
||||||
* ... settings.app_title
|
|
||||||
* };
|
|
||||||
* }
|
|
||||||
* </code></pre>
|
|
||||||
*
|
|
||||||
* The Settings object inspects "args.mode" to determine how to overload
|
|
||||||
* some settings values, and defaults to 'none' if not present.
|
|
||||||
* Alternatively, null can be passed for "env", and you could pass
|
|
||||||
* <pre>{ mode: 'development' }</pre> for args (or use 'production').
|
|
||||||
* Both values will be available later from settingsObject.settings_config
|
|
||||||
* and using the values from webpack gives full access to everything webpack
|
|
||||||
* knows.
|
|
||||||
*
|
|
||||||
* @param {Boolean} options.optional True if main file is optional
|
|
||||||
* @param {String} options.projectRoot Path to project root
|
|
||||||
* @param {String} options.primaryFile Name of main settings file
|
|
||||||
* @param {String} options.secondaryFile Name of secondary settings file
|
|
||||||
* @param {String} options.env Environment variables (from webpack)
|
|
||||||
* @param {String} options.args Arguments (from webpack)
|
|
||||||
*/
|
|
||||||
constructor({
|
|
||||||
optional = false,
|
|
||||||
projectRoot = './',
|
|
||||||
primaryFile = 'settings.js',
|
|
||||||
secondaryFile = 'settings-local.js',
|
|
||||||
env = null,
|
|
||||||
args = null,
|
|
||||||
} = {}) {
|
|
||||||
if (!projectRoot) {
|
|
||||||
throw new Error('projectRoot argument for Settings is not set.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remember settings, as provided
|
|
||||||
// eslint-disable-next-line prefer-rest-params
|
|
||||||
this.settings_config = arguments[0];
|
|
||||||
|
|
||||||
// Convert settings file names into absolute paths.
|
|
||||||
const primaryPath = path.resolve(projectRoot, primaryFile);
|
|
||||||
const secondaryPath = path.resolve(projectRoot, secondaryFile);
|
|
||||||
|
|
||||||
// Check if we can resolve the primary file and if we can, require it.
|
|
||||||
const _settings = requireHelper(primaryPath, env, args, optional);
|
|
||||||
|
|
||||||
// Merge secondary override file, if it exists
|
|
||||||
this.settings = merge(
|
|
||||||
_settings,
|
|
||||||
requireHelper(secondaryPath, env, args, true)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take one or more webpack configurations and merge them.
|
|
||||||
*
|
|
||||||
* This uses the webpack-merge functionality, but each argument is subjected
|
|
||||||
* to some pre-processing.
|
|
||||||
* - If the argument is a string, a 'require' is performed with it first
|
|
||||||
* - If the remaining value is a function, it is expected to be like a
|
|
||||||
* webpack initialization function which gets passed "env" and "args"
|
|
||||||
* and it is called like that.
|
|
||||||
* - The remaining value is fed to webpack-merge.
|
|
||||||
*
|
|
||||||
* @param {[object]} webpackConfig1 Object representing the config
|
|
||||||
* @return {[type]} Merged configuration
|
|
||||||
*/
|
|
||||||
webpackMerge(...packs) {
|
|
||||||
const unpack = (webpackConfig) => {
|
|
||||||
if (
|
|
||||||
typeof webpackConfig === 'string' ||
|
|
||||||
webpackConfig instanceof String
|
|
||||||
) {
|
|
||||||
webpackConfig = require(webpackConfig);
|
|
||||||
}
|
|
||||||
const config = this.settings_config;
|
|
||||||
if (typeof webpackConfig === 'function') {
|
|
||||||
return webpackConfig(config.env, config.args);
|
|
||||||
}
|
|
||||||
return webpackConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
return merge(packs.map((p) => unpack(p)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Settings;
|
|
@ -32,17 +32,15 @@
|
|||||||
|
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const CompressionPlugin = require('compression-webpack-plugin');
|
const CompressionPlugin = require('compression-webpack-plugin');
|
||||||
const NVRSettings = require('./NVRSettings');
|
|
||||||
const baseConfig = require('./base.config.js');
|
const baseConfig = require('./base.config.js');
|
||||||
|
const merge = require('webpack-merge');
|
||||||
|
|
||||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||||
|
|
||||||
module.exports = (env, args) => {
|
module.exports = (env, args) => {
|
||||||
const settingsObject = new NVRSettings(env, args);
|
return merge(baseConfig, {
|
||||||
const nvrSettings = settingsObject.settings;
|
devtool: 'source-map',
|
||||||
|
mode: 'production',
|
||||||
return settingsObject.webpackMerge(baseConfig, {
|
|
||||||
devtool: 'cheap-module-source-map',
|
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
@ -68,12 +66,7 @@ module.exports = (env, args) => {
|
|||||||
new UglifyJsPlugin({
|
new UglifyJsPlugin({
|
||||||
cache: true, // webpack4: default
|
cache: true, // webpack4: default
|
||||||
parallel: true, // webpack4: default
|
parallel: true, // webpack4: default
|
||||||
sourceMap:
|
sourceMap: true,
|
||||||
(args.devtool && /source-?map/.test(args.devtool)) ||
|
|
||||||
(args.plugins &&
|
|
||||||
args.plugins.some(
|
|
||||||
(p) => p instanceof webpack.SourceMapDevToolPlugin
|
|
||||||
)),
|
|
||||||
uglifyOptions: {
|
uglifyOptions: {
|
||||||
compress: {
|
compress: {
|
||||||
keep_infinity: true, // Do not change to 1/0
|
keep_infinity: true, // Do not change to 1/0
|
||||||
@ -120,9 +113,7 @@ module.exports = (env, args) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new CleanWebpackPlugin([nvrSettings.dist_dir], {
|
new CleanWebpackPlugin(),
|
||||||
root: nvrSettings._paths.project_root,
|
|
||||||
}),
|
|
||||||
new CompressionPlugin({
|
new CompressionPlugin({
|
||||||
asset: '[path].gz[query]',
|
asset: '[path].gz[query]',
|
||||||
algorithm: 'gzip',
|
algorithm: 'gzip',
|
||||||
|
Loading…
Reference in New Issue
Block a user