Vue+Webpack+Grunt集成

说明

Vue、Grunt、Webpack的知识请看官方网站

Grunt Tasks:构建、开发调试、打包,命令:grunt build,grunt default,grunt zipall。。。
Webpack:编译Vue、压缩文件
http2:启动http/2服务,命令  node http2.js
server:启动http/1.x服务,命令:node server.js

配置

webpack.config.js

var path = require('path')
var webpack = require('webpack')
module.exports = {
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, './dist'),
        publicPath: '/dist/',
        filename: 'build.js',
        chunkFilename: "[id].build.js?[chunkhash]"  //vue-router异步加载组件,分包构建的文件命令规则
    },
    resolveLoader: {
        root: path.join(__dirname, 'node_modules')
    },
    module: {
        loaders: [
            {
                test: /\.vue$/,
                loader: 'vue'
            },
            {
                test: /\.js$/,
                loader: 'babel',
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                loader: 'style!css'
            },
            {
                test: /\.(eot|svg|ttf|woff|woff2)$/,
                loader: 'file'
           },
            {
                test: /\.(png|jpg|gif|svg)$/,
                loader: 'file',
                query: {
                    name: '[name].[ext]?[hash]'
                }
            }
        ]
    },
    devtool: '#eval-source-map',
    plugins:[]
}

GruntFile.js

var webpack = require("webpack");
var webpackconfig = require("./webpack.config.js");
module.exports = function (grunt) {
    // 项目配置
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        clean: {
            build: {
                src: ["dist/**/*","prod/**/*",'apistore.zip','dist.zip','html.zip']  //清空文件和文件夹
            }
        },
        webpack:{//调用webpack功能进行编译构建
            options: webpackconfig,
            prod:{//产品构建
                devtool: '#source-map',
                plugins: webpackconfig.plugins.concat(
                    new webpack.DefinePlugin({
                        'process.env': {
                            NODE_ENV: '"production"'
                        }
                    }),
                    new webpack.optimize.UglifyJsPlugin({
                        compress: {
                            warnings: false
                        }
                    })
                )
            },
            dev:{//开发环境构建
                devtool: '#eval-source-map',
                plugins: webpackconfig.plugins.concat(
                    new webpack.optimize.DedupePlugin(),
                    new webpack.optimize.UglifyJsPlugin({
                        compress: {
                            warnings: false
                        }
                    })
                )
            }
        },
        "webpack-dev-server":{//开发调试,实时编译
            options: {
                webpack: webpackconfig,
                publicPath: webpackconfig.output.publicPath
            },
            start: {
                keepalive: true,
                port:8086,
                historyApiFallback: true,
                noInfo: true,
                inline:true,
                hot:true,
                compress: true,
                watchOptions: {
                    aggregateTimeout: 300,
                    poll: 1000
                }
            }
        },
        copy:{//拷贝文件
            common:{
                files: [
                    {expand: true, src: ['package.json'], dest: 'prod/'},
                    {expand: true, src: ['index.html'], dest: 'prod/'},
                    {expand: true, src: ['dist/**/*'], dest: 'prod/'},
                ]
            },
            http2:{
                files: [
                    {expand: true, src: ['http2.js'], dest: 'prod/'},
                    {expand: true, src: ['ssl/**/*'], dest: 'prod/'}
                ]
            },
            http:{
                files: [
                    {expand: true, src: ['http.js'], dest: 'prod/'},
                    {expand: true, src: ['mine.js'], dest: 'prod/'},
                    {expand: true, src: ['server.js'], dest: 'prod/'}
                ]
            }
        },
        zip: {//打zip包
            apistore:{
                dest:'apistore.zip',src:['prod/**/*']
            },
            dist:{
                dest:'dist.zip',src:['dist/**/*']
            },
            html:{
                dest:'html.zip',src:['dist/**/*.js','dist/**/*.css']
            }
        },
        shell: {//shell命令
            options: {
                stderr: true
            },
            dev: {
                command: 'npm run dev'
            }
        }
    });
    grunt.loadNpmTasks('grunt-contrib-requirejs');
    grunt.loadNpmTasks('grunt-webpack');
    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-imagemin');
    grunt.loadNpmTasks('grunt-zip');
    grunt.loadNpmTasks('grunt-shell');
    grunt.loadNpmTasks('grunt-contrib-copy');

    grunt.registerTask('ziph2', ['clean','webpack:prod','copy:common','copy:http2','zip:apistore']);
    grunt.registerTask('ziphttp', ['clean','webpack:prod','copy:common','copy:http','zip:apistore']);
    grunt.registerTask('zipall', ['clean','webpack:prod','copy:common','copy:http','copy:http2','zip:apistore']);
    grunt.registerTask('build-dev', ['clean','webpack:dev']);
    grunt.registerTask('build', ['clean','webpack:prod']);
    grunt.registerTask('default', ["webpack-dev-server"]);
}

http2.js

var http2 = require("http2"),
    url = require("url"),
    path = require("path"),
    fs = require("fs");
var port = 9443
    ,indexFile = "/index.html";//端口号和主文件地址

var options = {
    key: fs.readFileSync(__dirname + '/ssl/server.key.insecure'),
    cert: fs.readFileSync(__dirname + '/ssl/server.crt'),
    ca:  fs.readFileSync(__dirname + '/ssl/server.csr')
};
function app(request, response) {
    var uri = url.parse(request.url).pathname,
        filename = path.join(process.cwd(), uri);
    fs.exists(filename, function(exists) {
        if(!exists) {
            response.writeHead(404, {"Content-Type": "text/plain"});
            response.write("404 Not Found\n");
            response.end();
            return;
        }
        if (fs.statSync(filename).isDirectory()) {
            fs.exists(filename + indexFile, function(exists){
                if(exists) {
                    fs.readFile(filename + indexFile, "binary", loadFile);
                } else {
                    var files = fs.readdirSync(filename);
                    var html = '';
                    var showslash = uri + '/';
                    for (var i=0; i< files.length; i++){
                        if(uri == '/') {showslash = '/';} else {showslash = uri + '/';}
                        html += '<div><a href="' + showslash + files[i] + '">' + files[i] + "</a></div>";
                    }
                    response.writeHead(200);
                    response.write(html);
                    response.end();
                }
            });
        } else {
            fs.readFile(filename, "binary", loadFile);
        }
    });
    function loadFile(err, file) {
        if(err) {
            response.writeHead(500, {"Content-Type": "text/plain"});
            response.write(err + "\n");
            response.end();
            return;
        }
        response.writeHead(200);
        response.write(file, "binary");
        response.end();
    }
}
var server = http2.createServer(options, app);
server.listen(parseInt(port, 10));
console.log("Static file server running at\n  => https://localhost:" + port + "/\nCTRL + C to shutdown");

server.js

var resolve = require('path').resolve
    , join = require('path').join
    , exec = require('child_process').exec
    , connect = require('connect')
    , stylus = require('stylus')
    , jade = require('jade')
    , less = require('less-middleware')
    , url = require('url')
    ,compression = require('compression')
    ,bodyParser = require('body-parser')
    ,serveStatic = require('serve-static')
    ,serveIndex = require('serve-index')
    ,morgan = require('morgan')
    , fs = require('fs');
var config = {
    auth:undefined,//<user>:<pass> specify basic auth credentials
    logs:true,//disable request logging
    format:"dev",//fmt specify the log format string
    favicon:undefined,//path serve the given favicon
    jade:true,//disable jade rendering
    less:true,//disable less css rendering
    cors:true,//allows cross origin access serving
    compress:true,//gzip or deflate the response
    exec:undefined,//execute command on each request
    hidden:true,//enable hidden file serving
    dirs:true,//disable directory serving
    icons:true,//disable icons
    stylus:true,//disable stylus rendering
    port:9449//
}
// path
var path = resolve('.');
// setup the server
var server = connect();
// basic auth
if (config.auth) {
    var user = config.auth.split(':')[0];
    var pass = config.auth.split(':')[1];
    if (!user || !pass) throw new Error('user and pass required');
    server.use(connect.basicAuth(user, pass));
}
//
server.use(bodyParser.urlencoded());
// ignore favicon
if(config.favicon){
    favicon = require('serve-favicon');
    server.use(favicon(__dirname + config.favicon));
}
// logger
if (config.logs) server.use(morgan(config.format));
// convert .styl to .css to trick stylus.middleware
if (config.stylus) {
    server.use(function(req, res, next){
        req.url = req.url.replace(/\.styl$/, '.css');
        next();
    });
}
// jade
if (config.jade) {
    server.use(function(req, res, next){
        if (!req.url.match(/\.jade$/)) return next();
        var file = join(path, url.parse(req.url).pathname);
        fs.readFile(file, 'utf8', function(err, str){
            if (err) return next(err);
            try {
                var fn = jade.compile(str, { filename: file });
                str = fn();
                res.setHeader('Content-Type', 'text/html');
                res.setHeader('Content-Length', Buffer.byteLength(str));
                res.end(str);
            } catch (err) {
                next(err);
            }
        });
    });
}
// stylus
server.use(stylus.middleware({ src: path }));
// less
if (config.less) {
    server.use(less(path));
}
// CORS access for files
if (config.cors) {
    server.use(function(req, res, next){
        res.setHeader('Access-Control-Allow-Origin', '*');
        res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
        res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With, Accept, x-csrf-token, origin');
        if ('OPTIONS' == req.method) return res.end();
        next();
    });
}
// compression
if (config.compress) {
    server.use(compression());
}
// exec command
if (config.exec) {
    server.use(function (req, res, next){
        exec(program.exec, next);
    });
}
// static files
server.use(serveStatic(path, {
    maxAge: '1d',
    index: ['index.html'],
    //setHeaders: setCustomCacheControl,
    hidden: config.hidden
}))
// directory serving
if (config.dirs) {
    server.use(path,serveIndex(path, {
        hidden: config.hidden
        , icons: config.icons
    }));
}
// start the server
server.listen(config.port, function () {
    console.log('\033[90mserving \033[36m%s\033[90m on port \033[96m%d\033[0m', path, config.port);
});

package.json

{
  "name": "apistore",
  "description": "interferce project",
  "private": true,
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --config ./webpack.dev.config.js  --inline --hot --port 8086",
    "build": "cross-env NODE_ENV=production webpack --config ./webpack.prod.config.js --progress --hide-modules",
    "serve": "node server.js",
    "http2": "node http2.js"
  },
  "dependencies": {
    "element-ui": "^1.0.0",
    "vue": "^2.1.4",
    "vue-router":"^2.1.1",
    "http2":"^3.3.6",
    "http2-static-server":"^1.0.0",
    "serve": "^1.4.0",
    "path":"0.12.7",
    "http":"0.0.0",
    "connect": "3.5.0",
    "jade": "*",
    "less-middleware": "*",
    "stylus": "*",
    "serve-favicon":"2.3.2",
    "compression":"1.6.2",
    "body-parser":"1.15.2",
    "serve-static":"1.11.1",
    "serve-index":"1.8.0",
    "morgan":"1.7.0"
  },
  "devDependencies": {
    "babel-core": "^6.0.0",
    "babel-loader": "^6.0.0",
    "babel-preset-es2015": "^6.13.2",
    "cross-env": "^1.0.6",
    "css-loader": "^0.23.1",
    "file-loader": "^0.8.5",
    "style-loader": "^0.13.1",
    "vue-loader": "^9.5.1",
    "webpack": "2.1.0-beta.22",
    "webpack-dev-server": "^2.1.0-beta.0",
    "path":"0.12.7",
    "serve": "^1.4.0",
    "mockjs":"^1.0.1-beta3",
    "grunt-contrib-copy":"^1.0.0",
    "grunt-zip":"^0.17.1",
    "grunt-contrib-imagemin":"^1.0.1",
    "grunt-contrib-clean":"^1.0.0",
    "grunt-contrib-requirejs":"^1.0.0",
    "grunt-webpack":"^1.0.18",
    "http2":"^3.3.6",
    "http2-static-server":"^1.0.0",
    "grunt":"^1.0.1",
    "grunt-contrib-watch":"^1.0.0",
    "grunt-shell":"^2.1.0"
  }
}