前端工程化,简而言之就是软件工程 + 前端,以自动化的形式呈现。就个人理解而言:前端工程化,从开发阶段到代码发布生产环境,包含了以下几个内容:
下面我们根据上述几个内容,选择有代表性的几个方面进行深入学习前端工程化。
回顾:【青训营】- 了解前端工程化[2]
现在流行的前端脚手架基本上都是基于NodeJs
编写,比如我们常用的Vue-CLI
,比较火的create-react-app
,还有Dva-CLI
等。
随着前端工程化的概念越来越深入人心,脚手架的出现就是为减少重复性工作而引入的命令行工具,摆脱ctrl + c
, ctrl + v
,此话怎讲? 现在新建一个前端项目,已经不是在html
头部引入css
,尾部引入js
那么简单的事了,css
都是采用Sass
或则Less
编写,在js
中引入,然后动态构建注入到html
中;除了学习基本的js
,css
语法和热门框架,还需要学习构建工具webpack
,babel
这些怎么配置,怎么起前端服务,怎么热更新;为了在编写过程中让编辑器帮我们查错以及更加规范,我们还需要引入ESlint
;甚至,有些项目还需要引入单元测试(Jest
)。对于一个更入门的人来说,这无疑会让人望而却步。而前端脚手架的出现,就让事情简单化,一键命令,新建一个工程,再执行两个npm
命令,跑起一个项目。在入门时,无需关注配置什么的,只需要开心的写代码就好。
我们实现脚手架的核心思想就是自动化思维,将重复性的ctrl + c
, ctrl + v
创建项目,用程序来解决。解决步骤如下:
创建文件夹前,需要先删除清空:
// package.json
{
...
"scripts": {
"test": "rm -rf ./haha && node --experimental-modules index.js"
}
...
}
复制代码
创建文件夹:我们通过引入 nodejs
的 fs
模块,使用 mkdirSync
API来创建文件夹。
// index.js
import fs from 'fs';
function getRootPath() {
return "./haha";
}
// 生成文件夹
fs.mkdirSync(getRootPath());
复制代码
创建 index.js:使用 nodejs
的fs
模块的 writeFileSync
API 创建 index.js 文件:
// index.js
fs.writeFileSync(getRootPath() + "/index.js", createIndexTemplate(inputConfig));
复制代码
接着我们来看看,动态模板如何生成?我们最理想的方式是通过配置来动态生成文件模板,那么具体来看看 createIndexTemplate
实现的逻辑吧。
// index.js
import fs from 'fs';
import { createIndexTemplate } from "./indexTemplate.js";
// input
// process
// output
const inputConfig = {
middleWare: {
router: true,
static: true
}
}
function getRootPath() {
return "./haha";
}
// 生成文件夹
fs.mkdirSync(getRootPath());
// 生成 index.js 文件
fs.writeFileSync(getRootPath() + "/index.js", createIndexTemplate(inputConfig));
复制代码
// indexTemplate.js
import ejs from "ejs";
import fs from "fs";
import prettier from "prettier";// 格式化代码
// 问题驱动
// 模板
// 开发思想 - 小步骤的开发思想
// 动态生成代码模板
export function createIndexTemplate(config) {
// 读取模板
const template = fs.readFileSync("./template/index.ejs", "utf-8");
// ejs渲染
const code = ejs.render(template, {
router: config.middleware.router,
static: config.middleware.static,
port: config.port,
});
// 返回模板
return prettier.format(code, {
parser: "babel",
});
}
复制代码
// template/index.ejs
const Koa = require("koa");
<% if (router) { %>
const Router = require("koa-router");
<% } %>
<% if (static) { %>
const serve = require("koa-static");
<% } %>
const app = new Koa();
<% if (router) { %>
const router = new Router();
router.get("/", (ctx) => {
ctx.body = "hello koa-setup-heihei";
});
app.use(router.routes());
<% } %>
<% if (static) { %>
app.use(serve(__dirname + "/static"));
<% } %>
app.listen(<%= port %>, () => {
console.log("open server localhost:<%= port %>");
});
复制代码
创建 package.json 文件,实质是和创建 index.js 类似,都是采用动态生成模板的思路来实现,我们来看下核心方法 createPackageJsonTemplate
的实现代码:
// packageJsonTemplate.js
function createPackageJsonTemplate(config) {
const template = fs.readFileSync("./template/package.ejs", "utf-8");
const code = ejs.render(template, {
packageName: config.packageName,
router: config.middleware.router,
static: config.middleware.static,
});
return prettier.format(code, {
parser: "json",
});
}
复制代码
// template/package.ejs
{
"name": "<%= packageName %>",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"koa": "^2.13.1"
<% if (router) { %>
,"koa-router": "^10.1.1"
<% } %>
<% if (static) { %>
,"koa-static": "^5.0.0"
}
<% } %>
}
复制代码
要自动安装依赖,我们可以使用 nodejs
的 execa
库执行 yarn
安装命令:
execa("yarn", {
cwd: getRootPath(),
stdio: [2, 2, 2],
});
复制代码
至此,我们已经用 nodejs
实现了新建项目的脚手架了。最后我们可以重新梳理下可优化点将其升级完善。比如将程序配置升级成 GUI
用户配置(用户通过手动选择或是输入来传入配置参数,例如项目名)。
构建,或者叫作编译,是前端工程化体系中功能最繁琐、最复杂的模块,承担着从源代码转化为宿主浏览器可执行的代码,其核心是资源的管理。前端的产出资源包括JS、CSS、HTML等,分别对应的源代码则是:
以上源代码是无法在浏览器环境下运行的,构建工作的核心便是将其转化为宿主可执行代码,分别对应:
那么下面我们就一起学习下如今3大主流构建工具:Webpack、Rollup、Vite。
image.png
想要真正用好 Webpack
编译构建工具,我们需要先来了解下它的工作原理。Webpack
编译项目的工作机制是,递归找出所有依赖模块,转换源码为浏览器可执行代码,并构建输出bundle。具体工作流程步骤如下:
compiler
对象,执行run
方法开始编译entry
,确定入口文件loader
转译所有模块,得到转译后的最终内容和依赖关系chunk
,加到输出列表output
,确定输出路径和文件名,把文件内容写入输出目录(默认是dist
)entry
入口配置,webpack 编译构建时能找到编译的入口文件,进而构建内部依赖图。
output
输出配置,告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。
loader
模块转换器,loader 可以处理浏览器无法直接运行的文件模块,转换为有效模块。比如:css-loader和style-loader处理样式;url-loader和file-loader处理图片。
plugin
插件,解决 loader 无法实现的问题,在 webpack 整个构建生命周期都可以扩展插件。比如:打包优化,资源管理,注入环境变量等。
下面是 webpack 基本配置的简单示例:
const path = require("path");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
},
devServer: {
static: "./dist",
},
module: {
rules: [
{
// 匹配什么样子的文件
test: /\.css$/i,
// 使用loader , 从后到前执行
use: ["style-loader", "css-loader"],
}
],
},
};
复制代码
参考webpack官网:webpack.docschina.org/concepts/[3] (注意:使用不同版本的 webpack 切换对应版本的文档哦)
检测编译速度
寻找检测编译速度的工具,比如 speed-measure-webpack-plugin插件[4] ,用该插件分析每个loader和plugin执行耗时具体情况。
优化编译速度该怎么做呢?
- 减少搜索依赖的时间
- 配置 loader 匹配规则 test/include/exclue,缩小搜索范围,即可减少搜索时间
2 . 减少解析转换的时间
- noParse配置,精准过滤不用解析的模块
- loader性能消耗大的,开启多进程
3 . 减少构建输出的时间
- 压缩代码,开启多进程
4 . 合理使用缓存策略
- babel-loader开启缓存
- 中间模块启用缓存,比如使用 hard-source-webpack-plugin
具体优化措施可参考:webpack性能优化的一段经历|项目复盘[5]
检测包体积大小
寻找检测构建后包体积大小的工具,比如 webpack-bundle-analyzer插件[6] ,用该插件分析打包后生成Bundle的每个模块体积大小。
优化体积该怎么做呢?
- bundle去除第三方依赖
- 擦除无用代码 Tree Shaking
具体优化措施参考:webpack性能优化的一段经历|项目复盘[7]
Rollup[8] 是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如 library 或应用程序。并且可以对代码模块使用新的标准化格式,比如CommonJS
和 es module
。
我们先来了解下 Rollup
原理,其主要工作机制是:
Acorn
读取解析文件,获取抽象语法树 ASTRollup
相对 Webpack
而言,打包出来的包会更加轻量化,更适用于类库打包,因为内置了 Tree Shaking 机制,在分析代码阶段就知晓哪些文件引入并未调用,打包时就会自动擦除未使用的代码。
Acorn 是一个 JavaScript 语法解析器,它将 JavaScript 字符串解析成语法抽象树 AST 如果想了解 AST 语法树可以点下这个网址astexplorer.net/[9]
input
入口文件路径
output
输出文件、输出格式(amd/es6/iife/umd/cjs)、sourcemap启用等。
plugin
各种插件使用的配置
external
提取外部依赖
global
配置全局变量
下面是 Rollup 基础配置的简单示例:
import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
// 解析json
import json from '@rollup/plugin-json'
// 压缩代码
import { terser } from 'rollup-plugin-terser';
export default {
input: "src/main.js",
output: [{
file: "dist/esmbundle.js",
format: "esm",
plugins: [terser()]
},{
file: "dist/cjsbundle.js",
format: "cjs",
}],
// commonjs 需要放到 transform 插件之前,
// 但是又个例外, 是需要放到 babel 之后的
plugins: [json(), resolve(), commonjs()],
external: ["vue"]
};
复制代码
Vite[10],相比 Webpack、Rollup 等工具,极大地改善了前端开发者的开发体验,编译速度极快。
为什么 Vite 开发编译速度极快?我们就先来探究下它的原理吧。 由上图可见,Vite 原理是利用现代主流浏览器支持原生的 ESM 规范,配合 server 做拦截,把代码编译成浏览器支持的。
我们可以搭建一个Hello World版的Vite项目来感受下飞快的开发体验:
注意:Vite 需要 Node.js[11] 版本 >= 12.0.0。
使用 NPM:
$ npm init vite@latest
复制代码
使用 Yarn:
$ yarn create vite
复制代码
上图是Vite项目的编译时间,363ms,开发秒级编译的体验,真的是棒棒哒!
Webpack | Rollup | Vite | |
---|---|---|---|
编译速度 | 一般 | 较快 | 最快 |
HMR热更新 | 支持 | 需要额外引入插件 | 支持 |
Tree Shaking | 需要额外配置 | 支持 | 支持 |
适用范围 | 项目打包 | 类库打包 | 不考虑兼容性的项目 |
当我们前端项目越来越庞大时,开发迭代维护成本就会越来越高,数十个模块相互调用错综复杂,为了提高代码质量和可维护性,就需要写测试了。下面就给大家具体介绍下前端工程经常做的3类测试。
单元测试,是对最小可测试单元(一般为单个函数、类或组件)进行检查和验证。
做单元测试的框架有很多,比如 Mocha[12]、断言库Chai[13]、Sinon[14]、Jest[15]等。我们可以先选择 jest 来学习,因为它集成了 Mocha
,chai
,jsdom
,sinon
等功能。接下来,我们一起看看 jest
怎么写单元测试吧?
以验证求和函数为例:
// add函数
module.exports = (a,b) => {
return a+b;
}
复制代码
// 正确性测试验证
const add = require('./add.js');
test('should 1+1 = 2', ()=> {
// 准备测试数据 -> given
const a = 1;
const b = 1;
// 触发测试动作 -> when
const r = add(a,b);
// 验证 -> then
expect(r).toBe(2);
})
复制代码
image.png
// 错误性测试验证
test('should 1+1 = 2', ()=> {
// 准备测试数据 -> given
const a = 1;
const b = 2;
// 触发测试动作 -> when
const r = add(a,b)
// 验证 -> then
expect(r).toBe(2);
})
复制代码
image.png
组件测试,主要是针对某个组件功能进行测试,这就相对困难些,因为很多组件涉及了DOM
操作。组件测试,我们可以借助组件测试框架来做,比如使用 Cypress[16](它可以做组件测试,也可以做 e2e 测试)。我们就先来看看组件测试怎么做?
以 vue3 组件测试为例:
vue3
+ vite
项目,编写测试组件 cypress
环境 cypress/component
编写组件测试脚本文件 cypress open-ct
命令,启动 cypress component testing
的服务运行 xx.spec.js
测试脚本,便能直观看到单个组件自动执行操作逻辑 // Button.vue 组件
<template>
<div>Button测试</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
复制代码
// cypress/plugin/index.js 配置
const { startDevServer } = require('@cypress/vite-dev-server')
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
on('dev-server:start', (options) => {
const viteConfig = {
// import or inline your vite configuration from vite.config.js
}
return startDevServer({ options, viteConfig })
})
return config;
}
复制代码
// cypress/component/Button.spec.js Button组件测试脚本
import { mount } from "@cypress/vue";
import Button from "../../src/components/Button.vue";
describe("Button", () => {
it("should show button", () => {
// 挂载button
mount(Button);
cy.contains("Button");
});
});
复制代码
e2e 测试,也叫端到端测试,主要是模拟用户对页面进行一系列操作并验证其是否符合预期。我们同样也可以使用 cypress 来做 e2e 测试,具体怎么做呢?
以 todo list 功能验证为例:
vue3
+ vite
项目,编写测试组件 cypress
环境 cypress/integration
编写组件测试脚本文件 cypress open
命令,启动 cypress
的服务,选择 xx.spec.js
测试脚本,便能直观看到模拟用户的操作流程 // cypress/integration/todo.spec.js todo功能测试脚本
describe('example to-do app', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/todo')
})
it('displays two todo items by default', () => {
cy.get('.todo-list li').first().should('have.text', 'Pay electric bill')
cy.get('.todo-list li').last().should('have.text', 'Walk the dog')
})
it('can add new todo items', () => {
const newItem = 'Feed the cat'
cy.get('[data-test=new-todo]').type(`${newItem}{enter}`)
cy.get('.todo-list li')
.should('have.length', 3)
.last()
.should('have.text', newItem)
})
it('can check off an item as completed', () => {
cy.contains('Pay electric bill')
.parent()
.find('input[type=checkbox]')
.check()
cy.contains('Pay electric bill')
.parents('li')
.should('have.class', 'completed')
})
context('with a checked task', () => {
beforeEach(() => {
cy.contains('Pay electric bill')
.parent()
.find('input[type=checkbox]')
.check()
})
it('can filter for uncompleted tasks', () => {
cy.contains('Active').click()
cy.get('.todo-list li')
.should('have.length', 1)
.first()
.should('have.text', 'Walk the dog')
cy.contains('Pay electric bill').should('not.exist')
})
it('can filter for completed tasks', () => {
// We can perform similar steps as the test above to ensure
// that only completed tasks are shown
cy.contains('Completed').click()
cy.get('.todo-list li')
.should('have.length', 1)
.first()
.should('have.text', 'Pay electric bill')
cy.contains('Walk the dog').should('not.exist')
})
it('can delete all completed tasks', () => {
cy.contains('Clear completed').click()
cy.get('.todo-list li')
.should('have.length', 1)
.should('not.have.text', 'Pay electric bill')
cy.contains('Clear completed').should('not.exist')
})
})
})
复制代码
本文前言部分通过开发、构建、性能、测试、部署、规范六个方面,较全面地梳理了前端工程化的知识点,正文则主要介绍了在实践项目中落地使用的前端工程化核心技术点。
希望本文能够帮助到正在学前端工程化的小伙伴构建完整的知识图谱~
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/cVnIBvrdJS26koIj9DwS3Q
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。