gulp & webpack整合,魚(yú)與熊掌我都要!

為什么需要前端工程化?

前端工程化的意義在于讓前端這個(gè)行業(yè)由野蠻時(shí)代進(jìn)化為正規(guī)軍時(shí)代,近年來(lái)很多相關(guān)的工具和概念誕生。好奇心日?qǐng)?bào)在進(jìn)行前端工程化的過(guò)程中,主要的挑戰(zhàn)在于解決如下問(wèn)題:
? 如何管理多個(gè)項(xiàng)目的前端代碼?
? 如何同步修改復(fù)用代碼?
? 如何讓開(kāi)發(fā)體驗(yàn)更爽?

項(xiàng)目實(shí)在太多

之前寫(xiě)過(guò)一篇博文 如何管理被多個(gè)項(xiàng)目引用的通用項(xiàng)目?,文中提到過(guò)好奇心日?qǐng)?bào)的項(xiàng)目偏多(PC/Mobile/App/Pad),要為這么多項(xiàng)目開(kāi)發(fā)前端組件并維護(hù)是一個(gè)繁瑣的工作,并且會(huì)有很多冗余的工作。

更好的管理前端代碼

前端代碼要適配后臺(tái)目錄的規(guī)范,本來(lái)可以很美好的前端目錄結(jié)構(gòu)被拆得四分五裂,前端代碼分散不便于管理,并且開(kāi)發(fā)體驗(yàn)很不友好。
而有了前端工程化的概念,前端項(xiàng)目和后臺(tái)項(xiàng)目可以徹底分離,前端按自己想要的目錄結(jié)構(gòu)組織代碼, 然后按照一定的方式構(gòu)建輸出到后臺(tái)項(xiàng)目中,簡(jiǎn)直完美(是不是有種后宮佳麗三千的感覺(jué))。

技術(shù)選型

調(diào)研了市場(chǎng)主流的構(gòu)建工具,其中包括gulp、webpack、fis,最后決定圍繞gulp打造前端工程化方案,同時(shí)引入webpack來(lái)管理模塊化代碼,大致分工如下:
gulp:處理html壓縮/預(yù)處理/條件編譯,圖片壓縮,精靈圖自動(dòng)合并等任務(wù)
webpack:管理模塊化,構(gòu)建js/css。

至于為什么選擇gulp & webpack,主要原因在于gulp相對(duì)來(lái)說(shuō)更靈活,可以做更多的定制化任務(wù),而webpack在模塊化方案實(shí)在太優(yōu)秀(情不自禁的贊美)。

怎么設(shè)計(jì)前端項(xiàng)目目錄結(jié)構(gòu)?

抽離出來(lái)的前端項(xiàng)目目錄結(jié)構(gòu)如下


前端項(xiàng)目結(jié)構(gòu)

appfe目錄:appfe就是前面提到的前端項(xiàng)目,這個(gè)項(xiàng)目主要包含兩部分:前端代碼、構(gòu)建任務(wù)
appfe > gulp目錄:包含了所有的gulp子任務(wù),每個(gè)子任務(wù)包含相關(guān)任務(wù)的所有邏輯。
appfe > src目錄:包含了所有前端代碼,比如頁(yè)面、組件、圖片、字體文件等等。
appfe > package.json:這個(gè)不用說(shuō)了吧。
appfe > gulpfile.js:gulp入口文件,引入了所有的gulp子任務(wù)。

理想很豐滿(mǎn),現(xiàn)實(shí)卻很骨感,這么美好的愿望,在具體實(shí)踐過(guò)程中,注定要花不少心思,要踩不少坑。
好奇心日?qǐng)?bào)這次升級(jí)改造即將上線(xiàn),終于也有時(shí)間把之前零零碎碎的博文整合在一起,并且結(jié)合自己的體會(huì)分享給大家,當(dāng)然未來(lái)可能還會(huì)有較大的調(diào)整,這兒拋磚引玉,大家可以參考思路。

gulp 是什么?

gulp是一個(gè)基于流的構(gòu)建工具,相對(duì)其他構(gòu)件工具來(lái)說(shuō),更簡(jiǎn)潔更高效。
Tip:之前寫(xiě)過(guò)一篇gulp 入門(mén),可以參考下,如果對(duì)gulp已經(jīng)有一定的了解請(qǐng)直接跳過(guò)。

webpack 是什么?

webpack是模塊化管理的工具,使用webpack可實(shí)現(xiàn)模塊按需加載,模塊預(yù)處理,模塊打包等功能。
Tip:之前寫(xiě)過(guò)一篇webpack 入門(mén),可以參考下,如果對(duì)webpack已經(jīng)有一定的了解請(qǐng)直接跳過(guò)。

如何整合gulp & webpack

webpack是眾多gulp子任務(wù)中比較復(fù)雜的部分,主要對(duì)JS/CSS進(jìn)行相關(guān)處理。
包括:模塊分析、按需加載、JS代碼壓縮合并、抽離公共模塊、SourceMap、PostCSS、CSS代碼壓縮等等...

webpack-stream方案[不推薦]

使用webpack-stream雖然可以很方便的將webpack整合到gulp中,但是有致命的問(wèn)題存在:
如果關(guān)閉webpack的監(jiān)聽(tīng)模式,那么每次文件變動(dòng)就會(huì)全量編譯JS/CSS文件,非常耗時(shí)。
如果打開(kāi)webpack的監(jiān)聽(tīng)模式,那么會(huì)阻塞其他gulp任務(wù),導(dǎo)致其他gulp任務(wù)的監(jiān)聽(tīng)失效。
所以這種方案幾乎不可用!

webpack原生方案

直接使用webpack原生方案,相對(duì)來(lái)說(shuō)更靈活。
Tip:代碼較復(fù)雜,里面涉及的知識(shí)點(diǎn)也很多,建議看看形狀就好,如果真有興趣,可以好好研究研究,畢竟花了很長(zhǎng)時(shí)間去思考這些方案。

// webpack.config.js 關(guān)鍵地方都有大致注釋
var _ = require('lodash');
var path = require('path');
var webpack = require('webpack');
var ExtractTextPlugin = require("extract-text-webpack-plugin");

var autoprefixer = require('autoprefixer');
var flexibility = require('postcss-flexibility');
var sorting = require('postcss-sorting');
var color_rgba_fallback = require('postcss-color-rgba-fallback');
var opacity = require('postcss-opacity');
var pseudoelements = require('postcss-pseudoelements');
var will_change = require('postcss-will-change');
var cssnano = require('cssnano');

var project = require('./lib/project')();
var config = require('./config.' + project).webpack;


// loaders配置
var getLoaders = function(env) {
    return [{
        test: /\.jsx?$/,
        exclude: /(node_modules|bower_components|vendor)/,
        loader: 'babel?presets[]=es2015&cacheDirectory=true!preprocess?PROJECT=' + project
    }, {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract("style-loader", "css-loader!postcss-loader")
    }, {
        test: /\.less$/,
        loader: ExtractTextPlugin.extract("style-loader", "css-loader!postcss-loader!less-loader")
    }, {
        test: /\/jquery\.js$/,
        loader: 'expose?$!expose?jQuery!expose?jquery'
    }, {
        test: /\.xtpl$/,
        loader: 'xtpl'
    }, {
        test: /\.modernizrrc$/,
        loader: "modernizr"
    }];
};

// 別名配置
var getAlias = function(env) {
    return {
        // 特殊
        'jquery': path.resolve(__dirname, '../src/vendor/jquery2/jquery.js'),

        // 正常第三方庫(kù)
        'jquery.js': path.resolve(__dirname, '../src/vendor/jquery2/jquery.js'),
    };
};

// 插件配置
var getPlugins = function(env) {
    var defaultPlugins = [
        // 這個(gè)不僅是別名,還可以在遇到別名的時(shí)候自動(dòng)引入模塊
        new webpack.ProvidePlugin({
            '$': 'jquery.js',
            'jquery': 'jquery.js',
            'jQuery': 'jquery.js',
        }),
        // 抽離公共模塊
        new webpack.optimize.CommonsChunkPlugin('common', 'common.js'),
        new ExtractTextPlugin(
            path.join('../../stylesheets', project, '/[name].css'), {
                allChunks: true
            }
        )
    ];

    if (env == 'production') {
        // 線(xiàn)上模式的配置,去除依賴(lài)中重復(fù)的插件/壓縮js/排除報(bào)錯(cuò)的插件
        plugins = _.union(defaultPlugins, [
            new webpack.optimize.DedupePlugin(),
            new webpack.optimize.UglifyJsPlugin({
                sourceMap: false,
                mangle: {
                    except: ['$', 'jQuery']
                }
            }),
            new webpack.NoErrorsPlugin()
        ]);
    } else {
        plugins = _.union(defaultPlugins, []);
    }

    return plugins;
};

// postcss配置
var getPostcss = function(env) {
    var postcss = [
        autoprefixer({ browers: ['last 2 versions', 'ie >= 9', '> 5% in CN'] }),
        flexibility,
        will_change,
        color_rgba_fallback,
        opacity,
        pseudoelements,
        sorting
    ];

    if (env == 'production') {
        // 線(xiàn)上模式的配置,css壓縮
        return function() {
            return _.union([
                cssnano({
                    // 關(guān)閉cssnano的autoprefixer選項(xiàng),不然會(huì)和前面的autoprefixer沖突
                    autoprefixer: false, 
                    reduceIdents: false,
                    zindex: false,
                    discardUnused: false,
                    mergeIdents: false
                })
            ], postcss);
        };
    } else {
        return function() {
            return _.union([], postcss);
        }
    }
};

// 作為函數(shù)導(dǎo)出配置,代碼更簡(jiǎn)潔
module.exports = function(env) {
    return {
        context: config.context,
        entry: config.src,
        output: {
            path: path.join(config.jsDest, project),
            filename: '[name].js',
            chunkFilename: '[name].[chunkhash:8].js',
            publicPath: '/assets/' + project + '/'
        },
        devtool: "eval",
        watch: false,
        profile: true,
        cache: true,
        module: {
            loaders: getLoaders(env)
        },
        resolve: {
            alias: getAlias(env)
        },
        plugins: getPlugins(env),
        postcss: getPostcss(env)
    };
}
// webpack任務(wù)
var _ = require('lodash');
var del = require('del');
var webpack = require('webpack');
var gulp = require('gulp');
var plumber = require('gulp-plumber');
var newer = require('gulp-newer');
var logger = require('gulp-logger');

var project = require('../lib/project')();
var config = require('../config.' + project).webpack;
var compileLogger = require('../lib/compileLogger');
var handleErrors = require('../lib/handleErrors');


// 生成js/css
gulp.task('webpack', ['clean:webpack'], function(callback) {
    webpack(require('../webpack.config.js')(), function(err, stats) {
        compileLogger(err, stats);
        callback();
    });
});

// 生成js/css-監(jiān)聽(tīng)模式
gulp.task('watch:webpack', ['clean:webpack'], function() {
    webpack(_.merge(require('../webpack.config.js')(), {
        watch: true
    })).watch(200, function(err, stats) {
        compileLogger(err, stats);
    });
});

// 生成js/css-build模式
gulp.task('build:webpack', ['clean:webpack'], function(callback) {
    webpack(_.merge(require('../webpack.config.js')('production'), {
        devtool: null
    }), function(err, stats) {
        compileLogger(err, stats);
        callback();
    });
});

// 清理js/css
gulp.task('clean:webpack', function() {
    return del([
        config.jsDest,
        config.cssDest
    ], { force: true });
});

實(shí)踐中遇到那些坑?

如何組織gulp任務(wù)?

由于gulp任務(wù)較多,并且每個(gè)核心任務(wù)都有關(guān)聯(lián)任務(wù),比如webpack的關(guān)聯(lián)任務(wù)就有webpack/watch:webpack/build:webpack/clean:webpack,如何組織這些子任務(wù)是一個(gè)需要很小心的事情,出于一直以來(lái)的習(xí)慣:把關(guān)聯(lián)的邏輯放在一起,所以我的方案是webpack相關(guān)的任務(wù)放到一個(gè)文件,然后定義了default/clean/watch/build四個(gè)入口任務(wù)來(lái)引用對(duì)應(yīng)的子任務(wù)。

webpack任務(wù)結(jié)構(gòu)

gulp怎么實(shí)現(xiàn)錯(cuò)誤自啟動(dòng)

使用watch模式可以更高效的開(kāi)發(fā),監(jiān)聽(tīng)到改動(dòng)就自動(dòng)執(zhí)行任務(wù),但是如果過(guò)程中遇到錯(cuò)誤,gulp就會(huì)報(bào)錯(cuò)并終止watch模式,必須重新啟動(dòng)gulp,簡(jiǎn)直神煩!
利用gulp-plumber可以實(shí)現(xiàn)錯(cuò)誤自啟動(dòng),這樣就能開(kāi)心的在watch模式下開(kāi)發(fā)且不用擔(dān)心報(bào)錯(cuò)了。
進(jìn)一步結(jié)合gulp-notify,在報(bào)錯(cuò)時(shí)可以得到通知,便于發(fā)現(xiàn)問(wèn)題。

// 錯(cuò)誤處理
var notify = require("gulp-notify")

module.exports = function(errorObject, callback) {
    // 錯(cuò)誤通知
    notify.onError(errorObject.toString().split(': ').join(':\n'))
        .apply(this, arguments);
    
    // Keep gulp from hanging on this task
    if (typeof this.emit === 'function') {
        this.emit('end');
    }
}

// 任務(wù)
var gulp = require('gulp');
var plumber = require('gulp-plumber');

var project = require('../lib/project')(); // 得到當(dāng)前的后臺(tái)項(xiàng)目
var config = require('../config.' + project).views; // 讀取配置文件
var handleErrors = require('../lib/handleErrors');


gulp.task('views', function() {
    return gulp.src(config.src)
        .pipe(plumber(handleErrors)) // 錯(cuò)誤自啟動(dòng)
        .pipe(gulp.dest(config.dest));
});
gulp怎么處理同步任務(wù)和異步任務(wù)

同步任務(wù):gulp通過(guò)return stream的方式來(lái)結(jié)束當(dāng)前任務(wù)并且把stream傳遞到下一個(gè)任務(wù),大多數(shù)gulp任務(wù)都是同步模式。
異步任務(wù):實(shí)際項(xiàng)目中,有些任務(wù)的邏輯是異步函數(shù)執(zhí)行的,這種任務(wù)的return時(shí)機(jī)并不能準(zhǔn)確把控,通常需要在異步函數(shù)中調(diào)用callback()來(lái)告知gulp該任務(wù)結(jié)束,而這個(gè)callback什么都不是,就是傳到該任務(wù)中的一個(gè)參數(shù),沒(méi)有實(shí)際意義。

// 同步任務(wù)
gulp.task('views', function() {
    return gulp.src(config.src)
        .pipe(plumber(handleErrors))
        .pipe(gulp.dest(config.dest));
});

// 異步任務(wù)
gulp.task('webpack', function(callback) {
    webpack(config, function(err, stats) {
        compileLogger(err, stats);

        callback(); //異步任務(wù)的關(guān)鍵之處,如果沒(méi)有這行,任務(wù)會(huì)一直阻塞
    });
});
webpack怎么抽出獨(dú)立的css文件

webpack默認(rèn)是將css直接注入到html中,這種方法并不具有通用性,不推薦使用。
結(jié)合使用extract-text-webpack-plugin,可以生成一個(gè)獨(dú)立的css文件,extract-text-webpack-plugin會(huì)解析每一個(gè)require('*.css')然后處理輸出一個(gè)獨(dú)立的css文件。

// webpack.config.js
var ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    entry: {
        'homes/index': 'pages/homes/index.js'
    },
    output: {
        filename: "[name].js"
    },
    module: {
        loaders: [{
            test: /\.css$/,
            loader: ExtractTextPlugin.extract("style-loader", "css-loader")
        }]
    },
    plugins: [
        new ExtractTextPlugin("[name].css")
    ]
}
webpack怎么抽出通用邏輯和樣式

沒(méi)有webpack之前,想要抽離出公共模塊完全需要手動(dòng)維護(hù),因?yàn)閖s是動(dòng)態(tài)語(yǔ)言,所有依賴(lài)都是運(yùn)行時(shí)才能確定,webpack可以做靜態(tài)解析,分析文件之間的依賴(lài)關(guān)系,使用CommonsChunkPlugin就可以自動(dòng)抽離出公共模塊。

// webpack.config.js
var webpack = require('webpack');
var ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
    entry: {
        'homes/index': 'pages/homes/index.js'
    },
    output: {
        filename: "[name].js"
    },
    module: {
        loaders: [{
            test: /\.css$/,
            loader: ExtractTextPlugin.extract("style-loader", "css-loader")
        }]
    },
    plugins: [
        //抽離公共模塊,包含js和css
        new webpack.optimize.CommonsChunkPlugin("commons", "commons.js"), 
        new ExtractTextPlugin("[name].css")
    ]
}
webpack的watch模式

webpack相對(duì)來(lái)說(shuō)比較耗時(shí),尤其是項(xiàng)目較復(fù)雜時(shí),需要解析的文件較多。好奇心日?qǐng)?bào)web項(xiàng)目首次全量執(zhí)行webpack任務(wù)大概需要10s,所以必須引入增量構(gòu)建。增量構(gòu)建只需要簡(jiǎn)單的給webpack配置添加watch參數(shù)即可。


webpack任務(wù)輸出日志

但是問(wèn)題在于,如果給webpack-stream添加watch參數(shù),webpack-stream的任務(wù)會(huì)阻塞其他的watch任務(wù),最后導(dǎo)致其他任務(wù)的增量構(gòu)建失效。
所以如果要使用webpack的增量構(gòu)建,需要使用原生的webpack方案!

靈活的webpack入口文件

webpack入口文件接收三種格式:字符串,數(shù)組,對(duì)象,對(duì)于多頁(yè)應(yīng)用場(chǎng)景,只有對(duì)象能夠滿(mǎn)足條件,所以我們把所有的入口文件全部列出來(lái)即可。
但這種方案極不靈活,借鑒gulp的方案,是否可以讀取某個(gè)文件下的所有入口文件呢?為了解決這個(gè)問(wèn)題,自定義了一個(gè)函數(shù)來(lái)實(shí)現(xiàn)該功能。

//獲取文件夾下面的所有的文件(包括子文件夾)
var path = require('path'),
    glob = require('glob');

module.exports = function(dir, ext) {
    var files = glob.sync(dir + '/**/*.' + ext),
        res = {};

    files.forEach(function(file) {
        var relativePath = path.relative(dir, file),
            relativeName = relativePath.slice(0, relativePath.lastIndexOf('.'));

        res[relativeName] = './' + relativePath;
    });

    return res;
};
webpack的development/production配置合并

webpack任務(wù)的development配置和production配置差異巨大,并且各自擁有專(zhuān)屬的配置。
由于webpack.config.js默認(rèn)寫(xiě)法是返回一個(gè)對(duì)象,對(duì)象并不能根據(jù)不同條件有不同的輸出,所以將webpack.config.js改成函數(shù),通過(guò)傳入?yún)?shù)來(lái)實(shí)現(xiàn)不同的輸出。

// 其中定義了getLoaders,getAlias,getPlugins,getPostcss函數(shù)
// 都是為了解決development配置和production配置的差異問(wèn)題
// 既最大程度的復(fù)用配置,又允許差異的存在
module.exports = function(env) {
    return {
        context: config.context,
        entry: config.src,
        output: {
            path: path.join(config.jsDest, project),
            filename: '[name].js',
            chunkFilename: '[name].[chunkhash:8].js',
            publicPath: '/assets/' + project + '/'
        },
        devtool: "eval",
        watch: false,
        profile: true,
        cache: true,
        module: {
            loaders: getLoaders(env)
        },
        resolve: {
            alias: getAlias(env)
        },
        plugins: getPlugins(env),
        postcss: getPostcss(env)
    };
}
webpack怎么線(xiàn)上模式異步加載js文件

webpack可以將js代碼分片,把入口文件依賴(lài)的所有模塊打包成一個(gè)文件,但是有些場(chǎng)景下的js代碼并不需要打包到入口文件中,更適合異步延遲加載,這樣能最大程度的提升首屏加載速度。
比如好奇心日?qǐng)?bào)的登錄浮層,這里面包含了復(fù)雜的圖片上傳,圖片裁剪,彈框的邏輯,但是它沒(méi)必要打包在入口文件中,反倒很適合異步延遲加載,只有當(dāng)需要登錄/注冊(cè)的時(shí)候才去請(qǐng)求。


圖片上傳裁剪

我們可以通過(guò)webpack提供的requirerequire.ensure來(lái)實(shí)現(xiàn)異步加載,值得一提的是,除了指定的異步加載文件列表,webpack還會(huì)自動(dòng)解析回調(diào)函數(shù)的依賴(lài)及指定列表的深層次依賴(lài),并打包成一個(gè)文件。

但是實(shí)際項(xiàng)目中還得解決瀏覽器緩存的問(wèn)題,因?yàn)檫@些異步JS文件的時(shí)間戳是rails生產(chǎn)的,對(duì)于webpack是不可知的,也就是說(shuō)請(qǐng)求這個(gè)異步JS文件并不會(huì)命中。
為了解決這個(gè)問(wèn)題,我們?cè)趓ails4中自定義了一個(gè)rake任務(wù):生產(chǎn)沒(méi)有時(shí)間戳版本的異步JS文件。


rake任務(wù)

上圖中還有一個(gè)小細(xì)節(jié)就是,這些異步JS文件有兩個(gè)時(shí)間戳,前者為webpack時(shí)間戳,后者為rails時(shí)間戳,之所以有兩個(gè)時(shí)間戳,是為了解決瀏覽器緩存的問(wèn)題。

簡(jiǎn)而言之就是:
通過(guò)require/require.ensure,來(lái)生成異步JS文件,解決異步加載的問(wèn)題。
通過(guò)自定義rake任務(wù),來(lái)生成沒(méi)有rails時(shí)間戳的異步JS文件,解決webpack不識(shí)別rails時(shí)間戳的問(wèn)題。
通過(guò)webpack的chunkFileName配置,給異步JS文件加上webpack時(shí)間戳,解決瀏覽器緩存的問(wèn)題。

總結(jié)說(shuō)點(diǎn)啥?

前端工程化可以自動(dòng)化處理一些繁復(fù)的工作,提高開(kāi)發(fā)效率,減少低級(jí)錯(cuò)誤。
更重要的是,還是文章開(kāi)頭的說(shuō)的,前端工程化最大的意義在于給我們新的視角去看待前端開(kāi)發(fā),讓前端開(kāi)發(fā)可以做更復(fù)雜、更有挑戰(zhàn)的事情!

這是前端工程化實(shí)踐的第一篇博文,后續(xù)還有對(duì)之前零零散散的博文的總結(jié)。不過(guò)整體來(lái)說(shuō),webpack任務(wù)是最復(fù)雜、涵蓋知識(shí)最多的一個(gè)任務(wù)。
文章若有紕漏,歡迎大家指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • 在現(xiàn)在的前端開(kāi)發(fā)中,前后端分離、模塊化開(kāi)發(fā)、版本控制、文件合并與壓縮、mock數(shù)據(jù)等等一些原本后端的思想開(kāi)始...
    Charlot閱讀 5,460評(píng)論 1 32
  • gulpjs是一個(gè)前端構(gòu)建工具,與gruntjs相比,gulpjs無(wú)需寫(xiě)一大堆繁雜的配置參數(shù),API也非常簡(jiǎn)單,學(xué)...
    依依玖玥閱讀 3,163評(píng)論 7 55
  • 最近在學(xué)習(xí) Webpack,網(wǎng)上大多數(shù)入門(mén)教程都是基于 Webpack 1.x 版本的,我學(xué)習(xí) Webpack 的...
    My_Oh_My閱讀 8,199評(píng)論 40 247
  • 前言 本文默認(rèn)你已經(jīng)安裝好node環(huán)境,并且熟悉node命令,和window cd命令。 gulp簡(jiǎn)介 基于nod...
    9I閱讀 1,991評(píng)論 4 50
  • gulpjs是一個(gè)前端構(gòu)建工具,與gruntjs相比,gulpjs無(wú)需寫(xiě)一大堆繁雜的配置參數(shù),API也非常簡(jiǎn)單,學(xué)...
    井皮皮閱讀 1,317評(píng)論 0 10