企业网站建设目标,招聘网站数据分析怎么做,珲春市建设局网站,湖北省最新消息今天为什么需要前端工程化#xff1f;
前端工程化的意义在于让前端这个行业由野蛮时代进化为正规军时代#xff0c;近年来很多相关的工具和概念诞生。好奇心日报在进行前端工程化的过程中#xff0c;主要的挑战在于解决如下问题#xff1a;✦ 如何管理多个项目的前端代码…为什么需要前端工程化
前端工程化的意义在于让前端这个行业由野蛮时代进化为正规军时代近年来很多相关的工具和概念诞生。好奇心日报在进行前端工程化的过程中主要的挑战在于解决如下问题✦ 如何管理多个项目的前端代码✦ 如何同步修改复用代码✦ 如何让开发体验更爽
项目实在太多
之前写过一篇博文 如何管理被多个项目引用的通用项目文中提到过好奇心日报的项目偏多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.jsgulp入口文件引入了所有的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[]es2015cacheDirectorytrue!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改成函数通过传入参数来实现不同的输出。// 其中定义了getLoadersgetAliasgetPluginsgetPostcss函数
// 都是为了解决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我们一起学前端