素材巴巴 > 程序开发 >

大前端进击之路(五)前端工程化Gulp初体验

程序开发 2023-09-16 10:48:41

在这里插入图片描述

打工人!打工魂!前端才是人上人!此系列记录于拉勾教育大前端训练营学习过程,如果文章中有不对的地方,希望大家能进行批评改正,互相进步。转载请注明出处并附上原文地址 。

前端工程化是什么?

前端工程化指的是在团队内遵循一定的标准和规范,使用工具提高开发人员的的工作效率、降低维护成本,还可以减少因为人工操作失误而导致的不可控因素。

为我们解决了什么问题?

我们首先来列举在日常开发过程中经常会面临的一些问题(不仅限于此):

  1. 我们想要使用Less/Sass来增强CSS的编程性,但是运行环境不能直接支持,总是需要我们编译等重复性工作。
  2. 我们想要使用ECMAScript新特性时会有兼容性问题。
  3. 想要使用模块化的方式提高项目的可维护性,运行环境也不能直接支持。
  4. 项目部署上线前需要手动压缩资源文件,过程中需要手动上传代码到服务器,是容易出现问题的。
  5. 多人协作开发,每个人的代码风格是不一样的,无法硬性统一大家的代码风格。

我们将这些问题归纳总结为以下几点:

具体解决方案

我们以一个简单的项目开发流程为例,看看前端工程化在这个过程中的作用,让我们先从整体角度对前端工程化有一个全面的认识。从项目的创建、编码、预览、提交、部署,每一个环节我们都可以通过前端工程化的方式提高工作效率。

  1. 创建项目过程我们可以使用脚手架工具自动完成。
  2. 编码过程我们可以通过编译或者转化工具提前使用一些新特性。利用格式化和代码检查工具自动检测和修复代码中的基础问题。
  3. 预览时可以通过Web Server自动预览并享受热更新的体验,并且可以使用Mock的方式在后端服务接口未完成的情况下,直接开发具体的业务功能。
  4. 到了代码提交环节,我们可以使用Git Hook自动化在提交之前作出项目检查,确保不会提交有问题的代码,甚至连提交的日志都可以严格限制格式。
  5. 最后在部署阶段,我们可以使用一行命令代替传统手动的FTP上传,甚至还可以实现在代码提交过后自动部署到服务器。

工程化≠工具

现在Webpack功能强大,很多人一提到前端工程化就觉得是Webpack,有了Webpack就是有了工程化,但其实不是这样的,工具并不能代码工程化,工程化的核心应该是对项目的一种规划或者架构,工具只是落地实现的手段。

以一个普通项目为例,落实工程化的第一件事是规划整体项目的工作流架构,包括:

  1. 文件的组织结构
  2. 源代码的开发范式
  3. 语言或者语法规范
  4. 前后端分离方式
  5. 其它对开发阶段的需求

有了这些整体的规划之后,再来具体考虑搭配使用哪些具体的工具,配置具体的选项。这才是一个工程化的过程。

脚手架工具

在对前端工程化有了初步认识之后,先从脚手架开始,看看前端工程化在项目创建环节中的具体表现。

脚手架是什么?

脚手架可以简单理解为用来自动帮我们创建项目基础文件的工具,更重要的是提供给开发者一些约定或规范,包括:

我们实际开发过程中搭建新项目时会有大量的重复工作要做,使用脚手架可以避免这些重复的工作。我们通过脚手架工作快速搭建特定类型项目的基础骨架结构,例如Vue-cli,基于这个接触结构进行后续的开发工作。

实现一个我们自己的小型脚手架工具

思路

脚手架的工作过程:

  1. 用户使用脚手架,脚手架通过命令行交互询问用户问题
  2. 脚手架根据用户回答的结果生成相应文件

实现过程

  1. 新建目录MY-CLI

  2. 创建package.json

    npm iniy -y
     
  3. 在package.json中添加bin字段,指定脚手架的入口文件cli.js

    {"name": "my-cli","version": "1.0.0","description": "","main": "index.js","bin": "cli.js","scripts": {"test": "echo "Error: no test specified" && exit 1"}
     
  4. 安装依赖的插件inquirer和ejs

    npm i inquirer // 用于询问开发者问题
     npm i ejs      // 模板引擎
     
  5. 创建cli.js文件

    #!/usr/bin/env node
     // Node cli应用入口文件必须要有这样的文件头。
     // 让系统动态的去查找node,解决不同机器不同用户设置不一致的问题
     const fs = require("fs");
     const path = require("path");
     const inquirer = require("inquirer");
     const ejs = require("ejs");// 询问用户
     inquirer.prompt([{type: "input",name: "name",message: "Your Project Name?",},]).then((anwsers) => {// 模板目录const tmplDir = path.join(__dirname, "templates");// process.cwd()返回当前工作目录,也就是目标目录const destDir = process.cwd();// fs.readdir()获取模板目录下所有文件的文件名fs.readdir(tmplDir, (err, files) => {if (err) throw err;// 遍历文件名files.forEach((file) => {// 通过模板引擎渲染文件ejs.renderFile(path.join(tmplDir, file), anwsers, (err, result) => {if (err) throw err;// console.log(result);// 将结果写入目标文件路径fs.writeFileSync(path.join(destDir, file), result);});});});});
    // templates/index.html
     
     
     <%= name %>
     
     
     
     
  6. 将脚手架关联到全局

    npm link
     
  7. 测试

    MY-CLI
     

    在新建文件夹testcli文件夹下执行该命令,根据命令行询问内容输入name,在该文件夹下生成相应模板文件。

Gulp自动化构建

需求

使用gulp工具构建项目,主要任务包括scss文件的转化,ECMAScript新特性的转化,还有swig模板页面的转化,并且转化上线的dist包。优化构建过程,提高开发过程的开发效率。图片、字体文件、HTML、JS、CSS文件压缩。

实现步骤

  1. 安装gulp为开发依赖,创建gukpfile.js。
  2. 实现样式文件的编译。用到的插件是gulp-sass。由于需要使用gulp-useref文件引用处理,所以我们先将编译的文件放到temp文件夹下,经过useref处理后上线的代码放到dist文件夹下面。
  3. 实现脚本文件的编译,需要将ECMAScript新特性进行编译,用到的插件是gulp-babel、@babel/core和@babel/preset-env。gulp-bale只是一个转换平台,所以需要@babel/core和@babel/preset-env进行编译。
  4. 实现模板文件的编译,用到的gulp-swig插件。
  5. 其它文件以及文件的清除,用到的插件是del。我们需要将额外的一些公共文件直接转入dist文件夹下。每次构建时先将dist文件夹删除。
  6. 图片和字体文件转换压缩,用到的插件是gulp-imagemin。由于使用了多个gulp插件,一个一个的引入比较繁琐,所以我们使用gulp-load-plugins插件。
  7. 我们需要一个支持热更新的开发服务器用于调试我们的项目,用gulp来管理这个服务器,这样子后续我们在修改代码时可以自动编译、自动刷新浏览器页面,提高我们的开发效率,减少重复性操作。这里需要用到gulp的watch方法,来监视我们的文件,如果有更新就执行相应的任务,达到热更新的目的。
  8. 优化我们的构建过程。在开发阶段,image、font等资源可以直接使用原目录下的文件,这样子可以加快我们在开发过程中的构建效率。是代码中的develop任务,在开发中使用。
  9. 还需要创建编译后线上版本的任务,需要先清除,然后再编译样式、脚本、页面文件后使用useref通过构建注释解决线上版本文件引用的问题,同时将线上版本的html css js文件压缩,后同时将压缩后的图片、字体文件和其他公共文件编译到dist文件夹里面。具体为代码中的build任务,构建线上版本使用。
  10. 测试完成。

具体插件

gulp-sass gulp-babel gulp-clean gulp-clean gulp-swig gulp-imagemin gulp-load-plugins gulp-useref browser-sync gulp-htmlmin gulp-uglify gulp-clean-css gulp-if等。

实现完整代码

// gulpfile.js
 const { src, dest, series, parallel, watch } = require("gulp");
 const loadPlugins = require("gulp-load-plugins");
 const plugins = loadPlugins();
 const del = require("del");
 const browserSync = require("browser-sync");const bs = browserSync.create();
 // 模拟数据
 const data = {menus: [{name: "Home",icon: "aperture",link: "index.html",},{name: "Features",link: "features.html",},{name: "About",link: "about.html",},{name: "Contact",link: "#",children: [{name: "Twitter",link: "https://twitter.com/w_zce",},{name: "About",link: "https://weibo.com/zceme",},{name: "divider",},{name: "About",link: "https://github.com/zce",},],},],pkg: require("./package.json"),date: new Date(),
 };// 清除dist文件夹
 const clean = () => {return del(["dist", "temp"]);
 };// 样式编译
 const style = () => {// 读取src下面所有的scss文件,为其设置基准路径为src,保留src后面的目录结构return src("src/assets/styles/*.scss", { base: "src" }).pipe(plugins.sass({ outputStyle: "expanded" })) // 用sass编译scss文件,输出样式为完全展开.pipe(dest("temp")).pipe(bs.reload({ stream: true })); // 每次编译后调用bs的reload方法,以stream流的形式
 };
 // 脚本编译
 const script = () => {return src("src/assets/scripts/*.js", { base: "src" }).pipe(plugins.babel({ presets: ["@babel/preset-env"] })).pipe(dest("temp")).pipe(bs.reload({ stream: true }));
 };
 // 模板编译
 const page = () => {return src("src/*.html", { base: "src" }).pipe(plugins.swig({ data, defaults: { cache: false } })).pipe(dest("temp")).pipe(bs.reload({ stream: true }));
 };
 // 图片压缩
 const image = () => {return src("src/assets/images/**", { base: "src" }).pipe(plugins.imagemin()).pipe(dest("dist"));
 };
 // 字体
 const font = () => {return src("src/assets/fonts/**", { base: "src" }).pipe(plugins.imagemin()).pipe(dest("dist"));
 };
 // 额外的文件
 const extra = () => {return src("public/**", { base: "public" }).pipe(dest("dist"));
 };// 开发服务器启动任务
 const serve = () => {// 监视路径下文件如果发生变化就去执行相应的任务watch("src/assets/styles/*.scss", style);watch("src/assets/scripts/*.js", script);watch("src/*.html", page);watch(["src/assets/images/**", "src/assets/fonts/**", "public/**"],bs.reload);// 初始化bs.init({notify: false,port: 2080,// 监视dist文件夹下的所有文件夹会自动更新网页// files: "dist/**",server: {// 设置网站的根目录baseDir: ["temp", "src", "public"],// 先找routes下有无对应的配置 没有就去baseDir找routes: {"/node_modules": "node_modules",},},});
 };const useref = () => {return (src("temp/*.html", { base: "temp" }).pipe(plugins.useref({ searchPath: ["temp", "."] }))// 压缩html js css.pipe(plugins.if(/.js$/, plugins.uglify())).pipe(plugins.if(/.html$/,plugins.htmlmin({collapseWhitespace: true,minifyCSS: true,minifyJS: true,}))).pipe(plugins.if(/.css$/, plugins.cleanCss())).pipe(dest("dist")));
 };// src下面的文件编译
 const compile = parallel(style, script, page);
 // 所有任务
 const build = series(clean,parallel(series(compile, useref), image, font, extra)
 );const develop = series(compile, serve);
 module.exports = {develop,build,clean,compile,useref,
 };

历史文章传送门

参考

拉勾大前端训练营


标签:

素材巴巴 Copyright © 2013-2021 http://www.sucaibaba.com/. Some Rights Reserved. 备案号:备案中。