gulp webpack整合

为什么需要前端工程化?

前端工程化的意义在于让前端这个行业由野蛮时代进化为正规军时代,近年来很多相关的工具和概念诞生。好奇心日报在进行前端工程化的过程中,主要的挑战在于解决如下问题:
✦ 如何管理多个项目的前端代码?
✦ 如何同步修改复用代码?
✦ 如何让开发体验更爽?

项目实在太多

之前写过一篇博文 如何管理被多个项目引用的通用项目?,文中提到过好奇心日报的项目偏多(PC/Mobile/App/Pad),要为这么多项目开发前端组件并维护是一个繁琐的工作,并且会有很多冗余的工作。

更好的管理前端代码

前端代码要适配后台目录的规范,本来可以很美好的前端目录结构被拆得四分五裂,前端代码分散不便于管理,并且开发体验很不友好。
而有了前端工程化的概念,前端项目和后台项目可以彻底分离,前端按自己想要的目录结构组织代码, 然后按照一定的方式构建输出到后台项目中,简直完美(是不是有种后宫佳丽三千的感觉)。

技术选型

调研了市场主流的构建工具,其中包括gulp、webpack、fis,最后决定围绕gulp打造前端工程化方案,同时引入webpack来管理模块化代码,大致分工如下:

gulp
:处理html压缩/预处理/条件编译,图片压缩,精灵图自动合并等任务
webpack
:管理模块化,构建js/css。

至于为什么选择gulp & webpack,主要原因在于gulp相对来说更灵活,可以做更多的定制化任务,而webpack在模块化方案实在太优秀(情不自禁的赞美)。

怎么设计前端项目目录结构?

抽离出来的前端项目目录结构如下


前端项目结构

appfe目录
:appfe就是前面提到的前端项目,这个项目主要包含两部分:前端代码、构建任务
appfe > gulp目录
:包含了所有的gulp子任务,每个子任务包含相关任务的所有逻辑。
appfe > src目录
:包含了所有前端代码,比如页面、组件、图片、字体文件等等。
appfe > package.json
:这个不用说了吧。
appfe > gulpfile.js
:gulp入口文件,引入了所有的gulp子任务。

理想很丰满,现实却很骨感,这么美好的愿望,在具体实践过程中,注定要花不少心思,要踩不少坑。
好奇心日报这次升级改造即将上线,终于也有时间把之前零零碎碎的博文整合在一起,并且结合自己的体会分享给大家,当然未来可能还会有较大的调整,这儿抛砖引玉,大家可以参考思路。

gulp 是什么?

gulp是一个基于流的构建工具,相对其他构件工具来说,更简洁更高效。
Tip:之前写过一篇gulp 入门,可以参考下,如果对gulp已经有一定的了解请直接跳过。

webpack 是什么?

webpack是模块化管理的工具,使用webpack可实现模块按需加载,模块预处理,模块打包等功能。
Tip:之前写过一篇webpack 入门,可以参考下,如果对webpack已经有一定的了解请直接跳过。

如何整合gulp & webpack

webpack是众多gulp子任务中比较复杂的部分,主要对JS/CSS进行相关处理。
包括:模块分析、按需加载、JS代码压缩合并、抽离公共模块、SourceMap、PostCSS、CSS代码压缩等等...

webpack-stream方案[不推荐]

使用webpack-stream虽然可以很方便的将webpack整合到gulp中,但是有致命的问题存在:
如果关闭webpack的监听模式,那么每次文件变动就会全量编译JS/CSS文件,非常耗时。
如果打开webpack的监听模式,那么会阻塞其他gulp任务,导致其他gulp任务的监听失效。
所以这种方案几乎不可用!

webpack原生方案

直接使用webpack原生方案,相对来说更灵活。
Tip:代码较复杂,里面涉及的知识点也很多,建议看看形状就好,如果真有兴趣,可以好好研究研究,毕竟花了很长时间去思考这些方案。

// webpack.config.js 关键地方都有大致注释 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'), // 正常第三方库 'jquery.js': path.resolve(__dirname, '../src/vendor/jquery2/jquery.js'), }; }; // 插件配置 var getPlugins = function(env) { var defaultPlugins = [ // 这个不仅是别名,还可以在遇到别名的时候自动引入模块 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') { // 线上模式的配置,去除依赖中重复的插件/压缩js/排除报错的插件 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') { // 线上模式的配置,css压缩 return function() { return _.union([ cssnano({ // 关闭cssnano的autoprefixer选项,不然会和前面的autoprefixer冲突 autoprefixer: false, reduceIdents: false, zindex: false, discardUnused: false, mergeIdents: false }) ], postcss); }; } else { return function() { return _.union([], postcss); } } }; // 作为函数导出配置,代码更简洁 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任务 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-监听模式 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 }); });

实践中遇到那些坑?

如何组织gulp任务?

由于gulp任务较多,并且每个核心任务都有关联任务,比如webpack的关联任务就有

webpack
/
watch:webpack
/
build:webpack
/
clean:webpack
,如何组织这些子任务是一个需要很小心的事情,出于一直以来的习惯:把关联的逻辑放在一起,所以我的方案是webpack相关的任务放到一个文件,然后定义了
default
/
clean
/
watch
/
build
四个入口任务来引用对应的子任务。


webpack任务结构
gulp怎么实现错误自启动

使用watch模式可以更高效的开发,监听到改动就自动执行任务,但是如果过程中遇到错误,gulp就会报错并终止watch模式,必须重新启动gulp,简直神烦!
利用gulp-plumber可以实现错误自启动,这样就能开心的在watch模式下开发且不用担心报错了。
进一步结合gulp-notify,在报错时可以得到通知,便于发现问题。

// 错误处理 var notify = require("gulp-notify") module.exports = function(errorObject, callback) { // 错误通知 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'); } } // 任务 var gulp = require('gulp'); var plumber = require('gulp-plumber'); var project = require('../lib/project')(); // 得到当前的后台项目 var config = require('../config.' project).views; // 读取配置文件 var handleErrors = require('../lib/handleErrors'); gulp.task('views', function() { return gulp.src(config.src) .pipe(plumber(handleErrors)) // 错误自启动 .pipe(gulp.dest(config.dest)); });
gulp怎么处理同步任务和异步任务

同步任务
:gulp通过
return stream
的方式来结束当前任务并且把
stream
传递到下一个任务,大多数gulp任务都是同步模式。
异步任务
:实际项目中,有些任务的逻辑是异步函数执行的,这种任务的return时机并不能准确把控,通常需要在异步函数中调用
callback()
来告知gulp该任务结束,而这个
callback
什么都不是,就是传到该任务中的一个参数,没有实际意义。

// 同步任务 gulp.task('views', function() { return gulp.src(config.src) .pipe(plumber(handleErrors)) .pipe(gulp.dest(config.dest)); }); // 异步任务 gulp.task('webpack', function(callback) { webpack(config, function(err, stats) { compileLogger(err, stats); callback(); //异步任务的关键之处,如果没有这行,任务会一直阻塞 }); });
webpack怎么抽出独立的css文件

webpack默认是将css直接注入到html中,这种方法并不具有通用性,不推荐使用。
结合使用

extract-text-webpack-plugin
,可以生成一个独立的css文件,
extract-text-webpack-plugin
会解析每一个
require('*.css')
然后处理输出一个独立的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怎么抽出通用逻辑和样式

没有webpack之前,想要抽离出公共模块完全需要手动维护,因为js是动态语言,所有依赖都是运行时才能确定,webpack可以做静态解析,分析文件之间的依赖关系,使用

CommonsChunkPlugin
就可以自动抽离出公共模块。

// 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相对来说比较耗时,尤其是项目较复杂时,需要解析的文件较多。好奇心日报web项目首次全量执行webpack任务大概需要10s,所以必须引入增量构建。增量构建只需要简单的给webpack配置添加watch参数即可。


webpack任务输出日志

但是问题在于,如果给webpack-stream添加watch参数,webpack-stream的任务会阻塞其他的watch任务,最后导致其他任务的增量构建失效。
所以如果要使用webpack的增量构建,需要使用原生的webpack方案!

灵活的webpack入口文件

webpack入口文件接收三种格式:字符串,数组,对象,对于多页应用场景,只有对象能够满足条件,所以我们把所有的入口文件全部列出来即可。
但这种方案极不灵活,借鉴gulp的方案,是否可以读取某个文件下的所有入口文件呢?为了解决这个问题,自定义了一个函数来实现该功能。

//获取文件夹下面的所有的文件(包括子文件夹) 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任务的development配置和production配置差异巨大,并且各自拥有专属的配置。
由于

webpack.config.js
默认写法是返回一个对象,对象并不能根据不同条件有不同的输出,所以将
webpack.config.js
改成函数,通过传入参数来实现不同的输出。

// 其中定义了getLoaders,getAlias,getPlugins,getPostcss函数
// 都是为了解决development配置和production配置的差异问题
// 既最大程度的复用配置,又允许差异的存在
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怎么线上模式异步加载js文件

webpack可以将js代码分片,把入口文件依赖的所有模块打包成一个文件,但是有些场景下的js代码并不需要打包到入口文件中,更适合异步延迟加载,这样能最大程度的提升首屏加载速度。
比如好奇心日报的登录浮层,这里面包含了复杂的图片上传,图片裁剪,弹框的逻辑,但是它没必要打包在入口文件中,反倒很适合异步延迟加载,只有当需要登录/注册的时候才去请求。


图片上传裁剪

我们可以通过webpack提供的

require
require.ensure
来实现异步加载,值得一提的是,除了指定的异步加载文件列表,webpack还会自动解析回调函数的依赖及指定列表的深层次依赖,并打包成一个文件。

但是实际项目中还得解决浏览器缓存的问题,因为这些异步JS文件的时间戳是rails生产的,对于webpack是不可知的,也就是说请求这个异步JS文件并不会命中。
为了解决这个问题,我们在rails4中自定义了一个rake任务:生产没有时间戳版本的异步JS文件。


rake任务

上图中还有一个小细节就是,这些异步JS文件有两个时间戳,前者为webpack时间戳,后者为rails时间戳,之所以有两个时间戳,是为了解决浏览器缓存的问题。

简而言之就是:
通过

require
/
require.ensure
,来生成异步JS文件,解决异步加载的问题。
通过自定义rake任务,来生成没有rails时间戳的异步JS文件,解决webpack不识别rails时间戳的问题。
通过webpack的chunkFileName配置,给异步JS文件加上webpack时间戳,解决浏览器缓存的问题。

 

学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入学习交流群
343599877,我们一起学前端!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/368728.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot 入门第一章

一、前言 Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。 本系列以快速入门为主,可当作工具小手册阅…

【转】 VC++6.0 在Win7 64位下调试,Shift+F5无法退出

Win7 64位VC6.0调试代码无法关闭窗口解决方法  VC6.0 在64位Windows7下调试的时候,再结束调试,程序无法退出,只能关闭VC6.0 IDE环境。  问题描述:当我击F5开始一个项目的调试时,程序在我设置的断点处停止&#xff…

使用Infinispan创建自己的Drools和jBPM持久性

我 在这里发表的原始文章: 您好,欢迎来到我打算向您展示如何创建自己的Drools和jBPM持久性实现的帖子。 我已经为流口水对象开发了基于infinispan的持久性方案,并且在此过程中学到了很多东西。 如果您想做某种事情,我打算给您一些…

Html5 填表 表单(二) input type 各种输入, 各种用户选择,上传等等泛输入用户交互

<input> 无限制输入 type 限制输入 type 如下类型 type 后还可以跟一些属性: 如<input typetext maxlength 10> 限制文本的长度为10字节 list 可以用的时候再来查, list就是当一个建议值不够的时候添加到几个. <form> <input typ…

c语言 输出音频 单片机,单片机播放WAV格式音频的理解

CSDN账号注册了3年&#xff0c;一直没有上来过&#xff0c;更不用说写博客了。我不知道博客的具体用途&#xff0c;我只想把它当做一种心得来发表&#xff0c;可能是一些技术上的理解或者生活上的小故事。好了&#xff0c;下面我将记录我对WAV播放器的理解。很久以前就看到过某…

UVALive3989 Ladies' Choice —— 稳定婚姻问题 Gale - Shapely算法

题目链接&#xff1a;https://vjudge.net/problem/UVALive-3989 题解&#xff1a; 题意&#xff1a;有n个男生和n个女生。每个女生对男神都有个好感度排行&#xff0c;同时每个男生对每个女生也有一个好感度排行。问&#xff1a;怎样配对&#xff0c;才能使的每个女生尽可能幸福…

通过命令行界面使用AWS ElasticMapReduce

在本文中&#xff0c;我将通过针对EMR的CLI使用AWS MapReduce服务&#xff08;称为ElasticMapReduce &#xff09;。 使用EMR的过程可以大致分为三个步骤&#xff1a; 设置并填充S3存储桶 创建并运行EMR作业 从S3存储桶中获取结果 在开始这三个高级步骤之前&#xff0c;还…

[UE4]关卡蓝图

转载于:https://www.cnblogs.com/timy/p/9053876.html

iframe的缺点

一、iframe会阻塞主页面的onload事件&#xff1b; 二、搜索引擎检索程序无法解读这种页面&#xff0c;不利于SEO&#xff1b; 三、会影响页面的并行加载。 并行加载&#xff1a;同一时间对同一域名下的加载数量是有限制的&#xff1a; 解决方法&#xff1a; 使用js动态给ifr…

使用可视化工具redisclient连接redis

可视化工具推荐&#xff1a;http://database.51cto.com/art/201505/477692.htm 1.连接redis服务端 1.1 设置连接密码&#xff1a;在redis根目录下&#xff0c;双击redis-cli.exe&#xff0c; 输入命令&#xff1a;redis-cli.exe -h 127.0.0.1 -p 6379 -n 1 1就是密码 1.2 使…

android 蓝牙项目代码,Android蓝牙聊天开源项目

前言基于Android Classic Bluetooth的蓝牙聊天软件&#xff0c;目前仅支持一对一实时通信、文件传输、好友添加、好友分组、好友在线状态更新等功能&#xff0c;其中消息发送支持文本、表情等方式。前景蓝牙技术作为一种小范围无线连接技术&#xff0c;能够在设备间实现方便快捷…

你必须知道的.NET之特性和属性(转)

你必须知道的.NET之特性和属性2008-10-13 来源&#xff1a;网络 1. 引言 attribute是.NET框架引入的有一技术亮点&#xff0c;因此我们有必要花点时间走进一个发现attribute登堂入室的入口。因为.NET Framework中使用了大量的定制特性来完成代码约定&#xff0c;[Serializable]…

宽带阻抗匹配的工程实现-第一步,端口驻波仿真

概要 ADS仿真&#xff0c;Matlab仿真&#xff0c;宽带阻抗匹配&#xff0c;smith圆图。 其实阻抗匹配我工作以来经常说&#xff0c;也经常做&#xff0c;但是基本上都是直接在印制板上进行调试。现在想先用仿真软件直接设计出来&#xff0c;才发现很多东西嘴上说容易&#xf…

学web前端一定要这样学,不然学完找不到工作哭都来不及!

因为工作原因&#xff0c;经常关注有关互联网行业的最新动态。这不&#xff0c;刚送走了高考&#xff0c;又迎来了每年的毕业季&#xff0c;看到好多人都说今年的前端工作不好找&#xff0c;很多童鞋简历投了一大堆&#xff0c;也没有回应&#xff0c;发现连实习的机会都没有&a…

android视频通信和web端,探讨用webrtc在手机和浏览器之间实现音视频实时通信的实施环境...

探讨用webrtc在手机和浏览器之间实现音视频实时通信的实施环境Walker.Xuproduct/develop flow&#xff1a;技术需求&#xff1a;任务拆解:1.android客户端2.前端js网页客户端技术产品&#xff1a;落地&#xff0c;实施在实际应用环境中1.技术因素2.非技术因素选择浏览器的标准&…

XStream – XStreamely使用Java中的XML数据的简便方法

有时候&#xff0c;我们不得不处理XML数据。 而且大多数时候&#xff0c;这不是我们一生中最快乐的一天。 甚至有一个术语“ XML地狱”描述了程序员必须处理许多难以理解的XML配置文件时的情况。 但是&#xff0c;不管喜欢与否&#xff0c;有时我们别无选择&#xff0c;这主要是…

前端微信小程序实战篇

电商底部导航栏的制作 我想大家对电商一定不陌生&#xff0c;一般电商的底部导航栏有以下几个首页、分类、购物车、个人中心。 app.json是用来配置page路径以及导航栏属性的&#xff0c;那我们要做首页、分类、购物车、个人中心界面就要在page也添加这几个界面&#xff0c;所…

Spring MVC –使用@ResponseBody轻松实现基于REST的JSON服务

Spring 3使JSON REST服务非常容易。 本教程将通过几个步骤向您展示如何进行。 您可以在GitHub上获取代码。 先决条件 您应该有一个运行中的Spring MVC应用程序。 如果尚未设置正常的Spring MVC应用程序&#xff0c;请按照本教程进行操作 。 我们将定义三个REST服务&#xff1a…

(转)zabbix3.4使用percona-monitoring-plugins监控mysql

原文&#xff1a;https://blog.csdn.net/yanggd1987/article/details/79656771 简介 之前主要使用nagios监控mysql&#xff0c;本文主要介绍使用percona-monitoring-plugins监控mysql&#xff0c;percona监控插件是php开发&#xff0c;因此要在agent安装php环境。 配置安装 一.…

html基础-html简介-第一个网页(1)

今天刚刚开通博客园&#xff0c;把我最近整理的html/css来说一下&#xff0c;对于初学者还是有一定的帮助。 一、先来为大家简单普及以下html &#xff08;1&#xff09;、html英文即&#xff1a;hypertext markup language 中译 &#xff1a; 超文本标记语言 &#xff08;2&…