diff --git a/.gitignore b/.gitignore index e5aa4bfe..b2b5bf11 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ test/unit/coverage test/e2e/reports .tern-project pubstorm.json +build_old diff --git a/.netlify b/.netlify new file mode 100644 index 00000000..55f02dc3 --- /dev/null +++ b/.netlify @@ -0,0 +1 @@ +{"site_id":"f0cf1f6e-6afe-44e4-b4dc-7f055292d716","path":"dist"} \ No newline at end of file diff --git a/build/build.js b/build/build.js index a4164caf..b3c9aad4 100644 --- a/build/build.js +++ b/build/build.js @@ -1,19 +1,29 @@ // https://github.com/shelljs/shelljs +require('./check-versions')() require('shelljs/global') env.NODE_ENV = 'production' +var path = require('path') +var config = require('../config') var ora = require('ora') var webpack = require('webpack') -var conf = require('./webpack.prod.conf') +var webpackConfig = require('./webpack.prod.conf') + +console.log( + ' Tip:\n' + + ' Built files are meant to be served over an HTTP server.\n' + + ' Opening index.html over file:// won\'t work.\n' +) var spinner = ora('building for production...') spinner.start() -rm('-rf', 'dist') -mkdir('dist') -cp('-R', 'static', conf.output.path) +var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) +rm('-rf', assetsPath) +mkdir('-p', assetsPath) +cp('-R', 'static/*', assetsPath) -webpack(conf, function (err, stats) { +webpack(webpackConfig, function (err, stats) { spinner.stop() if (err) throw err process.stdout.write(stats.toString({ diff --git a/build/check-versions.js b/build/check-versions.js new file mode 100644 index 00000000..e2b6cf74 --- /dev/null +++ b/build/check-versions.js @@ -0,0 +1,45 @@ +var semver = require('semver') +var chalk = require('chalk') +var packageConfig = require('../package.json') +var exec = function (cmd) { + return require('child_process') + .execSync(cmd).toString().trim() +} + +var versionRequirements = [ + { + name: 'node', + currentVersion: semver.clean(process.version), + versionRequirement: packageConfig.engines.node + }, + { + name: 'npm', + currentVersion: exec('npm --version'), + versionRequirement: packageConfig.engines.npm + } +] + +module.exports = function () { + var warnings = [] + for (var i = 0; i < versionRequirements.length; i++) { + var mod = versionRequirements[i] + if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { + warnings.push(mod.name + ': ' + + chalk.red(mod.currentVersion) + ' should be ' + + chalk.green(mod.versionRequirement) + ) + } + } + + if (warnings.length) { + console.log('') + console.log(chalk.yellow('To use this template, you must update following to modules:')) + console.log() + for (var i = 0; i < warnings.length; i++) { + var warning = warnings[i] + console.log(' ' + warning) + } + console.log() + process.exit(1) + } +} diff --git a/build/dev-client.js b/build/dev-client.js index 89f00795..18aa1e21 100644 --- a/build/dev-client.js +++ b/build/dev-client.js @@ -1,3 +1,4 @@ +/* eslint-disable */ require('eventsource-polyfill') var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') diff --git a/build/dev-server.js b/build/dev-server.js index 3e5179af..b7eeae3a 100644 --- a/build/dev-server.js +++ b/build/dev-server.js @@ -1,28 +1,26 @@ +require('./check-versions')() +var config = require('../config') +if (!process.env.NODE_ENV) process.env.NODE_ENV = config.dev.env +var path = require('path') var express = require('express') var webpack = require('webpack') -var config = require('./webpack.dev.conf') +var opn = require('opn') var proxyMiddleware = require('http-proxy-middleware') +var webpackConfig = process.env.NODE_ENV === 'testing' + ? require('./webpack.prod.conf') + : require('./webpack.dev.conf') // default port where dev server listens for incoming traffic -var port = process.env.PORT || 8080 - -var app = express() -var compiler = webpack(config) - +var port = process.env.PORT || config.dev.port // Define HTTP proxies to your custom API backend // https://github.com/chimurai/http-proxy-middleware -var proxyTable = { - // '/api': { - // target: 'http://jsonplaceholder.typicode.com', - // changeOrigin: true, - // pathRewrite: { - // '^/api': '' - // } - // } -} +var proxyTable = config.dev.proxyTable + +var app = express() +var compiler = webpack(webpackConfig) var devMiddleware = require('webpack-dev-middleware')(compiler, { - publicPath: config.output.publicPath, + publicPath: webpackConfig.output.publicPath, stats: { colors: true, chunks: false @@ -58,12 +56,15 @@ app.use(devMiddleware) app.use(hotMiddleware) // serve pure static assets -app.use('/static', express.static('./static')) +var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) +app.use(staticPath, express.static('./static')) module.exports = app.listen(port, function (err) { if (err) { console.log(err) return } - console.log('Listening at http://localhost:' + port + '\n') + var uri = 'http://localhost:' + port + console.log('Listening at ' + uri + '\n') + opn(uri) }) diff --git a/build/prod-server.js b/build/prod-server.js deleted file mode 100644 index 5a78c477..00000000 --- a/build/prod-server.js +++ /dev/null @@ -1,27 +0,0 @@ -var express = require('express') -var port = process.env.PORT || 80 // Might have some issues running it from your local machine. Best to run this on server -var baseConfig = require('./webpack.base.conf') - -var app = express(), - path = baseConfig.output.distPath - console.log(path) - -// handle fallback for HTML5 history API -app.use(require('connect-history-api-fallback')()) - -// server webpack outputs -app.set('appPath', path) -app.use(express.static(baseConfig.output.path)) - -app.route('/*') - .get(function(req, res){ - res.sendFile(app.get('appPath') + '/index.html') - }) - -app.listen(port, function (err) { - if (err) { - console.log(err) - return - } - console.log('Listening at http://localhost:' + port + '\n') -}) diff --git a/build/css-loaders.js b/build/utils.js similarity index 53% rename from build/css-loaders.js rename to build/utils.js index 5db0d95d..dc3cdd01 100644 --- a/build/css-loaders.js +++ b/build/utils.js @@ -1,6 +1,15 @@ +var path = require('path') +var config = require('../config') var ExtractTextPlugin = require('extract-text-webpack-plugin') -module.exports = function (options) { +exports.assetsPath = function (_path) { + var assetsSubDirectory = process.env.NODE_ENV === 'production' + ? config.build.assetsSubDirectory + : config.dev.assetsSubDirectory + return path.posix.join(assetsSubDirectory, _path) +} + +exports.cssLoaders = function (options) { options = options || {} // generate loader string to be used with extract text plugin function generateLoaders (loaders) { @@ -16,6 +25,8 @@ module.exports = function (options) { return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') }).join('!') + // Extract CSS when that option is specified + // (which is the case during production build) if (options.extract) { return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) } else { @@ -23,9 +34,10 @@ module.exports = function (options) { } } - // http://vuejs.github.io/vue-loader/configurations/extract-css.html + // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html return { css: generateLoaders(['css']), + postcss: generateLoaders(['css']), less: generateLoaders(['css', 'less']), sass: generateLoaders(['css', 'sass?indentedSyntax']), scss: generateLoaders(['css', 'sass']), @@ -33,3 +45,17 @@ module.exports = function (options) { styl: generateLoaders(['css', 'stylus']) } } + +// Generate loaders for standalone style files (outside of .vue) +exports.styleLoaders = function (options) { + var output = [] + var loaders = exports.cssLoaders(options) + for (var extension in loaders) { + var loader = loaders[extension] + output.push({ + test: new RegExp('\\.' + extension + '$'), + loader: loader + }) + } + return output +} diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js index 552e552a..dc13c4f9 100644 --- a/build/webpack.base.conf.js +++ b/build/webpack.base.conf.js @@ -1,22 +1,32 @@ var path = require('path') -var cssLoaders = require('./css-loaders') +var config = require('../config') +var utils = require('./utils') var projectRoot = path.resolve(__dirname, '../') +var env = process.env.NODE_ENV +// check env & config/index.js to decide weither to enable CSS Sourcemaps for the +// various preprocessor loaders added to vue-loader at the end of this file +var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap) +var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap) +var useCssSourceMap = cssSourceMapDev || cssSourceMapProd + module.exports = { entry: { app: './src/main.js' }, output: { - path: path.resolve(__dirname, '../dist/static'), - distPath: path.resolve(__dirname, '../dist'), - publicPath: './static/', + path: config.build.assetsRoot, + publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, filename: '[name].js' }, resolve: { extensions: ['', '.js', '.vue'], fallback: [path.join(__dirname, '../node_modules')], alias: { - 'src': path.resolve(__dirname, '../src') + 'vue$': 'vue/dist/vue', + 'src': path.resolve(__dirname, '../src'), + 'assets': path.resolve(__dirname, '../src/assets'), + 'components': path.resolve(__dirname, '../src/components') } }, resolveLoader: { @@ -50,30 +60,35 @@ module.exports = { }, { test: /\.json$/, - loader: 'json', - exclude: /node_modules/ + loader: 'json' }, { - test: /\.html$/, - loader: 'vue-html' + test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, + loader: 'url', + query: { + limit: 10000, + name: utils.assetsPath('img/[name].[hash:7].[ext]') + } }, { - test: /\.(png|jpg|gif|woff2?|eot|ttf)(\?.*)?$/, + test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url', query: { limit: 10000, - name: '[name].[ext]?[hash:7]' - }, - exclude: /node_modules/ + name: utils.assetsPath('fonts/[name].[hash:7].[ext]') + } } ] }, - vue: { - loaders: cssLoaders() - }, eslint: { formatter: require('eslint-friendly-formatter') }, - noParse: ['node_modules','static'], - cache: true + vue: { + loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }), + postcss: [ + require('autoprefixer')({ + browsers: ['last 2 versions'] + }) + ] + } } diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js index 861da889..7e1a104f 100644 --- a/build/webpack.dev.conf.js +++ b/build/webpack.dev.conf.js @@ -1,22 +1,25 @@ +var config = require('../config') var webpack = require('webpack') var merge = require('webpack-merge') -var baseConfig = require('./webpack.base.conf') +var utils = require('./utils') +var baseWebpackConfig = require('./webpack.base.conf') var HtmlWebpackPlugin = require('html-webpack-plugin') // add hot-reload related code to entry chunks -Object.keys(baseConfig.entry).forEach(function (name) { - baseConfig.entry[name] = ['./build/dev-client'].concat(baseConfig.entry[name]) +Object.keys(baseWebpackConfig.entry).forEach(function (name) { + baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) }) -module.exports = merge(baseConfig, { +module.exports = merge(baseWebpackConfig, { + module: { + loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) + }, // eval-source-map is faster for development devtool: '#eval-source-map', - output: { - // necessary for the html plugin to work properly - // when serving the html from in-memory - publicPath: '/' - }, plugins: [ + new webpack.DefinePlugin({ + 'process.env': config.dev.env + }), // https://github.com/glenjamin/webpack-hot-middleware#installation--usage new webpack.optimize.OccurenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js index b44c4851..3e109bb4 100644 --- a/build/webpack.prod.conf.js +++ b/build/webpack.prod.conf.js @@ -1,34 +1,35 @@ +var path = require('path') +var config = require('../config') +var utils = require('./utils') var webpack = require('webpack') var merge = require('webpack-merge') -var baseConfig = require('./webpack.base.conf') -var cssLoaders = require('./css-loaders') +var baseWebpackConfig = require('./webpack.base.conf') var ExtractTextPlugin = require('extract-text-webpack-plugin') var HtmlWebpackPlugin = require('html-webpack-plugin') +var env = process.env.NODE_ENV === 'testing' + ? require('../config/test.env') + : config.build.env -// whether to generate source map for production files. -// disabling this can speed up the build. -var SOURCE_MAP = true - -module.exports = merge(baseConfig, { - devtool: SOURCE_MAP ? '#source-map' : false, +var webpackConfig = merge(baseWebpackConfig, { + module: { + loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) + }, + devtool: config.build.productionSourceMap ? '#source-map' : false, output: { - // naming output files with hashes for better caching. - // dist/index.html will be auto-generated with correct URLs. - filename: '[name].[chunkhash].js', - chunkFilename: '[id].[chunkhash].js' + path: config.build.assetsRoot, + filename: utils.assetsPath('js/[name].[chunkhash].js'), + chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') }, vue: { - loaders: cssLoaders({ - sourceMap: SOURCE_MAP, + loaders: utils.cssLoaders({ + sourceMap: config.build.productionSourceMap, extract: true }) }, plugins: [ - // http://vuejs.github.io/vue-loader/workflow/production.html + // http://vuejs.github.io/vue-loader/en/workflow/production.html new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: '"production"' - } + 'process.env': env }), new webpack.optimize.UglifyJsPlugin({ compress: { @@ -37,12 +38,14 @@ module.exports = merge(baseConfig, { }), new webpack.optimize.OccurenceOrderPlugin(), // extract css into its own file - new ExtractTextPlugin('[name].[contenthash].css'), + new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')), // generate dist index.html with correct asset hash for caching. // you can customize output by editing /index.html // see https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ - filename: '../index.html', + filename: process.env.NODE_ENV === 'testing' + ? 'index.html' + : config.build.index, template: 'index.html', inject: true, minify: { @@ -51,7 +54,49 @@ module.exports = merge(baseConfig, { removeAttributeQuotes: true // more options: // https://github.com/kangax/html-minifier#options-quick-reference + }, + // necessary to consistently work with multiple chunks via CommonsChunkPlugin + chunksSortMode: 'dependency' + }), + // split vendor js into its own file + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks: function (module, count) { + // any required modules inside node_modules are extracted to vendor + return ( + module.resource && + /\.js$/.test(module.resource) && + module.resource.indexOf( + path.join(__dirname, '../node_modules') + ) === 0 + ) } + }), + // extract webpack runtime and module manifest to its own file in order to + // prevent vendor hash from being updated whenever app bundle is updated + new webpack.optimize.CommonsChunkPlugin({ + name: 'manifest', + chunks: ['vendor'] }) ] }) + +if (config.build.productionGzip) { + var CompressionWebpackPlugin = require('compression-webpack-plugin') + + webpackConfig.plugins.push( + new CompressionWebpackPlugin({ + asset: '[path].gz[query]', + algorithm: 'gzip', + test: new RegExp( + '\\.(' + + config.build.productionGzipExtensions.join('|') + + ')$' + ), + threshold: 10240, + minRatio: 0.8 + }) + ) +} + +module.exports = webpackConfig diff --git a/package.json b/package.json index ebadaa41..c84795a6 100644 --- a/package.json +++ b/package.json @@ -79,5 +79,9 @@ "webpack-dev-middleware": "^1.4.0", "webpack-hot-middleware": "^2.6.0", "webpack-merge": "^0.8.3" - } + }, + "engines": { + "node": ">= 4.0.0", + "npm": ">= 3.0.0" +} }