diff --git a/visualdl/frontend/.babelrc b/visualdl/frontend/.babelrc
new file mode 100644
index 0000000000000000000000000000000000000000..f35b67b9f32475b0940b87ad5a08d865745a3c7f
--- /dev/null
+++ b/visualdl/frontend/.babelrc
@@ -0,0 +1,7 @@
+{
+ "presets": [
+ ["es2015", {"modules": false}],
+ "stage-0"
+ ],
+ "plugins": ["transform-class-properties"]
+}
diff --git a/visualdl/frontend/.fecsignore b/visualdl/frontend/.fecsignore
new file mode 100644
index 0000000000000000000000000000000000000000..77d57a3a22fa07a925648af0075673f2e8787ff7
--- /dev/null
+++ b/visualdl/frontend/.fecsignore
@@ -0,0 +1,6 @@
+node_modules/**/*
+dep/**/*
+test/**/*
+mock/**/*
+example/**/*
+output/**/*
diff --git a/visualdl/frontend/.fecsrc b/visualdl/frontend/.fecsrc
new file mode 100644
index 0000000000000000000000000000000000000000..c5bd35919dba798de45761156de33a9650eb4796
--- /dev/null
+++ b/visualdl/frontend/.fecsrc
@@ -0,0 +1,26 @@
+{
+ "files": [
+ "./src/**/*.san",
+ "./src/**/*.js",
+ "./template/**/*.html"
+ ],
+ "eslint": {
+ "rules": {
+ "fecs-esnext-ext": [
+ "2",
+ [
+ "js",
+ "san"
+ ]
+ ],
+ "fecs-valid-jsdoc": [
+ "0"
+ ]
+ }
+ },
+ "csshint": {},
+ "htmlcs": {},
+ "jformatter": {},
+ "esformatter": {},
+ "csscomb": {}
+}
diff --git a/visualdl/frontend/.gitignore b/visualdl/frontend/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..a7a3b001058056c400cb03b5db7934828af6cc29
--- /dev/null
+++ b/visualdl/frontend/.gitignore
@@ -0,0 +1,3 @@
+/node_modules
+/.vscode
+package-lock.json
diff --git a/visualdl/frontend/mock/example/mock.js b/visualdl/frontend/mock/example/mock.js
new file mode 100644
index 0000000000000000000000000000000000000000..5544c73a6c32264b37e16ea8bfed0f7760997cf9
--- /dev/null
+++ b/visualdl/frontend/mock/example/mock.js
@@ -0,0 +1,26 @@
+/**
+ * frontend mock data
+ *
+ * @param {string} path request path
+ * @param {Object} queryParam params
+ * @param {Object} postParam post post params
+ * @return {Object}
+ */
+module.exports = function (path, queryParam, postParam) {
+ return {
+ // delay
+ _timeout: 0,
+
+ // http code status
+ _status: 200,
+
+ // response data
+ _data: {
+ // 0 for sucsuss, others for error
+ status: 0,
+ // error msg
+ msg: '',
+ data: ''
+ }
+ };
+};
diff --git a/visualdl/frontend/package.json b/visualdl/frontend/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..d6115a19b91d55c75e848a76be89da2164b86c08
--- /dev/null
+++ b/visualdl/frontend/package.json
@@ -0,0 +1,72 @@
+{
+ "name": "front",
+ "version": "1.0.1",
+ "description": "VisualDL frontend",
+ "author": "ecomfe",
+ "private": true,
+ "scripts": {
+ "release": "cross-env NODE_ENV=production node ./tool/build.js",
+ "build": "cross-env NODE_ENV=dev node ./tool/build.js",
+ "dev": "cross-env NODE_ENV=dev node tool/dev-server.js",
+ "lint": "./node_modules/fecs/bin/fecs --rule",
+ "precommit": "npm run lint",
+ "prepush": "npm run lint"
+ },
+ "engines": {
+ "node": ">= 6.4.0"
+ },
+ "dependencies": {
+ "axios": "^0.16.1",
+ "file-saver": "^1.3.3",
+ "lodash": "^4.17.4",
+ "normalize.css": "^6.0.0",
+ "san": "3.2.3",
+ "san-mui": "^1.0.4",
+ "san-router": "^1.1.1",
+ "san-store": "^1.0.1",
+ "san-update": "^2.1.0",
+ "xlsx": "^0.11.3"
+ },
+ "devDependencies": {
+ "autoprefixer": "^6.7.7",
+ "autoresponse": "^0.2.0",
+ "babel-core": "^6.25.0",
+ "babel-loader": "^6.2.7",
+ "babel-plugin-transform-class-properties": "^6.19.0",
+ "babel-plugin-transform-runtime": "^6.23.0",
+ "babel-preset-es2015": "^6.18.0",
+ "babel-preset-stage-0": "^6.16.0",
+ "babel-register": "^6.0.0",
+ "babel-runtime": "^6.26.0",
+ "case-sensitive-paths-webpack-plugin": "^2.1.1",
+ "chalk": "^1.1.3",
+ "copy-webpack-plugin": "^4.0.1",
+ "cross-env": "^4.0.0",
+ "css-loader": "^0.28.0",
+ "extract-text-webpack-plugin": "^2.1.0",
+ "fecs": "^1.5.2",
+ "file-loader": "^0.11.1",
+ "friendly-errors-webpack-plugin": "^1.6.1",
+ "glob": "^7.1.1",
+ "html-loader": "^0.4.4",
+ "html-webpack-plugin": "^2.28.0",
+ "http-proxy-middleware": "^0.17.4",
+ "husky": "^0.14.3",
+ "json-loader": "^0.5.4",
+ "opn": "^5.1.0",
+ "optimize-css-assets-webpack-plugin": "^1.3.2",
+ "ora": "^1.1.0",
+ "postcss-loader": "^1.3.3",
+ "raw-loader": "^0.5.1",
+ "san-loader": "^0.0.4",
+ "style-loader": "^0.16.1",
+ "stylus": "^0.54.5",
+ "stylus-loader": "^3.0.1",
+ "url-loader": "^0.5.8",
+ "webpack": "^2.4.1",
+ "webpack-dev-server": "^2.4.2",
+ "webpack-hot-middleware": "^2.19.1",
+ "webpack-merge": "^4.1.0",
+ "yargs": "^8.0.2"
+ }
+}
diff --git a/visualdl/frontend/src/App.san b/visualdl/frontend/src/App.san
new file mode 100644
index 0000000000000000000000000000000000000000..3e8f7d7fb525d90d794af934a0b93afa35628249
--- /dev/null
+++ b/visualdl/frontend/src/App.san
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
diff --git a/visualdl/frontend/src/common/fun/downLoadCSV.js b/visualdl/frontend/src/common/fun/downLoadCSV.js
new file mode 100644
index 0000000000000000000000000000000000000000..cfb887be400ccc5456402347f47bae751221a881
--- /dev/null
+++ b/visualdl/frontend/src/common/fun/downLoadCSV.js
@@ -0,0 +1,52 @@
+import XLSX from 'xlsx';
+import FileSaver from 'file-saver';
+// const JSON_TO_SHEET = XLSX.utils.json_to_sheet;
+const aoaToSheet = XLSX.utils.aoa_to_sheet;
+const saveAs = FileSaver.saveAs;
+function s2ab(s) {
+ if (typeof ArrayBuffer !== 'undefined') {
+ let buf = new ArrayBuffer(s.length);
+ let view = new Uint8Array(buf);
+ for (let i = 0; i !== s.length; ++i) {
+ view[i] = s.charCodeAt(i) & 0xFF;
+ }
+ return buf;
+ }
+ let buf = new Array(s.length);
+ for (let i = 0; i !== s.length; ++i) {
+ buf[i] = s.charCodeAt(i) & 0xFF;
+ }
+ return buf;
+}
+
+/**
+ * download Excel
+ *
+ * @desc transform data like [['A', 'B', 'C'], ['1', '2', '3'],[['1-1', '2-1', '3-1']]] to xlsx and download
+ * @param {Array} data the data for the xlsx
+ * @param {string} name filename
+ */
+export const generateXLSXandAutoDownload = function (data, name) {
+ let wopts = {
+ bookType: 'xlsx',
+ bookSST: false,
+ type: 'binary'
+ };
+ let ws = aoaToSheet(data);
+ let wb = {
+ SheetNames: ['Export'],
+ Sheets: {},
+ Props: {}
+ };
+ wb.Sheets.Export = ws;
+ let wbout = XLSX.write(wb, wopts);
+ saveAs(
+ new Blob(
+ [s2ab(wbout)],
+ {
+ type: 'application/octet-stream'
+ }
+ ),
+ name + '.xlsx' || 'sheetjs.xlsx'
+ );
+};
diff --git a/visualdl/frontend/src/home/Home.san b/visualdl/frontend/src/home/Home.san
new file mode 100644
index 0000000000000000000000000000000000000000..dec8ff6b0928a736c0b0f71778e14405ae7c3c9e
--- /dev/null
+++ b/visualdl/frontend/src/home/Home.san
@@ -0,0 +1,18 @@
+
+
+
+
+ welcome
+
+
+
+
+
+
+
+
diff --git a/visualdl/frontend/src/home/index.js b/visualdl/frontend/src/home/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..0ec3406a49ecc93bdb8581d649c3188ddd01f985
--- /dev/null
+++ b/visualdl/frontend/src/home/index.js
@@ -0,0 +1,10 @@
+import {router} from 'san-router';
+
+
+import HomePage from './Home';
+
+router.add({
+ target: '#content',
+ rule: '/home',
+ Component: HomePage
+});
diff --git a/visualdl/frontend/src/index.js b/visualdl/frontend/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..74c3dd0a64db71261214c06f1b652b1c6a53bc7b
--- /dev/null
+++ b/visualdl/frontend/src/index.js
@@ -0,0 +1,9 @@
+import 'normalize.css/normalize.css';
+import 'san-mui/index.css';
+let App = require('./App');
+new App({
+ data: {
+ titleName: 'VisualDL'
+ }
+}).attach(document.getElementById('root'));
+
diff --git a/visualdl/frontend/src/style/variables.styl b/visualdl/frontend/src/style/variables.styl
new file mode 100644
index 0000000000000000000000000000000000000000..c93c863a4200dc234ffc9f8e09d45054a5df76ea
--- /dev/null
+++ b/visualdl/frontend/src/style/variables.styl
@@ -0,0 +1 @@
+prefix = 'visual-dl-'
diff --git a/visualdl/frontend/template/index.html b/visualdl/frontend/template/index.html
new file mode 100755
index 0000000000000000000000000000000000000000..0840dad12bcdd3ef7ba12c1985e541ba36c6c895
--- /dev/null
+++ b/visualdl/frontend/template/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ VisualDL
+
+
+
+
+
+
+
diff --git a/visualdl/frontend/tool/HtmlReplacePlugin.js b/visualdl/frontend/tool/HtmlReplacePlugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..ce67a520d36f6c7d0fa0fe61e9cee42241cec5f6
--- /dev/null
+++ b/visualdl/frontend/tool/HtmlReplacePlugin.js
@@ -0,0 +1,24 @@
+function noopReplace (val) { return val; }
+
+function HtmlReplacePlugin(options) {
+ this.replacer = options.replacer || noopReplace;
+}
+
+HtmlReplacePlugin.prototype.apply = function(compiler) {
+
+ var replacer = this.replacer;
+
+ compiler.plugin('compilation', function(compilation) {
+
+ compilation.plugin('html-webpack-plugin-after-html-processing', function(htmlPluginData, callback) {
+
+ htmlPluginData.html = replacer(htmlPluginData.html, htmlPluginData);
+
+ callback(null, htmlPluginData);
+
+ });
+ });
+
+};
+
+module.exports = HtmlReplacePlugin;
diff --git a/visualdl/frontend/tool/build.js b/visualdl/frontend/tool/build.js
new file mode 100644
index 0000000000000000000000000000000000000000..190d4e9e3538fe51ab3ad0fc13c1b83e2405e5bd
--- /dev/null
+++ b/visualdl/frontend/tool/build.js
@@ -0,0 +1,64 @@
+const webpack = require('webpack');
+const rm = require('rimraf');
+const ora = require('ora');
+const chalk = require('chalk');
+const HtmlReplacePlugin = require('./HtmlReplacePlugin');
+
+// env 'production'
+process.env.WEBPACK_ENV = 'production';
+
+let webpackConfig = require('./webpack.prod.config');
+
+let spinner = ora('building for production...');
+spinner.start();
+
+let feRoots = {
+ 'index': '/dist/'
+};
+
+webpackConfig.plugins = webpackConfig.plugins.concat([
+
+ new HtmlReplacePlugin({
+ replacer: function(html, opt) {
+
+ var name = opt.outputName.replace(/\.html$/, '');
+
+ var feRoot = feRoots[name];
+
+ if (feRoot) {
+ html = html
+ .replace(/href="/g, 'href="' + feRoot)
+ .replace(/src="/g, 'src="' + feRoot);
+ }
+
+ return html;
+
+ }
+ })
+
+]);
+
+rm(webpackConfig.output.path, err => {
+
+ if (err) throw err;
+
+ webpack(webpackConfig, function(err, stats) {
+ spinner.stop()
+ if (err) throw err
+
+ process.stdout.write(stats.toString({
+ colors: true,
+ modules: false,
+ children: false,
+ chunks: false,
+ chunkModules: false
+ }) + '\n\n');
+
+ console.log(chalk.cyan(' Build complete.\n'));
+ console.log(chalk.yellow(
+ ' Tip: built files are meant to be served over an HTTP server.\n' +
+ ' Opening index.html over file:// won\'t work.\n'
+ ));
+ })
+
+});
diff --git a/visualdl/frontend/tool/dev-client.js b/visualdl/frontend/tool/dev-client.js
new file mode 100644
index 0000000000000000000000000000000000000000..d882b7bf83be35b3fca65f4d790c7451120e35c9
--- /dev/null
+++ b/visualdl/frontend/tool/dev-client.js
@@ -0,0 +1,7 @@
+var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
+
+hotClient.subscribe(function (event) {
+ if (event.action === 'reload') {
+ window.location.reload()
+ }
+})
diff --git a/visualdl/frontend/tool/dev-server.js b/visualdl/frontend/tool/dev-server.js
new file mode 100644
index 0000000000000000000000000000000000000000..f86feb1f5751fa3fc6bc6335dcf783eb3716ccb9
--- /dev/null
+++ b/visualdl/frontend/tool/dev-server.js
@@ -0,0 +1,116 @@
+process.env.NODE_ENV = 'dev';
+let devPort = 8999;
+let opn = require('opn');
+let express = require('express');
+let webpack = require('webpack');
+let proxyMiddleware = require('http-proxy-middleware');
+let webpackConfig = require('./webpack.dev.config');
+let autoresponse = require('autoresponse');
+let path = require('path');
+
+let port = devPort;
+let autoOpenBrowser = false;
+
+let app = express();
+let compiler = webpack(webpackConfig);
+
+let devMiddleware = require('webpack-dev-middleware')(compiler, {
+ publicPath: webpackConfig.output.publicPath,
+ disableHostCheck: true,
+ quiet: false,
+ noInfo: false,
+ stats: {
+ colors: true
+ },
+ headers: {'Access-Control-Allow-Origin': '*'}
+});
+
+let hotMiddleware = require('webpack-hot-middleware')(compiler, {
+ heartbeat: 2000
+});
+// force page reload when html-webpack-plugin template changes
+compiler.plugin('compilation', function (compilation) {
+ compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
+ hotMiddleware.publish({
+ action: 'reload'
+ });
+ cb();
+ });
+});
+
+var context = [
+ '/example',
+];
+var proxypath = '';
+
+var options = {
+ target: proxypath,
+ changeOrigin: true,
+};
+if (context.length) {
+ // app.use(proxyMiddleware(context, options));
+ app.use('/example', proxyMiddleware({
+ target: 'www.baidu.com',
+ changeOrigin: true,
+ }));
+}
+app.use(autoresponse({
+ logLevel: 'debug',
+ root: path.dirname(__dirname),
+ rules: [
+ {
+ match: '/example/:id',
+ method: ['get']
+ }
+ ],
+ post: {
+ match: function (reqPathName) {
+ return !/\.(html|js|map)$/.test(reqPathName) && /^\/(api)(.*)/.test(reqPathName);
+ }
+ },
+ delete: {
+ match: function () {
+ return true;
+ }
+ },
+ get: {
+ match: function (reqPathName) {
+ return !/\.(html|js|map)$/.test(reqPathName) && /^\/(api)(.*)/.test(reqPathName);
+ }
+ }
+
+}));
+
+// serve webpack bundle output
+app.use(devMiddleware);
+
+// enable hot-reload and state-preserving
+// compilation error display
+app.use(hotMiddleware);
+
+let uri = 'http://localhost:' + port;
+
+let _resolve;
+let readyPromise = new Promise(resolve => {
+ _resolve = resolve;
+});
+
+console.log('> Starting dev server...');
+devMiddleware.waitUntilValid(() => {
+ console.log('> Listening at ' + uri + '\n');
+ // when env is testing, don't need open it
+ if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
+ opn(uri);
+ }
+
+ _resolve();
+});
+
+let server = app.listen(port);
+
+module.exports = {
+ ready: readyPromise,
+ close() {
+ server.close();
+ }
+};
diff --git a/visualdl/frontend/tool/entry.js b/visualdl/frontend/tool/entry.js
new file mode 100644
index 0000000000000000000000000000000000000000..78754c6a5dc394ade713c1522657690d9be3b3fc
--- /dev/null
+++ b/visualdl/frontend/tool/entry.js
@@ -0,0 +1,76 @@
+const path = require('path');
+const projectPath = path.resolve(__dirname, '..');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+
+/**
+ * default apps
+ *
+ * @type {Array}
+ */
+let defaultApps = [
+ {
+ name: 'index',
+ feRoot: '/dist'
+ }
+];
+
+/**
+ * get entry js file
+ *
+ * @param {Array} apps appname
+ * @return {string} file path
+ */
+function getModules(apps) {
+
+ let modules = {};
+ apps.forEach(function (item) {
+ let app = item.name;
+ modules[app] = path.join(projectPath, 'src/' + app + '.js');
+ });
+
+ return modules;
+}
+
+/**
+ * get HtmlWebpackPlugin
+ *
+ * @param {string} app appname
+ * @param {boolan} template use template
+ * @return {HtmlWebpackPlugin} HtmlWebpackPlugin
+ */
+function getTemplate(app, template) {
+ let templateUrl = 'template/index.html';
+ if (template) {
+ templateUrl = `ejs-render-loader!template/${template}.ejs`;
+ }
+ return new HtmlWebpackPlugin({
+ filename: app + '.html',
+ template: templateUrl
+ });
+}
+
+/**
+ * get entry config
+ *
+ * @param {string} app appname
+ * @param {boolan} template use template
+ * @return {Object} config
+ */
+function getEntry(app, template) {
+
+ let buildApps = defaultApps.filter(function (item) {
+ let name = item.name;
+ return name === app;
+ });
+
+ buildApps = buildApps.length > 0 ? buildApps : defaultApps;
+
+ return {
+ module: getModules(buildApps),
+ template: buildApps.map(item => getTemplate(item.name, template))
+ };
+}
+
+
+module.exports.get = getEntry;
+module.exports.entry = defaultApps;
diff --git a/visualdl/frontend/tool/webpack.config.js b/visualdl/frontend/tool/webpack.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..24764208c456bb784655b5934e7c272782abf101
--- /dev/null
+++ b/visualdl/frontend/tool/webpack.config.js
@@ -0,0 +1,133 @@
+const webpack = require('webpack');
+const path = require('path');
+const projectPath = path.resolve(__dirname, '..');
+const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
+const argv = require('yargs').argv;
+const isDev = process.env.NODE_ENV === 'dev';
+const entry = require('./entry');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+
+function getLoaders(isDev, ext) {
+ let arr = ['css-loader'];
+ if (ext) {
+ arr.push(ext + '-loader');
+ }
+ if (isDev) {
+ arr.unshift('style-loader');
+ return arr;
+ }
+
+ return ExtractTextPlugin.extract({
+ use: arr,
+ fallback: 'style-loader'
+ });
+
+}
+
+/**
+ * entry config
+ *
+ * @type {Object}
+ */
+
+const ENTR_CONFIG = entry.get(argv.app, argv.template);
+/**
+ * webpack config
+ *
+ * @type {Object}
+ */
+const config = {
+ entry: ENTR_CONFIG.module,
+ output: {
+ path: path.resolve(projectPath, 'dist'),
+ filename: '[name].[hash].js'
+ },
+ resolve: {
+
+ alias: {
+ 'san-mui': 'san-mui/lib',
+ axios: 'axios/dist/axios.min.js'
+ },
+
+ extensions: ['.js', '.json', '.styl', '.css', '.html', '.san']
+ },
+
+ module: {
+ noParse: [
+ /node_modules\/(san|axios)\//
+ ],
+ rules: [
+ {
+ test: /\.san$/,
+ loader: 'san-loader',
+ options: {
+ loaders: {
+ stylus: getLoaders(isDev, 'stylus')
+ }
+ }
+ },
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ include: [
+ path.resolve(projectPath, 'src')
+ ],
+ loader: 'babel-loader'
+ },
+ {
+ test: /\.json$/,
+ loader: 'json-loader'
+ },
+ {
+ test: /\.html/,
+ loader: 'html-loader',
+ options: {
+ minimize: false
+ }
+ },
+ {
+ test: /\.css$/,
+ use: getLoaders(isDev)
+ },
+ {
+ test: /\.styl$/,
+ use: getLoaders(isDev, 'stylus')
+ },
+ {
+ test: /\.(gif|png|jpe?g)$/i,
+ loader: 'file-loader',
+ options: {
+ name: 'images/[name].[hash].[ext]'
+ }
+ },
+ {
+ test: /\.woff2?$/,
+ loader: 'url-loader',
+ options: {
+ name: 'fonts/[name].[hash].[ext]',
+ limit: '10000',
+ mimetype: 'application/font-woff'
+ }
+ },
+ {
+ test: /\.(ttf|eot|svg)$/,
+ loader: 'file-loader',
+ options: {
+ name: 'fonts/[name].[hash].[ext]'
+ }
+ }
+ ]
+ },
+ plugins: [
+
+ new CaseSensitivePathsPlugin(),
+ new webpack.LoaderOptionsPlugin({
+ test: /\.(styl|san)$/
+ })
+ ]
+};
+
+// template config
+config.plugins = config.plugins.concat(ENTR_CONFIG.template);
+
+module.exports = config;
diff --git a/visualdl/frontend/tool/webpack.dev.config.js b/visualdl/frontend/tool/webpack.dev.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..5b16958fafa422373ac0ba00075de5616d4d97ca
--- /dev/null
+++ b/visualdl/frontend/tool/webpack.dev.config.js
@@ -0,0 +1,32 @@
+const webpack = require('webpack');
+const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin');
+let merge = require('webpack-merge');
+let baseWebpackConfig = require('./webpack.config');
+
+// add hot-reload related code to entry chunks
+Object.keys(baseWebpackConfig.entry).forEach(function (name) {
+ baseWebpackConfig.entry[name] = ['./tool/dev-client'].concat(baseWebpackConfig.entry[name]);
+});
+
+/**
+ * dev config
+ *
+ * @type {Object}
+ */
+
+module.exports = merge(baseWebpackConfig, {
+ // cheap-module-eval-source-map is faster for development
+ devtool: '#cheap-module-eval-source-map',
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': {
+ 'NODE_ENV': '"dev"'
+ }
+ }),
+ // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.NoEmitOnErrorsPlugin(),
+ // https://github.com/ampedandwired/html-webpack-plugin
+ new FriendlyErrorsPlugin()
+ ]
+});
diff --git a/visualdl/frontend/tool/webpack.prod.config.js b/visualdl/frontend/tool/webpack.prod.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..fa73c7837ef2a7ea12803ba5d6885f01520bbc8b
--- /dev/null
+++ b/visualdl/frontend/tool/webpack.prod.config.js
@@ -0,0 +1,73 @@
+const webpack = require('webpack');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const path = require('path');
+const autoprefixer = require('autoprefixer');
+const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin');
+const bizCss = new ExtractTextPlugin('biz.[chunkhash].css');
+let merge = require('webpack-merge');
+let baseWebpackConfig = require('./webpack.config');
+const autoPrefixOptions = {
+ browsers: [
+ 'iOS >= 7',
+ 'Android >= 4.0',
+ 'ExplorerMobile >= 10',
+ 'ie >= 9'
+ ]
+};
+
+/**
+ * pro config
+ *
+ * @type {Object}
+ */
+
+module.exports = merge(baseWebpackConfig, {
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': {
+ 'NODE_ENV': 'production'
+ }
+ }),
+
+ new webpack.LoaderOptionsPlugin({
+ test: /\.(styl|san)$/,
+ san: {
+ autoprefixer: autoPrefixOptions
+ }
+ }),
+
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'vendor',
+ filename: 'vendor.[chunkhash].js',
+ minChunks: function (module, count) {
+ const resPath = module.resource;
+ return resPath && /\.js$/.test(resPath)
+ && resPath.indexOf(
+ path.join(__dirname, '../node_modules')
+ ) === 0;
+ }
+ }),
+
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'manifest',
+ minChunks: Infinity
+ }),
+
+ new webpack.optimize.UglifyJsPlugin({
+ compress: {
+ 'screw_ie8': true, // no ie6/7/8
+ 'warnings': false
+ },
+ comments: false,
+ sourceMap: false
+ }),
+
+ bizCss,
+
+ new OptimizeCSSPlugin({
+ cssProcessorOptions: {
+ safe: true
+ }
+ })
+ ]
+});