Set up webpack and webpack-dev-server.
parent
202c35b517
commit
c45be9a476
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"presets": [
|
||||
["babel-preset-env", {
|
||||
"loose": false,
|
||||
"modules": false
|
||||
}],
|
||||
"babel-preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
"react-hot-loader/babel",
|
||||
"babel-plugin-transform-react-constant-elements", [
|
||||
"babel-plugin-transform-react-remove-prop-types", {}
|
||||
],
|
||||
"babel-plugin-transform-decorators-legacy",
|
||||
"babel-plugin-transform-class-properties", [
|
||||
"babel-plugin-transform-runtime", {
|
||||
"helpers": false,
|
||||
"polyfill": false,
|
||||
"regenerator": true,
|
||||
"moduleName": "nwb"
|
||||
}
|
||||
],
|
||||
"babel-plugin-syntax-dynamic-import"
|
||||
]
|
||||
}
|
38
index.js
38
index.js
|
@ -33,22 +33,28 @@ const app = express();
|
|||
|
||||
app.set('port', (process.env.PORT || 3000));
|
||||
|
||||
fs.stat(frontendDir, (err) => {
|
||||
if (!err) {
|
||||
console.log(`Serving static build from ${frontendDir}`);
|
||||
console.log('Run `npm run clean` to return to development mode');
|
||||
app.use('/', express.static(frontendDir));
|
||||
} else {
|
||||
console.log('Serving development build with nwb middleware');
|
||||
console.log('Run `npm run build` to create a production build');
|
||||
if (process.NODE_ENV !== 'production') {
|
||||
console.log('Serving development build with webpack dev middleware');
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
app.use(require('nwb/express')(express, {
|
||||
entry: 'src/index.jsx',
|
||||
reload: true,
|
||||
}));
|
||||
}
|
||||
});
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
require('babel-register');
|
||||
const webpackConfig = require('./webpack.config.babel.js').default({
|
||||
server: true,
|
||||
});
|
||||
const compiler = require('webpack')(webpackConfig);
|
||||
app.use(require('webpack-dev-middleware')(compiler, webpackConfig.devServer));
|
||||
app.use(require('webpack-hot-middleware')(compiler, {
|
||||
log: false,
|
||||
}));
|
||||
|
||||
app.get('/api/dev/webpackConfig', (req, res) => {
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
res.send(require('util').inspect(webpackConfig, { depth: null, colors: false }));
|
||||
});
|
||||
} else {
|
||||
console.log(`Serving static build from ${frontendDir}`);
|
||||
app.use('/', express.static(frontendDir));
|
||||
}
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
@ -60,7 +66,7 @@ app.use((req, res, next) => {
|
|||
next();
|
||||
});
|
||||
|
||||
function calculateRankings(id: Number) {
|
||||
function calculateRankings(id) {
|
||||
db.query('SELECT * FROM `vizon_web_rankings` WHERE `vizon_drawings_id`=?', [id], (err, results, fields) => {
|
||||
if (err) {
|
||||
console.error('Failed to query rankings from database:', err);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
|
@ -5,7 +5,9 @@
|
|||
"private": true,
|
||||
"main": "./index.js",
|
||||
"scripts": {
|
||||
"build": "nwb build-react-app ./src --preact",
|
||||
"build": "webpack",
|
||||
"build:production": "npm run -s build -- --env production",
|
||||
"build:development": "npm run -s build -- --env development",
|
||||
"clean": "nwb clean-app",
|
||||
"docker-compose": "docker-compose -f docker-compose.local.yml",
|
||||
"docker:down:clean": "npm run -s docker:down -- --rmi all -v",
|
||||
|
@ -17,8 +19,9 @@
|
|||
"docker:up": "npm run -s docker-compose -- up --build",
|
||||
"docker": "npm run -s docker:up",
|
||||
"lint": "eslint .",
|
||||
"prepare": "npm run -s build",
|
||||
"prepublish": "npm run -s build:production",
|
||||
"start": "node .",
|
||||
"start:development": "webpack-dev-server --env development --env server",
|
||||
"test:coverage": "nwb test-react --coverage",
|
||||
"test:watch": "nwb test-react --server",
|
||||
"test": "nwb test-react"
|
||||
|
@ -43,30 +46,49 @@
|
|||
"author": "Carl Kittelberger <icedream@icedream.pw>",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^7.1.2",
|
||||
"babel-eslint": "^7.2.3",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.1.1",
|
||||
"chalk": "^2.1.0",
|
||||
"css-loader": "^0.28.5",
|
||||
"eslint": "^4.5.0",
|
||||
"eslint-config-airbnb": "^15.1.0",
|
||||
"eslint-plugin-babel": "^4.1.2",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-json": "^1.2.0",
|
||||
"eslint-plugin-jsx-a11y": "^5.1.1",
|
||||
"eslint-plugin-react": "^7.2.1",
|
||||
"eslint": "^4.5.0",
|
||||
"extract-text-webpack-plugin": "^3.0.0",
|
||||
"file-loader": "^0.11.2",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"node-sass": "^4.5.3",
|
||||
"normalize-scss": "^7.0.0",
|
||||
"nwb-sass": "^0.8.1",
|
||||
"nwb": "^0.18.10",
|
||||
"preact-compat": "^3.17.0",
|
||||
"nwb-sass": "^0.8.1",
|
||||
"postcss-loader": "^2.0.6",
|
||||
"preact": "^8.2.1",
|
||||
"preact-compat": "^3.17.0",
|
||||
"progress-bar-webpack-plugin": "^1.10.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^15.6.1",
|
||||
"react-dom": "^15.6.1",
|
||||
"react-fontawesome": "^1.6.1",
|
||||
"react-helmet": "^5.1.3",
|
||||
"react-hot-loader": "^3.0.0-beta.7",
|
||||
"react-router": "^4.1.2",
|
||||
"react": "^15.6.1",
|
||||
"resolve-url-loader": "^2.1.0",
|
||||
"webfontloader": "^1.6.28"
|
||||
"sass-loader": "^6.0.6",
|
||||
"slash": "^1.0.0",
|
||||
"style-loader": "^0.18.2",
|
||||
"url-loader": "^0.5.9",
|
||||
"webfontloader": "^1.6.28",
|
||||
"webpack": "^3.5.5",
|
||||
"webpack-dev-middleware": "^1.12.0",
|
||||
"webpack-hot-middleware": "^2.18.2",
|
||||
"webpack-merge": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.17.2",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
|
||||
import './index.sass';
|
||||
import App from './App';
|
||||
|
@ -8,4 +9,15 @@ import App from './App';
|
|||
const rootContainer = document.createElement('div');
|
||||
document.body.appendChild(rootContainer);
|
||||
|
||||
render(<App />, rootContainer);
|
||||
const runRender = (Component) => {
|
||||
console.log('Rendering app...');
|
||||
render(<AppContainer>
|
||||
<Component />
|
||||
</AppContainer>, rootContainer);
|
||||
};
|
||||
|
||||
runRender(App);
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./App', () => { runRender(App); });
|
||||
}
|
||||
|
|
|
@ -0,0 +1,381 @@
|
|||
import {
|
||||
DefinePlugin,
|
||||
NamedModulesPlugin,
|
||||
HashedModuleIdsPlugin,
|
||||
LoaderOptionsPlugin,
|
||||
ContextReplacementPlugin,
|
||||
HotModuleReplacementPlugin,
|
||||
NoEmitOnErrorsPlugin,
|
||||
|
||||
optimize,
|
||||
} from 'webpack';
|
||||
|
||||
import path from 'path';
|
||||
import { isatty } from 'tty';
|
||||
import chalk from 'chalk';
|
||||
import slash from 'slash';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
|
||||
import ExtractTextPlugin from 'extract-text-webpack-plugin';
|
||||
import HtmlPlugin from 'html-webpack-plugin';
|
||||
import ProgressBarPlugin from 'progress-bar-webpack-plugin';
|
||||
|
||||
const {
|
||||
CommonsChunkPlugin,
|
||||
UglifyJsPlugin,
|
||||
ModuleConcatenationPlugin,
|
||||
} = optimize;
|
||||
|
||||
const locales = ['en'];
|
||||
|
||||
/**
|
||||
* Plugin for HtmlPlugin which inlines content for an extracted Webpack manifest
|
||||
* into the HTML in a <script> tag before other emitted asssets are injected by
|
||||
* HtmlPlugin itself.
|
||||
*/
|
||||
function injectManifestPlugin() {
|
||||
this.plugin('compilation', (compilation) => {
|
||||
compilation.plugin('html-webpack-plugin-before-html-processing', (data, cb) => {
|
||||
Object.keys(compilation.assets).forEach((key) => {
|
||||
if (!key.startsWith('manifest.')) { return; }
|
||||
const { children } = compilation.assets[key];
|
||||
if (children && children[0]) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
data.html = data.html.replace(/^(\s*)<\/body>/m, `$1<script>${children[0]._value}</script>\n$1</body>`);
|
||||
// Remove the manifest from HtmlPlugin's assets to prevent a <script>
|
||||
// tag being created for it.
|
||||
const manifestIndex = data.assets.js.indexOf(data.assets.publicPath + key);
|
||||
data.assets.js.splice(manifestIndex, 1);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
delete data.assets.chunks.manifest;
|
||||
}
|
||||
});
|
||||
cb(null, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default (options) => {
|
||||
const environment = {
|
||||
development: true,
|
||||
production: false,
|
||||
server: false,
|
||||
};
|
||||
|
||||
function processEnvironmentString(env) {
|
||||
switch (env.toLowerCase()) {
|
||||
case 'development':
|
||||
environment.development = true;
|
||||
environment.production = false;
|
||||
break;
|
||||
case 'production':
|
||||
environment.development = false;
|
||||
environment.production = true;
|
||||
break;
|
||||
case 'server':
|
||||
environment.server = true;
|
||||
break;
|
||||
default:
|
||||
console.warn('Unknown environment:', env);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (options) {
|
||||
switch (true) {
|
||||
case typeof (options) === 'string': // string
|
||||
processEnvironmentString(options);
|
||||
break;
|
||||
|
||||
case Array.isArray(options): // array
|
||||
options.forEach(processEnvironmentString);
|
||||
break;
|
||||
|
||||
default: // object
|
||||
Object.keys(options).forEach((k) => {
|
||||
environment[k] = options[k] || environment[k];
|
||||
});
|
||||
break;
|
||||
}
|
||||
} else if (process.env.NODE_ENV) {
|
||||
processEnvironmentString(process.env.NODE_ENV);
|
||||
}
|
||||
|
||||
const {
|
||||
development,
|
||||
production,
|
||||
server,
|
||||
} = environment;
|
||||
|
||||
const baseOutputFilename = development
|
||||
? 'assets/dev/[name].dev.[ext]'
|
||||
// Always use a hash (in production) to prevent files with the same name causing issues
|
||||
: 'assets/prod/[chunkhash:2]/[name].[chunkhash:8].[ext]';
|
||||
|
||||
const webpackChunkFilename = baseOutputFilename
|
||||
.replace(/\[ext(.*?)\]/g, 'js');
|
||||
const webpackOutputFilename = webpackChunkFilename;
|
||||
|
||||
const assetOutputFilename = baseOutputFilename
|
||||
.replace(/\[chunkhash(.*?)\]/g, '[hash$1]');
|
||||
|
||||
const cssOutputFileName = baseOutputFilename
|
||||
.replace(/\[ext(.*?)\]/g, 'css')
|
||||
.replace(/\[chunkhash(.*?)\]/g, '[contenthash$1]');
|
||||
const cssOutputRebasePath = `${slash(path.relative(path.dirname(cssOutputFileName), ''))}/`;
|
||||
|
||||
// Default options for url-loader
|
||||
const urlLoaderOptions = {
|
||||
limit: 1, // Don't inline anything (but empty files) by default
|
||||
name: assetOutputFilename,
|
||||
publicPath: cssOutputRebasePath,
|
||||
};
|
||||
|
||||
function styleLoaders(...preprocessingLoaders) {
|
||||
let cssLoaders = [
|
||||
{
|
||||
loader: 'style-loader',
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
sourceMap: true,
|
||||
modules: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
ident: 'postcss',
|
||||
plugins: [
|
||||
autoprefixer({
|
||||
browsers: '> 1%, last 4 versions, Firefox ESR, ios >= 8',
|
||||
grid: false,
|
||||
}),
|
||||
],
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
].filter(loader => loader !== false);
|
||||
|
||||
if (preprocessingLoaders && preprocessingLoaders.length > 0) {
|
||||
cssLoaders.push(
|
||||
{
|
||||
loader: 'resolve-url-loader',
|
||||
options: {
|
||||
fail: true,
|
||||
silent: false,
|
||||
},
|
||||
},
|
||||
...preprocessingLoaders.map(loader => Object.assign({}, loader, {
|
||||
options: Object.assign({}, loader.options || {}, {
|
||||
sourceMap: true,
|
||||
}),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
if (!server) {
|
||||
const fallback = cssLoaders.shift();
|
||||
cssLoaders = ExtractTextPlugin.extract({
|
||||
fallback,
|
||||
use: cssLoaders,
|
||||
});
|
||||
}
|
||||
|
||||
return cssLoaders;
|
||||
}
|
||||
|
||||
return {
|
||||
devServer: {
|
||||
// inline: true,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
historyApiFallback: true,
|
||||
hot: true,
|
||||
noInfo: true,
|
||||
overlay: true,
|
||||
publicPath: '',
|
||||
quiet: true,
|
||||
watchOptions: {
|
||||
ignored: /node_modules/,
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.jsx?/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
options: {
|
||||
// Look for babel configuration in project directory
|
||||
babelrc: true,
|
||||
// Cache transformations to the filesystem (in default temp dir)
|
||||
cacheDirectory: true,
|
||||
},
|
||||
},
|
||||
...[
|
||||
/\.(gif|png|webp)$/i, // graphics
|
||||
/\.svg$/i, // svg
|
||||
/\.jpe?g$/i, // jpeg
|
||||
/\.(mp4|ogg|webm)$/i, // video
|
||||
/\.(eot|otf|ttf|woff|woff2)$/i, // fonts
|
||||
/\.(wav|mp3|m4a|aac|oga)$/i, // audio
|
||||
].map(test => ({
|
||||
test,
|
||||
loader: 'url-loader',
|
||||
options: urlLoaderOptions,
|
||||
})),
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: styleLoaders(),
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/,
|
||||
use: styleLoaders(
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
strictExportPresence: true,
|
||||
},
|
||||
output: {
|
||||
filename: webpackOutputFilename,
|
||||
chunkFilename: webpackChunkFilename,
|
||||
path: path.join(__dirname, 'dist'),
|
||||
publicPath: '',
|
||||
},
|
||||
plugins: [
|
||||
// Show progress as a bar during build
|
||||
isatty(1) && new ProgressBarPlugin({
|
||||
complete: chalk.white('\u2588'),
|
||||
incomplete: chalk.grey('\u2591'),
|
||||
format: `:bar ${chalk.cyan.bold(':percent')} Webpack build: ${chalk.grey(':msg')}`,
|
||||
}),
|
||||
|
||||
// Enforce case-sensitive import paths
|
||||
new CaseSensitivePathsPlugin(),
|
||||
// Replace specified expressions with values
|
||||
new DefinePlugin({
|
||||
'process.env.__DEV__': JSON.stringify(development),
|
||||
'process.env.__PROD__': JSON.stringify(production),
|
||||
'process.env.__SERVER__': JSON.stringify(server),
|
||||
'process.env.NODE_ENV': JSON.stringify(production ? 'production' : 'development'),
|
||||
}),
|
||||
|
||||
// Dev server build
|
||||
...[
|
||||
// Hot module reloading
|
||||
new HotModuleReplacementPlugin(),
|
||||
new NoEmitOnErrorsPlugin(),
|
||||
|
||||
// Use paths as names when serving
|
||||
new NamedModulesPlugin(),
|
||||
].filter(() => server),
|
||||
|
||||
// If we're not serving, we're creating a static build
|
||||
...[// Extract imported stylesheets out into .css files
|
||||
new ExtractTextPlugin({
|
||||
allChunks: true,
|
||||
filename: cssOutputFileName,
|
||||
}),
|
||||
].filter(() => !server),
|
||||
|
||||
// Move modules imported from node_modules/ into a vendor chunk when enabled
|
||||
new CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks(module) {
|
||||
return (module.resource && module.resource.includes('node_modules'));
|
||||
},
|
||||
}),
|
||||
|
||||
// If we're generating an HTML file, we must be building a web app, so
|
||||
// configure deterministic hashing for long-term caching.
|
||||
|
||||
// Generate stable module ids instead of having Webpack assign integers.
|
||||
// NamedModulesPlugin allows for easier debugging and
|
||||
// HashedModuleIdsPlugin does this without adding too much to bundle
|
||||
// size.
|
||||
development
|
||||
? new NamedModulesPlugin()
|
||||
: new HashedModuleIdsPlugin(),
|
||||
|
||||
// The Webpack manifest is normally folded into the last chunk, changing
|
||||
// its hash - prevent this by extracting the manifest into its own
|
||||
// chunk - also essential for deterministic hashing.
|
||||
new CommonsChunkPlugin({ name: 'manifest' }),
|
||||
|
||||
// Inject the Webpack manifest into the generated HTML as a <script>
|
||||
injectManifestPlugin,
|
||||
|
||||
// Production builds
|
||||
...[
|
||||
// JavaScript minification
|
||||
new LoaderOptionsPlugin({ debug: false, minimize: true }),
|
||||
new UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false,
|
||||
},
|
||||
output: {
|
||||
comments: false,
|
||||
},
|
||||
sourceMap: true,
|
||||
}),
|
||||
|
||||
// Hoisting
|
||||
new ModuleConcatenationPlugin(),
|
||||
].filter(() => production),
|
||||
|
||||
new HtmlPlugin({
|
||||
template: 'src/index.html',
|
||||
filename: 'index.html',
|
||||
hash: false,
|
||||
inject: true,
|
||||
compile: true,
|
||||
favicon: false,
|
||||
minify: false,
|
||||
cache: true,
|
||||
showErrors: true,
|
||||
chunks: 'all',
|
||||
excludeChunks: [],
|
||||
title: 'Webpack App',
|
||||
xhtml: false,
|
||||
chunksSortMode: 'dependency',
|
||||
}),
|
||||
|
||||
// Only include selected locales for moment.js
|
||||
new ContextReplacementPlugin(/moment[/\\]locale$/, new RegExp(`^\\.\\/(${locales.join('|')})$`)),
|
||||
].filter(plugin => plugin !== false),
|
||||
resolve: {
|
||||
extensions: [
|
||||
'.js', '.json', '.jsx',
|
||||
],
|
||||
alias: {
|
||||
// Use preact instead of react
|
||||
/* react: 'preact-compat\\dist\\preact-compat',
|
||||
'react-dom': 'preact-compat\\dist\\preact-compat',
|
||||
'create-react-class': 'preact-compat/lib/create-react-class', */
|
||||
|
||||
'moment-timezone': 'moment-timezone/builds/moment-timezone-with-data-2012-2022.js',
|
||||
},
|
||||
},
|
||||
resolveLoader: {
|
||||
modules: ['node_modules', 'nwb'],
|
||||
},
|
||||
devtool: server ? 'cheap-module-source-map' : 'source-map',
|
||||
entry: {
|
||||
app: [
|
||||
...[
|
||||
'eventsource-polyfill',
|
||||
'react-hot-loader/patch',
|
||||
'webpack-hot-middleware/client',
|
||||
].filter(() => server),
|
||||
'./src',
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue