
打工人!打工魂!前端才是人上人!此系列總結于大前端進擊之路程序中的學習,如果文章中有不對的地方,希望大家能進行批評改正,互相進步,轉載請注明出處并附上原文地址 ,
前端工程化是什么?
前端工程化指的是在團隊內遵循一定的標準和規范,使用工具提高開發人員的的作業效率、降低維護成本,還可以減少因為人工操作失誤而導致的不可控因素,
為我們解決了什么問題?
我們首先來列舉在日常開發程序中經常會面臨的一些問題(不僅限于此):
- 我們想要使用Less/Sass來增強CSS的編程性,但是運行環境不能直接支持,總是需要我們編譯等重復性作業,
- 我們想要使用ECMAScript新特性時會有兼容性問題,
- 想要使用模塊化的方式提高專案的可維護性,運行環境也不能直接支持,
- 專案部署上線前需要手動壓縮資源檔案,程序中需要手動上傳代碼到服務器,是容易出現問題的,
- 多人協作開發,每個人的代碼風格是不一樣的,無法硬性統一大家的代碼風格,
- …
我們將這些問題歸納總結為以下幾點:
- 傳統語言和語法的弊端
- 無法使用模塊化或組件化
- 重復的機械式作業
- 代碼風格、格式無法硬性統一
- 倉庫代碼質量無法保證
- 依賴后端服務介面支持
- 整體依賴后端專案
具體解決方案
我們以一個簡單的專案開發流程為例,看看前端工程化在這個程序中的作用,讓我們先從整體角度對前端工程化有一個全面的認識,從專案的創建、編碼、預覽、提交、部署,每一個環節我們都可以通過前端工程化的方式提高作業效率,
- 創建專案程序我們可以使用腳手架工具自動完成,
- 編碼程序我們可以通過編譯或者轉化工具提前使用一些新特性,利用格式化和代碼檢查工具自動檢測和修復代碼中的基礎問題,
- 預覽時可以通過Web Server自動預覽并享受熱更新的體驗,并且可以使用Mock的方式在后端服務介面未完成的情況下,直接開發具體的業務功能,
- 到了代碼提交環節,我們可以使用Git Hook自動化在提交之前作出專案檢查,確保不會提交有問題的代碼,甚至連提交的日志都可以嚴格限制格式,
- 最后在部署階段,我們可以使用一行命令代替傳統手動的FTP上傳,甚至還可以實作在代碼提交過后自動部署到服務器,
工程化≠工具
現在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 <!DOCTYPE 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自動化構建
需求
使用gulp工具構建專案,主要任務包括scss檔案的轉化,ECMAScript新特性的轉化,還有swig模板頁面的轉化,并且轉化上線的dist包,優化構建程序,提高開發程序的開發效率,圖片、字體檔案、HTML、JS、CSS檔案壓縮,
實作步驟
- 安裝gulp為開發依賴,創建gukpfile.js,
- 實作樣式檔案的編譯,用到的插件是gulp-sass,由于需要使用gulp-useref檔案參考處理,所以我們先將編譯的檔案放到temp檔案夾下,經過useref處理后上線的代碼放到dist檔案夾下面,
- 實作腳本檔案的編譯,需要將ECMAScript新特性進行編譯,用到的插件是gulp-babel、@babel/core和@babel/preset-env,gulp-bale只是一個轉換平臺,所以需要@babel/core和@babel/preset-env進行編譯,
- 實作模板檔案的編譯,用到的gulp-swig插件,
- 其它檔案以及檔案的清除,用到的插件是del,我們需要將額外的一些公共檔案直接轉入dist檔案夾下,每次構建時先將dist檔案夾洗掉,
- 圖片和字體檔案轉換壓縮,用到的插件是gulp-imagemin,由于使用了多個gulp插件,一個一個的引入比較繁瑣,所以我們使用gulp-load-plugins插件,
- 我們需要一個支持熱更新的開發服務器用于除錯我們的專案,用gulp來管理這個服務器,這樣子后續我們在修改代碼時可以自動編譯、自動重繪瀏覽器頁面,提高我們的開發效率,減少重復性操作,這里需要用到gulp的watch方法,來監視我們的檔案,如果有更新就執行相應的任務,達到熱更新的目的,
- 優化我們的構建程序,在開發階段,image、font等資源可以直接使用原目錄下的檔案,這樣子可以加快我們在開發程序中的構建效率,是代碼中的develop任務,在開發中使用,
- 還需要創建編譯后線上版本的任務,需要先清除,然后再編譯樣式、腳本、頁面檔案后使用useref通過構建注釋解決線上版本檔案參考的問題,同時將線上版本的html css js檔案壓縮,后同時將壓縮后的圖片、字體檔案和其他公共檔案編譯到dist檔案夾里面,具體為代碼中的build任務,構建線上版本使用,
- 測驗完成,
具體插件
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,
};
歷史文章傳送門
- 大前端進擊之路(一)函式式編程
- 大前端進擊之路(二)JS異步編程
- 大前端進擊之路(番外篇)手寫Promise
- 大前端進擊之路(三)ECMAScript新特性
- 大前端進擊之路(四)TypeScript入門基礎
參考
拉勾大前端訓練營
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/250240.html
標籤:其他
