Reduce bundle size

- Use the browsers fetch rather than the request module
 - base64-loader -> raw-loader
 - Remove ol3 because it's been broken for a while
 - Removed old GitHub gist support as it's no longer functional
 - Removed Mousetrap as we were only using a small part of the functionality
 - Moved to single js file to make things simplier
This commit is contained in:
orangemug 2018-08-22 09:33:01 +01:00
parent 70f1f9ffac
commit 922ee616ec
12 changed files with 134 additions and 321 deletions

View file

@ -14,25 +14,6 @@ var OUTPATH = artifacts.pathSync("/build");
module.exports = { module.exports = {
entry: { entry: {
app: './src/index.jsx', app: './src/index.jsx',
vendor: [
'file-saver',
'mapbox-gl/dist/mapbox-gl.js',
"lodash.clonedeep",
"lodash.throttle",
'color',
'react',
"react-dom",
"react-color",
"react-file-reader-input",
"react-collapse",
"react-height",
"react-icon-base",
"react-motion",
"react-sortable-hoc",
"request",
//TODO: Icons raise multi vendor errors?
//"react-icons",
]
}, },
output: { output: {
path: OUTPATH, path: OUTPATH,
@ -55,7 +36,6 @@ module.exports = {
}, },
plugins: [ plugins: [
new webpack.NoEmitOnErrorsPlugin(), new webpack.NoEmitOnErrorsPlugin(),
new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: '[chunkhash].vendor.js' }),
new WebpackCleanupPlugin(), new WebpackCleanupPlugin(),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {

View file

@ -10,8 +10,7 @@
"test-watch": "cross-env NODE_ENV=test wdio config/wdio.conf.js --watch", "test-watch": "cross-env NODE_ENV=test wdio config/wdio.conf.js --watch",
"start": "webpack-dev-server --progress --profile --colors --config config/webpack.config.js", "start": "webpack-dev-server --progress --profile --colors --config config/webpack.config.js",
"lint": "eslint --ext js --ext jsx {src,test}", "lint": "eslint --ext js --ext jsx {src,test}",
"lint-styles": "stylelint 'src/styles/*.scss'", "lint-styles": "stylelint 'src/styles/*.scss'"
"nsp": "nsp check --reporter summary"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -27,7 +26,6 @@
"codemirror": "^5.37.0", "codemirror": "^5.37.0",
"color": "^3.0.0", "color": "^3.0.0",
"file-saver": "^1.3.8", "file-saver": "^1.3.8",
"github-api": "^3.0.0",
"jsonlint": "github:josdejong/jsonlint#85a19d7", "jsonlint": "github:josdejong/jsonlint#85a19d7",
"lodash.capitalize": "^4.2.1", "lodash.capitalize": "^4.2.1",
"lodash.clamp": "^4.0.3", "lodash.clamp": "^4.0.3",
@ -37,28 +35,24 @@
"mapbox-gl": "^0.47.0", "mapbox-gl": "^0.47.0",
"mapbox-gl-inspect": "^1.3.1", "mapbox-gl-inspect": "^1.3.1",
"maputnik-design": "github:maputnik/design", "maputnik-design": "github:maputnik/design",
"mousetrap": "^1.6.1",
"ol-mapbox-style": "^2.10.1", "ol-mapbox-style": "^2.10.1",
"ol": "^4.6.5", "ol": "^4.6.5",
"prop-types": "^15.6.0", "prop-types": "^15.6.0",
"react": "^16.3.2", "react": "^16.3.2",
"react-addons-pure-render-mixin": "^15.6.2",
"react-aria-menubutton": "^5.1.1", "react-aria-menubutton": "^5.1.1",
"react-aria-modal": "^2.12.1", "react-aria-modal": "^2.12.1",
"react-autobind": "^1.0.6",
"react-autocomplete": "^1.7.2", "react-autocomplete": "^1.7.2",
"react-codemirror2": "^4.2.1", "react-codemirror2": "^4.2.1",
"react-collapse": "^4.0.3", "react-collapse": "^4.0.3",
"react-color": "^2.14.1", "react-color": "^2.14.1",
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.3.2", "react-dom": "^16.3.2",
"react-file-reader-input": "^1.1.4", "react-file-reader-input": "^1.1.4",
"react-height": "^3.0.0",
"react-icon-base": "^2.1.1", "react-icon-base": "^2.1.1",
"react-icons": "^2.2.7", "react-icons": "^2.2.7",
"react-motion": "^0.5.2", "react-motion": "^0.5.2",
"react-sortable-hoc": "^0.6.8", "react-sortable-hoc": "^0.6.8",
"reconnecting-websocket": "^3.2.2", "reconnecting-websocket": "^3.2.2",
"request": "^2.85.0",
"url": "^0.11.0" "url": "^0.11.0"
}, },
"jshintConfig": { "jshintConfig": {
@ -135,7 +129,7 @@
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"mocha": "^5.1.1", "mocha": "^5.1.1",
"node-sass": "^4.9.0", "node-sass": "^4.9.0",
"nsp": "^3.1.0", "raw-loader": "^0.5.1",
"react-hot-loader": "^3.1.1", "react-hot-loader": "^3.1.1",
"sass-loader": "^7.0.1", "sass-loader": "^7.0.1",
"selenium-standalone": "^6.14.0", "selenium-standalone": "^6.14.0",
@ -147,7 +141,6 @@
"uglifyjs-webpack-plugin": "^1.2.4", "uglifyjs-webpack-plugin": "^1.2.4",
"uuid": "^3.1.0", "uuid": "^3.1.0",
"wdio-mocha-framework": "^0.5.13", "wdio-mocha-framework": "^0.5.13",
"wdio-phantomjs-service": "^0.2.2",
"wdio-selenium-standalone-service": "0.0.10", "wdio-selenium-standalone-service": "0.0.10",
"wdio-spec-reporter": "^0.1.2", "wdio-spec-reporter": "^0.1.2",
"webdriverio": "^4.12.0", "webdriverio": "^4.12.0",

View file

@ -1,12 +1,11 @@
import autoBind from 'react-autobind';
import React from 'react' import React from 'react'
import Mousetrap from 'mousetrap'
import cloneDeep from 'lodash.clonedeep' import cloneDeep from 'lodash.clonedeep'
import clamp from 'lodash.clamp' import clamp from 'lodash.clamp'
import {arrayMove} from 'react-sortable-hoc' import {arrayMove} from 'react-sortable-hoc'
import url from 'url' import url from 'url'
import MapboxGlMap from './map/MapboxGlMap' import MapboxGlMap from './map/MapboxGlMap'
import OpenLayers3Map from './map/OpenLayers3Map'
import LayerList from './layers/LayerList' import LayerList from './layers/LayerList'
import LayerEditor from './layers/LayerEditor' import LayerEditor from './layers/LayerEditor'
import Toolbar from './Toolbar' import Toolbar from './Toolbar'
@ -54,6 +53,8 @@ function updateRootSpec(spec, fieldName, newValues) {
export default class App extends React.Component { export default class App extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
autoBind(this);
this.revisionStore = new RevisionStore() this.revisionStore = new RevisionStore()
this.styleStore = new ApiStyleStore({ this.styleStore = new ApiStyleStore({
onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, false) onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, false)
@ -187,14 +188,33 @@ export default class App extends React.Component {
}) })
} }
handleKeyPress(e) {
if(navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
if(e.metaKey && e.shiftKey && e.keyCode === 90) {
console.log("redo");
this.onRedo(e);
}
else if(e.metaKey && e.keyCode === 90) {
console.log("undo");
this.onUndo(e);
}
}
else {
if(e.ctrlKey && e.keyCode === 90) {
this.onUndo(e);
}
else if(e.ctrlKey && e.keyCode === 89) {
this.onRedo(e);
}
}
}
componentDidMount() { componentDidMount() {
Mousetrap.bind(['mod+z'], this.onUndo.bind(this)); window.addEventListener("keydown", this.handleKeyPress);
Mousetrap.bind(['mod+y', 'mod+shift+z'], this.onRedo.bind(this));
} }
componentWillUnmount() { componentWillUnmount() {
Mousetrap.unbind(['mod+z'], this.onUndo.bind(this)); window.removeEventListener("keydown", this.handleKeyPress);
Mousetrap.unbind(['mod+y', 'mod+shift+z'], this.onRedo.bind(this));
} }
saveStyle(snapshotStyle) { saveStyle(snapshotStyle) {
@ -371,7 +391,9 @@ export default class App extends React.Component {
console.warn("Failed to normalizeSourceURL: ", err); console.warn("Failed to normalizeSourceURL: ", err);
} }
fetch(url) fetch(url, {
mode: 'cors',
})
.then((response) => { .then((response) => {
return response.json(); return response.json();
}) })
@ -423,12 +445,12 @@ export default class App extends React.Component {
// Check if OL3 code has been loaded? // Check if OL3 code has been loaded?
if(renderer === 'ol3') { if(renderer === 'ol3') {
mapElement = <OpenLayers3Map {...mapProps} /> mapElement = <div>TODO</div>
} else { } else {
mapElement = <MapboxGlMap {...mapProps} mapElement = <MapboxGlMap {...mapProps}
inspectModeEnabled={this.state.inspectModeEnabled} inspectModeEnabled={this.state.inspectModeEnabled}
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]} highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
onLayerSelect={this.onLayerSelect.bind(this)} /> onLayerSelect={this.onLayerSelect} />
} }
const elementStyle = {}; const elementStyle = {};

View file

@ -11,206 +11,8 @@ import Modal from './Modal'
import MdFileDownload from 'react-icons/lib/md/file-download' import MdFileDownload from 'react-icons/lib/md/file-download'
import TiClipboard from 'react-icons/lib/ti/clipboard' import TiClipboard from 'react-icons/lib/ti/clipboard'
import style from '../../libs/style' import style from '../../libs/style'
import GitHub from 'github-api'
import { CopyToClipboard } from 'react-copy-to-clipboard'
class Gist extends React.Component {
static propTypes = {
mapStyle: PropTypes.object.isRequired,
onStyleChanged: PropTypes.func.isRequired,
}
constructor(props) {
super(props);
this.state = {
preview: false,
public: false,
saving: false,
latestGist: null,
}
}
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({
...this.state,
preview: !!(nextProps.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']
})
}
onSave() {
this.setState({
...this.state,
saving: true
});
const preview = this.state.preview;
const mapboxToken = (this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token'];
const mapStyleStr = preview ?
styleSpec.format(stripAccessTokens(style.replaceAccessTokens(this.props.mapStyle))) :
styleSpec.format(stripAccessTokens(this.props.mapStyle));
const styleTitle = this.props.mapStyle.name || 'Style';
const htmlStr = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>`+styleTitle+` Preview</title>
<link rel="stylesheet" type="text/css" href="https://api.mapbox.com/mapbox-gl-js/v0.44.0/mapbox-gl.css" />
<script src="https://api.mapbox.com/mapbox-gl-js/v0.44.0/mapbox-gl.js"></script>
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>
<div id='map'></div>
<script>
mapboxgl.accessToken = '${mapboxToken}';
var map = new mapboxgl.Map({
container: 'map',
style: 'style.json',
attributionControl: true,
hash: true
});
map.addControl(new mapboxgl.NavigationControl());
</script>
</body>
</html>
`
const files = {
"style.json": {
content: mapStyleStr
}
}
if(preview) {
files["index.html"] = {
content: htmlStr
}
}
const gh = new GitHub();
let gist = gh.getGist(); // not a gist yet
gist.create({
public: this.state.public,
description: styleTitle,
files: files
}).then(function({data}) {
return gist.read();
}).then(function({data}) {
this.setState({
...this.state,
latestGist: data,
saving: false,
});
}.bind(this));
}
onPreviewChange(value) {
this.setState({
...this.state,
preview: value
})
}
onPublicChange(value) {
this.setState({
...this.state,
public: value
})
}
changeMetadataProperty(property, value) {
const changedStyle = {
...this.props.mapStyle,
metadata: {
...this.props.mapStyle.metadata,
[property]: value
}
}
this.props.onStyleChanged(changedStyle)
}
renderPreviewLink() {
const gist = this.state.latestGist;
const user = gist.user || 'anonymous';
const preview = !!gist.files['index.html'];
if(preview) {
return <span><a target="_blank" rel="noopener noreferrer" href={"https://bl.ocks.org/"+user+"/"+gist.id}>Preview</a>,{' '}</span>
}
return null;
}
renderLatestGist() {
const gist = this.state.latestGist;
const saving = this.state.saving;
if(saving) {
return <p>Saving...</p>
} else if(gist) {
const user = gist.user || 'anonymous';
const rawGistLink = "https://gist.githubusercontent.com/" + user + "/" + gist.id + "/raw/" + gist.history[0].version + "/style.json"
const maputnikStyleLink = "https://maputnik.github.io/editor/?style=" + rawGistLink
return <div className="maputnik-render-gist">
<p>
Latest saved gist:{' '}
{this.renderPreviewLink(this)}
<a target="_blank" rel="noopener noreferrer" href={"https://gist.github.com/" + user + "/" + gist.id}>Source</a>
</p>
<p>
<CopyToClipboard text={maputnikStyleLink}>
<span>Share this style: <Button><TiClipboard size={18} /></Button></span>
</CopyToClipboard>
<StringInput value={maputnikStyleLink} />
</p>
</div>
}
}
render() {
return <div className="maputnik-export-gist">
<Button onClick={this.onSave.bind(this)}>
<MdFileDownload />
Save to Gist (anonymous)
</Button>
<div className="maputnik-modal-sub-section">
<CheckboxInput
value={this.state.public}
name='gist-style-public'
onChange={this.onPublicChange.bind(this)}
/>
<span> Public gist</span>
</div>
<div className="maputnik-modal-sub-section">
<CheckboxInput
value={this.state.preview}
name='gist-style-preview'
onChange={this.onPreviewChange.bind(this)}
/>
<span> Include preview</span>
</div>
{this.state.preview ?
<div>
<InputBlock
label={"OpenMapTiles Access Token: "}>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}/>
</InputBlock>
<InputBlock
label={"Mapbox Access Token: "}>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}/>
</InputBlock>
<a target="_blank" rel="noopener noreferrer" href="https://openmaptiles.com/hosting/">Get your free access token</a>
</div>
: null}
{this.renderLatestGist()}
</div>
}
}
function stripAccessTokens(mapStyle) { function stripAccessTokens(mapStyle) {
const changedMetadata = { ...mapStyle.metadata } const changedMetadata = { ...mapStyle.metadata }
@ -294,10 +96,6 @@ class ExportModal extends React.Component {
</Button> </Button>
</div> </div>
<div className="maputnik-modal-section hide">
<h4>Save style</h4>
<Gist mapStyle={this.props.mapStyle} onStyleChanged={this.props.onStyleChanged}/>
</div>
</Modal> </Modal>
} }
} }

View file

@ -4,7 +4,6 @@ import LoadingModal from './LoadingModal'
import Modal from './Modal' import Modal from './Modal'
import Button from '../Button' import Button from '../Button'
import FileReaderInput from 'react-file-reader-input' import FileReaderInput from 'react-file-reader-input'
import request from 'request'
import FileUploadIcon from 'react-icons/lib/md/file-upload' import FileUploadIcon from 'react-icons/lib/md/file-upload'
import AddIcon from 'react-icons/lib/md/add-circle-outline' import AddIcon from 'react-icons/lib/md/add-circle-outline'
@ -77,30 +76,36 @@ class OpenModal extends React.Component {
onStyleSelect(styleUrl) { onStyleSelect(styleUrl) {
this.clearError(); this.clearError();
const reqOpts = { const activeRequest = fetch(styleUrl, {
url: styleUrl, mode: 'cors',
withCredentials: false, credentials: "same-origin"
} })
.then(function(response) {
const activeRequest = request(reqOpts, (error, response, body) => { return response.json();
})
.then((body) => {
this.setState({ this.setState({
activeRequest: null, activeRequest: null,
activeRequestUrl: null activeRequestUrl: null
}); });
if (!error && response.statusCode == 200) { const mapStyle = style.ensureStyleValidity(body)
const mapStyle = style.ensureStyleValidity(JSON.parse(body)) console.log('Loaded style ', mapStyle.id)
console.log('Loaded style ', mapStyle.id) this.props.onStyleOpen(mapStyle)
this.props.onStyleOpen(mapStyle) this.onOpenToggle()
this.onOpenToggle() })
} else { .catch((err) => {
console.warn('Could not open the style URL', styleUrl) this.setState({
} activeRequest: null,
activeRequestUrl: null
});
console.error(err);
console.warn('Could not open the style URL', styleUrl)
}) })
this.setState({ this.setState({
activeRequest: activeRequest, activeRequest: activeRequest,
activeRequestUrl: reqOpts.url activeRequestUrl: styleUrl
}) })
} }

View file

@ -107,7 +107,7 @@ class SettingsModal extends React.Component {
data-wd-key="modal-settings.maputnik:renderer" data-wd-key="modal-settings.maputnik:renderer"
options={[ options={[
['mbgljs', 'MapboxGL JS'], ['mbgljs', 'MapboxGL JS'],
['ol3', 'Open Layers 3'], // ['ol3', 'Open Layers 3'],
]} ]}
value={metadata['maputnik:renderer'] || 'mbgljs'} value={metadata['maputnik:renderer'] || 'mbgljs'}
onChange={this.changeMetadataProperty.bind(this, 'maputnik:renderer')} onChange={this.changeMetadataProperty.bind(this, 'maputnik:renderer')}

View file

@ -1,8 +1,8 @@
import lodash from 'lodash' import throttle from 'lodash.throttle'
// Throttle for 3 seconds so when a user enables it they don't have to refresh the page. // Throttle for 3 seconds so when a user enables it they don't have to refresh the page.
const reducedMotionEnabled = lodash.throttle(() => { const reducedMotionEnabled = throttle(() => {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches return window.matchMedia("(prefers-reduced-motion: reduce)").matches
}, 3000); }, 3000);

View file

@ -1,4 +1,3 @@
import request from 'request'
import style from './style.js' import style from './style.js'
import ReconnectingWebSocket from 'reconnecting-websocket' import ReconnectingWebSocket from 'reconnecting-websocket'
@ -14,15 +13,20 @@ export class ApiStyleStore {
} }
init(cb) { init(cb) {
request(localUrl + '/styles', (error, response, body) => { fetch(localUrl + '/styles', {
if (!error && body && response.statusCode == 200) { mode: 'cors',
const styleIds = JSON.parse(body) })
this.latestStyleId = styleIds[0] .then(function(response) {
this.notifyLocalChanges() return response.json();
cb(null) })
} else { .then(function(body) {
cb(new Error('Can not connect to style API')) const styleIds = body;
} this.latestStyleId = styleIds[0]
this.notifyLocalChanges()
cb(null)
})
.catch(function() {
cb(new Error('Can not connect to style API'))
}) })
} }
@ -44,8 +48,14 @@ export class ApiStyleStore {
latestStyle(cb) { latestStyle(cb) {
if(this.latestStyleId) { if(this.latestStyleId) {
request(localUrl + '/styles/' + this.latestStyleId, (error, response, body) => { fetch(localUrl + '/styles/' + this.latestStyleId, {
cb(style.ensureStyleValidity(JSON.parse(body))) mode: 'cors',
})
.then(function(response) {
return response.json();
})
.then(function(body) {
cb(style.ensureStyleValidity(body))
}) })
} else { } else {
throw new Error('No latest style available. You need to init the api backend first.') throw new Error('No latest style available. You need to init the api backend first.')
@ -55,11 +65,15 @@ export class ApiStyleStore {
// Save current style replacing previous version // Save current style replacing previous version
save(mapStyle) { save(mapStyle) {
const id = mapStyle.id const id = mapStyle.id
request.put({ fetch(localUrl + '/styles/' + id, {
url: localUrl + '/styles/' + id, method: "PUT",
json: true, mode: 'cors',
body: mapStyle headers: {
}, (error, response, body) => { "Content-Type": "application/json; charset=utf-8",
},
body: JSON.stringify(mapStyle)
})
.catch(function(error) {
if(error) console.error(error) if(error) console.error(error)
}) })
return mapStyle return mapStyle

View file

@ -1,9 +1,9 @@
import MapboxGl from 'mapbox-gl/dist/mapbox-gl.js' import MapboxGl from 'mapbox-gl'
// Load mapbox-gl-rtl-text using object urls without needing http://localhost for AJAX. // Load mapbox-gl-rtl-text using object urls without needing http://localhost for AJAX.
const data = require("base64-loader?mimetype=text/javascript!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.js"); const data = require("raw-loader?mimetype=text/javascript!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.js");
const blob = new window.Blob([window.atob(data)]); const blob = new window.Blob([data]);
const objectUrl = window.URL.createObjectURL(blob, { const objectUrl = window.URL.createObjectURL(blob, {
type: "text/javascript" type: "text/javascript"
}); });

View file

@ -1,22 +1,19 @@
import request from 'request'
import npmurl from 'url' import npmurl from 'url'
function loadJSON(url, defaultValue, cb) { function loadJSON(url, defaultValue, cb) {
request({ fetch(url, {
url: url, mode: 'cors',
withCredentials: false, credentials: "same-origin"
}, (error, response, body) => { })
if (!error && body && response.statusCode == 200) { .then(function(response) {
try { return response.json();
cb(JSON.parse(body)) })
} catch(err) { .then(function(body) {
console.error(err) cb(body)
cb(defaultValue) })
} .catch(function() {
} else { console.warn('Can not metadata for ' + url)
console.warn('Can not metadata for ' + url) cb(defaultValue)
cb(defaultValue)
}
}) })
} }

View file

@ -2,7 +2,6 @@ import { colorizeLayers } from './style.js'
import style from './style.js' import style from './style.js'
import { loadStyleUrl } from './urlopen' import { loadStyleUrl } from './urlopen'
import publicSources from '../config/styles.json' import publicSources from '../config/styles.json'
import request from 'request'
const storagePrefix = "maputnik" const storagePrefix = "maputnik"
const stylePrefix = 'style' const stylePrefix = 'style'

View file

@ -1,4 +1,3 @@
import request from 'request'
import url from 'url' import url from 'url'
import style from './style.js' import style from './style.js'
@ -9,34 +8,40 @@ export function initialStyleUrl() {
export function loadStyleUrl(styleUrl, cb) { export function loadStyleUrl(styleUrl, cb) {
console.log('Loading style', styleUrl) console.log('Loading style', styleUrl)
request({ fetch(styleUrl, {
url: styleUrl, mode: 'cors',
withCredentials: false, credentials: "same-origin"
}, (error, response, body) => { })
if (!error && response.statusCode == 200) { .then(function(response) {
cb(style.ensureStyleValidity(JSON.parse(body))) return response.json();
} else { })
console.warn('Could not fetch default style', styleUrl) .then(function(body) {
cb(style.emptyStyle) cb(style.ensureStyleValidity(body))
} })
.catch(function() {
console.warn('Could not fetch default style', styleUrl)
cb(style.emptyStyle)
}) })
} }
export function loadJSON(url, defaultValue, cb) { export function loadJSON(url, defaultValue, cb) {
request({ fetch(url, {
url: url, mode: 'cors',
withCredentials: false, credentials: "same-origin"
}, (error, response, body) => { })
if (!error && body && response.statusCode == 200) { .then(function(response) {
try { return response.json();
cb(JSON.parse(body)) })
} catch(err) { .then(function(body) {
console.error(err) try {
cb(defaultValue) cb(body)
} } catch(err) {
} else { console.error(err)
console.error('Can not load JSON from ' + url)
cb(defaultValue) cb(defaultValue)
} }
}) })
.catch(function() {
console.error('Can not load JSON from ' + url)
cb(defaultValue)
})
} }