打工人!打工魂!前端才是人上人!此系列总结于大前端进击之路过程中的学习,如果文章中有不对的地方,希望大家能进行批评改正,互相进步。转载请注明出处并附上原文地址 。
前端工程化指的是在团队内遵循一定的标准和规范,使用工具提高开发人员的的工作效率、降低维护成本,还可以减少因为人工操作失误而导致的不可控因素。
我们首先来列举在日常开发过程中经常会面临的一些问题(不仅限于此):
我们将这些问题归纳总结为以下几点:
我们以一个简单的项目开发流程为例,看看前端工程化在这个过程中的作用,让我们先从整体角度对前端工程化有一个全面的认识。从项目的创建、编码、预览、提交、部署,每一个环节我们都可以通过前端工程化的方式提高工作效率。
现在Webpack功能强大,很多人一提到前端工程化就觉得是Webpack,有了Webpack就是有了工程化,但其实不是这样的,工具并不能代码工程化,工程化的核心应该是对项目的一种规划或者架构,工具只是落地实现的手段。
以一个普通项目为例,落实工程化的第一件事是规划整体项目的工作流架构,包括:
- 文件的组织结构
- 源代码的开发范式
- 语言或者语法规范
- 前后端分离方式
- 其它对开发阶段的需求
有了这些整体的规划之后,再来具体考虑搭配使用哪些具体的工具,配置具体的选项。这才是一个工程化的过程。
在对前端工程化有了初步认识之后,先从脚手架开始,看看前端工程化在项目创建环节中的具体表现。
脚手架可以简单理解为用来自动帮我们创建项目基础文件的工具,更重要的是提供给开发者一些约定或规范,包括:
- 相同的文件组织结构
- 相同的代码开发范式
- 相同的模块依赖
- 相同的工具模块配置
- 相同的基础代码
我们实际开发过程中搭建新项目时会有大量的重复工作要做,使用脚手架可以避免这些重复的工作。我们通过脚手架工作快速搭建特定类型项目的基础骨架结构,例如Vue-cli,基于这个接触结构进行后续的开发工作。
脚手架的工作过程:
新建目录MY-CLI
创建package.json
npm iniy -y
在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"
}
安装依赖的插件inquirer和ejs
npm i inquirer // 用于询问开发者问题
npm i ejs // 模板引擎
创建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
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= name %>title>
head>
<body>
body>
html>
将脚手架关联到全局
npm link
测试
MY-CLI
在新建文件夹testcli文件夹下执行该命令,根据命令行询问内容输入name,在该文件夹下生成相应模板文件。
使用gulp工具构建项目,主要任务包括scss文件的转化,ECMAScript新特性的转化,还有swig模板页面的转化,并且转化上线的dist包。优化构建过程,提高开发过程的开发效率。图片、字体文件、HTML、JS、CSS文件压缩。
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,
};
拉勾大前端训练营