tabs -> spaces

This commit is contained in:
Jason Miller
2018-03-29 15:43:14 -04:00
parent 5e6500d196
commit 76ceea0e52
19 changed files with 503 additions and 471 deletions

View File

@@ -3,27 +3,27 @@ let componentPath = require.resolve('./async-component');
module.exports = function () { }; module.exports = function () { };
module.exports.pitch = function (remainingRequest) { module.exports.pitch = function (remainingRequest) {
this.cacheable && this.cacheable(); this.cacheable && this.cacheable();
let query = loaderUtils.getOptions(this) || {}; let query = loaderUtils.getOptions(this) || {};
let routeName = typeof query.name === 'function' ? query.name(this.resourcePath) : null; let routeName = typeof query.name === 'function' ? query.name(this.resourcePath) : null;
let name; let name;
if (routeName !== null) { if (routeName !== null) {
name = routeName; name = routeName;
} }
else if ('name' in query) { else if ('name' in query) {
name = query.name; name = query.name;
} }
else if ('formatName' in query) { else if ('formatName' in query) {
name = query.formatName(this.resourcePath); name = query.formatName(this.resourcePath);
} }
return ` return `
import async from ${JSON.stringify(componentPath)}; import async from ${JSON.stringify(componentPath)};
function load(cb) { function load(cb) {
require.ensure([], function (require) { require.ensure([], function (require) {
cb( require(${loaderUtils.stringifyRequest(this, '!!' + remainingRequest)}) ); cb( require(${loaderUtils.stringifyRequest(this, '!!' + remainingRequest)}) );
}${name ? (', ' + JSON.stringify(name)) : ''}); }${name ? (', ' + JSON.stringify(name)) : ''});
} }
export default async(load); export default async(load);
`; `;
}; };

View File

@@ -1,30 +1,30 @@
import { h, Component } from 'preact'; import { h, Component } from 'preact';
export default function (req) { export default function (req) {
function Async() { function Async() {
Component.call(this); Component.call(this);
let b, old; let b, old;
this.componentWillMount = () => { this.componentWillMount = () => {
b = this.base = this.nextBase || this.__b; // short circuits 1st render b = this.base = this.nextBase || this.__b; // short circuits 1st render
req(m => { req(m => {
this.setState({ child: m.default || m }); this.setState({ child: m.default || m });
}); });
}; };
this.shouldComponentUpdate = (_, nxt) => { this.shouldComponentUpdate = (_, nxt) => {
nxt = nxt.child === void 0; nxt = nxt.child === void 0;
if (nxt && old === void 0 && !!b) { if (nxt && old === void 0 && !!b) {
old = h(b.nodeName, { dangerouslySetInnerHTML: { __html: b.innerHTML } }); old = h(b.nodeName, { dangerouslySetInnerHTML: { __html: b.innerHTML } });
} }
else { else {
old = ''; // dump it old = ''; // dump it
} }
return !nxt; return !nxt;
}; };
this.render = (p, s) => s.child ? h(s.child, p) : old; this.render = (p, s) => s.child ? h(s.child, p) : old;
} }
(Async.prototype = new Component()).constructor = Async; (Async.prototype = new Component()).constructor = Async;
return Async; return Async;
} }

View File

@@ -1,27 +0,0 @@
import { h, render } from 'preact';
if (process.env.NODE_ENV === 'development') {
// enable preact devtools
require('preact/debug');
}
else if (process.env.ADD_SW && 'serviceWorker' in navigator && location.protocol === 'https:') {
// eslint-disable-next-line no-undef
navigator.serviceWorker.register(__webpack_public_path__ + 'sw.js');
}
const interopDefault = m => m && m.default ? m.default : m;
let app = interopDefault(require('app-entry-point'));
if (typeof app === 'function') {
let root = document.getElementById('app') || document.body.firstElementChild;
let init = () => {
let app = interopDefault(require('app-entry-point'));
root = render(h(app), document.body, root);
};
if (module.hot) module.hot.accept('app-entry-point', init);
init();
}

View File

@@ -0,0 +1,64 @@
const path = require('path');
const vm = require('vm');
module.exports = function (content) {
const jsdom = require('jsdom');
const preact = require('preact');
const renderToString = require('preact-render-to-string');
this.cacheable && this.cacheable();
const callback = this.async();
// const dom = new jsdom.JSDOM(`<!DOCTYPE html><html><head></head><body></body></html>`, {
const dom = new jsdom.JSDOM(content, {
includeNodeLocations: false,
runScripts: 'outside-only'
});
const { window } = dom;
const { document } = window;
// console.log(content);
const root = document.getElementById('app');
this.loadModule(path.join(__dirname, 'client-boot.js'), (err, source) => {
if (err) return callback(err);
console.log(source);
let mod = eval(source);
let props = {};
// console.log(mod);
let vnode = preact.createElement(mod, props);
let frag = document.createElement('div');
frag.innerHTML = renderToString(vnode);
root.parentNode.replaceChild(frag.firstChild, root);
let html = dom.serialize();
callback(null, html);
// return html = `module.exports = ${JSON.stringify(html)}`;
// return 'module.exports = ' + JSON.stringify(content).replace(/\{\{PRERENDER\}\}/gi, `" + require("preact-render-to-string")(require("app-entry-point")) + "`);
});
// global.window = global;
// global.document = {};
// return 'module.exports = ' + JSON.stringify(content).replace(/\{\{PRERENDER\}\}/gi, `" + require("preact-render-to-string")(require("app-entry-point")) + "`);
/*
let callback = this.async();
let parts = content.split(/\{\{prerender\}\}/gi);
if (parts.length<2) {
// callback(null, `module.exports = ${JSON.stringify(content)}`);
callback(null, content);
return;
}
// let html = `
// window = {};
// module.exports = ${JSON.stringify(parts[0])} + require("preact-render-to-string")(require("app-entry-point")) + ${JSON.stringify(parts[1])}`;
let html = `module.exports = ${JSON.stringify(parts[0])} + require("preact-render-to-string")(require("app-entry-point")) + ${JSON.stringify(parts[1])}`;
callback(null, html);
*/
};

View File

@@ -5,16 +5,16 @@ let renderToString = require('preact-render-to-string');
let appPath = path.join(__dirname, '../src/index'); let appPath = path.join(__dirname, '../src/index');
module.exports = function(options) { module.exports = function(options) {
options = options || {}; options = options || {};
let url = typeof options==='string' ? options : options.url; let url = typeof options==='string' ? options : options.url;
global.history = {}; global.history = {};
global.location = { href: url, pathname: url }; global.location = { href: url, pathname: url };
// let app = require('app-entry-point'); // let app = require('app-entry-point');
let app = require(appPath); let app = require(appPath);
let html = renderToString(preact.h(app, { url })); let html = renderToString(preact.h(app, { url }));
console.log(html); console.log(html);
return html; return html;
}; };

View File

@@ -8,23 +8,23 @@ const fs = require('fs');
* https://github.com/Jimdo/typings-for-css-modules-loader/issues/48#issuecomment-347036461 * https://github.com/Jimdo/typings-for-css-modules-loader/issues/48#issuecomment-347036461
*/ */
module.exports = class WatchTimestampsPlugin { module.exports = class WatchTimestampsPlugin {
constructor(patterns) { constructor(patterns) {
this.patterns = patterns; this.patterns = patterns;
} }
apply(compiler) { apply(compiler) {
compiler.plugin('watch-run', (watch, callback) => { compiler.plugin('watch-run', (watch, callback) => {
const patterns = this.patterns; const patterns = this.patterns;
const timestamps = watch.fileTimestamps; const timestamps = watch.fileTimestamps;
for (const filepath of timestamps) { for (const filepath of timestamps) {
if (patterns.some(pat => pat instanceof RegExp ? pat.test(filepath) : filepath.indexOf(pat) === 0)) { if (patterns.some(pat => pat instanceof RegExp ? pat.test(filepath) : filepath.indexOf(pat) === 0)) {
let time = fs.statSync(filepath).mtime; let time = fs.statSync(filepath).mtime;
if (timestamps instanceof Map) timestamps.set(filepath, time); if (timestamps instanceof Map) timestamps.set(filepath, time);
else timestamps[filepath] = time; else timestamps[filepath] = time;
} }
} }
callback(); callback();
}); });
} }
}; };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,21 +1,25 @@
@import '~style/helpers.scss'; @import '~style/helpers.scss';
.app { .app {
position: absolute; position: absolute;
top: $toolbar-height; display: flex;
left: 0; flex-direction: column;
width: 100%; top: 0;
bottom: 0; left: 0;
contain: size layout style; width: 100%;
overflow: auto; bottom: 0;
-webkit-overflow-scrolling: touch; overflow: hidden;
z-index: 1;
> .content { .header {
position: absolute; flex: 0 0 auto;
left: 0; position: relative;
top: 0; }
width: 100%;
height: 100%; .content {
overflow: visible; flex: 1 1 auto;
} contain: size layout style;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
} }

View File

@@ -1,2 +1,3 @@
export const app: string; export const app: string;
export const header: string;
export const content: string; export const content: string;

View File

@@ -1,29 +1,29 @@
@import '~style/helpers.scss'; @import '~style/helpers.scss';
:global { :global {
// @import '~preact-material-components/Drawer/style.css'; // @import '~preact-material-components/Drawer/style.css';
@import '~preact-material-components/List/mdc-list.scss'; @import '~preact-material-components/List/mdc-list.scss';
} }
.drawer { .drawer {
:global(.mdc-list-item__start-detail) { :global(.mdc-list-item__start-detail) {
margin-right: 16px; margin-right: 16px;
} }
} }
.logo { .logo {
width: 50%; width: 50%;
} }
.category img { .category img {
opacity: .6; opacity: .6;
} }
.bottom { .bottom {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
bottom: constant(safe-area-inset-bottom); bottom: constant(safe-area-inset-bottom);
bottom: env(safe-area-inset-bottom); bottom: env(safe-area-inset-bottom);
left: 0; left: 0;
width: 100%; width: 100%;
} }

View File

@@ -1,18 +1,18 @@
@import '~style/helpers.scss'; @import '~style/helpers.scss';
:global { :global {
@import '~preact-material-components/Fab/mdc-fab.scss'; @import '~preact-material-components/Fab/mdc-fab.scss';
} }
.fab { .fab {
position: fixed; position: fixed;
right: 14px; right: 14px;
bottom: 14px; bottom: 14px;
z-index: 4; z-index: 4;
.progress { .progress {
width: 24px; width: 24px;
height: 24px; height: 24px;
color: white; color: white;
--mdc-theme-primary: #fff; --mdc-theme-primary: #fff;
} }
} }

View File

@@ -1,51 +1,52 @@
@import '~style/helpers.scss'; @import '~style/helpers.scss';
:global { :global {
@import '~preact-material-components/Toolbar/mdc-toolbar.scss'; @import '~preact-material-components/Toolbar/mdc-toolbar.scss';
} }
.toolbar { .toolbar {
height: $toolbar-height; // height: $toolbar-height;
&.minimal { &.minimal {
height: $toolbar-height / 2; display: none;
} // height: $toolbar-height / 2;
}
// > * { // > * {
// min-height: 0; // min-height: 0;
// } // }
} }
.fileInput { .fileInput {
position: absolute; position: absolute;
left: 0; left: 0;
top: -999px; top: -999px;
} }
.fab { .fab {
position: fixed; position: fixed;
display: block; display: block;
right: 14px; right: 14px;
bottom: 14px; bottom: 14px;
// z-index: 999; // z-index: 999;
// transform: translateZ(0); // transform: translateZ(0);
} }
.logo { .logo {
height: 1em; height: 1em;
} }
.menu { .menu {
position: absolute; position: absolute;
top: $toolbar-height; top: $toolbar-height;
right: 5px; right: 5px;
.menuItem { .menuItem {
margin-right: 16px; margin-right: 16px;
} }
} }
.title { .title {
padding: 3px 0 0; padding: 3px 0 0;
font-weight: 300; font-weight: 300;
font-size: 140%; font-size: 140%;
} }

View File

@@ -1,20 +1,20 @@
@import '~style/helpers.scss'; @import '~style/helpers.scss';
// :global { // :global {
// @import '~preact-material-components/Button/mdc-button.scss'; // @import '~preact-material-components/Button/mdc-button.scss';
// // @import '~preact-material-components/Switch/mdc-switch.scss'; // // @import '~preact-material-components/Switch/mdc-switch.scss';
// } // }
.home { .home {
padding: 20px; padding: 20px;
opacity: 0; opacity: 0;
} }
.active { .active {
animation: fadeIn 2s forwards ease 1; animation: fadeIn 2s forwards ease 1;
} }
@keyframes fadeIn { @keyframes fadeIn {
from { opacity: 0; } from { opacity: 0; }
to { opacity: 1; } to { opacity: 1; }
} }

View File

@@ -6,23 +6,11 @@
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#673ab8">
<link rel="manifest" href="/manifest.json"> <link rel="manifest" href="/manifest.json">
<!--
<% for (var chunk of webpack.chunks) { %>
<% for (var file of chunk.files) { %>
<% if (htmlWebpackPlugin.options.preload && file.match(/\.(js|css)$/)) { %>
<link rel="preload" href="<%= htmlWebpackPlugin.files.publicPath + file %>" as="<%= file.match(/\.css$/)?'style':'script' %>">
<% } else if (file.match(/manifest\.json$/)) { %>
<link rel="manifest" href="<%= htmlWebpackPlugin.files.publicPath + file %>">
<% } %>
<% } %>
<% } %>
-->
</head> </head>
<body> <body>
<%= <div id="app" prerender></div>
/*require('../config/prerender')()*/ <!-- <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" /> -->
htmlWebpackPlugin.options.prerender()
%>
</body> </body>
</html> </html>

View File

@@ -1,26 +1,26 @@
import { options } from 'preact'; import { options } from 'preact';
const classNameDescriptor = { const classNameDescriptor = {
enumerable: false, enumerable: false,
configurable: true, configurable: true,
get() { get() {
return this.class; return this.class;
}, },
set(value) { set(value) {
this.class = value; this.class = value;
} }
}; };
let old = options.vnode; let old = options.vnode;
options.vnode = vnode => { options.vnode = vnode => {
let a = vnode.attributes; let a = vnode.attributes;
if (a!=null) { if (a != null) {
if ('className' in a) { if ('className' in a) {
a.class = a.className; a.class = a.className;
} }
if ('class' in a) { if ('class' in a) {
Object.defineProperty(a, 'className', classNameDescriptor); Object.defineProperty(a, 'className', classNameDescriptor);
} }
} }
if (old != null) old(vnode); if (old != null) old(vnode);
}; };

View File

@@ -1,26 +1,27 @@
// @import './material-icons.scss'; // @import './material-icons.scss';
// @import 'material-components-web/material-components-web'; // @import 'material-components-web/material-components-web';
@import './reset.scss'; @import './reset.scss';
@import url('https://fonts.googleapis.com/icon?family=Material+Icons');
html, body { html, body {
height: 100%; height: 100%;
width: 100%; width: 100%;
padding: 0; padding: 0;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
overscroll-behavior: none; overscroll-behavior: none;
} }
html { html {
background: #FAFAFA; background: #FAFAFA;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 400; font-weight: 400;
color: #444; color: #444;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.mdc-theme--dark { .mdc-theme--dark {
background-color: #333; background-color: #333;
color: #fff; color: #fff;
} }

View File

@@ -1,28 +1,28 @@
@font-face { @font-face {
font-family: 'Material Icons'; font-family: 'Material Icons';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: local('Material Icons'), src: local('Material Icons'),
local('MaterialIcons-Regular'), local('MaterialIcons-Regular'),
url(https://example.com/MaterialIcons-Regular.woff2) format('woff2'), url(https://example.com/MaterialIcons-Regular.woff2) format('woff2'),
url(https://example.com/MaterialIcons-Regular.woff) format('woff'), url(https://example.com/MaterialIcons-Regular.woff) format('woff'),
url(https://example.com/MaterialIcons-Regular.ttf) format('truetype'); url(https://example.com/MaterialIcons-Regular.ttf) format('truetype');
} }
.material-icons { .material-icons {
font-family: 'Material Icons'; font-family: 'Material Icons';
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
font-size: 24px; font-size: 24px;
display: inline-block; display: inline-block;
line-height: 1; line-height: 1;
text-transform: none; text-transform: none;
letter-spacing: normal; letter-spacing: normal;
word-wrap: normal; word-wrap: normal;
white-space: nowrap; white-space: nowrap;
direction: ltr; direction: ltr;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
font-feature-settings: 'liga'; font-feature-settings: 'liga';
} }

View File

@@ -1,12 +1,12 @@
button, a, img, input, select, textarea { button, a, img, input, select, textarea {
-webkit-tap-highlight-color: rgba(0,0,0,0); -webkit-tap-highlight-color: rgba(0,0,0,0);
} }
a, button, img, [inert], .inert { a, button, img, [inert], .inert {
user-select: none; user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
user-drag: none; user-drag: none;
-webkit-user-drag: none; -webkit-user-drag: none;
touch-callout: none; touch-callout: none;
-webkit-touch-callout: none; -webkit-touch-callout: none;
} }

View File

@@ -10,114 +10,114 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const PreloadWebpackPlugin = require('preload-webpack-plugin'); const PreloadWebpackPlugin = require('preload-webpack-plugin');
const ReplacePlugin = require('webpack-plugin-replace'); const ReplacePlugin = require('webpack-plugin-replace');
const CopyPlugin = require('copy-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');
const WatchTimestampsPlugin = require('./config/watch-timestamps-plugin'); const WatchTimestampsPlugin = require('./config/watch-timestamps-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
function readJson(filename) { function readJson(filename) {
return JSON.parse(fs.readFileSync(filename)); return JSON.parse(fs.readFileSync(filename));
} }
module.exports = function(_, env) { module.exports = function(_, env) {
const isProd = env.mode === 'production'; const isProd = env.mode === 'production';
const nodeModules = path.join(__dirname, 'node_modules'); const nodeModules = path.join(__dirname, 'node_modules');
const componentStyleDirs = [ const componentStyleDirs = [
path.join(__dirname, 'src/components'), path.join(__dirname, 'src/components'),
path.join(__dirname, 'src/routes') path.join(__dirname, 'src/routes')
]; ];
return { return {
mode: isProd ? 'production' : 'development', mode: isProd ? 'production' : 'development',
entry: path.join(__dirname, 'config/client-boot.js'), entry: './src/index',
output: { output: {
filename: isProd ? '[name].[chunkhash:5].js' : '[name].js', filename: isProd ? '[name].[chunkhash:5].js' : '[name].js',
chunkFilename: '[name].chunk.[chunkhash:5].js', chunkFilename: '[name].chunk.[chunkhash:5].js',
path: path.join(__dirname, 'build'), path: path.join(__dirname, 'build'),
publicPath: '/' publicPath: '/'
}, },
resolve: { resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.scss', '.css'], extensions: ['.ts', '.tsx', '.js', '.jsx', '.scss', '.css'],
alias: { alias: {
'app-entry-point': path.join(__dirname, 'src/index'), style: path.join(__dirname, 'src/style')
style: path.join(__dirname, 'src/style') }
} },
}, resolveLoader: {
resolveLoader: { alias: {
alias: { // async-component-loader returns a wrapper component that waits for the import to load before rendering:
// async-component-loader returns a wrapper component that waits for the import to load before rendering: async: path.join(__dirname, 'config/async-component-loader')
async: path.join(__dirname, 'config/async-component-loader') }
} },
}, module: {
module: { rules: [
rules: [ {
{ test: /\.tsx?$/,
test: /\.tsx?$/, // Ensure typescript is compiled prior to Babel running:
// Ensure typescript is compiled prior to Babel running: enforce: 'pre',
enforce: 'pre', loader: 'ts-loader',
loader: 'ts-loader', // Don't transpile anything in node_modules:
// Don't transpile anything in node_modules: exclude: nodeModules,
exclude: nodeModules,
options: { options: {
// Offload type checking to ForkTsCheckerWebpackPlugin for better performance: // Offload type checking to ForkTsCheckerWebpackPlugin for better performance:
transpileOnly: true transpileOnly: true
} }
}, },
{ {
test: /\.(tsx?|jsx?)$/, test: /\.(tsx?|jsx?)$/,
loader: 'babel-loader', loader: 'babel-loader',
// Don't respect any Babel RC files found on the filesystem: // Don't respect any Babel RC files found on the filesystem:
options: Object.assign(readJson('.babelrc'), { babelrc: false }) options: Object.assign(readJson('.babelrc'), { babelrc: false })
}, },
{ {
test: /\.(scss|sass)$/, test: /\.(scss|sass)$/,
loader: 'sass-loader', loader: 'sass-loader',
// SCSS gets preprocessed, then treated like any other CSS: // SCSS gets preprocessed, then treated like any other CSS:
enforce: 'pre', enforce: 'pre',
options: { options: {
sourceMap: true, sourceMap: true,
includePaths: [nodeModules] includePaths: [nodeModules]
} }
}, },
{ {
test: /\.(scss|sass|css)$/, test: /\.(scss|sass|css)$/,
// Only enable CSS Modules within `src/{components,routes}/*` // Only enable CSS Modules within `src/{components,routes}/*`
include: componentStyleDirs, include: componentStyleDirs,
use: [ use: [
// In production, CSS is extracted to files on disk. In development, it's inlined into JS: // In production, CSS is extracted to files on disk. In development, it's inlined into JS:
isProd ? MiniCssExtractPlugin.loader : 'style-loader', isProd ? MiniCssExtractPlugin.loader : 'style-loader',
{ {
// This is a fork of css-loader that auto-generates .d.ts files for CSS module imports. // This is a fork of css-loader that auto-generates .d.ts files for CSS module imports.
// The result is a definition file with the exported String classname mappings. // The result is a definition file with the exported String classname mappings.
loader: 'typings-for-css-modules-loader', loader: 'typings-for-css-modules-loader',
options: { options: {
modules: true, modules: true,
localIdentName: '[local]__[hash:base64:5]', localIdentName: '[local]__[hash:base64:5]',
namedExport: true, namedExport: true,
camelCase: true, camelCase: true,
importLoaders: 1, importLoaders: 1,
sourceMap: isProd, sourceMap: isProd,
sass: true sass: true
} }
} }
] ]
}, },
{ {
test: /\.(scss|sass|css)$/, test: /\.(scss|sass|css)$/,
// Process non-modular CSS everywhere *except* `src/{components,routes}/*` // Process non-modular CSS everywhere *except* `src/{components,routes}/*`
exclude: componentStyleDirs, exclude: componentStyleDirs,
use: [ use: [
isProd ? MiniCssExtractPlugin.loader : 'style-loader', isProd ? MiniCssExtractPlugin.loader : 'style-loader',
{ {
loader: 'css-loader', loader: 'css-loader',
options: { options: {
importLoaders: 1, importLoaders: 1,
sourceMap: isProd sourceMap: isProd
} }
} }
] ]
} }
] ]
}, },
plugins: [ plugins: [
// Runs tslint & type checking in a worker pool // Runs tslint & type checking in a worker pool
new ForkTsCheckerWebpackPlugin({ new ForkTsCheckerWebpackPlugin({
tslint: true, tslint: true,
@@ -127,140 +127,140 @@ module.exports = function(_, env) {
}), }),
new ForkTsCheckerNotifierWebpackPlugin({ excludeWarnings: true }), new ForkTsCheckerNotifierWebpackPlugin({ excludeWarnings: true }),
// Pretty progressbar showing build progress: // Pretty progressbar showing build progress:
new ProgressBarPlugin({ new ProgressBarPlugin({
format: '\u001b[90m\u001b[44mBuild\u001b[49m\u001b[39m [:bar] \u001b[32m\u001b[1m:percent\u001b[22m\u001b[39m (:elapseds) \u001b[2m:msg\u001b[22m', format: '\u001b[90m\u001b[44mBuild\u001b[49m\u001b[39m [:bar] \u001b[32m\u001b[1m:percent\u001b[22m\u001b[39m (:elapseds) \u001b[2m:msg\u001b[22m',
renderThrottle: 100, renderThrottle: 100,
summary: false, summary: false,
clear: true clear: true
}), }),
// Remove old files before outputting a production build: // Remove old files before outputting a production build:
isProd && new CleanWebpackPlugin([ isProd && new CleanWebpackPlugin([
'assets', 'assets',
'**/*.{css,js,json,html}' '**/*.{css,js,json,html}'
], { ], {
root: path.join(__dirname, 'build'), root: path.join(__dirname, 'build'),
beforeEmit: true beforeEmit: true
}), }),
// Automatically split code into async chunks. // Automatically split code into async chunks.
// See: https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366 // See: https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
isProd && new webpack.optimize.SplitChunksPlugin({}), isProd && new webpack.optimize.SplitChunksPlugin({}),
// In production, extract all CSS to produce files on disk, even for // In production, extract all CSS to produce files on disk, even for
// lazy-loaded CSS chunks. CSS for async chunks is loaded on-demand. // lazy-loaded CSS chunks. CSS for async chunks is loaded on-demand.
// This is a modern Webpack 4 replacement for ExtractTextPlugin. // This is a modern Webpack 4 replacement for ExtractTextPlugin.
// See: https://github.com/webpack-contrib/mini-css-extract-plugin // See: https://github.com/webpack-contrib/mini-css-extract-plugin
// See also: https://twitter.com/wsokra/status/970253245733113856 // See also: https://twitter.com/wsokra/status/970253245733113856
isProd && new MiniCssExtractPlugin({ isProd && new MiniCssExtractPlugin({
chunkFilename: '[name].chunk.[contenthash:5].css' chunkFilename: '[name].chunk.[contenthash:5].css'
}), }),
// These plugins fix infinite loop in typings-for-css-modules-loader. // These plugins fix infinite loop in typings-for-css-modules-loader.
// See: https://github.com/Jimdo/typings-for-css-modules-loader/issues/35 // See: https://github.com/Jimdo/typings-for-css-modules-loader/issues/35
new webpack.WatchIgnorePlugin([ new webpack.WatchIgnorePlugin([
/(c|sc|sa)ss\.d\.ts$/ /(c|sc|sa)ss\.d\.ts$/
]), ]),
new WatchTimestampsPlugin([ new WatchTimestampsPlugin([
/(c|sc|sa)ss\.d\.ts$/ /(c|sc|sa)ss\.d\.ts$/
]), ]),
// For now we're not doing SSR. // For now we're not doing SSR.
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: path.join(__dirname, 'build/index.html'), filename: path.join(__dirname, 'build/index.html'),
template: '!!ejs-loader!src/index.html', template: '!!ejs-loader!src/index.html',
// template: '!!'+path.join(__dirname, 'config/prerender-loader')+'!src/index.html', // template: '!!'+path.join(__dirname, 'config/prerender-loader')+'!src/index.html',
minify: isProd && { minify: isProd && {
collapseWhitespace: true, collapseWhitespace: true,
removeScriptTypeAttributes: true, removeScriptTypeAttributes: true,
removeRedundantAttributes: true, removeRedundantAttributes: true,
removeStyleLinkTypeAttributes: true, removeStyleLinkTypeAttributes: true,
removeComments: true removeComments: true
}, },
manifest: readJson('./src/manifest.json'), manifest: readJson('./src/manifest.json'),
inject: true, inject: true,
compile: true compile: true
}), }),
// Inject <link rel="preload"> for resources // Inject <link rel="preload"> for resources
isProd && new PreloadWebpackPlugin(), isProd && new PreloadWebpackPlugin(),
// Inline constants during build, so they can be folded by UglifyJS. // Inline constants during build, so they can be folded by UglifyJS.
new webpack.DefinePlugin({ new webpack.DefinePlugin({
// We set node.process=false later in this config. // We set node.process=false later in this config.
// Here we make sure if (process && process.foo) still works: // Here we make sure if (process && process.foo) still works:
process: '{}' process: '{}'
}), }),
// Babel embeds helpful error messages into transpiled classes that we don't need in production. // Babel embeds helpful error messages into transpiled classes that we don't need in production.
// Here we replace the constructor and message with a static throw, leaving the message to be DCE'd. // Here we replace the constructor and message with a static throw, leaving the message to be DCE'd.
// This is useful since it shows the message in SourceMapped code when debugging. // This is useful since it shows the message in SourceMapped code when debugging.
isProd && new ReplacePlugin({ isProd && new ReplacePlugin({
include: /babel-helper$/, include: /babel-helper$/,
patterns: [{ patterns: [{
regex: /throw\s+(?:new\s+)?((?:Type|Reference)?Error)\s*\(/g, regex: /throw\s+(?:new\s+)?((?:Type|Reference)?Error)\s*\(/g,
value: (s, type) => `throw 'babel error'; (` value: (s, type) => `throw 'babel error'; (`
}] }]
}), }),
// Copying files via Webpack allows them to be served dynamically by `webpack serve` // Copying files via Webpack allows them to be served dynamically by `webpack serve`
new CopyPlugin([ new CopyPlugin([
{ from: 'src/manifest.json', to: 'manifest.json' }, { from: 'src/manifest.json', to: 'manifest.json' },
{ from: 'src/assets', to: 'assets' } { from: 'src/assets', to: 'assets' }
]), ]),
// For production builds, output module size analysis to build/report.html // For production builds, output module size analysis to build/report.html
isProd && new BundleAnalyzerPlugin({ isProd && new BundleAnalyzerPlugin({
analyzerMode: 'static', analyzerMode: 'static',
defaultSizes: 'gzip', defaultSizes: 'gzip',
openAnalyzer: false openAnalyzer: false
}), }),
// Generate a ServiceWorker using Workbox. // Generate a ServiceWorker using Workbox.
isProd && new WorkboxPlugin.GenerateSW({ isProd && new WorkboxPlugin.GenerateSW({
swDest: 'sw.js', swDest: 'sw.js',
clientsClaim: true, clientsClaim: true,
skipWaiting: true, skipWaiting: true,
// allow for offline client-side routing: // allow for offline client-side routing:
navigateFallback: '/', navigateFallback: '/',
navigateFallbackBlacklist: [/\.[a-z0-9]+$/i] navigateFallbackBlacklist: [/\.[a-z0-9]+$/i]
}) })
].filter(Boolean), // Filter out any falsey plugin array entries. ].filter(Boolean), // Filter out any falsey plugin array entries.
// Turn off various NodeJS environment polyfills Webpack adds to bundles. // Turn off various NodeJS environment polyfills Webpack adds to bundles.
// They're supposed to be added only when used, but the heuristic is loose // They're supposed to be added only when used, but the heuristic is loose
// (eg: existence of a variable called setImmedaite in any scope) // (eg: existence of a variable called setImmedaite in any scope)
node: { node: {
console: false, console: false,
// Keep global, it's just an alias of window and used by many third party modules: // Keep global, it's just an alias of window and used by many third party modules:
global: true, global: true,
// Turn off process to avoid bundling a nextTick implementation: // Turn off process to avoid bundling a nextTick implementation:
process: false, process: false,
// Inline __filename and __dirname values: // Inline __filename and __dirname values:
__filename: 'mock', __filename: 'mock',
__dirname: 'mock', __dirname: 'mock',
// Never embed a portable implementation of Node's Buffer module: // Never embed a portable implementation of Node's Buffer module:
Buffer: false, Buffer: false,
// Never embed a setImmediate implementation: // Never embed a setImmediate implementation:
setImmediate: false setImmediate: false
}, },
devServer: { devServer: {
// Any unmatched request paths will serve static files from src/*: // Any unmatched request paths will serve static files from src/*:
contentBase: path.join(__dirname, 'src'), contentBase: path.join(__dirname, 'src'),
inline: true, inline: true,
hot: true, hot: true,
// Request paths not ending in a file extension serve index.html: // Request paths not ending in a file extension serve index.html:
historyApiFallback: true, historyApiFallback: true,
// Don't output server address info to console on startup: // Don't output server address info to console on startup:
noInfo: true, noInfo: true,
// Suppress forwarding of Webpack logs to the browser console: // Suppress forwarding of Webpack logs to the browser console:
clientLogLevel: 'none', clientLogLevel: 'none',
// Supress the extensive stats normally printed after a dev build (since sizes are mostly useless): // Supress the extensive stats normally printed after a dev build (since sizes are mostly useless):
stats: 'minimal', stats: 'minimal',
// Don't embed an error overlay ("redbox") into the client bundle: // Don't embed an error overlay ("redbox") into the client bundle:
overlay: false overlay: false
} }
}; };
}; };