大家好,欢迎来到IT知识分享网。
(1). 工程化体系定义:
①. 广义上,一切以"提高效率、降低成本、保障质量"为目的的手段,都属于工程化的范畴. ②. 通过一系列的规范、流程、工具达到"研发提效、自动化、保障质量、服务稳定、预警监控"等. ③. 可以借助于Node,将研发链路延伸到整个DevOps中去. ④. 前端工程化指使用软件工程的技术与方法对前端开发的技术、工具、流程、经验、方案等指标标准化: a. 模块化 b. 组件化 c. 规范化 d. 自动化 ⑤. 目的: a. 降低成本 b. 增加效率
(2). 团队标准:
①. 互联网前端标配: 组件化、工程化、自动化 ②. 规模的团队: a. 根据自身业务与梯度来设计符合业务的DevOps流程.
(3). 简单DevOps:
①. 常规基建: a. 组件库 + 脚手架 + 工具库 + 模板 + CLI ②. Git Flow: a. 通过常规Git Flow工作流,不同branch不同功能 + Code Review ③. CICD: a. Webhook +脚本 ④. 说明: a. 上述DevOps流程,作为小型团队搭建工程化的起点,性价比极高. b. 在团队没有制定规则,也没有基础建设时,通常先从最基础的CLI工具开始然后切入到整个工程化的搭建.
(4). 个人发展:
编写业务代码 => 使用前端工程化解决生产问题 => 前端架构设计 => 技术管理岗晋升
(5). 业务痛点:
随着需求迭代的步伐加速,可能会产生以下问题
①. 构建配置、打包配置、公共组件、工具函数等代码片段,每次新开项目都要复制粘贴 ②. 团队成员的编码风格大相径庭,导致从仓库拉取下来的代码运行起来让控制台一片红 ③. 团队协作的规范、环境、模块、仓库和文档,太多基建措施导致团队新成员无从入手 ④. 随着需求迭代引起项目结构与工程文件不断变化,处理不当让项目直接走向重构道路
前端工程化的开发思维与解决方案应用到项目中,解决非业务需求,为业务降本增效.
①. 前端工程化不是某个具体的工具: a. 对项目的整体架构与整体规划,使开发者能在未来可判时间内动态规划发展走向,以提升整个项目对用户的服务周期 ②. 闭环: a. 理解项目的完整流程 b. 在复杂的流程中快速定位并解决问题 c. 根据知识储备制定一些可扩展流程 d. 预见项目的未来发展方向
前端工程化体系:
①. 明确前后端任务分离的能力: a. 任务属于前端还是后端,利于前端工程化的接入 b. 基于前端工程化解决问题的基础 ②. 核心特性: a. 模块化、组件化、规范化和自动化 b. 如何实现?各自的标准是什么? ③. 前端工程化领域实践: a. 利用工程架构的知识重构项目 b. 脚手架、组件库、工具库、多包仓库、私有仓库、接口系统、文档系统、监控系统、CI/CD、可移植容器 c. 从手动处理流程转换为自动处理流程,让其它成员更专注于自身业务需求
前端工程化的意义:
①. 前后分离: a. 前后端自成体系,且与后端分离 b. 不限于规范、服务、环境、构建、组织和部署方面 ②. 技术选型: a. 不能以一个框架满足所有业务场景 b. 制定多套框架解决方案避免技术瓶颈的出现 ③. 重构封装: a. 新生技术不断涌现就要避免改头换面式的重构 b. 重复需求不断出现就要学会举一反三的封装 ④. 工程设计: a. 解决方案要合理分层且互相独立,随时应对各种变化 b. 任何一层可低成本被替换与淘汰 ⑤. 所有的基建都是要依托业务才能发挥最大的作用
2. 如何开发一个前端脚手架?
①. 功能: a. 脚手架是一套命令集,不只用来创建项目. b. 解耦 - 脚手架与模板分离: (1). 脚手架负责构建流程,通过命令行与用户交互,获取项目信息 (2). 脚手架需要检测模板的版本是否有更新,支持模板的删除与新建 (3). 模板负责统一项目结构、工作流程、依赖项管理 ②. 作用: a. 减少重复工作,不需要复制其它项目再删除无关代码,或从零创建一个项目和文件. b. 根据交互动态生成项目结构和配置文件.
1. CLI工具集:
①. 构建: a. 提供本地构建功能 b. 接管发布构建 ②. 质量: a. 自动化测试 b. Eslint校验 ③. 模板: a. 创建模板 b. 创建区块 ④. 工具合集: a. 其他可以内置的工具类
(1). 构建:
①. 小团队构建流程: a. 在一套或多套模板中使用webpack或rollup构建工具,配置多个文件,如.env.production、.env.development、.env.staging b. 通过Shell脚本来构建项目 c. 进行部署,实现了简单、通用的CI/CD流程 ②. 构建过程不可控: a. 团队的开发成员都可以修改发布配置项 b. 误操作,如选择的是dev模式,没有对构建代码压缩混淆、没有注入一些全局统一方法等. ③. 优化: a. 构建配置和项目模板分离: (1). 将构建配置、过程从项目模板中抽离出来,统一使用CLI管理构建流程: (2). 不再读取项目中的配置 (3). 通过CLI使用统一配置(每一类项目都可以自定义一套标准构建配置)进行构建. b. 避免业务开发同学修改了错误配置而导致的生产问题.
(2). 质量:
①. 通用的自动化测试、常规的格式校验统一: a. 如每个开发的习惯不同,导致ESLINT校验规则不同. b. 同一个团队必须使用同一套代码校验规则最好. ②. 优化: a. 将自动化测试、校验从项目中剥离,使用CLI接管,从而保证整个团队同一类项目代码格式的统一性.
(3). 模板:
①. 可以快速、便捷初始化一个项目或代码片段. ②. Cli工具产出最高、收益最明显的功能模块.
(4). 工具合集:
①. 通用的工具类: a. 图片压缩(png压缩)、上传CDN等 b. 项目升级(如通用配置更新了,提供一键升级模板的功能) c. 项目部署、发布npm包等操作 ②. 其它一些重复性的操作
3. 生成最简化脚手架:
(1). 初始化package.json文件:
yarn init
(2). 在package.json中,新增bin属性:
{
"name": "cli", "main": "index.js", "bin": {
"gl-cli": "./index.js" // gl-cli表示脚手架的名称 } }
(3). 根目录下新增cli.js文件:
#!/usr/bin/env node // Node CLI 应用入口文件必须要有这样的文件头,用于指定脚本的解释程序 console.log('gl-cli working!') 注: ①. Linux或maxOS,需要修改此文件的读写权限为755: chmod 755 cli.js
(4). 把本项目/应用链接到yarn全局缓存(链接到全局),只是方便开发调试:
yarn link // 在当前根目录执行,yarn unlink可卸载 注: ①. 检查当前yarn的bin位置: a. yarn global bin => /Users/xxx/.yarn/bin => 有一个gl-cli执行命令 ②. 检查当前 yarn 的 全局安装位置 a. yarn global dir => /Users/xxx/.config/yarn/global => 下面有一个link文件
(5). 测试执行本cli命令:
gl-cli
①. 当打包引入的第三方库时,vender.js会很大: a. 一些常用固定的第三方库,不会改动源代码,不会每次都发生变化. b. 导致加载时空白页时间过长. c. 没必要每次都生成hash值,让用户重新加载.同时还会消耗带宽流量. ②. webpack提供的externals属性: a. externals可以将依赖的第三方库从打包文件剔除 b. 大大减小了文件包大小,同时大幅提升编译效率.
(1). 工作原理:
①. externals配置在所创建bundle时: a. 会依赖于用户环境(consumer's environment)中的依赖,防止将某些import的包(package)打包到bundle中 b. 在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies) ②. webpack会检测这些组件是否在externals中注册,如果注册则不会将其打包到app.js中 ③. 修改了记得重启webpack ④. 在需要使用它的时候,可以通过CMD、AMD、或window全局方式访问
2. 哪些第三方库适合?
①. vue、vue-router、axios、element-ui、qs、crypto-js、vuex、moment、highlight.js ②. 要考虑大小不超过500kb,如果用到ueditor大型工具库需要单独打包.
(2). element-ui分析:
①. 都会把element-ui打包进去,每次修改都会下载element-ui. ②. 独立出去用cdn加载,用户下一次就有缓存. ③. 后边随便怎么改,只要有缓存就不会在下element-ui.
(3). 例子:
// externals中的key是后面需要require的名字,value是第三方库暴露出来的方法名 // 'alias': 'ObjName' // 简单的配置如上,alias 是项目内使用时的组件名称,ObjName 是某外部组件对外暴露的名称。 // 比如 vue 的 window 全局名称是 Vue // 比如 vue-router的 window 全局名称是 VueRouter // 比如 jquery 的 window 全局名称是 Jquery module.exports = {
externals: {
'vue': 'Vue', 'vue-router': 'VueRouter', 'axios': 'axios', 'element-ui': 'Element', 'qs': 'Qs' } }
(1). 优化vue.js:
①. 修改vue.config.js: const isProd = process.env.NODE_ENV === 'production' const getProdExternals = () => {
return {
'vue': 'Vue', // 'vue-router': 'VueRouter', // 'vuex': 'Vuex' } } module.exports = {
... configureWebpack: {
... externals: isProd ? getProdExternals() : {
} } } ②. 在public/index.html文件中引入vue cdn路径: <script src="//cdn.bootcss.com/vue/2.6.10/vue.min.js"></script> a. 不写协议前缀,会与网站的协议相同.所以,可以不写https. ②. 'vue': 'Vue'说明: a. key是node模块名称,value是项目中对模块的引用 b. 前面的vue是代码中import A from B中的B c. 后面的Vue是引入的cdn暴露的变量: (1). 可以在console控制台打印window,会发现window.Vue (2). 这个Vue就是需要的变量名称
(2). 优化index.html写法:
vue.config.js:
const cdn = {
css: [], js: [ // 与package.json里面的版本对应 '//cdn.bootcss.com/vue/2.6.10/vue.min.js', '//cdn.bootcss.com/vue-router/3.0.6/vue-router.min.js', '//cdn.bootcss.com/vuex/3.1.0/vuex.min.js' ] } module.exports = {
chainWebpack(config) {
... config.plugins.delete('prefetch') // 加载配置 config.plugin('html').tap(args => {
if (process.env.NODE_ENV === 'production') {
args[0].cdn = cdn } return args }) ... } }
index.html:
<!-- 使用CDN的CSS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="external nofollow" rel="external nofollow" rel="preload" as="style"> <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="external nofollow" rel="external nofollow" rel="stylesheet"> <% } %> <!-- 使用CDN的JS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
<!-- <link href="<%= htmlWebpackPlugin.options.cdn.js[i] %>" rel="external nofollow" rel="preload" as="script"> --> <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script> <% } %>
1. 全局引入:
①. 安装: npm i element-ui -S ②. main.js引入: import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); ③. 弊端: a. 打包的文件过大.
2. 按需引入:
①. 安装组件: yarn add babel-plugin-component -D ②. 修改babel.config.js: {
"plugins": [ [ "component", {
"libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] } ③. main.js引入(下面有项目实战): import {
Button, Select } from 'element-ui'; import App from './App.vue'; // 方式一 Vue.component(Button.name, Button); Vue.component(Select.name, Select); // 方式二 Vue.use(Button) Vue.use(Select)
(2). 项目中完整组件列表和引入方式 – src/core/lazy_use.js:
import Vue from 'vue' import {
Pagination, Dialog, // Autocomplete, // Dropdown, // DropdownMenu, // DropdownItem, // Menu, // Submenu, // MenuItem, // MenuItemGroup, Input, // InputNumber, // Radio, // RadioGroup, // RadioButton, // Checkbox, // CheckboxButton, // CheckboxGroup, Switch, Select, Option, // OptionGroup, Button, // ButtonGroup, Table, TableColumn, DatePicker, // TimeSelect, // TimePicker, // Popover, // Tooltip, // Breadcrumb, // BreadcrumbItem, Form, FormItem, // Tabs, // TabPane, // Tag, // Tree, Alert, // Slider, // Icon, Row, Col, // Upload, // Progress, // Spinner, // Badge, Card, // Rate, Steps, Step, // Carousel, // CarouselItem, // Collapse, // CollapseItem, // Cascader, // ColorPicker, // Transfer, // Container, // Header, // Aside, // Main, // Footer, // Timeline, // TimelineItem, // Link, // Divider, // Image, // Calendar, // Backtop, // PageHeader, // CascaderPanel, // Loading, MessageBox, Message, // Notification, Drawer } from 'element-ui' const maps = {
Pagination, Dialog, // Autocomplete, // Dropdown, // DropdownMenu, // DropdownItem, // Menu, // Submenu, // MenuItem, // MenuItemGroup, Input, // InputNumber, // Radio, // RadioGroup, // RadioButton, // Checkbox, // CheckboxButton, // CheckboxGroup, Switch, Select, Option, // OptionGroup, Button, // ButtonGroup, Table, TableColumn, DatePicker, // TimeSelect, // TimePicker, // Popover, // Tooltip, // Breadcrumb, // BreadcrumbItem, Form, FormItem, // Tabs, // TabPane, // Tag, // Tree, Alert, // Slider, // Icon, Row, Col, // Upload, // Progress, // Spinner, // Badge, Card, // Rate, Steps, Step, // Carousel, // CarouselItem, // Collapse, // CollapseItem, // Cascader, // ColorPicker, // Transfer, // Container, // Header, // Aside, // Main, // Footer, // Timeline, // TimelineItem, // Link, // Divider, // Image, // Calendar, // Backtop, // PageHeader, // CascaderPanel, Drawer } // 只有一部分组件是use引入 Object.keys(maps).forEach(item => {
Vue.use(maps[item]) }) // Vue.use(Loading.directive) // Vue.prototype.$loading = Loading.service Vue.prototype.$msgbox = MessageBox Vue.prototype.$alert = MessageBox.alert // Vue.prototype.$confirm = MessageBox.confirm // Vue.prototype.$prompt = MessageBox.prompt // Vue.prototype.$notify = Notification Vue.prototype.$message = Message
(3). 项目中main.js引入:
import './core/lazy_use' // 之前的全部注释掉 // import ElementUI from 'element-ui' // import 'element-ui/lib/theme-chalk/index.css' // import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n // set ElementUI lang to EN // Vue.use(ElementUI, { locale }) // 如果想要中文版 element-ui,按如下方式声明 // Vue.use(ElementUI)
(4). 打包出来chunck可以放到cdn上.
3. 在index.html中指定版本cdn加载:
<!-- 引入样式 --> <link rel="stylesheet" href="https://unpkg.com/element-ui@3.x.x/lib/theme-chalk/index.css"> <!-- 引入组件库 --> <script src="https://unpkg.com/element-ui@3.x.x/lib/index.js"></script>
(1). 安装插件:
$ yarn add babel-plugin-transform-remove-console -D
(2). 修改babel.config.js文件:
宸汐项目
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV) const plugins = [ [ 'component', {
'libraryName': 'element-ui', 'styleLibraryName': 'theme-chalk' } ] ] // 只有生产环境去掉console.log if (IS_PROD) {
plugins.push('transform-remove-console') } module.exports = {
... plugins }
(3). 修改babel.config.js文件(vue-cli4):
疾控项目
module.exports = {
env: {
development: {
plugins: ["dynamic-import-node"] }, production: {
plugins: ["transform-remove-console"] } } }
少了1kb左右,在源码中也找不到console.log
(4). 缺点:
①. 自己写的console去除了. ②. index.html内联的runtime代码没去除console,自己单独分离的chunk也没去除.
1. why?
①. 如果存在很多过大文件时,会导致可能阻塞后面的进程. ②. 减少包的大小: a. 更快的加载速度以及更好的用户体验.
(1). gzip:
①. 是一种 http 请求优化方式: a. 通过减少文件体积来提高加载速度 b. 对于用户量多的网站,开启 gizp 压缩会大大降低服务器压力,提高加载速度、降低服务器流量成本. c. 节省了服务器的网络带宽,节约的流量非常可观. ②. 必须浏览器与服务器都支持gzip. ③. gzip算法特性: a. 代码相似率越大压缩效率越高.
(2). 工作原理图:
①. 浏览器发送请求: a. 在 request header 中设置属性 accept-encoding:gzip b. 表示浏览器支持 gzip. ②. 服务器收到请求后: a. 判断浏览器是否支持 gzip: (1). 如果支持 gzip,则向浏览器传送压缩过的内容. (2). 不支持则向浏览器发送未经压缩的内容. b. Response headers返回包含 content-encoding:gzip. ③. 浏览器接收响应后判断内容是否被压缩,如果被压缩则解压缩显示页面内容: a. 浏览器先解压再使用,对于用户是无感知的.
2. 两种 gzip 压缩方式:
①. webpack打包生成 .gz 文件: a. 通过 webpack 配置生成对应的 .gz 文件. b. 浏览器请求 xx.js/css 等文件时,服务器返回对应的 xxx.js.gz 文件. ②. 服务器实时在线将请求 xx.js 文件进行gzip压缩后传输给浏览器: a. 压缩文件过程本身有额外开销. b. 服务器压缩的时间开销和 CPU 开销(及浏览器解析压缩文件的开销)为代价,来节省传输过程中的时间开销.
1. 配置:
(1). 安装插件:
①. 安装 compression-webpack-plugin: yarn add compression-webpack-plugin@6.1.1 -D ②. 新版本 7.x 会报错: a. Cannot read property 'tapPromise' of undefined
(2). 在 vue.config.js 中配置:
const CompressionPlugin = require('compression-webpack-plugin'); module.exports = {
chainWebpack(config) {
... // 方式一: config .when(process.env.NODE_ENV === 'production', config => {
config .plugin('compression') .use(CompressionPlugin) .tap(() => [{
test: /\.js$|\.html$|\.css$/, // 匹配文件名,开启js、css压缩 filename: '[path].gz[query]', // 压缩后的文件名(保持原文件名,后缀加.gz) minRatio: 1, // 压缩率小于1才会压缩 threshold: 10240, // 对超过10k的数据压缩 deleteOriginalAssets: false // 是否删除未压缩的源文件(不设置或设置为false) // 保留非gzip的资源,删除打包后的gz后还可以加载到原始资源文件,建议不要设置为true }]) } ) // 方式二: if (process.env.NODE_ENV === 'production') {
config.plugin('compression-webpack-plugin') .use(new CompressionPlugin({
test: /\.js$|\.html$|\.css/, threshold: 10240, deleteOriginalAssets: false })) } } } ①. test 另种写法: const productionGzipExtensions = ['html', 'js', 'css'] test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$') ②. 打包后目录会多出 .gz 文件: -rw-r--r-- 1 xx staff 42756 3 28 23:01 app.9c5d6e51.js -rw-r--r-- 1 xx staff 14495 3 28 23:01 app.9c5d6e51.js.gz -rw-r--r-- 1 xx staff 14072 3 28 23:01 chunk-19edcdf1.2e.js -rw-r--r-- 1 xx staff 4791 3 28 23:01 chunk-19edcdf1.2e.js.gz // 有些没有gz是因为大小没有超过设定的10k -rw-r--r-- 1 xx staff 11 3 28 23:01 chunk-47179b48.01af0134.js ... ③. 打包只有一个没有名称的 .gz 文件,并提示: warning Conflict: Multiple assets emit different content to the same filename static/js/.gz ... -rw-r--r-- 1 xx staff 48K 3 29 10:39 .gz // 没有名字的gz文件 -rw-r--r-- 1 xx staff 39K 3 29 10:39 app.3c690d0c.js a. 要修改 filename 的设置为 filename ,老版本为'[path].gz[query]'.
(3). 服务器开启 gzip:
server {
// 表示静态加载本地的gz文件 // 浏览器请求xx.js/css等文件时,服务器返回对应的xxx.js.gz文件 // 服务器会根据Request Headers的Accept-Encoding标签进行鉴别,如果支持gzip就返回.gz文件. // gzip_static开启后,nginx就会读取预先压缩的gz文件,可以减少每次请求进行gzip压缩的CPU资源消耗 gzip_static on; gzip_http_version 1.1; }
(4). 检查是否开启Gzip成功:
curl -I -H "accept-encoding: gzip, deflate" "https://admin.chaidoudou.cn/static/css/chunk-elementUI.a8b08852.css" HTTP/2 200 server: nginx/1.14.0 (Ubuntu) date: Tue, 30 Mar 2021 15:59:15 GMT content-type: text/css content-length: 33216 last-modified: Tue, 30 Mar 2021 08:15:15 GMT etag: "6062de13-81c0" content-encoding: gzip
(4). 看Network:
如果发现两个大小不一样,表示Gzip压缩过
2. 分析:
(1). gzip 压缩比率:
①. 压缩前: a. 整个页面加载完是 8.89s. b. 最大的 js 文件加载是 8.31s ,大小为 593k. ②. 压缩后: a. 整个页面加载完是 2.21s. b. 最大的 js 文件加载是 1.75s,大小为 146k. ③. gzip 压缩比率在 4 倍左右.
压缩前:
压缩后:
(2). Request、Response 比对:
①. Request Headers: a. Accept-Encoding: gzip, deflate: (1). 表示用户浏览器支持二种压缩,包括 gzip 的压缩方式. (2). deflate 与 gzip 使用的压缩算法几乎相同.
压缩前的 request:
压缩后的 request:
(3). 其它:
②. nginx 配置了静态 gz 加载后,请求文件变小不会导致请求卡线程. ③. 保留了源文件,当删除 gz 后,浏览器会自动去请求原始文件,不会导致界面出现任何问题. ④. 静态加载 gz 文件的响应头: Content-Encoding: gzip
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/134875.html