[前端进阶] 工程/工具篇

Babel

Babel 是一个 JavaScript 编译器,用于将高版本的JavaScript代码转为向后兼容的JS代码,ES6+ => ES5/ES3。同时也可转换 JSX 语法为 JavaScript。

可单独使用 babel 或者通过下面三种方式:

  • 使用单体文件 (standalone script),如 .babelrc,.babelrc.js,babel.config.js,babel.config.json;
  • 命令行 (cli)。package.json中的 scripts 段落中的某条命令;
  • 构建工具的插件 (webpack 的 babel-loader, rollup 的 rollup-plugin-babel)。

babel 本身不具有任何转化功能,它把转化的功能都分解到一个个 plugin 里面。因此当我们不配置任何插件时,经过 babel 的代码和输入是相同的。

Babel插件

  • 语法插件:作用于解析阶段,使得babel能够解析更多的语法,官方的语法插件以babel-plugin-syntax开头。语法插件虽名为插件,但其本身并不具有功能性。语法插件所对应的语法功能其实都已在@babel/parser里实现,插件的作用只是将对应语法的解析功能打开。
  • 转换插件:在转换这一步把源码转换并输出,官方的转换插件以babel-plugin-transform(正式)或 babel-plugin-proposal(提案)开头。转换插件将启用相应的语法插件,因此不必同时指定这两种插件。

babel 的转译过程分为三个阶段,这三步具体是:

  1. 解析 Parse: 将代码解析生成抽象语法树( 即 AST ),即词法分析与语法分析的过程;
  2. 转换 Transform: 对于 AST 进行变换一系列的操作,babel 接受得到 AST 并通过 babel-traverse 对其进行遍历,在此过程中进行添加、更新及移除等操作;
  3. 生成 Generate: 将变换后的 AST 再转换为 JS 代码, 使用到的模块是 babel-generator

单独使用babel来完成整个工作流时,通过 @babel/cli 在终端(命令行)中快速使用 babel 命令进行代码转换。

// Babel 的核心功能包含在 `@babel/core` 模块中
npm install @babel/core @babel/cli @babel/preset-env -D

// 通过babel命令,解析src下的所有js文件,并转换到lib文件夹中
./node_modules/.bin/babel src --out-dir lib --presets=@babel/env
npx babel src --out-dir lib --presets=@babel/env
// --out-dir 可缩写为 -o

上面的 @babel/preset-env 结合 @babel/core 预设进行代码转换。命令 preset 还可添加详细参数进行控制,其他命令同样如此,为避免命令行过长,可利用配置文件 babel.config.json:

{
	"presets": [
		[
			"@babel/preset-env",
			{
				// targets 简易写到.browserslistrc文件中,不推荐配置在这里
				"targets": "last 2 Chrome versions",
				// 在预设中配置include后,转换时总是会启用include的插件
				"include": ["@babel/plugin-transform-arrow-functions"],
				// 决定是否引入polyfill,false(不引入)、usage(按需引入)和entry(项目入口处引入)
				"useBuiltIns": usage,
				// core-js 版本,可以选择2(默认)或者3,只有当useBuiltIns 不为 false 时才会生效
				"corejs": 3
			}
		]
	],
	"plugins": [
		[
			// 用于转换箭头函数的插件
			"@babel/plugin-transform-arrow-functions",
			{
				"spec": true
			}
		]
	]
}

Babel提供了一百来个插件,并提供预设(presets),预先设置好的一系列插件包。一些常用的预设有:

  • @babel/preset-env
  • @babel/preset-flow
  • @babel/preset-react
  • @babel/preset-typescript
  • @vue/cli-plugin-babel/preset

虽然 @babel/preset-env 可以转换大多高版本的JS语法,但是一些ES6原型链上的函数(比如数组实例上的的filter、fill、find等函数)以及新增的内置对象(比如Promise、Proxy等对象),是低版本浏览器本身内核就不支持。需要通过 @babel/polyfill,加入兼容代码。但是打包出来生成的文件非常的大,且污染全局变量,polyfill 给很多类的原型链上添加函数,因此推荐引入core-jsregenerator-runtime 两个包来替代。

在 webpack 中使用 babel 简单例子如下:

module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ["@babel/preset-env"]
            }
          }
        ]
      }
    ]
  }
}

更过配置见官网

Webpack

webpack 细则见官网,是一个模块化打包工具,通过loader转换文件,plugin注入钩子,最后输出由多个模块合成的文件,如图所示。
在这里插入图片描述

它主要是分析项目结构,找到js模块以及浏览器不能直接运行的拓展语言(TS,less等),并将其打包成合适的格式供浏览器使用。webpack 专注于模块化项目,开箱即用,具有如下特性:

  • 通过plugin扩展,灵活性更高。
  • 生态好,社区庞大活跃。
  • 使用场景不限于web开发。

Webpack 与 grunt、gulp 的区别

  • webpack 是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。和其他的工具最大的不同在于他支持 code-splitting、模块化(AMD,ESM,CommonJs)、全局分析。
  • grunt 和 gulp 是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。

与 webpack 类似的工具还有 rollup、parcel 等。webpack适用于大型复杂的前端站点构建,rollup适用于基础库的打包,如vue、react,parcel适用于简单的实验性项目,他可以满足低门槛的快速看到效果。

webpack 具有四个核心的概念,分别是 Entry(入口)、Output(输出)、loader 和 Plugins(插件)。

开发中通常将基础配置文件拆分为base,client,server等,搭建一个简单的Vue脚手架例子如下(基于 webpack4.x):
vue-loader.config.js

module.exports = (isDev) => {
  return {
    preserveWhitepace: true,
    extractCSS: !isDev,
    cssModules: {
      localIdentName: isDev ? '[path]-[name]-[hash:base64:5]' : '[hash:base64:5]',
      camelCase: true
    },
    // hotReload: false, // 根据环境变量生成
  }
}

webpack.config.base.js:搭建基础配置

const path = require("path");
const createVueLoaderOptions = require("./vue-loader.config");

const isDev = process.env.NODE_ENV === "development";

const config = {
	mode: process.env.NODE_ENV || "production", // development || production
	target: "web",
	entry: path.join(__dirname, "../client/index.js"),
	output: {
		filename: "bundle.[hash:8].js",
		path: path.join(__dirname, "../dist"),
	},
	module: {
		rules: [
			{
				test: /\.(vue|js|jsx)$/,
				loader: "eslint-loader",
				exclude: /node_modules/,
				enforce: "pre",
			},
			{
				test: /\.vue$/,
				loader: "vue-loader",
				options: createVueLoaderOptions(isDev),
			},
			{
				test: /\.jsx$/,
				loader: "babel-loader",
			},
			{
				test: /\.js$/,
				loader: "babel-loader",
				exclude: /node_modules/,
			},
			{
				test: /\.(gif|jpg|jpeg|png|svg)$/,
				use: [
					{
						loader: "url-loader",
						options: {
							limit: 1024,
							name: "resources/[path][name].[hash:8].[ext]",
						},
					},
				],
			},
		],
	},
};

module.exports = config;

webpack.config.client.js:搭建客户端配置

const path = require("path");
const HTMLPlugin = require("html-webpack-plugin");
const webpack = require("webpack");
const merge = require("webpack-merge");
const ExtractPlugin = require("extract-text-webpack-plugin");
const baseConfig = require("./webpack.config.base");

const isDev = process.env.NODE_ENV === "development";

const defaultPluins = [
	new webpack.DefinePlugin({
		"process.env": {
			NODE_ENV: isDev ? '"development"' : '"production"',
		},
	}),
	new HTMLPlugin(),
];

const devServer = {
	port: 8000,
	host: "0.0.0.0",
	overlay: {
		errors: true,
	},
	hot: true,
};

let config;

if (isDev) {
	config = merge(baseConfig, {
		devtool: "#cheap-module-eval-source-map",
		module: {
			rules: [
				{
					test: /\.styl/,
					use: [
						"vue-style-loader",
						"css-loader",
						{
							loader: "postcss-loader",
							options: {
								sourceMap: true,
							},
						},
						"stylus-loader",
					],
				},
			],
		},
		devServer,
		plugins: defaultPluins.concat([
			new webpack.HotModuleReplacementPlugin(),
			// new webpack.NoEmitOnErrorsPlugin()
		]),
	});
} else {
	config = merge(baseConfig, {
		entry: {
			app: path.join(__dirname, "../client/index.js"),
			// vendor: ['vue']
		},
		output: {
			filename: "[name].[chunkhash:8].js",
		},
		module: {
			rules: [
				{
					test: /\.styl/,
					use: ExtractPlugin.extract({
						fallback: "vue-style-loader",
						use: [
							"css-loader",
							{
								loader: "postcss-loader",
								options: {
									sourceMap: true,
								},
							},
							"stylus-loader",
						],
					}),
				},
			],
		},
		optimization: {
			splitChunks: {
				chunks: "all",
			},
			runtimeChunk: true,
		},
		plugins: defaultPluins.concat([
			new ExtractPlugin("styles.[contentHash:8].css"),
			// new webpack.optimize.CommonsChunkPlugin({
			//   name: 'vendor'
			// }),
			// new webpack.optimize.CommonsChunkPlugin({
			//   name: 'runtime'
			// })
		]),
	});
}

module.exports = config;

构建流程
通过上面例子,总结webpack的构建流程如下:

  1. 初始化参数:从配置文件和Shell语句中读取并合并参数。
  2. 开始编译:通过上一步得到的参数初始化对象的run方法开始执行编译。
  3. 确定入口:根据entry文件找到所有的入口文件。
  4. 编译模块:从入口文件触发,调用所有配置的lodar对模块进行翻译,再找出该模块所依赖的模块,然后递归本次步骤,直到所有入口依赖的文件都经过本次步骤的处理。
  5. 完成编译模块:处理每个模块被翻译的内容和他们之间的依赖关系。
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个多个模块的chunk,再将每个chunk转化为一个单独的文件加入到输出列表。这一步是修改输出内容的最后机会。
  7. 输出完成:确定好输出的内容后,根据配置确定输出的文件名和路径,将文件内容写入到文件系统中。

提高webpack的构建速度

  • 多入口情况下,使用 CommonsChunkPlugin 来提取公共代码
  • 通过externals 配置来提取常用库
  • 利用DllPluginDllReferencePlugin预编译资源模块 通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin 将预编译的模块加载进来
  • 使用 Happypackthread-loader 实现多线程加速编译
  • 使用 webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度
  • 使用 Tree-shakingScope Hoisting 来剔除多余代码

webpack的热更新原理
热更新HMR,可以在不刷新浏览器的情况下,将新变更的模块替换掉旧的模块。就是webpack和浏览器之间维护一个websocket,当本地资源发生变化的时候,webpack会向浏览器推送更新,并带上构建时的hash,让浏览器与上一次的资源进行比对。

  • 通过webpack-dev-server创建两个服务器:提供静态资源的服务(express)和Socket服务
  • express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
  • socket server 是一个 websocket 的长连接,双方可以通信
  • 当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)
  • 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
  • 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新

lodar与plugin

  • lodar是加载器,因为原生的webpack只能解析js文件,lodar可以帮助webpack解析和加载非js文件。
  • plugin是插件,可以扩展webpack的功能,使其更加灵活。同时plugin可以监听webpack在运行的生命周期中广播的事件。

关于bundle,chunk 和 module

  • bundle 是由 webpack 打包出来的文件
  • chunk 是指 webpack 在进行模块的依赖分析的时候,代码分割出来的代码块
  • module 是开发中的单个模块

文件指纹
文件指纹是指打包后输出文件的名的后缀。

  • hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 就会变化。
  • chunkhash:和webpack打包的 chunk 有关,不同的 chunk、不同的 entry 会生成不同的 chunkhash。
  • contenthash:根据文件内容来定义 hash,文件内容不发生变化,则contenthash就不会变化。直接在输出文件名添加对应的 hash值即可。

关于 source map
source map 是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 source map。map 文件只要不打开开发者工具,浏览器是不会加载的。显示环境一般有三种处理方案:

  • hidden-source-map:借助第三方错误监控平台 Sentry 使用。
  • nosource-source-map:只会显示具体行数以及查看源代码的错误栈。安全性比 source-map 高。
  • source-map:通过 nginx 设置将.map文件指对白名单开发。

注意:在生产环境中避免使用 inline- 和 eval-,因为它们会增加 bundle 体积大小,并降低整体性能。

webpack-dev-server和http服务器如nginx的区别

  • webpack-dev-server使用内存来存储webpack开发环境下的打包文件
  • 基于 websocket 可以使用模块热更新
  • 比传统的http服务对开发更加简单高效

如何利用webpack来优化前端性能?

  1. 压缩代码。删除多余的代码、注释、简化代码的写法等等方式。可以利用webpack的 UglifyJsPluginParallelUglifyPlugin 来压缩JS文件, 利用 optimize-css-assets-webpack-plugin 来压缩css
  2. 利用CDN加速。在构建过程中,将引用的静态资源路径修改为CDN上对应的路径。可以利用webpack对于output参数和各loader的publicPath参数来修改资源路径
  3. 删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。可以通过在启动webpack时追加参数–optimize-minimize来实现
  4. 提取公共代码。

关于 Tree-shaking
Tree-shaking 是指在打包中去除那些引入了,但是在代码中没有被用到的那些死代码。在webpack中Tree-shaking是通过 uglifyJsPlugin 来Tree-shaking JS,CSS 需要使用 Purify-CSS

我们常用 webpack 来配置单页应用,多页应用配置可参考 https://zhuanlan.zhihu.com/p/117656993。所谓单页应用(SPA),就是整个应用只有一个主页面,其余的“页面”,实际上是一个个的“组件”。单页应用中的“页面跳转”,实际上是组件的切换,在这个过程中,只会更新局部资源,页面不会整个刷新。也正是因为这个原因,当我们的前端代码有更改,重新部署之后,如果用户不主动刷新,就会发现有“资源缓存”。单页应用明显的弊端就是不利于 SEO。

Git

生成公/私密钥

ssh-keygen -t rsa -C "your_email@example.com"

gitignore失效问题
git ignore只会对不在git仓库中的文件进行忽略,如果这些文件已经在git仓库中,则不会忽略。所以如果需要忽略的文件已经提交到本地仓库,则需要从本地仓库中删除掉,如果已经提交到远端仓库,则需要从远端仓库中删除。删除.gitignore文件才能实际生效。.gitignore 文件只能作用于 Untracked Files,也就是那些从来没有被 Git 记录过的文件(自添加以后,从未 add 及 commit 过的文件)。

想要删除已经提交的文件,使用:

 git rm -r --cached xxx.js
 git commit -m " commit ....."
 git push

git rm --cached 删除的是追踪状态,而不是物理文件。

常用git命令
目前可知的主要环境细分如下:

  • release(线上):发布至网络后,对重要的release进行版本发布
  • beta(预发布):模拟生产环境,测试各种数据的正确性
  • test(测试):主要对代码的鲁棒性进行测试
  • develop(开发):细分到个人的开发分支,所有人开发后合并到的主分支
  1. 从创建本地仓库开始
git config --global user.name "你的名字或昵称"
git config --global user.email "你的邮箱"
  1. 配置完后要进行本地密码与账户名的保存
git config --global credential.helper store

此时会在本地保存一个文本文档,用于记录账户与密码,以便以后不用每次操作命令都输入密码。

  1. 进入项目文件夹
git init

执行完在本文件夹下生成 .git 文件夹,生成文件树。

  1. 将文件树中的内容 add 到暂存区(Stage)中
git add .
  1. 准备好当前文件夹下的全部文件 commit 到本地版本库中
git commit -m 'description'
  1. 确定服务器地址及分支,随后将本地代码提交到远程仓库中:
# 法1
git remote add origin https://git.coding.net/username/projectname.git
# 法2 申请Github token,通过token的方式避免重复输入用户名密码,git clone 的时候同样带上token
git remote set-url origin https://token@github.com/YourName/xxx.git/

git pull origin master
git push origin master
  1. 新建分支命令
git branch [branch name]
  1. 切换分支
git checkout [branch name]

# 或直接创建并切换
git checkout -b [branch name]
  1. 提交到新分支
git add .
git commit -m "msg"
git push origin [branch name]

但是有时某个分支的命名与远程仓库中的分支冲突时我们需要将本地仓库中的分支删除掉,以下是删除本地分支与远程分支的基本步骤:

  • 先切换到别的分支: git checkout branch1
  • 删除本地分支: git branch -d branch2
  • 如果删除不了可以强制删除,git branch -D branch2
  • 有必要的情况下,删除远程分支:git push origin --delete branch2
  • 在从公用的仓库fetch代码:git fetch origin branch2:branch2
  • 然后切换分支即可:git checkout branch2

在push的过程中必须保持本地与远程分支的名称一致性,否则会报错。

cherry-pick
目的:把某个分支中的其中部分 commit merge 到 master 中。步骤如下:

  1. 先用 git log 查看 xxx 分支中某 commit 的 id
  2. git checkout 到 master 分支下
  3. git cherry-pick <xxx_commit_id>

如果出现冲突,则先解决冲突,再 git add(将解决了冲突的文件添加到暂存区),然后 git cherry-pick --continue

merge 合并分支

git merge feature

下面内容来自 https://juejin.cn/post/6872020320333594637
在这里插入图片描述

  • 找到 feature 分支和 master 分支的最近共同祖先 commit 节点 c1;
  • 把 feature 分支的最新一次 commit 节点 c3 和 master 分支上的最新一次 commit 节点 c5 合并,此时若有冲突,则一次性解决所有冲突,然后生成一个新的 commit 节点 c6;
  • 同时根据两个分支上的 commit 时间的先后顺序,依次放到 master 分支上,使用git log可以看到时间顺序。

git fetch&git pull

  • git fetch的意思是将远程主机的最新内容拉到本地,用户再检查无误后再决定是否合并到工作本地分支中
  • git pull 是将远程主机中的最新内容拉取下来后直接合并,即:git pull = git fetch+git merge,这样可能会产生冲突,需要手动解决。

pull 后回滚到之前版本

git reflog master  # (查看本地master分支历史变动纪录)
git reset --hard <COMMIT_ID> #(恢复到之前位置)
# git reset --hard master@{1} 

git clone

# -b 指定分支
git clone -b [chekout name] https://token@github.com/Name/xxx.git/

DevOps & CI/CD

软件从零开始到最终交付,大概包括以下几个阶段:规划、编码、构建、测试、发布、部署和维护。

瀑布式开发
在这里插入图片描述
瀑布式开发的基本流程是 需求 → 设计 → 开发 → 测试 , 是一个更倾向于严格控制的管理模式 。
敏捷开发
在这里插入图片描述
敏捷开发是一种以用户需求进化为核心、迭代、循序渐进的开发方法。首先把 用户(客户 )最关注的软件原型做出来,交付或上线,在实际场景中去 快速 修改弥补需求中的不足,再次发布版本。通过一些敏捷实践方式,细化story ,提供更小的迭代。如此循环,直到用户(客户)满意。适用于需求不明确、创新性或者需要抢占市场的项目。
DevOps
在这里插入图片描述
DevOps 强调通过一系列手段来实现既快又稳的工作流程,使每个想法(比如一个新的软件功能,一个功能增强请求或者一个 bug 修复)在从开发到生产环境部署的整个流程中,都能不断地为用户带来价值。这种方式需要开发团队和运维团队密切交流、高效协作并且彼此体谅。此外,DevOps 还要能够方便扩展,灵活部署。有了 DevOps,需求最迫切的工作就能通过自助服务和自动化得到解决;通常在标准开发环境编写代码的开发人员也可与 IT 运维人员紧密合作,加速软件的构建、测试和发布,同时保障开发成果的稳定可靠。

DevOps 即开发运维一体化。DevOps平台搭建工具如下(例子):

  • 平台搭建工具项目管理(PM):jira。运营可以上去提问题,可以看到各个问题的完整的工作流,待解决未解决等;
  • 代码管理:gitlab。jenkins 或者 K8S 都可以集成 gitlab,进行代码管理,上线,回滚等;
  • 持续集成CI(Continuous Integration):gitlab ci。开发人员提交了新代码之后,立刻进行构建、(单元)测试。根据测试结果,我们可以确定新代码和原有代码能否正确地集成在一起。
  • 持续交付CD(Continuous Delivery):gitlab cd。完成单元测试后,可以把代码部署到连接数据库的 Staging 环境中更多的测试。如果代码没有问题,可以继续手动部署到生产环境中。
  • 镜像仓库: VMware Harbor,私服 nexus
  • 容器: Docker
  • 编排: K8S
  • 服务治理: Consul
  • 脚本语言: Python
  • 日志管理: Cat+Sentry,还有种常用的是ELK
  • 系统监控: Prometheus
  • 负载均衡: Nginx
  • 网关: Kong,zuul
  • 链路追踪: Zipkin
  • 产品和UI图: 蓝湖
  • 公司内部文档: Confluence
  • **报警:**推送到工作群

参考 https://q.shanyue.tech/deploy/https://www.zhihu.com/question/58702398/answer/1755254160

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值