Adds an app skeleton. Still needs some work.

This commit is contained in:
Jason Miller
2018-03-12 23:03:31 -04:00
parent 619080f85a
commit e691ec9580
28 changed files with 1826 additions and 917 deletions

33
.babelrc Normal file
View File

@@ -0,0 +1,33 @@
{
"presets": [
[
"env",
{
"loose": true,
"uglify": true,
"modules": "commonjs",
"targets": {
"browsers": "last 2 versions"
},
"exclude": [
"transform-regenerator",
"transform-es2015-typeof-symbol"
]
}
]
],
"plugins": [
"syntax-dynamic-import",
"transform-decorators-legacy",
"transform-class-properties",
"transform-object-rest-spread",
"transform-react-constant-elements",
"transform-react-remove-prop-types",
[
"transform-react-jsx",
{
"pragma": "h"
}
]
]
}

View File

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

30
config/async-component.js Normal file
View File

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

27
config/client-boot.js Normal file
View File

@@ -0,0 +1,27 @@
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();
}

20
config/prerender.js Normal file
View File

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

View File

@@ -0,0 +1,23 @@
let fs = require('fs');
module.exports = WatchTimestampsPlugin;
function WatchTimestampsPlugin(patterns) {
this.patterns = patterns;
}
WatchTimestampsPlugin.prototype.apply = function (compiler) {
compiler.plugin('watch-run', (watch, callback) => {
let patterns = this.patterns;
let timestamps = watch.fileTimestamps || watch.compiler.fileTimestamps;
Object.keys(timestamps).forEach( filepath => {
if (patterns.some( pat => pat instanceof RegExp ? pat.test(filepath) : filepath.indexOf(pat)===0 )) {
let time = fs.statSync(filepath).mtime;
if (timestamps instanceof Map) timestamps.set(filepath, time);
else timestamps[filepath] = time;
}
});
callback();
});
};

0
global.d.ts vendored Normal file
View File

1970
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"version": "0.0.0", "version": "0.0.0",
"license": "apache-2.0", "license": "apache-2.0",
"scripts": { "scripts": {
"start": "webpack-dev-server --hot", "start": "webpack serve --hot --inline",
"build": "webpack -p", "build": "webpack -p",
"lint": "eslint src" "lint": "eslint src"
}, },
@@ -18,6 +18,7 @@
"build/*" "build/*"
], ],
"devDependencies": { "devDependencies": {
"@types/node": "^9.4.7",
"awesome-typescript-loader": "^4.0.1", "awesome-typescript-loader": "^4.0.1",
"babel-loader": "^7.1.4", "babel-loader": "^7.1.4",
"babel-plugin-jsx-pragmatic": "^1.0.2", "babel-plugin-jsx-pragmatic": "^1.0.2",
@@ -29,25 +30,39 @@
"babel-plugin-transform-react-jsx": "^6.24.1", "babel-plugin-transform-react-jsx": "^6.24.1",
"babel-plugin-transform-react-remove-prop-types": "^0.4.13", "babel-plugin-transform-react-remove-prop-types": "^0.4.13",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.6.1",
"babel-register": "^6.26.0",
"copy-webpack-plugin": "^4.5.1",
"css-loader": "^0.28.10", "css-loader": "^0.28.10",
"ejs-loader": "^0.3.1",
"eslint": "^4.18.2", "eslint": "^4.18.2",
"eslint-config-developit": "^1.1.1", "eslint-config-developit": "^1.1.1",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"html-webpack-plugin": "^3.0.6", "html-webpack-plugin": "^3.0.6",
"if-env": "^1.0.4", "if-env": "^1.0.4",
"mini-css-extract-plugin": "^0.2.0", "mini-css-extract-plugin": "^0.2.0",
"node-sass": "^4.7.2", "node-sass": "^4.7.2",
"preact-render-to-string": "^3.7.0",
"preload-webpack-plugin": "github:GoogleChromeLabs/preload-webpack-plugin",
"sass-loader": "^6.0.7", "sass-loader": "^6.0.7",
"script-ext-html-webpack-plugin": "^2.0.0",
"style-loader": "^0.20.3",
"typescript": "^2.7.2",
"typescript-loader": "^1.1.3", "typescript-loader": "^1.1.3",
"typings-for-css-modules-loader": "^1.7.0",
"webpack": "^4.1.1", "webpack": "^4.1.1",
"webpack-cli": "^2.0.10", "webpack-cli": "^2.0.11",
"webpack-dev-server": "^3.1.0" "webpack-dev-server": "^3.1.1",
"webpack-plugin-replace": "^1.1.1"
}, },
"dependencies": { "dependencies": {
"material-components-web": "^0.31.0", "classnames": "^2.2.5",
"material-components-web": "^0.32.0",
"material-radial-progress": "git+https://gist.github.com/02134901c77c5309924bfcf8b4435ebe.git", "material-radial-progress": "git+https://gist.github.com/02134901c77c5309924bfcf8b4435ebe.git",
"preact": "^8.2.7", "preact": "^8.2.7",
"preact-i18n": "^1.2.0", "preact-i18n": "^1.2.0",
"preact-material-components": "^1.3.7", "preact-material-components": "^1.3.7",
"preact-router": "^2.6.0" "preact-material-components-drawer": "git+https://gist.github.com/a78fceed440b98e62582e4440b86bfab.git",
"preact-router": "^2.6.0",
"ts-loader": "^4.0.1"
} }
} }

View File

@@ -1,36 +0,0 @@
import { Component } from 'preact';
import { updater, toggle } from '../../lib/util';
import Fab from '../fab';
import Header from '../header';
import Drawer from '../drawer';
import Home from '../home';
import style from './style';
export default class App extends Component {
state = {
showDrawer: false,
showFab: true
};
openDrawer = updater(this, 'showDrawer', true);
closeDrawer = updater(this, 'showDrawer', false);
toggleDrawer = updater(this, 'showDrawer', toggle);
openFab = updater(this, 'showFab', true);
closeFab = updater(this, 'showFab', false);
toggleFab = updater(this, 'showFab', toggle);
render({ url }, { showDrawer, showFab }) {
return (
<div id="app" class={style.app}>
<Fab showing={showFab} />
<Header toggleDrawer={this.toggleDrawer} />
<Drawer showing={showDrawer} openDrawer={this.openDrawer} closeDrawer={this.closeDrawer} />
<div class={style.content} paint-outside>
<Home />
</div>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
</div>
);
}
}

View File

@@ -0,0 +1,110 @@
import { h, Component } from 'preact';
import { updater, toggle, When } from '../../lib/util';
import Fab from '../fab';
import Header from '../header';
// import Drawer from 'async!../drawer';
import Home from '../home';
import * as style from './style.scss';
type Props = {
url?: String
}
type FileObj = {
id: any,
data: any,
error: Error | DOMError | String,
file: File,
loading: Boolean
};
type State = {
showDrawer: Boolean,
showFab: Boolean,
files: FileObj[]
};
let counter = 0;
export default class App extends Component<Props, State> {
state: State = {
showDrawer: false,
showFab: true,
files: []
};
loadFile = (file: File) => {
let fileObj = {
id: ++counter,
file,
error: null,
loading: true,
data: null
};
this.setState({
files: [fileObj]
});
let fr = new FileReader();
// fr.readAsArrayBuffer();
fr.onerror = () => {
let files = this.state.files.slice();
files.splice(0, files.indexOf(fileObj), {
...fileObj,
error: fr.error,
loading: false
});
this.setState({ files });
};
fr.onloadend = () => {
let files = this.state.files.slice();
files.splice(0, files.indexOf(fileObj), {
...fileObj,
data: fr.result,
loading: false
});
this.setState({ files });
};
fr.readAsDataURL(file);
};
enableDrawer = false;
openDrawer = updater(this, 'showDrawer', true);
closeDrawer = updater(this, 'showDrawer', false);
toggleDrawer = updater(this, 'showDrawer', toggle);
openFab = updater(this, 'showFab', true);
closeFab = updater(this, 'showFab', false);
toggleFab = updater(this, 'showFab', toggle);
render({ url }, { showDrawer, showFab, files }) {
if (showDrawer) this.enableDrawer = true;
return (
<div id="app" class={style.app}>
<Fab showing={showFab} />
<Header toggleDrawer={this.toggleDrawer} loadFile={this.loadFile} />
{/* Avoid loading & rendering the drawer until the first time it is shown. */}
{/*
<When value={showDrawer}>
<Drawer showing={showDrawer} openDrawer={this.openDrawer} closeDrawer={this.closeDrawer} />
</When>
*/}
{/*
Note: this is normally where a <Router> with auto code-splitting goes.
Since we don't seem to need one (yet?), it's omitted.
*/}
<div class={style.content}>
<Home files={files} />
</div>
{/* This ends up in the body when prerendered, which makes it load async. */}
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
</div>
);
}
}

2
src/components/app/style.scss.d.ts vendored Normal file
View File

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

View File

@@ -1,4 +1,4 @@
import { Component } from 'preact'; import { h, Component } from 'preact';
import MdlDrawer from 'preact-material-components-drawer'; import MdlDrawer from 'preact-material-components-drawer';
import 'preact-material-components/Drawer/style.css'; import 'preact-material-components/Drawer/style.css';
import List from 'preact-material-components/List'; import List from 'preact-material-components/List';
@@ -7,14 +7,27 @@ import { Text } from 'preact-i18n';
import style from './style'; import style from './style';
export default class Drawer extends Component { export default class Drawer extends Component {
render({ showing, openDrawer, closeDrawer }) { state = {
rendered: false
};
setRendered = () => {
this.setState({ rendered: true });
};
render({ showing, openDrawer, closeDrawer }, { rendered }) {
if (showing && !rendered) {
setTimeout(this.setRendered, 20);
showing = false;
}
return ( return (
<MdlDrawer open={showing} onOpen={openDrawer} onClose={closeDrawer}> <MdlDrawer open={showing} onOpen={openDrawer} onClose={closeDrawer}>
<MdlDrawer.Header autosize class="mdc-theme--primary-bg"> <MdlDrawer.Header class="mdc-theme--primary-bg">
<img class={style.logo} alt="logo" src="/assets/icon.png" /> <img class={style.logo} alt="logo" src="/assets/icon.png" />
</MdlDrawer.Header> </MdlDrawer.Header>
<MdlDrawer.Content autosize class={style.list}> <MdlDrawer.Content class={style.list}>
<List autosize> <List>
<List.LinkItem href="/"> <List.LinkItem href="/">
<List.ItemIcon>verified_user</List.ItemIcon> <List.ItemIcon>verified_user</List.ItemIcon>
<Text id="SIGN_IN">Sign In</Text> <Text id="SIGN_IN">Sign In</Text>
@@ -26,7 +39,7 @@ export default class Drawer extends Component {
</List> </List>
</MdlDrawer.Content> </MdlDrawer.Content>
<div class={style.bottom} autosize> <div class={style.bottom}>
<List.LinkItem href="/preferences"> <List.LinkItem href="/preferences">
<List.ItemIcon>settings</List.ItemIcon> <List.ItemIcon>settings</List.ItemIcon>
<Text id="PREFERENCES">Preferences</Text> <Text id="PREFERENCES">Preferences</Text>

14
src/components/drawer/style.scss.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
export const mdcListItemSecondaryText: string;
export const mdcListItemGraphic: string;
export const mdcListItemMeta: string;
export const mdcListItem: string;
export const mdcListDivider: string;
export const mdcListGroup: string;
export const mdcListGroupSubheader: string;
export const drawer: string;
export const logo: string;
export const category: string;
export const bottom: string;
export const mdcRippleFgRadiusIn: string;
export const mdcRippleFgOpacityIn: string;
export const mdcRippleFgOpacityOut: string;

View File

@@ -1,4 +1,4 @@
import { Component } from 'preact'; import { h, Component } from 'preact';
import Icon from 'preact-material-components/Icon'; import Icon from 'preact-material-components/Icon';
import 'preact-material-components/Icon/style.css'; import 'preact-material-components/Icon/style.css';
import Fab from 'preact-material-components/Fab'; import Fab from 'preact-material-components/Fab';
@@ -20,7 +20,7 @@ export default class AppFab extends Component {
{ loading ? ( { loading ? (
<RadialProgress primary class={style.progress} /> <RadialProgress primary class={style.progress} />
) : ( ) : (
<Icon autosize>file_download</Icon> <Icon>file_download</Icon>
) } ) }
</Fab> </Fab>
); );

5
src/components/fab/style.scss.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
export const fab: string;
export const progress: string;
export const mdcRippleFgRadiusIn: string;
export const mdcRippleFgOpacityIn: string;
export const mdcRippleFgOpacityOut: string;

View File

@@ -1,34 +1,45 @@
import { Component } from 'preact'; import { h, Component } from 'preact';
import Toolbar from 'preact-material-components/Toolbar'; import Toolbar from 'preact-material-components/Toolbar';
// import 'preact-material-components/Toolbar/mdc-toolbar.scss'; import cx from 'classnames';
// import Icon from 'preact-material-components/Icon';
// import 'preact-material-components/Icon/style.css';
// import Fab from 'preact-material-components/Fab';
// import 'preact-material-components/Fab/mdc-fab.scss';
import style from './style'; import style from './style';
export default class Header extends Component { export default class Header extends Component {
// fabClick = () => { setInputRef = c => {
// alert("Hello"); this.input = c;
// }; };
handleFiles = () => {
let files = this.input.files;
if (files.length) {
this.props.loadFile(files[0]);
}
};
upload = () => {
// let input = document.createElement('input');
// input.type = 'file';
// // input.multiple = true;
// document.body.appendChild(input);
// input.addEventListener('change', e => {
// });
// input.click();
this.input.click();
};
render({ toggleDrawer, showHeader, showFab }) { render({ toggleDrawer, showHeader, showFab }) {
return ( return (
<Toolbar class={`${style.toolbar} ${showHeader === false ? style.minimal : ''} inert`} fixed> <Toolbar fixed class={cx(style.toolbar, 'inert', showHeader===false && style.minimal)}>
<Toolbar.Row> <Toolbar.Row>
<Toolbar.Title autosize class={style.title}> <Toolbar.Title class={style.title}>
<img class={style.logo} src="/assets/icon.png" /> <img class={style.logo} src="/assets/icon.png" />
<Toolbar.Icon ripple onClick={this.upload}>file_upload</Toolbar.Icon>
</Toolbar.Title> </Toolbar.Title>
<Toolbar.Section autosize align-end> <Toolbar.Section align-end>
<Toolbar.Icon autosize menu onClick={toggleDrawer}>menu</Toolbar.Icon> <Toolbar.Icon ripple onClick={toggleDrawer}>menu</Toolbar.Icon>
</Toolbar.Section> </Toolbar.Section>
</Toolbar.Row> </Toolbar.Row>
<input class={style.fileInput} ref={this.setInputRef} type="file" onChange={this.handleFiles} />
{/*
<Fab class={style.fab} exited={showFab===false} ripple onClick={this.fabClick}>
<Icon>create</Icon>
</Fab>
*/}
</Toolbar> </Toolbar>
); );
} }

View File

@@ -15,6 +15,12 @@
// } // }
} }
.fileInput {
position: absolute;
left: 0;
top: -999px;
}
.fab { .fab {
position: fixed; position: fixed;
display: block; display: block;

8
src/components/header/style.scss.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
export const toolbar: string;
export const minimal: string;
export const fileInput: string;
export const fab: string;
export const logo: string;
export const menu: string;
export const menuItem: string;
export const title: string;

View File

@@ -1,16 +0,0 @@
import { Component } from 'preact';
// import Button from 'preact-material-components/Button';
// import Switch from 'preact-material-components/Switch';
// import 'preact-material-components/Switch/style.css';
import style from './style';
export default class Home extends Component {
render() {
return (
<div class={style.home}>
<h1>Home</h1>
</div>
);
}
}

View File

@@ -0,0 +1,36 @@
import { h, Component } from 'preact';
// import Button from 'preact-material-components/Button';
// import Switch from 'preact-material-components/Switch';
// import 'preact-material-components/Switch/style.css';
import * as style from './style.scss';
type Props = {
files: {
data: any
}[]
};
type State = {
active: boolean
};
export default class Home extends Component<Props, State> {
state: State = {
active: false
};
componentDidMount() {
setTimeout( () => {
this.setState({ active: true });
});
}
render({ files }, { active }) {
return (
<div class={style.home+' '+(active ? style.active : '')}>
{ files && files[0] && (
<img src={files[0].data} />
) }
</div>
);
}
}

View File

@@ -7,4 +7,14 @@
.home { .home {
padding: 20px; padding: 20px;
opacity: 0;
}
.active {
animation: fadeIn 2s forwards ease 1;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
} }

3
src/components/home/style.scss.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
export const home: string;
export const active: string;
export const fadeIn: string;

28
src/index.html Normal file
View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Squoosh</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<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>
<body>
<%=
/*require('../config/prerender')()*/
htmlWebpackPlugin.options.prerender()
%>
</body>
</html>

View File

@@ -13,6 +13,6 @@ if (typeof window!=='undefined') {
e.preventDefault(); e.preventDefault();
return false; return false;
} }
} while (target = target.parentNode); } while ((target = target.parentNode));
}); });
} }

View File

@@ -1,3 +1,5 @@
import { Component } from 'preact';
export function updater(obj, property, value) { export function updater(obj, property, value) {
return e => { return e => {
let update = {}; let update = {};
@@ -8,3 +10,10 @@ export function updater(obj, property, value) {
export const toggle = value => !value; export const toggle = value => !value;
export class When extends Component {
state = { ready: !!this.props.value };
render({ value, children: [child] }, { ready }) {
if (value && !ready) this.setState({ ready: true });
return ready ? (typeof child === 'function' ? child() : child) : null;
}
}

12
tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"compileOnSave": false,
"compilerOptions": {
"target": "es2017",
"module": "esnext",
"moduleResolution": "node",
"sourceMap": true,
"jsx": "react",
"jsxFactory": "h",
"allowJs": true
}
}

View File

@@ -1,78 +1,191 @@
let path = require('path'); let path = require('path');
let webpack = require('webpack'); let webpack = require('webpack');
// let ExtractTextPlugin = require('extract-text-webpack-plugin');
let MiniCssExtractPlugin = require('mini-css-extract-plugin'); let MiniCssExtractPlugin = require('mini-css-extract-plugin');
let HtmlWebpackPlugin = require('html-webpack-plugin');
let PreloadWebpackPlugin = require('preload-webpack-plugin');
let ReplacePlugin = require('webpack-plugin-replace');
let CopyPlugin = require('copy-webpack-plugin');
let WatchTimestampsPlugin = require('./config/watch-timestamps-plugin');
module.exports = function(_, env) { module.exports = function(_, env) {
let isProd = env.mode === 'production';
let nodeModules = path.join(__dirname, 'node_modules');
let componentStyleDirs = [
path.join(__dirname, 'src/components'),
path.join(__dirname, 'src/routes')
];
let babelRc = JSON.parse(require('fs').readFileSync('.babelrc'));
babelRc.babelrc = false;
babelRc.presets[0][1].modules = isProd ? false : 'commonjs';
return { return {
mode: env.mode || 'development', mode: env.mode || 'development',
entry: './src/index', entry: path.join(__dirname, 'config/client-boot.js'),
// entry: './src/index',
output: {
filename: isProd ? '[name].[chunkhash:5].js' : '[name].js',
chunkFilename: '[name].chunk.[chunkhash:5].js',
path: path.join(__dirname, 'build'),
publicPath: '/'
},
resolve: { resolve: {
extensions: ['.ts', '.js', '.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: {
alias: {
async: path.join(__dirname, 'config/async-component-loader')
}
},
module: { module: {
rules: [ rules: [
{ {
test: /\.s?css$/, test: /\.tsx?$/,
enforce: 'pre',
// loader: 'awesome-typescript-loader',
loader: 'ts-loader',
exclude: nodeModules
},
{
test: /\.(tsx?|jsx?)$/,
loader: 'babel-loader',
options: babelRc
},
{
test: /\.(scss|sass)$/,
loader: 'sass-loader',
enforce: 'pre',
options: {
sourceMap: true,
includePaths: [nodeModules]
}
},
{
test: /\.(scss|sass|css)$/,
include: componentStyleDirs,
use: [ use: [
MiniCssExtractPlugin.loader, isProd ? MiniCssExtractPlugin.loader : 'style-loader',
{ loader: 'css-loader' },
// 'sass-loader?includePaths='+encodeURIComponent(path.join(__dirname, 'node_modules'))
{ {
loader: 'sass-loader', // loader: 'typings-for-css-modules-loader?modules&localIdentName=[local]__[hash:base64:5]&importLoaders=1'+(isProd ? '&sourceMap' : '')
// loader: 'css-loader',
loader: 'typings-for-css-modules-loader',
options: { options: {
includePaths: [path.join(__dirname, 'node_modules')] modules: true,
localIdentName: '[local]__[hash:base64:5]',
namedExport: true,
camelCase: true,
importLoaders: 1,
sourceMap: isProd,
sass: true
} }
} }
] ]
// loader: ExtractTextPlugin.extract({
// fallback: 'style-loader',
// use: [{
// loader: 'css-loader',
// options: {
// modules: true,
// localIdentName: '[local]__[hash:base64:5]',
// importLoaders: 1,
// sourceMap: isProd
// }
// }]
// })
}, },
{ {
test: /\.ts$/, test: /\.(scss|sass|css)$/,
// loader: 'awesome-typescript-loader' exclude: componentStyleDirs,
loader: 'typescript-loader' use: [
}, isProd ? MiniCssExtractPlugin.loader : 'style-loader',
{ {
test: /\.js$/, loader: 'css-loader',
loader: 'babel-loader', options: {
options: { importLoaders: 1,
presets: [ sourceMap: isProd
['env', { }
loose: true, }
uglify: true, ]
modules: false, // loader: ExtractTextPlugin.extract({
targets: { // fallback: 'style-loader',
browsers: 'last 2 versions' // use: [{
}, // loader: 'css-loader',
exclude: [ // options: {
'transform-regenerator', // importLoaders: 1,
'transform-es2015-typeof-symbol' // sourceMap: isProd
] // }
}] // }]
], // })
plugins: [
'syntax-dynamic-import',
'transform-decorators-legacy',
'transform-class-properties',
'transform-object-rest-spread',
'transform-react-constant-elements',
'transform-react-remove-prop-types',
['transform-react-jsx', {
pragma: 'h'
}],
['jsx-pragmatic', {
module: 'preact',
export: 'h',
import: 'h'
}]
]
}
} }
] ]
}, },
plugins: [ plugins: [
new webpack.optimize.SplitChunksPlugin({}) isProd && new webpack.optimize.SplitChunksPlugin({}),
] isProd && new MiniCssExtractPlugin({}),
// new ExtractTextPlugin({
// filename: isProd ? 'style.[contenthash:5].css' : 'style.css',
// disable: !isProd,
// allChunks: true
// }),
// fixes infinite loop in typings-for-css-modules-loader:
new webpack.WatchIgnorePlugin([
/(c|sc|sa)ss\.d\.ts$/
]),
new WatchTimestampsPlugin([
/(c|sc|sa)ss\.d\.ts$/
]),
new HtmlWebpackPlugin({
filename: path.join(__dirname, 'build/index.html'),
template: '!!ejs-loader!src/index.html',
minify: isProd && {
collapseWhitespace: true,
removeScriptTypeAttributes: true,
removeRedundantAttributes: true,
removeStyleLinkTypeAttributes: true,
removeComments: true
},
manifest: require('./src/manifest.json'),
/** @todo Finish implementing prerendering similar to that of Preact CLI. */
prerender() {
return '<div id="app_root"></div>';
// require('babel-register')({ ignore: false });
// return require('./config/prerender')();
},
inject: true,
compile: true
}),
isProd && new PreloadWebpackPlugin(),
isProd && new ReplacePlugin({
include: /babel-helper$/,
patterns: [{
regex: /throw\s+(new\s+)?(Type|Reference)?Error\s*\(/g,
value: s => `return;${Array(s.length - 7).join(' ')}(`
}]
}),
new CopyPlugin([
{ from: 'src/manifest.json', to: 'manifest.json' },
{ from: 'src/assets', to: 'assets' }
])
].filter(Boolean),
devServer: {
contentBase: path.join(__dirname, 'src'),
inline: true,
hot: true,
historyApiFallback: true,
noInfo: true,
progress: true,
// quiet: true,
clientLogLevel: 'none',
stats: 'minimal',
overlay: false
}
}; };
}; };