1
0
mirror of https://github.com/trambarhq/relaks-wordpress-example.git synced 2025-09-02 04:32:37 +02:00

Initial check in.

This commit is contained in:
Chung Leong
2018-12-21 18:29:09 +01:00
commit af9c572a9c
20 changed files with 11827 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules
webpack.debug.js

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM node:8
WORKDIR /opt/starwars
# install dependencies
COPY package.json ./
COPY package-lock.json ./
RUN npm install strip-ansi -g && npm install -g npm@6.3 && npm ci --only=production
# copy code
COPY server ./
EXPOSE 8080
CMD node index.js

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Chung Leong
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
docker-compose exec varnish varnishstat
docker-compose exec varnish varnishncsa -F '%h %U%q %{Varnish:hitmiss}x'

8480
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

52
package.json Normal file
View File

@@ -0,0 +1,52 @@
{
"name": "relaks-wordpress-example",
"version": "1.0.0",
"description": "An example of using Relaks to build an isomorphic WordPress frontend",
"scripts": {
"watch": "webpack --watch",
"build": "webpack",
"start": "webpack-dev-server && exit 0",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git@github.com:trambarhq/relaks-wordpress-example.git"
},
"author": "Chung Leong",
"license": "MIT",
"homepage": "https://github.com/trambarhq/relaks-wordpress-example",
"devDependencies": {
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-syntax-class-properties": "^6.13.0",
"babel-plugin-transform-regenerator": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.4.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"css-loader": "^1.0.1",
"extract-text-webpack-plugin": "^3.0.2",
"html-webpack-plugin": "^2.28.0",
"node-sass": "^4.5.3",
"react": "^16.6.3",
"regenerator-runtime": "^0.12.0",
"relaks": "^1.1.7",
"relaks-harvest": "^0.0.2",
"relaks-route-manager": "0.0.18",
"sass-loader": "^6.0.5",
"uglifyjs-webpack-plugin": "^0.4.6",
"webpack": "^3.1.0",
"webpack-bundle-analyzer": "^2.13.1",
"webpack-dev-server": "^2.11.2"
},
"dependencies": {
"cross-fetch": "^2.2.2",
"dnscache": "^1.0.1",
"express": "^4.16.3",
"express-cache-controller": "^1.1.0",
"react-dom": "^16.6.3",
"schedule": "^0.5.0",
"spider-detector": "^1.0.18"
}
}

105
src/application.jsx Normal file
View File

@@ -0,0 +1,105 @@
import React, { PureComponent } from 'react';
import Wordpress from 'wordpress';
import { Route } from 'routing';
import SideNav from 'widgets/side-nav';
import TopNav from 'widgets/top-nav';
import 'style.scss';
class Application extends PureComponent {
static displayName = 'Application';
constructor(props) {
super(props);
let { routeManager, dataSource } = this.props;
this.state = {
route: new Route(routeManager),
wp: new Wordpress(dataSource, props.ssr),
};
}
/**
* Render the application
*
* @return {VNode}
*/
render() {
let { route, wp } = this.state;
let { topNavCollapsed } = this.state;
let PageComponent = route.params.module.default;
return (
<div>
<SideNav route={route} wp={wp} />
<TopNav route={route} wp={wp} collapsed={topNavCollapsed} />
<div className="page-container">
<PageComponent route={route} wp={wp} />
</div>
</div>
);
}
/**
* Added change handlers when component mounts
*/
componentDidMount() {
let { routeManager, dataSource } = this.props;
routeManager.addEventListener('change', this.handleRouteChange);
dataSource.addEventListener('change', this.handleDataSourceChange);
document.addEventListener('scroll', this.handleScroll);
}
/**
* Remove change handlers when component mounts
*/
componentWillUnmount() {
let { routeManager, dataSource } = this.props;
routeManager.removeEventListener('change', this.handleRouteChange);
dataSource.removeEventListener('change', this.handleDataSourceChange);
document.removeEventListener('scroll', this.handleScroll);
}
/**
* Called when the data source changes
*
* @param {RelaksWordpressDataSourceEvent} evt
*/
handleDataSourceChange = (evt) => {
this.setState({ wp: new Wordpress(evt.target) });
}
/**
* Called when the route changes
*
* @param {RelaksRouteManagerEvent} evt
*/
handleRouteChange = (evt) => {
this.setState({ route: new Route(evt.target) });
}
/**
* Called when the user scrolls the page contents
*
* @param {Event} evt
*/
handleScroll = (evt) => {
let { topNavCollapsed } = this.state;
let container = document.body.parentElement;
let previousPos = this.previousScrollPosition || 0;
let currentPos = container.scrollTop;
let delta = currentPos - previousPos;
if (delta > 0) {
if (!topNavCollapsed && currentPos > 120) {
this.setState({ topNavCollapsed: true });
}
} else {
if (topNavCollapsed) {
this.setState({ topNavCollapsed: false });
}
}
this.previousScrollPosition = currentPos;
}
}
export {
Application as default,
Application
};

12
src/index.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous">
<title>Relaks WordPress Example</title>
</head>
<body>
<div id="app-container"><!--APP--></div>
</body>
</html>

64
src/main.js Normal file
View File

@@ -0,0 +1,64 @@
import { createElement } from 'react';
import { hydrate, render } from 'react-dom';
import { Application } from 'application';
import { routes } from 'routing';
import WordpressDataSource from 'wordpress-data-source';
import RouteManager from 'relaks-route-manager';
import { harvest } from 'relaks-harvest';
import Relaks from 'relaks';
const pageBasePath = '/';
if (typeof(window) === 'object') {
async function initialize(evt) {
// create data source
let host = `${location.protocol}//${location.host}`;
let dataSource = new WordpressDataSource({
baseURL: `${host}/wp-json`,
});
dataSource.activate();
// create route manager
let routeManager = new RouteManager({
routes,
basePath: pageBasePath,
preloadingDelay: 2000,
});
routeManager.activate();
await routeManager.start();
let appContainer = document.getElementById('app-container');
if (!appContainer) {
throw new Error('Unable to find app element in DOM');
}
//let ssrElement = createElement(Application, { dataSource, routeManager, ssr: 'hydrate' });
//let seeds = await harvest(ssrElement, { seeds: true });
//Relaks.set('seeds', seeds);
//hydrate(ssrElement, appContainer);
let appElement = createElement(Application, { dataSource, routeManager });
render(appElement, appContainer);
}
window.addEventListener('load', initialize);
} else {
async function serverSideRender(options) {
let dataSource = new WordpressDataSource({
baseURL: `${options.host}/wp-json`,
fetchFunc: options.fetch,
});
dataSource.activate();
let routeManager = new RouteManager({
routes,
basePath: pageBasePath,
});
routeManager.activate();
await routeManager.start(options.path);
let ssrElement = createElement(Application, { dataSource, routeManager, ssr: options.target });
return harvest(ssrElement);
}
exports.render = serverSideRender;
}

View File

@@ -0,0 +1,15 @@
import React, { PureComponent } from 'react';
import { AsyncComponent } from 'relaks';
class CategoryPage extends AsyncComponent {
static displayName = 'CategoryPage';
async renderAsync(meanwhile) {
return <div>Category</div>;
}
}
export {
CategoryPage as default,
CategoryPage,
};

View File

@@ -0,0 +1,15 @@
import React, { PureComponent } from 'react';
import { AsyncComponent } from 'relaks';
class CategoryStoryPage extends AsyncComponent {
static displayName = 'CategoryStoryPage';
async renderAsync(meanwhile) {
return <div>Category > Story</div>;
}
}
export {
CategoryStoryPage as default,
CategoryStoryPage,
};

View File

@@ -0,0 +1,34 @@
import React, { PureComponent } from 'react';
import { AsyncComponent } from 'relaks';
class WelcomePage extends AsyncComponent {
static displayName = 'WelcomePage';
async renderAsync(meanwhile) {
return (
<div>
<h1>Welcome</h1>
<p>Lorem ipsum dolor sit amet, utinam quodsi expetendis in has. Primis accumsan mnesarchum ne eam, simul ludus no est, duo ne ferri minim facilisis. Virtute detraxit intellegam quo ad, usu mundi minimum at, soleat insolens intellegam no est. Dicta viderer efficiantur id vix, simul zril legere te sea. Sed quaeque alienum principes ex, ne idque dicit duo, dolor voluptaria in vel.</p>
<p>Ne nisl essent cum, at quod antiopam has. Sanctus graecis ocurreret sed at, veniam urbanitas at cum. Epicurei ullamcorper est ut, no mei virtute lobortis indoctum. Putant inermis definiebas ne nec, habeo mazim offendit ei vim. Dicat tempor no duo, mea ex cibo autem. Eos probatus ocurreret rationibus no.</p>
<p>Nullam volumus probatus ad mea. Ferri dolores ex nec, purto explicari et pri. Cu luptatum legendos sadipscing cum, fabulas accusamus maiestatis cu sea. In sea vidit accumsan, essent facete persius ex qui. Cum vide aperiri ne, te vix odio elitr labores, ut eum tale eleifend.</p>
<p>Mel iudico ancillae eu. Nisl aliquid vulputate sed cu. Harum dolore eloquentiam eum no, ei mazim corpora persecuti ius. Nostrum fastidii eam in, eum at velit alienum blandit. Ei fabulas dolorem maluisset vel, eu mediocrem incorrupte pro. Ut menandri oportere sit. Eam ei vitae posidonium.</p>
<p>An sumo iisque posidonium mei, pro ei vide mutat posidonium. Mel meis adolescens intellegam ei, iriure integre dolorem ei sed, duo ea cibo graeci luptatum. Ad vocibus argumentum persequeris his, et mei agam elitr, purto copiosae rationibus pro eu. Duo nostrud perpetua at, antiopam inciderint in vis. Eum et adhuc quaestio iracundia. Illum offendit percipitur id sea, pro numquam verterem comprehensam te, est ex agam mucius.</p>
<p>Lorem ipsum dolor sit amet, wisi dicit facete an vim, hinc velit veniam cum ad. Pri inani blandit no, pri virtute fierent petentium at. Nec nihil quodsi id, mutat eruditi nam ei. Cu autem aliquid fabulas mel, an sea sale epicuri deleniti. Ex qui viderer definitionem, quo et vide corrumpit.</p>
<p>Ad mei ignota contentiones, ad dicant legendos has. Qui eripuit constituto disputando ne. Qui quem vidit id, nisl nulla mei id. No iudico perfecto patrioque per, et usu repudiare honestatis. An mutat reque impedit sit.</p>
<p>Eum prima eruditi ancillae no, pri alia veri nulla ei, at meis volumus vix. Vim ei nemore cetero quaerendum. Eos ad lorem erroribus, ut vidisse scripserit quo. No nostro sensibus dissentias duo. In pri paulo oblique voluptatum, ius habeo veritus an, id ius sumo agam similique. Harum adolescens liberavisse ut sea, id duis tacimates duo. Quo eu alia elitr offendit, eum brute ipsum temporibus ei.</p>
<p>Ei maiorum recteque nec. Eum ex habeo quaerendum. Sed tamquam consulatu elaboraret cu. Mea latine offendit singulis et, malis atomorum sensibus ex eum, has vidit laoreet vituperata ei. Et pro quot sonet viderer. Et vero idque delicatissimi qui.</p>
<p>Ius quaeque prodesset disputando eu, et veritus nominavi consulatu eum, per ex perfecto praesent. Per in vero sonet, sit at salutandi concludaturque, cu sea tation nominavi mnesarchum. Ea fugit iusto his, mei idque conclusionemque ex. Ea nec omittam deserunt dissentias, iisque maiestatis ius ut, cu mollis voluptua sit. Has graecis placerat pertinacia ut, nihil ornatus eu has. At modus insolens pro, eros dicta cu vel.</p>
<p>Ad vis integre constituam sadipscing, magna convenire iudicabit cu nec. Illum apeirian eu mel. Splendide persequeris mel id. Scaevola gloriatur deseruisse cu usu, discere invenire in sit, id viderer eruditi ocurreret quo. Te audire assentior pri, ne pericula neglegentur per. Sint pericula duo ea, feugiat molestie delectus ea eam.</p>
<p>Cu harum tation tempor ius, has magna aliquip qualisque ne, quo solet tation omnesque no. Rebum salutatus per ei, mei ferri dolorum ne. Eum ipsum sanctus ei, ei mei voluptua inciderint sadipscing. Eos prompta delenit ei, his dolorum vituperata at. Ne tibique propriae perfecto quo, molestiae prodesset accommodare no eam, mea te ullum appetere. In ludus labores salutandi pri, quo ei nulla forensibus efficiendi, lorem debet facilisi vim no. Ex sea vidit maiestatis.</p>
<p>Liber congue eligendi cu mea, in vim veniam ignota. Ex sea quot mollis ocurreret. Ut nihil propriae qualisque vel, et nam delectus mandamus voluptaria. At suas vidit mel.</p>
<p>At prodesset consequuntur cum, eum laudem oportere consetetur id. Usu an tation maluisset, at inani aeterno vel, no clita persequeris duo. Fabellas abhorreant ei per, elitr causae usu cu. An sed antiopam salutatus, duo eu postea percipit. Vim quidam habemus contentiones ea, sed essent delectus suavitate te. Assum dicit deleniti sed ei.</p>
<p>Everti saperet mea ei, eu per indoctum postulant, tacimates explicari id sea. His saepe voluptaria an, cum vide percipitur dissentiet id. Ei sea natum rebum appareat. Percipit honestatis cu vix. Discere recusabo eu sed, nobis fabellas definitionem ad cum, scribentur consectetuer ea pro. Ea pri simul verear, ridens similique adolescens his eu. Ius reque commune delicata cu, ex eos ferri tractatos, usu vitae habemus patrioque et.</p>
</div>
);
}
}
export {
WelcomePage as default,
WelcomePage,
};

52
src/routing.js Normal file
View File

@@ -0,0 +1,52 @@
class Route {
constructor(routeManager) {
this.routeManager = routeManager;
this.name = routeManager.name;
this.params = routeManager.params;
this.history = routeManager.history;
}
change(url, options) {
return this.routeManager.change(url, options);
}
find(name, params) {
return this.routeManager.find(name, params);
}
extractID(url) {
var si = url.lastIndexOf('/');
var ei;
if (si === url.length - 1) {
ei = si;
si = url.lastIndexOf('/', ei - 1);
}
var text = url.substring(si + 1, ei);
return parseInt(text);
}
}
let routes = {
'welcome-page': {
path: '/',
load: async (match) => {
match.params.module = await import('pages/welcome-page' /* webpackChunkName: "welcome" */);
}
},
'category-page': {
path: '/${category}/',
params: { category: String },
load: async (match) => {
match.params.module = await import('pages/category-page' /* webpackChunkName: "category" */);
}
},
'story-page': {
path: '/${category}/${slug}/',
params: { category: String, slug: String },
load: async (match) => {
match.params.module = await import('pages/category-story-page' /* webpackChunkName: "category-story" */);
}
},
};
export { Route, routes };

148
src/style.scss Normal file
View File

@@ -0,0 +1,148 @@
* {
box-sizing: border-box;
}
BODY {
font-family: sans-serif;
margin: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.contents {
margin: 0.5em;
}
A {
&:link, &:visited {
text-decoration: none;
color: #000;
}
&:hover {
text-decoration: underline;
color: #f11;
}
}
.side-nav {
position: fixed;
width: 20em;
left: 0;
top: 0;
bottom: 0;
background-color: #66023c;
}
.top-nav {
position: fixed;
left: 20em;
top: 0;
right: 0;
.title-bar {
display: flex;
align-items: center;
color: #ffffff;
background-color: #990000;
height: 8em;
transition: height 0.5s;
.title {
.fa {
font-size: 4em;
transition: font-size 0.5s, margin-left 0.5s, margin-right 0.5s;
margin-left: 0.5em;
margin-right: 0.5em;
vertical-align: middle;
}
.site-name {
font-size: 2em;
transition: font-size 0.5s;
vertical-align: middle;
text-transform: uppercase;
}
}
}
.page-bar {
display: flex;
align-items: center;
background-color: #800000;
color: #cccccc;
height: 1.75em;
overflow: hidden;
transition: height 0.4s;
.button {
padding-left: 0.5em;
padding-right: 0.5em;
border-right: 1px solid transparentize(#cccccc, 0.75);
&:last-of-type {
border-right: 0;
}
}
}
.search-bar {
display: flex;
flex-direction: column;
background-color: #660000;
overflow: hidden;
height: 1.75em;
justify-content: center;
align-items: flex-end;
transition: height 0.3s;
.input-container {
position: relative;
margin-right: 0.25em;
INPUT {
max-width: 20em;
width: 100%;
padding-left: 1.6em;
}
.fa-search {
position: absolute;
left: 4px;
top: 3px;
font-size: 0.9em;
color: transparentize(#660000, 0.75);
pointer-events: none;
}
}
}
&.collapsed {
.title-bar {
height: 1.75em;
.title {
.fa {
font-size: 1em;
}
.site-name {
font-size: 1em;
}
}
}
.page-bar, .search-bar {
height: 0;
}
}
}
.page-container {
padding-top: 11em;
padding-bottom: 1em;
padding-left: 1em;
padding-right: 1em;
margin-left: 20em;
max-width: 60em;
}

19
src/widgets/side-nav.jsx Normal file
View File

@@ -0,0 +1,19 @@
import React, { PureComponent } from 'react';
import { AsyncComponent } from 'relaks';
class SideNavSync extends PureComponent {
static displayName = 'SideNavSync';
render() {
return (
<div className="side-nav">
Side-Nav
</div>
)
}
}
export {
SideNavSync as default,
SideNavSync,
};

60
src/widgets/top-nav.jsx Normal file
View File

@@ -0,0 +1,60 @@
import React, { PureComponent } from 'react';
import { AsyncComponent } from 'relaks';
class TopBarSync extends PureComponent {
static displayName = 'TopNavSync';
render() {
let className = 'top-nav';
if (this.props.collapsed) {
className += ' collapsed';
}
return (
<div className={className}>
{this.renderTitleBar()}
{this.renderPageLinkBar()}
{this.renderSearchBar()}
</div>
);
}
renderTitleBar() {
return (
<div className="title-bar">
<div className="title">
<i className="fa fa-home" />
<span className="site-name">Romanes eunt domus</span>
</div>
</div>
);
}
renderPageLinkBar() {
return (
<div className="page-bar">
<div className="button">
Hello
</div>
<div className="button">
World
</div>
</div>
);
}
renderSearchBar() {
return (
<div className="search-bar">
<span className="input-container">
<input type="text" value="Hello" />
<i className="fa fa-search" />
</span>
</div>
);
}
}
export {
TopBarSync as default,
TopBarSync,
};

2511
src/wordpress-data-source.js Normal file

File diff suppressed because it is too large Load Diff

56
src/wordpress.js Normal file
View File

@@ -0,0 +1,56 @@
class Wordpress {
/**
* Remember the data source
*/
constructor(dataSource, ssr) {
this.dataSource = dataSource;
this.ssr = ssr;
}
/**
* Fetch one object from data source
*
* @param {String} url
* @param {Object} options
*
* @return {Promise<Object>}
*/
fetchOne(url, options) {
return this.dataSource.fetchOne(url, options);
}
/**
* Fetch a list of objects from data source
*
* @param {String} url
* @param {Object} options
*
* @return {Promise<Array>}
*/
fetchList(url, options) {
if (this.ssr === 'seo') {
options = Object.assign({}, options, { minimum: '100%' });
}
return this.dataSource.fetchList(url, options);
}
/**
* Fetch multiple objects from data source
*
* @param {Array<String>} urls
* @param {Object} options
*
* @return {Promise<Array>}
*/
fetchMultiple(urls, options) {
if (this.ssr === 'seo') {
options = Object.assign({}, options, { minimum: '100%' });
}
return this.dataSource.fetchMultiple(urls, options);
}
}
export {
Wordpress as default,
Wordpress,
};

134
webpack.config.js Normal file
View File

@@ -0,0 +1,134 @@
var FS = require('fs');
var Path = require('path');
var Webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var DefinePlugin = Webpack.DefinePlugin;
var NamedChunksPlugin = Webpack.NamedChunksPlugin;
var NamedModulesPlugin = Webpack.NamedModulesPlugin;
var UglifyJSPlugin = require('uglifyjs-webpack-plugin');
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
var ExtractTextPlugin = require("extract-text-webpack-plugin");
var event = process.env.npm_lifecycle_event;
var clientConfig = {
context: Path.resolve('./src'),
entry: './main',
output: {
path: Path.resolve('./server/www'),
filename: 'app.js',
},
resolve: {
extensions: [ '.js', '.jsx' ],
modules: [ Path.resolve('./src'), Path.resolve('./node_modules') ],
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: [
'env',
'react',
'stage-0',
],
plugins: [
'syntax-async-functions',
'syntax-class-properties',
'transform-regenerator',
'transform-runtime',
]
}
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
use: 'css-loader!sass-loader',
})
},
]
},
plugins: [
new NamedChunksPlugin,
new NamedModulesPlugin,
new BundleAnalyzerPlugin({
analyzerMode: (event === 'build') ? 'static' : 'disabled',
reportFilename: `report.html`,
}),
new HtmlWebpackPlugin({
template: Path.resolve(`./src/index.html`),
filename: Path.resolve(`./server/www/index.html`),
}),
new ExtractTextPlugin("styles.css"),
],
devtool: (event === 'build') ? false : 'inline-source-map',
devServer: {
inline: true,
historyApiFallback: {
rewrites: [
{
from: /.*/,
to: function(context) {
return context.parsedUrl.pathname.replace(/.*\/(.*)$/, '/$1');
}
}
]
}
}
};
var serverConfig = {
context: clientConfig.context,
entry: clientConfig.entry,
target: 'node',
output: {
path: Path.resolve('./server/client'),
filename: 'app.js',
libraryTarget: 'commonjs2',
},
resolve: clientConfig.resolve,
module: clientConfig.module,
plugins: [
new NamedChunksPlugin,
new NamedModulesPlugin,
new HtmlWebpackPlugin({
template: Path.resolve(`./src/index.html`),
filename: Path.resolve(`./server/client/index.html`),
}),
new ExtractTextPlugin('styles.css'),
],
devtool: clientConfig.devtool,
};
var configs = module.exports = clientConfig; //[ clientConfig, serverConfig ];
var constants = {};
if (event === 'build') {
console.log('Optimizing JS code');
configs.forEach((config) => {
// set NODE_ENV to production
var plugins = config.plugins;
var constants = {
'process.env.NODE_ENV': '"production"',
};
plugins.unshift(new DefinePlugin(constants));
// use Uglify to remove dead-code
plugins.unshift(new UglifyJSPlugin({
uglifyOptions: {
compress: {
drop_console: true,
}
}
}));
})
}
// copy webpack.resolve.js into webpack.debug.js to resolve Babel presets
// and plugins to absolute paths, required when linked modules are used
if (FS.existsSync('./webpack.debug.js')) {
configs.map(require('./webpack.debug.js'));
}

30
webpack.resolve.js Normal file
View File

@@ -0,0 +1,30 @@
var Path = require('path');
module.exports = function(config) {
config.module.rules.forEach((rule) => {
if (rule.loader === 'babel-loader' && rule.query) {
if (rule.query.presets) {
rule.query.presets = rule.query.presets.map((preset) => {
return resolve('preset', preset);
})
}
if (rule.query.plugins) {
rule.query.plugins = rule.query.plugins.map((plugin) => {
return resolve('plugin', plugin);
})
}
}
})
};
function resolve(type, module) {
if (module instanceof Array) {
module[0] = resolve(type, module[0]);
return module;
} else {
if (!/^[\w\-]+$/.test(module)) {
return module;
}
return Path.resolve(`./node_modules/babel-${type}-${module}`);
}
}