目录:
一、gulp、webpack对比总结
二、前端工程化简介
前言:经验不足,仅记录下当前自己的认识,非常希望可以得到反馈,无论是指点还是提出问题,一起讨论成长。
一、gulp、webpack对比总结:
这两者正巧都可以完成打包的功能,经常拿来作比较罢了。分析对比过程中,越比较越觉得就只能探讨打包形式及过程中用的包,根本没得好比的嘛,还是总结下:建议webpack常用,gulp辅助小流程工具。还是分别讲解大致讲解下吧:
打包编译的过程,都有绕不开的共同问题:mode开发模式、mainfest文件映射关系、入口文件、出口文件、每个文件的处理步骤、检测代码实时更新、多入口多环境。
gulp | webpack | |
---|---|---|
背景 | html、css、js、img静态资源的压缩、转译是每次发布代码需要的固定流程,重复无意义的操作需要解放开发者丢给电脑 | 项目大,代码复杂,对代码、文件的解耦需求;减少http请求的优化需求 |
区别 | 以工作间角度实现工序链的每个步骤,规范开发流程 | 着眼于前端模块化的思想,文件处理流程(压缩合并、预处理)是附带实现功能 |
解释 | 任何一个重复劳动的过程都可以简化成一个工作间的流程default。流程有入src有出dest,中间每个固定的步骤都是一个task,需要多方插件补充完成需要的工作;在这个处理的过程中,处理的对象以 流 的形式在工序链上(pipe管道里)运转处理;每个工序位置又以顺序series/并发prallel的形式组合在一起;其中某个步骤有变化时,需要watch重新获取新的结果。每个文件间的引用关系可以靠插件rev生成一个manifest.json去替换打包生成后的文件地址,保证程序的正常运行。 | webpack将任何一个文件当做模块去分析处理其之间的关系,生成一个“数据合集mainfest”,在runtime需要引用时会自动加载对应模块,可根据“依赖图”(webpack-bundle-analyzer插件,code-shaking进行一定的优化Webpack 大法之 Code Splitting 传送门)分析关系(为避免实际未引用的加载浪费资源,出现了tree-shaking及其副作用问题);webpack将所有文件打包再bundle.js中,打包形成的文件内容与实际代码有很大的出入,因此需要设定sourceMap映射编译之后的代码报错时在真实编码文件的位置来锁定问题;webpack通过“热更新”来检测代码的变化更新页面视图; |
打包方式 | 多种开发模式、多环境的不同需求,需要视情况开发应用某个流程;通常多文件模块分开管理流程,视情况应用 | 倾向于一个文件根据配置来完成 |
步骤处理 | pipe管道,主依赖“插件”思想 | “模块module”(函数)和“插件plugin”(类)的思想 |
webpack模块及模块化理解
模块
webpack中的定义
开发者将程序分解成离散功能块,成为模块;调试、校验、测试简单,提供可靠的抽象和封装界限,调理清晰有明确设计目的的功能块。
简单理解
相互独立、低耦合、职责单一、可替换的功能块;如公用函数、定义类之类的作为以一个文件模块的形式存在。
模块化
webpack中的定义
模块化是一种处理复杂系统分解成为更好的可管理模块的方式,它可以把系统代码划分为一系列职责单一,高度解耦且可替换的模块,系统中某一部分的变化将如何影响其它部分就会变得显而易见,系统的可维护性更加简单易得。
简单理解
一种将复杂拆解简单的管理代码模式,理清模块间的关系,清晰数据出入在何处发生什么结果是什么,便于维护和添加新的步骤。原本开发常用lib库(类比java库)添加新的公用函数等本身就是如此效果,相比之下更新了模块之间的关系和依赖,形成了一个项目全部直接的模块管理关系,组织好碎片化的前端资源,尽可能优化浏览器的加载更新;且方便合作开发。
前端模块化是前端组件化、工程化的基石。
什么是webpack模块
对比 Node.js 模块,webpack 模块能够以各种方式表达它们的依赖关系,几个例子如下:
ES2015 import 语句
CommonJS require() 语句
AMD define 和 require 语句
css/sass/less 文件中的 @import 语句。
样式(url(…))或 HTML 文件(</img src=…/>)中的图片链接(image url)
webpack的执行过程:
(compiler模块是webpack的支柱引擎,扩展自Tapable类;)
1、初始化Compiler:new Webpack(config) 得到 Compiler对象
2、开始编译:调用Compiler对象的 run 方法
3、确定入口:entry
4、编译模块:从入口文件出发,调用所以配置的 loader 对模块进行编译,再找出该模块依赖模块,递归加载;
最终形成每个模块被编译后的最终内容及它们之间的依赖关系。
5、输出资源:根据依赖关系,组装多个模块的chunk,再把该chunk转换成一个单独文件加到输出列表。
可在此及之前修改输出内容;
6、输出完成:根据配置确定输入文件和路径,内容写入。
webpack关键模块简介:
loader\plugin\webpack的简写在createWebpack文件夹下(下附demo地址)。
loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!loader是个函数,接收三个参数content, map, meta,回调三个参数,默认从后向前执行;其中可添加pitch函数,执行顺序相反。
plugin插件是 webpack 的支柱功能。webpack 自身也是构建于,你在 webpack 配置中用到的相同的插件系统之上!插件目的在于解决 loader 无法实现的其他事。webpack 插件是一个具有 apply 属性的 JavaScript 对象。apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问
二、前端工程化简介
工程化分为四个方面:模块化、组件化、规范化、自动化。参考
前端模块化是前端工程化的基石。
模块化
将任何文件尽可能简化成多个单独耦合性低的文件模块,统一打包和加载,方便多人合作和文件资源管理。
组件化
组件是UI层面的拆分,对页面的每个单独可以成块的部分进行拆分,设计单独通用组件,便于通用管理。
规范化
开发管理规范,如:git flow工作流、驼峰命名规范、目录结构约定(典型egg)、编码格式(eslint校验)、风格统一(css的模块、js规范)、定期code review等开发层面能做到的管理规范。方便多人协作。(这部分可以借助脚手架工具来完成大部分的需求)。
自动化
尽可能解放重复无意义的操作,让代码做该做的事。gulp\webpack是实现打包这步的工具,CI/CD、自动发布是自动化的终极目标;提交代码的那一刻,剩下的都交给电脑运行,在这方面,大公司的处理就非常完善。监测git push,提交完代码之后,自动打包镜像部署服务kubernate(有些需人工确认的开发按钮确认继续或回退),构建成功返回结果,看部署服务器是否启动即可。
脚手架工具:Yeoman\Plop(不知以后用不用得上,先记录下优秀博客)前端工程化===Webpack?No,我来告诉你什么是前端工程化!
附录:
个人测试demo地址,后续补充webpack的loader、plugin简写。
gulp的打包样例:
const gulp = require('gulp');
const gulpClean = require('gulp-clean');
const gulpHtmlMin = require('gulp-htmlmin');
const replace = require('gulp-replace');
const rev = require('gulp-rev');
const revCollector = require('gulp-rev-collector');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');
const mincss = require('gulp-clean-css');
const autoprefixer = require('autoprefixer');
const postcss = require('gulp-postcss');
var browserSync = require('browser-sync').create();/*** 思路:* 0、注册gulp事件、绑定default执行事件* 1、清除目的文件夹* 2、处理每种类型文件* 2.1、js* 2.2、image* 2.3、css* 2.4、html* 压缩* 3、打包压缩至目的文件夹*/const srcPath = '../src';
const destPath = 'dest';gulp.task('cleanFile', function(){return gulp.src(destPath, {read: false, allowEmpty: true}).pipe(gulpClean());
});gulp.task('minJs', function () {//压缩js文件,且将es6语法转换为es5return gulp.src(`${srcPath}/**/*.js`)// .pipe(babel({"presets": ["@babel/preset-env"]})) // TODO:es6语法转换为es5 有误,待处理;可能是babel版本问题或配置不标准.pipe(uglify()).pipe(rev()) // md5.pipe(gulp.dest(`${destPath}`)).pipe(rev.manifest({path: `${destPath}/rev-manifest.json`, // 需要配合merge使用merge: true})).pipe(gulp.dest('./'))
});gulp.task('images', function () {return gulp.src(`${srcPath}/imgs/**`).pipe(gulp.dest(`${destPath}/imgs`))
});gulp.task('minCss', function () {//合并文件return gulp.src(`${srcPath}/css/*.css`).pipe(postcss([autoprefixer()])) // 配合package.json的 browserslist.pipe(mincss()).pipe(rev()).pipe(gulp.dest(`${destPath}/css`)).pipe(rev.manifest({path: `${destPath}/rev-manifest.json`,merge: true})).pipe(gulp.dest('./'))
});gulp.task('minHtmlFile', function(){return gulp.src([`${destPath}/*.json`, `${srcPath}/html/*.html`]).pipe(gulpHtmlMin({collapseWhitespace: true, //删除空格 removeComments: true, //删除注释})).pipe(replace(`${srcPath}/`, './')) // 打包后对应的目录.pipe(revCollector({replaceReved: true, //替换html中对应的记录})) .pipe(gulp.dest(`${destPath}/html/`))
});gulp.task('minHtml', function(){return gulp.src([`${destPath}/*.json`, `${srcPath}/*.html`]).pipe(gulpHtmlMin({collapseWhitespace: true, //删除空格 removeComments: true, //删除注释})).pipe(replace(`${srcPath}/`, './')) // 打包后对应的目录.pipe(revCollector({replaceReved: true, //替换html中对应的记录// dirReplacements: {// './html/': './'// }})) .pipe(gulp.dest(destPath))
});gulp.task("reload", function () {return gulp.src(`${destPath}/**/*`).pipe(browserSync.reload()); //页面重新加载
});gulp.task('watch', function() {gulp.watch(`${srcPath}/*.html`, gulp.series('minHtml'));gulp.watch(`${srcPath}/html/*.html`, gulp.series('minHtmlFile'));gulp.watch(`${srcPath}/css/**/*.css`, gulp.series('minCss'));gulp.watch(`${srcPath}/js/**/*.js`, gulp.series('minJs'));gulp.watch(`${srcPath}/imgs/**/*`, gulp.series('images'));gulp.watch(`${destPath}/**/*`, gulp.series('reload'));// gulp.watch(`${destPath}/**/*`, gulp.series('reload')).on('change', function(e){// // console.log('e ---> ', e); // 所有监听到的文件地址// browserSync.reload();// });
});gulp.task('browser', function(){browserSync.init({server: './dest', // 访问目录port: 3000,// proxy: "你的域名或IP" // 设置代理});});gulp.task('default', gulp.series('cleanFile',gulp.parallel('minJs', 'minCss', 'images'),gulp.parallel('minHtmlFile', 'minHtml'),gulp.parallel('browser', 'watch'))
);
webpack样例
// wp是node写出来的 node写法
let path = require('path'); //内置模块
let HtmlWebpackPlugin = require('html-webpack-plugin'); // 有人习惯 -- 或者说插件都是类 大写是个类
let miniCssExtractPlugin = require('mini-css-extract-plugin'); // 需要自己压缩文件
let optimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
let uglifyjsPlugin = require('uglifyjs-webpack-plugin');
let webpack = require('webpack');
module.exports = {optimization: { // 优化项minimizer: [new uglifyjsPlugin(),new optimizeCssAssetsWebpackPlugin()]},mode: 'development', //模式:默认两种 production\ developmententry: '../src/js/index.js', //入口output: {filename: 'bundle.[hash:8].js', //打包后的文件名, hash戳新文件放置覆盖缓存,:8只显示8位path: path.resolve(__dirname, 'build'), //路径必须是一个绝对路径 resolve将相对路径->绝对路径;__dirname以当前目录下产生dist// publicPath: 'http://xxxx/xx' 也可以在需要的地方添加该前缀即可},plugins: [ // Array 所有webpack插件new HtmlWebpackPlugin({template: '../src/index.html', filename: 'index.html',minify: { //压缩removeAttributeQuotes: true, //删除属性的双引号collapseWhitespace: true, //变成一行},hash: true, // hash戳}),new HtmlWebpackPlugin({template: '../src/html/login.html', // 打包多个即需多个配置,可以用代码循环生成之后,concat到plugins上filename: './html/login.html',minify: { //压缩removeAttributeQuotes: true, //删除属性的双引号collapseWhitespace: true, //变成一行},hash: true, // hash戳}),new miniCssExtractPlugin({filename: 'css/main.css'}),// new webpack.ProvidePlugin({ //在每个模块中都注入$// $: 'jquery'// })],module: { //模块rules: [ // 规则{test: /\.html$/,use: 'html-withimg-loader' },{test: /\.(png|jpg|gif)$/,// use: 'file-loader',use: {loader: 'url-loader',options: {limit: 200*1024, // 200k 给1 -> 保证正常产出outputPath: 'img/'}}},{test: /\.js$/,use: {loader: 'babel-loader',options: {// 用babel-loader 需把es6转es5presets: [ // 大插件集合// preset-env// '@babel/preset-env' // 转模块文件,不会转API'env'],// plugins: [// '@babel/plugin-proposal-class-properties'// ]"plugins": [["@babel/plugin-proposal-decorators", { "legacy": true }],["@babel/plugin-proposal-class-properties", { "loose" : true }],'@babel/plugin-transform-runtime']}},include: path.resolve(__dirname, 'src'),exclude: '/node_modules/'},{test:/\.css$/, use: [miniCssExtractPlugin.loader,'css-loader','postcss-loader']}, {test:/\.less$/, use: [miniCssExtractPlugin.loader,'css-loader','postcss-loader','less-loader']},// node-sass sass-loader; stylus stylus-loader]}
}
PS:有时间的话,完善“模块化的发展认识”及“CommonJS”的实现